1 /***************************************************************************
2                          testqgsprocessing.cpp
3                          ---------------------
4     begin                : January 2017
5     copyright            : (C) 2017 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsprocessingregistry.h"
19 #include "qgsprocessingprovider.h"
20 #include "qgsprocessingutils.h"
21 #include "qgsprocessingalgorithm.h"
22 #include "qgsprocessingcontext.h"
23 #include "qgsprocessingparametertype.h"
24 #include "qgsprocessingmodelalgorithm.h"
25 #include "qgsprocessingmodelgroupbox.h"
26 #include "qgsnativealgorithms.h"
27 #include <QObject>
28 #include <QtTest/QSignalSpy>
29 #include <QList>
30 #include <QFileInfo>
31 #include "qgis.h"
32 #include "qgstest.h"
33 #include "qgsrasterlayer.h"
34 #include "qgsmeshlayer.h"
35 #include "qgspluginlayer.h"
36 #include "qgsproject.h"
37 #include "qgspoint.h"
38 #include "qgsgeometry.h"
39 #include "qgsvectorfilewriter.h"
40 #include "qgsexpressioncontext.h"
41 #include "qgsxmlutils.h"
42 #include "qgsreferencedgeometry.h"
43 #include "qgssettings.h"
44 #include "qgsmessagelog.h"
45 #include "qgsvectorlayer.h"
46 #include "qgsexpressioncontextutils.h"
47 #include "qgsprintlayout.h"
48 #include "qgslayoutmanager.h"
49 #include "qgslayoutitemlabel.h"
50 #include "qgscoordinatetransformcontext.h"
51 #include "qgsrasterfilewriter.h"
52 #include "qgsprocessingparameterfieldmap.h"
53 #include "qgsprocessingparameteraggregate.h"
54 #include "qgsprocessingparametertininputlayers.h"
55 #include "qgsprocessingparameterdxflayers.h"
56 #include "qgsprocessingparametermeshdataset.h"
57 #include "qgsdxfexport.h"
58 #include "qgspointcloudlayer.h"
59 #include "qgsannotationlayer.h"
60 #include "qgsconfig.h"
61 
62 class DummyAlgorithm : public QgsProcessingAlgorithm
63 {
64   public:
65 
DummyAlgorithm(const QString & name)66     DummyAlgorithm( const QString &name ) : mName( name ) { mFlags = QgsProcessingAlgorithm::flags(); }
67 
initAlgorithm(const QVariantMap &=QVariantMap ())68     void initAlgorithm( const QVariantMap & = QVariantMap() ) override {}
name() const69     QString name() const override { return mName; }
displayName() const70     QString displayName() const override { return mName; }
processAlgorithm(const QVariantMap &,QgsProcessingContext &,QgsProcessingFeedback *)71     QVariantMap processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); }
72 
flags() const73     Flags flags() const override { return mFlags; }
createInstance() const74     DummyAlgorithm *createInstance() const override { return new DummyAlgorithm( name() ); }
75 
76     QString mName;
77 
78     Flags mFlags;
79 
checkParameterVals()80     void checkParameterVals()
81     {
82       addParameter( new QgsProcessingParameterString( "p1" ) );
83       QVariantMap params;
84       QgsProcessingContext context;
85 
86       QVERIFY( !checkParameterValues( params, context ) );
87       params.insert( "p1", "a" );
88       QVERIFY( checkParameterValues( params, context ) );
89       // optional param
90       addParameter( new QgsProcessingParameterString( "p2", QString(), QVariant(), false, true ) );
91       QVERIFY( checkParameterValues( params, context ) );
92       params.insert( "p2", "a" );
93       QVERIFY( checkParameterValues( params, context ) );
94     }
95 
runParameterChecks()96     void runParameterChecks()
97     {
98       QVERIFY( parameterDefinitions().isEmpty() );
99       QVERIFY( addParameter( new QgsProcessingParameterBoolean( "p1" ) ) );
100       QCOMPARE( parameterDefinitions().count(), 1 );
101       QCOMPARE( parameterDefinitions().at( 0 )->name(), QString( "p1" ) );
102       QCOMPARE( parameterDefinitions().at( 0 )->algorithm(), this );
103 
104       QVERIFY( !addParameter( nullptr ) );
105       QCOMPARE( parameterDefinitions().count(), 1 );
106       // duplicate name!
107       QgsProcessingParameterBoolean *p2 = new QgsProcessingParameterBoolean( "p1" );
108       QVERIFY( !addParameter( p2 ) );
109       QCOMPARE( parameterDefinitions().count(), 1 );
110 
111       QCOMPARE( parameterDefinition( "p1" ), parameterDefinitions().at( 0 ) );
112       // parameterDefinition should be case insensitive
113       QCOMPARE( parameterDefinition( "P1" ), parameterDefinitions().at( 0 ) );
114       QVERIFY( !parameterDefinition( "invalid" ) );
115 
116       QCOMPARE( countVisibleParameters(), 1 );
117       QgsProcessingParameterBoolean *p3 = new QgsProcessingParameterBoolean( "p3" );
118       QVERIFY( addParameter( p3 ) );
119       QCOMPARE( countVisibleParameters(), 2 );
120       QgsProcessingParameterBoolean *p4 = new QgsProcessingParameterBoolean( "p4" );
121       p4->setFlags( QgsProcessingParameterDefinition::FlagHidden );
122       QVERIFY( addParameter( p4 ) );
123       QCOMPARE( countVisibleParameters(), 2 );
124 
125 
126       //destination styleparameters
127       QVERIFY( destinationParameterDefinitions().isEmpty() );
128       QgsProcessingParameterFeatureSink *p5 = new QgsProcessingParameterFeatureSink( "p5" );
129       QVERIFY( addParameter( p5, false ) );
130       QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 );
131       QgsProcessingParameterFeatureSink *p6 = new QgsProcessingParameterFeatureSink( "p6" );
132       QVERIFY( addParameter( p6, false ) );
133       QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 << p6 );
134 
135       // remove parameter
136       removeParameter( "non existent" );
137       removeParameter( "p6" );
138       QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 );
139       removeParameter( "p5" );
140       QVERIFY( destinationParameterDefinitions().isEmpty() );
141 
142       // try with auto output creation
143       QgsProcessingParameterVectorDestination *p7 = new QgsProcessingParameterVectorDestination( "p7", "my output" );
144       QVERIFY( addParameter( p7 ) );
145       QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p7 );
146       QVERIFY( outputDefinition( "p7" ) );
147       QCOMPARE( outputDefinition( "p7" )->name(), QStringLiteral( "p7" ) );
148       QCOMPARE( outputDefinition( "p7" )->type(), QStringLiteral( "outputVector" ) );
149       QCOMPARE( outputDefinition( "p7" )->description(), QStringLiteral( "my output" ) );
150 
151       // duplicate output name
152       QVERIFY( addOutput( new QgsProcessingOutputVectorLayer( "p8" ) ) );
153       QgsProcessingParameterVectorDestination *p8 = new QgsProcessingParameterVectorDestination( "p8" );
154       // this should fail - it would result in a duplicate output name
155       QVERIFY( !addParameter( p8 ) );
156 
157       // default vector format extension
158       QgsProcessingParameterFeatureSink *sinkParam = new QgsProcessingParameterFeatureSink( "sink" );
159       QCOMPARE( sinkParam->defaultFileExtension(), QStringLiteral( "gpkg" ) ); // before alg is accessible
160       QVERIFY( !sinkParam->algorithm() );
161       QVERIFY( !sinkParam->provider() );
162       QVERIFY( addParameter( sinkParam ) );
163       QCOMPARE( sinkParam->defaultFileExtension(), QStringLiteral( "gpkg" ) );
164       QCOMPARE( sinkParam->algorithm(), this );
165       QVERIFY( !sinkParam->provider() );
166 
167       // default raster format extension
168       QgsProcessingParameterRasterDestination *rasterParam = new QgsProcessingParameterRasterDestination( "raster" );
169       QCOMPARE( rasterParam->defaultFileExtension(), QStringLiteral( "tif" ) ); // before alg is accessible
170       QVERIFY( addParameter( rasterParam ) );
171       QCOMPARE( rasterParam->defaultFileExtension(), QStringLiteral( "tif" ) );
172 
173       // should allow parameters with same name but different case (required for grass provider)
174       QgsProcessingParameterBoolean *p1C = new QgsProcessingParameterBoolean( "P1" );
175       QVERIFY( addParameter( p1C ) );
176       QCOMPARE( parameterDefinitions().count(), 8 );
177 
178       // remove parameter and auto created output
179       QgsProcessingParameterVectorDestination *p9 = new QgsProcessingParameterVectorDestination( "p9", "output" );
180       QVERIFY( addParameter( p9 ) );
181       QVERIFY( outputDefinition( "p9" ) );
182       QCOMPARE( outputDefinition( "p9" )->name(), QStringLiteral( "p9" ) );
183       QCOMPARE( outputDefinition( "p9" )->type(), QStringLiteral( "outputVector" ) );
184       removeParameter( "p9" );
185       QVERIFY( !outputDefinition( "p9" ) );
186 
187       // remove parameter and check manually added output isn't removed
188       QVERIFY( addParameter( new QgsProcessingParameterVectorDestination( "p10", "output" ), false ) );
189       QVERIFY( addOutput( new QgsProcessingOutputVectorLayer( "p10" ) ) );
190       QCOMPARE( outputDefinition( "p10" )->name(), QStringLiteral( "p10" ) );
191       QCOMPARE( outputDefinition( "p10" )->type(), QStringLiteral( "outputVector" ) );
192       removeParameter( "p10" );
193       QVERIFY( outputDefinition( "p10" ) );
194 
195       // parameterDefinition should be case insensitive, but prioritize correct case matches
196       QCOMPARE( parameterDefinition( "p1" ), parameterDefinitions().at( 0 ) );
197       QCOMPARE( parameterDefinition( "P1" ), parameterDefinitions().at( 7 ) );
198     }
199 
runParameterChecks2()200     void runParameterChecks2()
201     {
202       // default vector format extension, taken from provider
203       QgsProcessingParameterFeatureSink *sinkParam = new QgsProcessingParameterFeatureSink( "sink2" );
204       QCOMPARE( sinkParam->defaultFileExtension(), QStringLiteral( "gpkg" ) ); // before alg is accessible
205       QVERIFY( !sinkParam->algorithm() );
206       QVERIFY( !sinkParam->provider() );
207       QVERIFY( addParameter( sinkParam ) );
208       QCOMPARE( sinkParam->defaultFileExtension(), QStringLiteral( "xshp" ) );
209       QCOMPARE( sinkParam->algorithm(), this );
210       QCOMPARE( sinkParam->provider(), provider() );
211 
212       // default raster format extension
213       QgsProcessingParameterRasterDestination *rasterParam = new QgsProcessingParameterRasterDestination( "raster2" );
214       QCOMPARE( rasterParam->defaultFileExtension(), QStringLiteral( "tif" ) ); // before alg is accessible
215       QVERIFY( addParameter( rasterParam ) );
216       QCOMPARE( rasterParam->defaultFileExtension(), QStringLiteral( "pcx" ) );
217     }
218 
runOutputChecks()219     void runOutputChecks()
220     {
221       QVERIFY( outputDefinitions().isEmpty() );
222       QVERIFY( addOutput( new QgsProcessingOutputVectorLayer( "p1" ) ) );
223       QCOMPARE( outputDefinitions().count(), 1 );
224       QCOMPARE( outputDefinitions().at( 0 )->name(), QString( "p1" ) );
225 
226       // make sure manually added outputs are not deleted by calling removeParameter
227       removeParameter( "p1" );
228       QCOMPARE( outputDefinitions().count(), 1 );
229 
230       QVERIFY( !addOutput( nullptr ) );
231       QCOMPARE( outputDefinitions().count(), 1 );
232       // duplicate name!
233       QgsProcessingOutputVectorLayer *p2 = new QgsProcessingOutputVectorLayer( "p1" );
234       QVERIFY( !addOutput( p2 ) );
235       QCOMPARE( outputDefinitions().count(), 1 );
236 
237       QCOMPARE( outputDefinition( "p1" ), outputDefinitions().at( 0 ) );
238       // parameterDefinition should be case insensitive
239       QCOMPARE( outputDefinition( "P1" ), outputDefinitions().at( 0 ) );
240       QVERIFY( !outputDefinition( "invalid" ) );
241 
242       QVERIFY( !hasHtmlOutputs() );
243       QgsProcessingOutputHtml *p3 = new QgsProcessingOutputHtml( "p3" );
244       QVERIFY( addOutput( p3 ) );
245       QVERIFY( hasHtmlOutputs() );
246     }
247 
runValidateInputCrsChecks()248     void runValidateInputCrsChecks()
249     {
250       addParameter( new QgsProcessingParameterMapLayer( "p1" ) );
251       addParameter( new QgsProcessingParameterMapLayer( "p2" ) );
252       QVariantMap parameters;
253 
254       QgsVectorLayer *layer3111 = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" );
255       QgsProject p;
256       p.addMapLayer( layer3111 );
257 
258       const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
259       const QString raster1 = testDataDir + "landsat_4326.tif";
260       const QFileInfo fi1( raster1 );
261       QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
262       QVERIFY( r1->isValid() );
263       p.addMapLayer( r1 );
264 
265       QgsVectorLayer *layer4326 = new QgsVectorLayer( "Point?crs=epsg:4326", "v1", "memory" );
266       p.addMapLayer( layer4326 );
267 
268       QgsProcessingContext context;
269       context.setProject( &p );
270 
271       // flag not set
272       mFlags = QgsProcessingAlgorithm::Flags();
273       parameters.insert( "p1", QVariant::fromValue( layer3111 ) );
274       QVERIFY( validateInputCrs( parameters, context ) );
275       mFlags = FlagRequiresMatchingCrs;
276       QVERIFY( validateInputCrs( parameters, context ) );
277 
278       // two layers, different crs
279       parameters.insert( "p2", QVariant::fromValue( layer4326 ) );
280       // flag not set
281       mFlags = QgsProcessingAlgorithm::Flags();
282       QVERIFY( validateInputCrs( parameters, context ) );
283       mFlags = FlagRequiresMatchingCrs;
284       QVERIFY( !validateInputCrs( parameters, context ) );
285 
286       // raster layer
287       parameters.remove( "p2" );
288       addParameter( new QgsProcessingParameterRasterLayer( "p3" ) );
289       parameters.insert( "p3", QVariant::fromValue( r1 ) );
290       QVERIFY( !validateInputCrs( parameters, context ) );
291 
292       // feature source
293       parameters.remove( "p3" );
294       addParameter( new QgsProcessingParameterFeatureSource( "p4" ) );
295       parameters.insert( "p4", layer4326->id() );
296       QVERIFY( !validateInputCrs( parameters, context ) );
297 
298       parameters.remove( "p4" );
299       addParameter( new QgsProcessingParameterMultipleLayers( "p5" ) );
300       parameters.insert( "p5", QVariantList() << layer4326->id() << r1->id() );
301       QVERIFY( !validateInputCrs( parameters, context ) );
302 
303       // extent
304       parameters.clear();
305       parameters.insert( "p1", QVariant::fromValue( layer3111 ) );
306       addParameter( new QgsProcessingParameterExtent( "extent" ) );
307       parameters.insert( "extent", QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) ) );
308       QVERIFY( !validateInputCrs( parameters, context ) );
309       parameters.insert( "extent", QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) ) );
310       QVERIFY( validateInputCrs( parameters, context ) );
311 
312       // point
313       parameters.clear();
314       parameters.insert( "p1", QVariant::fromValue( layer3111 ) );
315       addParameter( new QgsProcessingParameterPoint( "point" ) );
316       parameters.insert( "point", QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) ) );
317       QVERIFY( !validateInputCrs( parameters, context ) );
318       parameters.insert( "point", QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) ) );
319       QVERIFY( validateInputCrs( parameters, context ) );
320     }
321 
runAsPythonCommandChecks()322     void runAsPythonCommandChecks()
323     {
324       addParameter( new QgsProcessingParameterString( "p1" ) );
325       addParameter( new QgsProcessingParameterString( "p2" ) );
326       QgsProcessingParameterString *hidden = new QgsProcessingParameterString( "p3" );
327       hidden->setFlags( QgsProcessingParameterDefinition::FlagHidden );
328       addParameter( hidden );
329 
330       QVariantMap params;
331       QgsProcessingContext context;
332 
333       QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {})" ) );
334       params.insert( "p1", "a" );
335       QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a'})" ) );
336       params.insert( "p2", QVariant() );
337       QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a','p2':None})" ) );
338       params.insert( "p2", "b" );
339       QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a','p2':'b'})" ) );
340 
341       // hidden, shouldn't be shown
342       params.insert( "p3", "b" );
343       QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a','p2':'b'})" ) );
344     }
345 
addDestParams()346     void addDestParams()
347     {
348       QgsProcessingParameterFeatureSink *sinkParam1 = new QgsProcessingParameterFeatureSink( "supports" );
349       sinkParam1->setSupportsNonFileBasedOutput( true );
350       addParameter( sinkParam1 );
351       QgsProcessingParameterFeatureSink *sinkParam2 = new QgsProcessingParameterFeatureSink( "non_supports" );
352       sinkParam2->setSupportsNonFileBasedOutput( false );
353       addParameter( sinkParam2 );
354     }
355 
356 };
357 
358 //dummy provider for testing
359 class DummyProvider : public QgsProcessingProvider // clazy:exclude=missing-qobject-macro
360 {
361   public:
362 
DummyProvider(const QString & id)363     DummyProvider( const QString &id ) : mId( id ) {}
364 
id() const365     QString id() const override { return mId; }
366 
name() const367     QString name() const override { return "dummy"; }
368 
unload()369     void unload() override { if ( unloaded ) { *unloaded = true; } }
370 
defaultVectorFileExtension(bool) const371     QString defaultVectorFileExtension( bool ) const override
372     {
373       return "xshp"; // shape-X. Just like shapefiles, but to the max!
374     }
375 
defaultRasterFileExtension() const376     QString defaultRasterFileExtension() const override
377     {
378       return "pcx"; // next-gen raster storage
379     }
380 
supportsNonFileBasedOutput() const381     bool supportsNonFileBasedOutput() const override
382     {
383       return supportsNonFileOutputs;
384     }
385 
isActive() const386     bool isActive() const override
387     {
388       return active;
389     }
390 
391     bool active = true;
392     bool *unloaded = nullptr;
393     bool supportsNonFileOutputs = false;
394 
395   protected:
396 
loadAlgorithms()397     void loadAlgorithms() override
398     {
399       QVERIFY( addAlgorithm( new DummyAlgorithm( "alg1" ) ) );
400       QVERIFY( addAlgorithm( new DummyAlgorithm( "alg2" ) ) );
401 
402       //dupe name
403       QgsProcessingAlgorithm *a = new DummyAlgorithm( "alg1" );
404       QVERIFY( !addAlgorithm( a ) );
405       delete a;
406 
407       QVERIFY( !addAlgorithm( nullptr ) );
408     }
409 
410     QString mId;
411 
412     friend class TestQgsProcessing;
413 };
414 
415 class DummyProviderNoLoad : public DummyProvider // clazy:exclude=missing-qobject-macro
416 {
417   public:
418 
DummyProviderNoLoad(const QString & id)419     DummyProviderNoLoad( const QString &id ) : DummyProvider( id ) {}
420 
load()421     bool load() override
422     {
423       return false;
424     }
425 
426 };
427 
428 class DummyAlgorithm2 : public QgsProcessingAlgorithm
429 {
430   public:
431 
DummyAlgorithm2(const QString & name)432     DummyAlgorithm2( const QString &name ) : mName( name ) { mFlags = QgsProcessingAlgorithm::flags(); }
433 
initAlgorithm(const QVariantMap &=QVariantMap ())434     void initAlgorithm( const QVariantMap & = QVariantMap() ) override
435     {
436       addParameter( new QgsProcessingParameterVectorDestination( QStringLiteral( "vector_dest" ) ) );
437       addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "raster_dest" ) ) );
438       addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "sink" ) ) );
439     }
name() const440     QString name() const override { return mName; }
displayName() const441     QString displayName() const override { return mName; }
processAlgorithm(const QVariantMap &,QgsProcessingContext &,QgsProcessingFeedback *)442     QVariantMap processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); }
443 
flags() const444     Flags flags() const override { return mFlags; }
createInstance() const445     DummyAlgorithm2 *createInstance() const override { return new DummyAlgorithm2( name() ); }
446 
447     QString mName;
448 
449     Flags mFlags;
450 
451 };
452 
453 class DummyProvider3 : public QgsProcessingProvider // clazy:exclude=missing-qobject-macro
454 {
455   public:
456 
457     DummyProvider3()  = default;
id() const458     QString id() const override { return QStringLiteral( "dummy3" ); }
name() const459     QString name() const override { return QStringLiteral( "dummy3" ); }
460 
supportedOutputVectorLayerExtensions() const461     QStringList supportedOutputVectorLayerExtensions() const override
462     {
463       return QStringList() << QStringLiteral( "mif" ) << QStringLiteral( "tab" );
464     }
465 
supportedOutputTableExtensions() const466     QStringList supportedOutputTableExtensions() const override
467     {
468       return QStringList() << QStringLiteral( "dbf" );
469     }
470 
supportedOutputRasterLayerExtensions() const471     QStringList supportedOutputRasterLayerExtensions() const override
472     {
473       return QStringList() << QStringLiteral( "mig" ) << QStringLiteral( "sdat" );
474     }
475 
loadAlgorithms()476     void loadAlgorithms() override
477     {
478       QVERIFY( addAlgorithm( new DummyAlgorithm2( "alg1" ) ) );
479     }
480 
481 };
482 
483 class DummyProvider4 : public QgsProcessingProvider // clazy:exclude=missing-qobject-macro
484 {
485   public:
486 
487     DummyProvider4()  = default;
id() const488     QString id() const override { return QStringLiteral( "dummy4" ); }
name() const489     QString name() const override { return QStringLiteral( "dummy4" ); }
490 
supportsNonFileBasedOutput() const491     bool supportsNonFileBasedOutput() const override
492     {
493       return false;
494     }
495 
supportedOutputVectorLayerExtensions() const496     QStringList supportedOutputVectorLayerExtensions() const override
497     {
498       return QStringList() << QStringLiteral( "mif" );
499     }
500 
supportedOutputRasterLayerExtensions() const501     QStringList supportedOutputRasterLayerExtensions() const override
502     {
503       return QStringList() << QStringLiteral( "mig" );
504     }
505 
loadAlgorithms()506     void loadAlgorithms() override
507     {
508       QVERIFY( addAlgorithm( new DummyAlgorithm2( "alg1" ) ) );
509     }
510 
511 };
512 
513 class DummyParameterType : public QgsProcessingParameterType
514 {
515 
516 
517     // QgsProcessingParameterType interface
518   public:
create(const QString & name) const519     QgsProcessingParameterDefinition *create( const QString &name ) const override
520     {
521       return new QgsProcessingParameterString( name );
522     }
523 
description() const524     QString description() const override
525     {
526       return QStringLiteral( "Description" );
527     }
528 
name() const529     QString name() const override
530     {
531       return QStringLiteral( "ParamType" );
532     }
533 
id() const534     QString id() const override
535     {
536       return QStringLiteral( "paramType" );
537     }
538 };
539 
540 class DummyPluginLayer: public QgsPluginLayer
541 {
542   public:
543 
DummyPluginLayer(const QString & layerType,const QString & layerName)544     DummyPluginLayer( const QString &layerType, const QString &layerName ): QgsPluginLayer( layerType, layerName )
545     {
546       mValid = true;
547     };
548 
clone() const549     DummyPluginLayer *clone() const override { return new DummyPluginLayer( "dummylayer", "test" ); };
550 
createMapRenderer(QgsRenderContext & rendererContext)551     QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override
552     {
553       Q_UNUSED( rendererContext );
554       return nullptr;
555     };
556 
writeXml(QDomNode & layerNode,QDomDocument & doc,const QgsReadWriteContext & context) const557     bool writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const override
558     {
559       Q_UNUSED( layerNode );
560       Q_UNUSED( doc );
561       Q_UNUSED( context );
562       return true;
563     };
readSymbology(const QDomNode & node,QString & errorMessage,QgsReadWriteContext & context,StyleCategories categories=AllStyleCategories)564     bool readSymbology( const QDomNode &node, QString &errorMessage,
565                         QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories ) override
566     {
567       Q_UNUSED( node );
568       Q_UNUSED( errorMessage );
569       Q_UNUSED( context );
570       Q_UNUSED( categories );
571       return true;
572     };
writeSymbology(QDomNode & node,QDomDocument & doc,QString & errorMessage,const QgsReadWriteContext & context,StyleCategories categories=AllStyleCategories) const573     bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context,
574                          StyleCategories categories = AllStyleCategories ) const override
575     {
576       Q_UNUSED( node );
577       Q_UNUSED( doc );
578       Q_UNUSED( errorMessage );
579       Q_UNUSED( context );
580       Q_UNUSED( categories );
581       return true;
582     };
583 
setTransformContext(const QgsCoordinateTransformContext & transformContext)584     void setTransformContext( const QgsCoordinateTransformContext &transformContext ) override { Q_UNUSED( transformContext ); };
585 };
586 
587 class TestQgsProcessing: public QObject
588 {
589     Q_OBJECT
590 
591   private slots:
592     void initTestCase();// will be called before the first testfunction is executed.
593     void cleanupTestCase(); // will be called after the last testfunction was executed.
init()594     void init() {} // will be called before each testfunction is executed.
cleanup()595     void cleanup() {} // will be called after every testfunction.
596     void instance();
597     void addProvider();
598     void providerById();
599     void removeProvider();
600     void compatibleLayers();
601     void encodeDecodeUriProvider();
602     void normalizeLayerSource();
603     void context();
604     void feedback();
605     void mapLayers();
606     void mapLayerFromStore();
607     void mapLayerFromString();
608     void algorithm();
609     void features();
610     void uniqueValues();
611     void createIndex();
612     void generateTemporaryDestination();
613     void parseDestinationString();
614     void createFeatureSink();
615 #ifdef ENABLE_PGTEST
616     void createFeatureSinkPostgres();
617 #endif
618     void source();
619     void parameters();
620     void algorithmParameters();
621     void algorithmOutputs();
622     void parameterGeneral();
623     void parameterBoolean();
624     void parameterCrs();
625     void parameterMapLayer();
626     void parameterExtent();
627     void parameterPoint();
628     void parameterGeometry();
629     void parameterFile();
630     void parameterMatrix();
631     void parameterLayerList();
632     void parameterNumber();
633     void parameterDistance();
634     void parameterDuration();
635     void parameterScale();
636     void parameterRange();
637     void parameterRasterLayer();
638     void parameterEnum();
639     void parameterString();
640     void parameterAuthConfig();
641     void parameterExpression();
642     void parameterField();
643     void parameterVectorLayer();
644     void parameterMeshLayer();
645     void parameterFeatureSource();
646     void parameterFeatureSink();
647     void parameterVectorOut();
648     void parameterRasterOut();
649     void parameterFileOut();
650     void parameterFolderOut();
651     void parameterBand();
652     void parameterLayout();
653     void parameterLayoutItem();
654     void parameterColor();
655     void parameterCoordinateOperation();
656     void parameterMapTheme();
657     void parameterDateTime();
658     void parameterProviderConnection();
659     void parameterDatabaseSchema();
660     void parameterDatabaseTable();
661     void parameterFieldMapping();
662     void parameterAggregate();
663     void parameterTinInputLayers();
664     void parameterMeshDatasetGroups();
665     void parameterMeshDatasetTime();
666     void parameterDxfLayers();
667 #ifdef HAVE_EPT
668     void parameterPointCloudLayer();
669 #endif
670     void parameterAnnotationLayer();
671     void checkParamValues();
672     void combineLayerExtent();
673     void processingFeatureSource();
674     void processingFeatureSink();
675     void algorithmScope();
676     void modelScope();
677     void validateInputCrs();
678     void generateIteratingDestination();
679     void asPythonCommand();
680     void modelerAlgorithm();
681     void modelExecution();
682     void modelBranchPruning();
683     void modelBranchPruningConditional();
684     void modelWithProviderWithLimitedTypes();
685     void modelVectorOutputIsCompatibleType();
686     void modelAcceptableValues();
687     void modelValidate();
688     void modelInputs();
689     void modelDependencies();
690     void tempUtils();
691     void convertCompatible();
692     void create();
693     void combineFields();
694     void fieldNamesToIndices();
695     void indicesToFields();
696     void variantToPythonLiteral();
697     void stringToPythonLiteral();
698     void defaultExtensionsForProvider();
699     void supportedExtensions();
700     void supportsNonFileBasedOutput();
701     void addParameterType();
702     void removeParameterType();
703     void parameterTypes();
704     void parameterType();
705     void sourceTypeToString_data();
706     void sourceTypeToString();
707     void modelSource();
708 
709   private:
710 
711 };
712 
initTestCase()713 void TestQgsProcessing::initTestCase()
714 {
715   QgsApplication::init();
716   QgsApplication::initQgis();
717 
718   // Set up the QgsSettings environment
719   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
720   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
721   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
722 
723   QgsSettings settings;
724   settings.clear();
725 
726   QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) );
727 }
728 
cleanupTestCase()729 void TestQgsProcessing::cleanupTestCase()
730 {
731   QFile::remove( QDir::tempPath() + "/create_feature_sink.tab" );
732   QgsVectorFileWriter::deleteShapeFile( QDir::tempPath() + "/create_feature_sink2.gpkg" );
733 
734   QgsApplication::exitQgis();
735 }
736 
instance()737 void TestQgsProcessing::instance()
738 {
739   // test that application has a registry instance
740   QVERIFY( QgsApplication::processingRegistry() );
741 }
742 
addProvider()743 void TestQgsProcessing::addProvider()
744 {
745   QgsProcessingRegistry r;
746   QSignalSpy spyProviderAdded( &r, &QgsProcessingRegistry::providerAdded );
747 
748   QVERIFY( r.providers().isEmpty() );
749 
750   QVERIFY( !r.addProvider( nullptr ) );
751 
752   // add a provider
753   DummyProvider *p = new DummyProvider( "p1" );
754   QVERIFY( r.addProvider( p ) );
755   QCOMPARE( r.providers(), QList< QgsProcessingProvider * >() << p );
756   QCOMPARE( spyProviderAdded.count(), 1 );
757   QCOMPARE( spyProviderAdded.last().at( 0 ).toString(), QString( "p1" ) );
758 
759   //try adding another provider
760   DummyProvider *p2 = new DummyProvider( "p2" );
761   QVERIFY( r.addProvider( p2 ) );
762   QCOMPARE( qgis::listToSet( r.providers() ), QSet< QgsProcessingProvider * >() << p << p2 );
763   QCOMPARE( spyProviderAdded.count(), 2 );
764   QCOMPARE( spyProviderAdded.last().at( 0 ).toString(), QString( "p2" ) );
765 
766   //try adding a provider with duplicate id
767   DummyProvider *p3 = new DummyProvider( "p2" );
768   QVERIFY( !r.addProvider( p3 ) );
769   QCOMPARE( qgis::listToSet( r.providers() ), QSet< QgsProcessingProvider * >() << p << p2 );
770   QCOMPARE( spyProviderAdded.count(), 2 );
771 
772   // test that adding a provider which does not load means it is not added to registry
773   DummyProviderNoLoad *p4 = new DummyProviderNoLoad( "p4" );
774   QVERIFY( !r.addProvider( p4 ) );
775   QCOMPARE( qgis::listToSet( r.providers() ), QSet< QgsProcessingProvider * >() << p << p2 );
776   QCOMPARE( spyProviderAdded.count(), 2 );
777 }
778 
providerById()779 void TestQgsProcessing::providerById()
780 {
781   QgsProcessingRegistry r;
782 
783   // no providers
784   QVERIFY( !r.providerById( "p1" ) );
785 
786   // add a provider
787   DummyProvider *p = new DummyProvider( "p1" );
788   QVERIFY( r.addProvider( p ) );
789   QCOMPARE( r.providerById( "p1" ), p );
790   QVERIFY( !r.providerById( "p2" ) );
791 
792   //try adding another provider
793   DummyProvider *p2 = new DummyProvider( "p2" );
794   QVERIFY( r.addProvider( p2 ) );
795   QCOMPARE( r.providerById( "p1" ), p );
796   QCOMPARE( r.providerById( "p2" ), p2 );
797   QVERIFY( !r.providerById( "p3" ) );
798 }
799 
removeProvider()800 void TestQgsProcessing::removeProvider()
801 {
802   QgsProcessingRegistry r;
803   QSignalSpy spyProviderRemoved( &r, &QgsProcessingRegistry::providerRemoved );
804 
805   QVERIFY( !r.removeProvider( nullptr ) );
806   QVERIFY( !r.removeProvider( "p1" ) );
807   // provider not in registry
808   DummyProvider *p = new DummyProvider( "p1" );
809   QVERIFY( !r.removeProvider( p ) );
810   QCOMPARE( spyProviderRemoved.count(), 0 );
811 
812   // add some providers
813   QVERIFY( r.addProvider( p ) );
814   DummyProvider *p2 = new DummyProvider( "p2" );
815   QVERIFY( r.addProvider( p2 ) );
816 
817   // remove one by pointer
818   bool unloaded = false;
819   p->unloaded = &unloaded;
820   QVERIFY( r.removeProvider( p ) );
821   QCOMPARE( spyProviderRemoved.count(), 1 );
822   QCOMPARE( spyProviderRemoved.last().at( 0 ).toString(), QString( "p1" ) );
823   QCOMPARE( r.providers(), QList< QgsProcessingProvider * >() << p2 );
824 
825   //test that provider was unloaded
826   QVERIFY( unloaded );
827 
828   // should fail, already removed
829   QVERIFY( !r.removeProvider( "p1" ) );
830 
831   // remove one by id
832   QVERIFY( r.removeProvider( "p2" ) );
833   QCOMPARE( spyProviderRemoved.count(), 2 );
834   QCOMPARE( spyProviderRemoved.last().at( 0 ).toString(), QString( "p2" ) );
835   QVERIFY( r.providers().isEmpty() );
836 }
837 
compatibleLayers()838 void TestQgsProcessing::compatibleLayers()
839 {
840   QgsProject p;
841 
842   // add a bunch of layers to a project
843   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
844   const QString raster1 = testDataDir + "tenbytenraster.asc";
845   const QString raster2 = testDataDir + "landsat.tif";
846   const QString raster3 = testDataDir + "/raster/band1_float32_noct_epsg4326.tif";
847   const QFileInfo fi1( raster1 );
848   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
849   QVERIFY( r1->isValid() );
850   const QFileInfo fi2( raster2 );
851   QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" );
852   QVERIFY( r2->isValid() );
853   const QFileInfo fi3( raster3 );
854   QgsRasterLayer *r3 = new QgsRasterLayer( fi3.filePath(), "zz" );
855   QVERIFY( r3->isValid() );
856 
857   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" );
858   QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" );
859   QgsVectorLayer *v3 = new QgsVectorLayer( "LineString", "v3", "memory" );
860   QgsVectorLayer *v4 = new QgsVectorLayer( "none", "vvvv4", "memory" );
861 
862   const QFileInfo fm( testDataDir + "/mesh/quad_and_triangle.2dm" );
863   QgsMeshLayer *m1 = new QgsMeshLayer( fm.filePath(), "MX", "mdal" );
864   QVERIFY( m1->isValid() );
865   QgsMeshLayer *m2 = new QgsMeshLayer( fm.filePath(), "mA", "mdal" );
866   QVERIFY( m2->isValid() );
867 
868 #ifdef HAVE_EPT
869   QFileInfo fpc( testDataDir + "/point_clouds/ept/sunshine-coast/ept.json" );
870   QgsPointCloudLayer *pc1 = new QgsPointCloudLayer( fpc.filePath(), "PCX", "ept" );
871   QgsPointCloudLayer *pc2 = new QgsPointCloudLayer( fpc.filePath(), "pcA", "ept" );
872 #endif
873 
874   DummyPluginLayer *pl1 = new DummyPluginLayer( "dummylayer", "PX" );
875   DummyPluginLayer *pl2 = new DummyPluginLayer( "dummylayer", "pA" );
876 
877   QgsAnnotationLayer *al = new QgsAnnotationLayer( "secondary annotation layer", QgsAnnotationLayer::LayerOptions( p.transformContext() ) );
878 
879 #ifdef HAVE_EPT
880   p.addMapLayers( QList<QgsMapLayer *>() << r1 << r2 << r3 << v1 << v2 << v3 << v4 << m1 << m2 << pl1 << pl2 << pc1 << pc2 << al );
881 #else
882   p.addMapLayers( QList<QgsMapLayer *>() << r1 << r2 << r3 << v1 << v2 << v3 << v4 << m1 << m2 << pl1 << pl2 << al );
883 #endif
884 
885   // compatibleRasterLayers
886   QVERIFY( QgsProcessingUtils::compatibleRasterLayers( nullptr ).isEmpty() );
887 
888   // sorted
889   QStringList lIds;
890   const QList<QgsRasterLayer *>  layers = QgsProcessingUtils::compatibleRasterLayers( &p );
891   for ( QgsRasterLayer *rl : layers )
892     lIds << rl->name();
893   QCOMPARE( lIds, QStringList() << "ar2" << "R1" << "zz" );
894 
895   // unsorted
896   lIds.clear();
897   for ( QgsRasterLayer *rl : QgsProcessingUtils::compatibleRasterLayers( &p, false ) )
898     lIds << rl->name();
899   QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" );
900 
901   // compatibleMeshLayers
902   QVERIFY( QgsProcessingUtils::compatibleMeshLayers( nullptr ).isEmpty() );
903 
904   // sorted
905   lIds.clear();
906   for ( QgsMeshLayer *rl : QgsProcessingUtils::compatibleMeshLayers( &p ) )
907     lIds << rl->name();
908   QCOMPARE( lIds, QStringList() << "mA" << "MX" );
909 
910   // unsorted
911   lIds.clear();
912   for ( QgsMeshLayer *rl : QgsProcessingUtils::compatibleMeshLayers( &p, false ) )
913     lIds << rl->name();
914   QCOMPARE( lIds, QStringList() << "MX" << "mA" );
915 
916   // compatibleVectorLayers
917   QVERIFY( QgsProcessingUtils::compatibleVectorLayers( nullptr ).isEmpty() );
918 
919   // sorted
920   lIds.clear();
921   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p ) )
922     lIds << vl->name();
923   QCOMPARE( lIds, QStringList() << "v1" << "v3" << "V4" << "vvvv4" );
924 
925   // unsorted
926   lIds.clear();
927   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>(), false ) )
928     lIds << vl->name();
929   QCOMPARE( lIds, QStringList() << "V4" << "v1" << "v3" << "vvvv4" );
930 
931   // compatiblePluginLayers
932   QVERIFY( QgsProcessingUtils::compatiblePluginLayers( nullptr ).isEmpty() );
933 
934   // sorted
935   lIds.clear();
936   for ( QgsPluginLayer *pl : QgsProcessingUtils::compatiblePluginLayers( &p ) )
937     lIds << pl->name();
938   QCOMPARE( lIds, QStringList() << "pA" << "PX" );
939 
940   // unsorted
941   lIds.clear();
942   for ( QgsPluginLayer *pl : QgsProcessingUtils::compatiblePluginLayers( &p, false ) )
943     lIds << pl->name();
944   QCOMPARE( lIds, QStringList() << "PX" << "pA" );
945 
946 #ifdef HAVE_EPT
947   // compatiblePointCloudLayers
948   QVERIFY( QgsProcessingUtils::compatiblePointCloudLayers( nullptr ).isEmpty() );
949 
950   // sorted
951   lIds.clear();
952   for ( QgsPointCloudLayer *pcl : QgsProcessingUtils::compatiblePointCloudLayers( &p ) )
953     lIds << pcl->name();
954   QCOMPARE( lIds, QStringList() << "pcA" << "PCX" );
955 
956   // unsorted
957   lIds.clear();
958   for ( QgsPointCloudLayer *pcl : QgsProcessingUtils::compatiblePointCloudLayers( &p, false ) )
959     lIds << pcl->name();
960   QCOMPARE( lIds, QStringList() << "PCX" << "pcA" );
961 #endif
962 
963   // compatibleAnnotationLayers
964   QVERIFY( QgsProcessingUtils::compatibleAnnotationLayers( nullptr ).isEmpty() );
965 
966   // sorted
967   lIds.clear();
968   for ( QgsAnnotationLayer *pcl : QgsProcessingUtils::compatibleAnnotationLayers( &p ) )
969     lIds << pcl->name();
970   QCOMPARE( lIds, QStringList() << QObject::tr( "Annotations" ) << "secondary annotation layer" );
971 
972   // unsorted
973   lIds.clear();
974   for ( QgsAnnotationLayer *pcl : QgsProcessingUtils::compatibleAnnotationLayers( &p, false ) )
975     lIds << pcl->name();
976   QCOMPARE( lIds, QStringList() << "secondary annotation layer" << QObject::tr( "Annotations" ) );
977 
978   // point only
979   lIds.clear();
980   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVectorPoint ) )
981     lIds << vl->name();
982   QCOMPARE( lIds, QStringList() << "v1" );
983 
984   // polygon only
985   lIds.clear();
986   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVectorPolygon ) )
987     lIds << vl->name();
988   QCOMPARE( lIds, QStringList() << "V4" );
989 
990   // line only
991   lIds.clear();
992   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVectorLine ) )
993     lIds << vl->name();
994   QCOMPARE( lIds, QStringList() << "v3" );
995 
996   // point and line only
997   lIds.clear();
998   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine ) )
999     lIds << vl->name();
1000   QCOMPARE( lIds, QStringList() << "v1" << "v3" );
1001 
1002   // any vector w geometry
1003   lIds.clear();
1004   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVectorAnyGeometry ) )
1005     lIds << vl->name();
1006   QCOMPARE( lIds, QStringList() << "v1" << "v3" << "V4" );
1007 
1008   // any vector
1009   lIds.clear();
1010   for ( QgsVectorLayer *vl : QgsProcessingUtils::compatibleVectorLayers( &p, QList<int>() << QgsProcessing::TypeVector ) )
1011     lIds << vl->name();
1012   QCOMPARE( lIds, QStringList() << "v1" << "v3" << "V4" << "vvvv4" );
1013 
1014   // all layers
1015   QVERIFY( QgsProcessingUtils::compatibleLayers( nullptr ).isEmpty() );
1016 
1017   // sorted
1018   lIds.clear();
1019   for ( QgsMapLayer *l : QgsProcessingUtils::compatibleLayers( &p ) )
1020     lIds << l->name();
1021 #ifdef HAVE_EPT
1022   QCOMPARE( lIds, QStringList() << QObject::tr( "Annotations" ) << "ar2" << "mA" << "MX" << "pA" << "pcA" << "PCX" << "PX" << "R1" << "secondary annotation layer" << "v1" << "v3" << "V4" << "vvvv4" <<  "zz" );
1023 #else
1024   QCOMPARE( lIds, QStringList() << QObject::tr( "Annotations" ) << "ar2" << "mA" << "MX" << "pA" << "PX" << "R1" << "secondary annotation layer" << "v1" << "v3" << "V4" << "vvvv4" <<  "zz" );
1025 #endif
1026 
1027   // unsorted
1028   lIds.clear();
1029   for ( QgsMapLayer *l : QgsProcessingUtils::compatibleLayers( &p, false ) )
1030     lIds << l->name();
1031 #ifdef HAVE_EPT
1032   QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz"  << "V4" << "v1" << "v3" << "vvvv4" << "MX" << "mA" << "PCX" << "pcA" << "secondary annotation layer" << QObject::tr( "Annotations" ) << "PX" << "pA" );
1033 #else
1034   QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz"  << "V4" << "v1" << "v3" << "vvvv4" << "MX" << "mA" << "secondary annotation layer" << QObject::tr( "Annotations" ) << "PX" << "pA" );
1035 #endif
1036 }
1037 
encodeDecodeUriProvider()1038 void TestQgsProcessing::encodeDecodeUriProvider()
1039 {
1040   QString provider;
1041   QString uri;
1042   QCOMPARE( QgsProcessingUtils::encodeProviderKeyAndUri( QStringLiteral( "ogr" ), QStringLiteral( "/home/me/test.shp" ) ), QStringLiteral( "ogr:///home/me/test.shp" ) );
1043   QVERIFY( QgsProcessingUtils::decodeProviderKeyAndUri( QStringLiteral( "ogr:///home/me/test.shp" ), provider, uri ) );
1044   QCOMPARE( provider, QStringLiteral( "ogr" ) );
1045   QCOMPARE( uri, QStringLiteral( "/home/me/test.shp" ) );
1046   QCOMPARE( QgsProcessingUtils::encodeProviderKeyAndUri( QStringLiteral( "ogr" ), QStringLiteral( "http://mysourcem/a.json" ) ), QStringLiteral( "ogr://http://mysourcem/a.json" ) );
1047   QVERIFY( QgsProcessingUtils::decodeProviderKeyAndUri( QStringLiteral( "ogr://http://mysourcem/a.json" ), provider, uri ) );
1048   QCOMPARE( provider, QStringLiteral( "ogr" ) );
1049   QCOMPARE( uri, QStringLiteral( "http://mysourcem/a.json" ) );
1050   QCOMPARE( QgsProcessingUtils::encodeProviderKeyAndUri( QStringLiteral( "postgres" ), QStringLiteral( "host=blah blah etc" ) ), QStringLiteral( "postgres://host=blah blah etc" ) );
1051   QVERIFY( QgsProcessingUtils::decodeProviderKeyAndUri( QStringLiteral( "postgres://host=blah blah etc" ), provider, uri ) );
1052   QCOMPARE( provider, QStringLiteral( "postgres" ) );
1053   QCOMPARE( uri, QStringLiteral( "host=blah blah etc" ) );
1054 
1055   // should reject non valid providers
1056   QVERIFY( !QgsProcessingUtils::decodeProviderKeyAndUri( QStringLiteral( "asdasda://host=blah blah etc" ), provider, uri ) );
1057   QVERIFY( !QgsProcessingUtils::decodeProviderKeyAndUri( QStringLiteral( "http://mysourcem/a.json" ), provider, uri ) );
1058 }
1059 
normalizeLayerSource()1060 void TestQgsProcessing::normalizeLayerSource()
1061 {
1062   QCOMPARE( QgsProcessingUtils::normalizeLayerSource( "data\\layers\\test.shp" ), QString( "data/layers/test.shp" ) );
1063   QCOMPARE( QgsProcessingUtils::normalizeLayerSource( "data\\layers \"new\"\\test.shp" ), QString( "data/layers \"new\"/test.shp" ) );
1064 }
1065 
1066 
1067 class TestPostProcessor : public QgsProcessingLayerPostProcessorInterface
1068 {
1069   public:
1070 
TestPostProcessor(bool * deleted)1071     TestPostProcessor( bool *deleted )
1072       : deleted( deleted )
1073     {}
1074 
~TestPostProcessor()1075     ~TestPostProcessor() override
1076     {
1077       *deleted = true;
1078     }
1079 
1080     bool *deleted = nullptr;
1081 
postProcessLayer(QgsMapLayer *,QgsProcessingContext &,QgsProcessingFeedback *)1082     void postProcessLayer( QgsMapLayer *, QgsProcessingContext &, QgsProcessingFeedback * ) override
1083     {
1084     }
1085 };
1086 
1087 
context()1088 void TestQgsProcessing::context()
1089 {
1090   QgsProcessingContext context;
1091 
1092   // simple tests for getters/setters
1093   context.setDefaultEncoding( "my_enc" );
1094   QCOMPARE( context.defaultEncoding(), QStringLiteral( "my_enc" ) );
1095 
1096   context.setFlags( QgsProcessingContext::Flags() );
1097   QCOMPARE( context.flags(), QgsProcessingContext::Flags() );
1098 
1099   QCOMPARE( context.ellipsoid(), QString() );
1100   QCOMPARE( context.distanceUnit(), QgsUnitTypes::DistanceUnknownUnit );
1101   QCOMPARE( context.areaUnit(), QgsUnitTypes::AreaUnknownUnit );
1102 
1103   QgsProject p;
1104   p.setCrs( QgsCoordinateReferenceSystem( "EPSG:4536" ) );
1105   p.setEllipsoid( QStringLiteral( "WGS84" ) );
1106   p.setDistanceUnits( QgsUnitTypes::DistanceFeet );
1107   p.setAreaUnits( QgsUnitTypes::AreaHectares );
1108   context.setProject( &p );
1109   QCOMPARE( context.project(), &p );
1110   QCOMPARE( context.ellipsoid(), QStringLiteral( "WGS84" ) );
1111   QCOMPARE( context.distanceUnit(), QgsUnitTypes::DistanceFeet );
1112   QCOMPARE( context.areaUnit(), QgsUnitTypes::AreaHectares );
1113 
1114   // if context ellipsoid/units are already set then setting the project shouldn't overwrite them
1115   p.setEllipsoid( QStringLiteral( "WGS84v2" ) );
1116   p.setDistanceUnits( QgsUnitTypes::DistanceMiles );
1117   p.setAreaUnits( QgsUnitTypes::AreaAcres );
1118   context.setProject( &p );
1119   QCOMPARE( context.ellipsoid(), QStringLiteral( "WGS84" ) );
1120   QCOMPARE( context.distanceUnit(), QgsUnitTypes::DistanceFeet );
1121   QCOMPARE( context.areaUnit(), QgsUnitTypes::AreaHectares );
1122 
1123   context.setLogLevel( QgsProcessingContext::Verbose );
1124   QCOMPARE( static_cast< int >( context.logLevel() ), static_cast< int >( QgsProcessingContext::Verbose ) );
1125 
1126   context.setInvalidGeometryCheck( QgsFeatureRequest::GeometrySkipInvalid );
1127   QCOMPARE( context.invalidGeometryCheck(), QgsFeatureRequest::GeometrySkipInvalid );
1128 
1129   QgsVectorLayer *vector = new QgsVectorLayer( "Polygon", "vector", "memory" );
1130   context.temporaryLayerStore()->addMapLayer( vector );
1131   QCOMPARE( context.temporaryLayerStore()->mapLayer( vector->id() ), vector );
1132 
1133   QgsProcessingContext context2;
1134   context2.copyThreadSafeSettings( context );
1135   QCOMPARE( context2.defaultEncoding(), context.defaultEncoding() );
1136   QCOMPARE( context2.invalidGeometryCheck(), context.invalidGeometryCheck() );
1137   QCOMPARE( context2.flags(), context.flags() );
1138   QCOMPARE( context2.project(), context.project() );
1139   QCOMPARE( static_cast< int >( context2.logLevel() ), static_cast< int >( QgsProcessingContext::Verbose ) );
1140   // layers from temporaryLayerStore must not be copied by copyThreadSafeSettings
1141   QVERIFY( context2.temporaryLayerStore()->mapLayers().isEmpty() );
1142 
1143   // layers to load on completion
1144   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V1", "memory" );
1145   QgsVectorLayer *v2 = new QgsVectorLayer( "Polygon", "V2", "memory" );
1146   QVERIFY( context.layersToLoadOnCompletion().isEmpty() );
1147   QVERIFY( !context.willLoadLayerOnCompletion( v1->id() ) );
1148   QVERIFY( !context.willLoadLayerOnCompletion( v2->id() ) );
1149   QMap< QString, QgsProcessingContext::LayerDetails > layers;
1150   QgsProcessingContext::LayerDetails details( QStringLiteral( "v1" ), &p );
1151   bool ppDeleted = false;
1152   TestPostProcessor *pp = new TestPostProcessor( &ppDeleted );
1153   details.setPostProcessor( pp );
1154   layers.insert( v1->id(), details );
1155   context.setLayersToLoadOnCompletion( layers );
1156   QCOMPARE( context.layersToLoadOnCompletion().count(), 1 );
1157   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() );
1158   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v1" ) );
1159   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).postProcessor(), pp );
1160   QVERIFY( context.willLoadLayerOnCompletion( v1->id() ) );
1161   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).name, QStringLiteral( "v1" ) );
1162   QVERIFY( !context.willLoadLayerOnCompletion( v2->id() ) );
1163   context.addLayerToLoadOnCompletion( v2->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v2" ), &p ) );
1164   QCOMPARE( context.layersToLoadOnCompletion().count(), 2 );
1165   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() );
1166   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v1" ) );
1167   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).postProcessor(), pp );
1168   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() );
1169   QCOMPARE( context.layersToLoadOnCompletion().values().at( 1 ).name, QStringLiteral( "v2" ) );
1170   QVERIFY( !context.layersToLoadOnCompletion().values().at( 1 ).postProcessor() );
1171   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).name, QStringLiteral( "v1" ) );
1172   QCOMPARE( context.layerToLoadOnCompletionDetails( v2->id() ).name, QStringLiteral( "v2" ) );
1173   QVERIFY( context.willLoadLayerOnCompletion( v1->id() ) );
1174   QVERIFY( context.willLoadLayerOnCompletion( v2->id() ) );
1175 
1176   // ensure that copyThreadSafeSettings doesn't copy layersToLoadOnCompletion()
1177   context2.copyThreadSafeSettings( context );
1178   QVERIFY( context2.layersToLoadOnCompletion().isEmpty() );
1179 
1180   layers.clear();
1181   layers.insert( v2->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v2" ), &p ) );
1182   context.setLayersToLoadOnCompletion( layers );
1183   QVERIFY( ppDeleted );
1184 
1185   QCOMPARE( context.layersToLoadOnCompletion().count(), 1 );
1186   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v2->id() );
1187   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v2" ) );
1188   context.addLayerToLoadOnCompletion( v1->id(), QgsProcessingContext::LayerDetails( QString(), &p ) );
1189   QCOMPARE( context.layersToLoadOnCompletion().count(), 2 );
1190   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() );
1191   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() );
1192 
1193   context.temporaryLayerStore()->addMapLayer( v1 );
1194   context.temporaryLayerStore()->addMapLayer( v2 );
1195 
1196   // test takeResultsFrom
1197   context2.takeResultsFrom( context );
1198   QVERIFY( context.temporaryLayerStore()->mapLayers().isEmpty() );
1199   QVERIFY( context.layersToLoadOnCompletion().isEmpty() );
1200   // should now be in context2
1201   QCOMPARE( context2.temporaryLayerStore()->mapLayer( v1->id() ), v1 );
1202   QCOMPARE( context2.temporaryLayerStore()->mapLayer( v2->id() ), v2 );
1203   QCOMPARE( context2.layersToLoadOnCompletion().count(), 2 );
1204   QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), v1->id() );
1205   QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 1 ), v2->id() );
1206 
1207   // make sure postprocessor is correctly deleted
1208   ppDeleted = false;
1209   pp = new TestPostProcessor( &ppDeleted );
1210   details = QgsProcessingContext::LayerDetails( QStringLiteral( "v1" ), &p );
1211   details.setPostProcessor( pp );
1212   layers.insert( v1->id(), details );
1213   context.setLayersToLoadOnCompletion( layers );
1214   // overwrite with existing
1215   context.setLayersToLoadOnCompletion( layers );
1216   QVERIFY( !ppDeleted );
1217   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).postProcessor(), pp );
1218   bool pp2Deleted = false;
1219   TestPostProcessor *pp2 = new TestPostProcessor( &pp2Deleted );
1220   details = QgsProcessingContext::LayerDetails( QStringLiteral( "v1" ), &p );
1221   details.setPostProcessor( pp2 );
1222   layers.insert( v1->id(), details );
1223   context.setLayersToLoadOnCompletion( layers );
1224   QVERIFY( ppDeleted );
1225   QVERIFY( !pp2Deleted );
1226   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).postProcessor(), pp2 );
1227   ppDeleted = false;
1228   pp = new TestPostProcessor( &ppDeleted );
1229   details = QgsProcessingContext::LayerDetails( QStringLiteral( "v1" ), &p );
1230   details.setPostProcessor( pp );
1231   context.addLayerToLoadOnCompletion( v1->id(), details );
1232   QVERIFY( !ppDeleted );
1233   QVERIFY( pp2Deleted );
1234   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).postProcessor(), pp );
1235   pp2Deleted = false;
1236   pp2 = new TestPostProcessor( &pp2Deleted );
1237   context.layerToLoadOnCompletionDetails( v1->id() ).setPostProcessor( pp2 );
1238   QVERIFY( ppDeleted );
1239   QVERIFY( !pp2Deleted );
1240   QCOMPARE( context.layerToLoadOnCompletionDetails( v1->id() ).postProcessor(), pp2 );
1241 
1242   // take result layer
1243   QgsMapLayer *result = context2.takeResultLayer( v1->id() );
1244   QCOMPARE( result, v1 );
1245   QString id = v1->id();
1246   delete v1;
1247   QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) );
1248   QVERIFY( !context2.takeResultLayer( id ) );
1249   result = context2.takeResultLayer( v2->id() );
1250   QCOMPARE( result, v2 );
1251   id = v2->id();
1252   delete v2;
1253   QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) );
1254 }
1255 
feedback()1256 void TestQgsProcessing::feedback()
1257 {
1258   QgsProcessingFeedback f;
1259   f.pushInfo( QStringLiteral( "info" ) );
1260   f.reportError( QStringLiteral( "error" ) );
1261   f.pushDebugInfo( QStringLiteral( "debug" ) );
1262   f.pushCommandInfo( QStringLiteral( "command" ) );
1263   f.pushConsoleInfo( QStringLiteral( "console" ) );
1264 
1265   QCOMPARE( f.htmlLog(), QStringLiteral( "info<br/><span style=\"color:red\">error</span><br/><span style=\"color:#777\">debug</span><br/><code>command</code><br/><code style=\"color:#777\">console</code><br/>" ) );
1266   QCOMPARE( f.textLog(), QStringLiteral( "info\nerror\ndebug\ncommand\nconsole\n" ) );
1267 }
1268 
mapLayers()1269 void TestQgsProcessing::mapLayers()
1270 {
1271   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
1272   const QString raster = testDataDir + "landsat.tif";
1273   const QString vector = testDataDir + "points.shp";
1274 
1275   // test loadMapLayerFromString with raster
1276   QgsMapLayer *l = QgsProcessingUtils::loadMapLayerFromString( raster, QgsCoordinateTransformContext() );
1277   QVERIFY( l->isValid() );
1278   QCOMPARE( l->type(), QgsMapLayerType::RasterLayer );
1279   QCOMPARE( l->name(), QStringLiteral( "landsat" ) );
1280 
1281   delete l;
1282 
1283   // use encoded provider/uri string
1284   l = QgsProcessingUtils::loadMapLayerFromString( QStringLiteral( "gdal://%1" ).arg( raster ), QgsCoordinateTransformContext() );
1285   QVERIFY( l->isValid() );
1286   QCOMPARE( l->type(), QgsMapLayerType::RasterLayer );
1287   QCOMPARE( l->name(), QStringLiteral( "landsat" ) );
1288   delete l;
1289 
1290   //test with vector
1291   l = QgsProcessingUtils::loadMapLayerFromString( vector, QgsCoordinateTransformContext() );
1292   QVERIFY( l->isValid() );
1293   QCOMPARE( l->type(), QgsMapLayerType::VectorLayer );
1294   QCOMPARE( l->name(), QStringLiteral( "points" ) );
1295   delete l;
1296 
1297   // use encoded provider/uri string
1298   l = QgsProcessingUtils::loadMapLayerFromString( QStringLiteral( "ogr://%1" ).arg( vector ), QgsCoordinateTransformContext() );
1299   QVERIFY( l->isValid() );
1300   QCOMPARE( l->type(), QgsMapLayerType::VectorLayer );
1301   QCOMPARE( l->name(), QStringLiteral( "points" ) );
1302   delete l;
1303 
1304   l = QgsProcessingUtils::loadMapLayerFromString( QString(), QgsCoordinateTransformContext() );
1305   QVERIFY( !l );
1306   l = QgsProcessingUtils::loadMapLayerFromString( QStringLiteral( "so much room for activities!" ), QgsCoordinateTransformContext() );
1307   QVERIFY( !l );
1308   l = QgsProcessingUtils::loadMapLayerFromString( testDataDir + "multipoint.shp", QgsCoordinateTransformContext() );
1309   QVERIFY( l->isValid() );
1310   QCOMPARE( l->type(), QgsMapLayerType::VectorLayer );
1311   QCOMPARE( l->name(), QStringLiteral( "multipoint" ) );
1312   delete l;
1313 
1314   // Test layers from a string with parameters
1315   const QString osmFilePath = testDataDir + "openstreetmap/testdata.xml";
1316   std::unique_ptr< QgsVectorLayer > osm( qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath, QgsCoordinateTransformContext() ) ) );
1317   QVERIFY( osm->isValid() );
1318   QCOMPARE( osm->geometryType(), QgsWkbTypes::PointGeometry );
1319 
1320   osm.reset( qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath + "|layerid=3", QgsCoordinateTransformContext() ) ) );
1321   QVERIFY( osm->isValid() );
1322   QCOMPARE( osm->geometryType(), QgsWkbTypes::PolygonGeometry );
1323 
1324   osm.reset( qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath + "|layerid=3|subset=\"building\" is not null", QgsCoordinateTransformContext() ) ) );
1325   QVERIFY( osm->isValid() );
1326   QCOMPARE( osm->geometryType(), QgsWkbTypes::PolygonGeometry );
1327   QCOMPARE( osm->subsetString(), QStringLiteral( "\"building\" is not null" ) );
1328 }
1329 
mapLayerFromStore()1330 void TestQgsProcessing::mapLayerFromStore()
1331 {
1332   // test mapLayerFromStore
1333 
1334   QgsMapLayerStore store;
1335 
1336   // add a bunch of layers to a project
1337   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
1338   const QString raster1 = testDataDir + "tenbytenraster.asc";
1339   const QString raster2 = testDataDir + "landsat.tif";
1340   const QFileInfo fi1( raster1 );
1341   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
1342   QVERIFY( r1->isValid() );
1343   const QFileInfo fi2( raster2 );
1344   QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" );
1345   QVERIFY( r2->isValid() );
1346 
1347   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" );
1348   QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" );
1349   store.addMapLayers( QList<QgsMapLayer *>() << r1 << r2 << v1 << v2 );
1350 
1351   QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QString(), nullptr ) );
1352   QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QStringLiteral( "v1" ), nullptr ) );
1353   QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QString(), &store ) );
1354   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( raster1, &store ), r1 );
1355   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( raster2, &store ), r2 );
1356   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "R1", &store ), r1 );
1357   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "ar2", &store ), r2 );
1358   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "V4", &store ), v1 );
1359   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "v1", &store ), v2 );
1360   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( r1->id(), &store ), r1 );
1361   QCOMPARE( QgsProcessingUtils::mapLayerFromStore( v1->id(), &store ), v1 );
1362 }
1363 
mapLayerFromString()1364 void TestQgsProcessing::mapLayerFromString()
1365 {
1366   // test mapLayerFromString
1367 
1368   QgsProcessingContext c;
1369   QgsProject p;
1370 
1371   // add a bunch of layers to a project
1372   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
1373   const QString raster1 = testDataDir + "tenbytenraster.asc";
1374   const QString raster2 = testDataDir + "landsat.tif";
1375   const QFileInfo fi1( raster1 );
1376   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
1377   QVERIFY( r1->isValid() );
1378   const QFileInfo fi2( raster2 );
1379   QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" );
1380   QVERIFY( r2->isValid() );
1381 
1382   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" );
1383   QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" );
1384   p.addMapLayers( QList<QgsMapLayer *>() << r1 << r2 << v1 << v2 );
1385 
1386   // no project set yet
1387   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QString(), c ) );
1388   QVERIFY( !c.getMapLayer( QString() ) );
1389   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QStringLiteral( "v1" ), c ) );
1390   QVERIFY( !c.getMapLayer( QStringLiteral( "v1" ) ) );
1391 
1392   c.setProject( &p );
1393 
1394   // layers from current project
1395   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QString(), c ) );
1396   QVERIFY( !c.getMapLayer( QString() ) );
1397   QCOMPARE( QgsProcessingUtils::mapLayerFromString( raster1, c ), r1 );
1398   QCOMPARE( c.getMapLayer( raster1 ), r1 );
1399   QCOMPARE( QgsProcessingUtils::mapLayerFromString( raster1, c, true, QgsProcessingUtils::LayerHint::Raster ), r1 );
1400   QVERIFY( !QgsProcessingUtils::mapLayerFromString( raster1, c, true, QgsProcessingUtils::LayerHint::Vector ) );
1401   QCOMPARE( QgsProcessingUtils::mapLayerFromString( raster2, c ), r2 );
1402   QCOMPARE( c.getMapLayer( raster2 ), r2 );
1403   QVERIFY( !QgsProcessingUtils::mapLayerFromString( raster2, c, true, QgsProcessingUtils::LayerHint::Vector ) );
1404   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "R1", c ), r1 );
1405   QCOMPARE( c.getMapLayer( "R1" ), r1 );
1406   QVERIFY( !QgsProcessingUtils::mapLayerFromString( "R1", c, true, QgsProcessingUtils::LayerHint::Vector ) );
1407   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "ar2", c ), r2 );
1408   QCOMPARE( c.getMapLayer( "ar2" ), r2 );
1409   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V4", c ), v1 );
1410   QCOMPARE( c.getMapLayer( "V4" ), v1 );
1411   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V4", c, true, QgsProcessingUtils::LayerHint::Vector ), v1 );
1412   QVERIFY( !QgsProcessingUtils::mapLayerFromString( "V4", c, true, QgsProcessingUtils::LayerHint::Raster ) );
1413   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "v1", c ), v2 );
1414   QCOMPARE( c.getMapLayer( "v1" ), v2 );
1415   QCOMPARE( QgsProcessingUtils::mapLayerFromString( r1->id(), c ), r1 );
1416   QCOMPARE( c.getMapLayer( r1->id() ), r1 );
1417   QCOMPARE( QgsProcessingUtils::mapLayerFromString( v1->id(), c ), v1 );
1418   QCOMPARE( c.getMapLayer( v1->id() ), v1 );
1419 
1420   // check that layers in context temporary store are used
1421   QgsVectorLayer *v5 = new QgsVectorLayer( "Polygon", "V5", "memory" );
1422   QgsVectorLayer *v6 = new QgsVectorLayer( "Point", "v6", "memory" );
1423   c.temporaryLayerStore()->addMapLayers( QList<QgsMapLayer *>() << v5 << v6 );
1424   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V5", c ), v5 );
1425   QCOMPARE( c.getMapLayer( "V5" ), v5 );
1426   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V5", c, true, QgsProcessingUtils::LayerHint::Vector ), v5 );
1427   QVERIFY( !QgsProcessingUtils::mapLayerFromString( "V5", c, true, QgsProcessingUtils::LayerHint::Raster ) );
1428   QCOMPARE( QgsProcessingUtils::mapLayerFromString( "v6", c ), v6 );
1429   QCOMPARE( c.getMapLayer( "v6" ), v6 );
1430   QCOMPARE( QgsProcessingUtils::mapLayerFromString( v5->id(), c ), v5 );
1431   QCOMPARE( c.getMapLayer( v5->id() ), v5 );
1432   QCOMPARE( QgsProcessingUtils::mapLayerFromString( v6->id(), c ), v6 );
1433   QCOMPARE( c.getMapLayer( v6->id() ), v6 );
1434   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( "aaaaa", c ) );
1435   QVERIFY( !c.getMapLayer( "aaaa" ) );
1436 
1437   // if specified, check that layers can be loaded
1438   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( "aaaaa", c ) );
1439   const QString newRaster = testDataDir + "requires_warped_vrt.tif";
1440   // don't allow loading
1441   QVERIFY( ! QgsProcessingUtils::mapLayerFromString( newRaster, c, false ) );
1442   QVERIFY( !c.getMapLayer( newRaster ) );
1443   // allow loading
1444   QgsMapLayer *loadedLayer = QgsProcessingUtils::mapLayerFromString( newRaster, c, true );
1445   QVERIFY( loadedLayer->isValid() );
1446   QCOMPARE( loadedLayer->type(), QgsMapLayerType::RasterLayer );
1447   // should now be in temporary store
1448   QCOMPARE( c.temporaryLayerStore()->mapLayer( loadedLayer->id() ), loadedLayer );
1449 
1450   // since it's now in temporary store, should be accessible even if we deny loading new layers
1451   QCOMPARE( QgsProcessingUtils::mapLayerFromString( newRaster, c, false ), loadedLayer );
1452   QCOMPARE( c.getMapLayer( newRaster ), loadedLayer );
1453 }
1454 
algorithm()1455 void TestQgsProcessing::algorithm()
1456 {
1457   DummyAlgorithm alg( "test" );
1458   DummyProvider *p = new DummyProvider( "p1" );
1459   QCOMPARE( alg.id(), QString( "test" ) );
1460   alg.setProvider( p );
1461   QCOMPARE( alg.provider(), p );
1462   QCOMPARE( alg.id(), QString( "p1:test" ) );
1463 
1464   QVERIFY( p->algorithms().isEmpty() );
1465 
1466   const QSignalSpy providerRefreshed( p, &DummyProvider::algorithmsLoaded );
1467   p->refreshAlgorithms();
1468   QCOMPARE( providerRefreshed.count(), 1 );
1469 
1470   for ( int i = 0; i < 2; ++i )
1471   {
1472     QCOMPARE( p->algorithms().size(), 2 );
1473     QCOMPARE( p->algorithm( "alg1" )->name(), QStringLiteral( "alg1" ) );
1474     QCOMPARE( p->algorithm( "alg1" )->provider(), p );
1475     QCOMPARE( p->algorithm( "alg2" )->provider(), p );
1476     QCOMPARE( p->algorithm( "alg2" )->name(), QStringLiteral( "alg2" ) );
1477     QVERIFY( !p->algorithm( "aaaa" ) );
1478     QVERIFY( p->algorithms().contains( p->algorithm( "alg1" ) ) );
1479     QVERIFY( p->algorithms().contains( p->algorithm( "alg2" ) ) );
1480 
1481     // reload, then retest on next loop
1482     // must be safe for providers to reload their algorithms
1483     p->refreshAlgorithms();
1484     QCOMPARE( providerRefreshed.count(), 2 + i );
1485   }
1486 
1487   // inactive provider, should not load algorithms
1488   p->active = false;
1489   p->refreshAlgorithms();
1490   QCOMPARE( providerRefreshed.count(), 3 );
1491   QVERIFY( p->algorithms().empty() );
1492   p->active = true;
1493   p->refreshAlgorithms();
1494   QCOMPARE( providerRefreshed.count(), 4 );
1495   QVERIFY( !p->algorithms().empty() );
1496 
1497   QgsProcessingRegistry r;
1498   QVERIFY( r.addProvider( p ) );
1499   QCOMPARE( r.algorithms().size(), 2 );
1500   QVERIFY( r.algorithms().contains( p->algorithm( "alg1" ) ) );
1501   QVERIFY( r.algorithms().contains( p->algorithm( "alg2" ) ) );
1502 
1503   // algorithmById
1504   QCOMPARE( r.algorithmById( "p1:alg1" ), p->algorithm( "alg1" ) );
1505   QCOMPARE( r.algorithmById( "p1:alg2" ), p->algorithm( "alg2" ) );
1506   QVERIFY( !r.algorithmById( "p1:alg3" ) );
1507   QVERIFY( !r.algorithmById( "px:alg1" ) );
1508 
1509   // alias support
1510   QVERIFY( !r.algorithmById( QStringLiteral( "fake:fakealg" ) ) );
1511   r.addAlgorithmAlias( QStringLiteral( "fake:fakealg" ), QStringLiteral( "nope:none" ) );
1512   QVERIFY( !r.algorithmById( QStringLiteral( "fake:fakealg" ) ) );
1513   r.addAlgorithmAlias( QStringLiteral( "fake:fakealg" ), QStringLiteral( "p1:alg1" ) );
1514   QCOMPARE( r.algorithmById( QStringLiteral( "fake:fakealg" ) ), p->algorithm( "alg1" ) );
1515 
1516   // test that algorithmById can transparently map 'qgis' algorithms across to matching 'native' algorithms
1517   // this allows us the freedom to convert qgis python algs to c++ without breaking api or existing models
1518   QCOMPARE( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:dissolve" ) )->id(), QStringLiteral( "native:dissolve" ) );
1519   QCOMPARE( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:clip" ) )->id(), QStringLiteral( "native:clip" ) );
1520   QVERIFY( !QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:notanalg" ) ) );
1521 
1522   // createAlgorithmById
1523   QVERIFY( !r.createAlgorithmById( "p1:alg3" ) );
1524   std::unique_ptr< QgsProcessingAlgorithm > creation( r.createAlgorithmById( "p1:alg1" ) );
1525   QVERIFY( creation.get() );
1526   QCOMPARE( creation->provider()->id(), QStringLiteral( "p1" ) );
1527   QCOMPARE( creation->id(), QStringLiteral( "p1:alg1" ) );
1528   creation.reset( r.createAlgorithmById( "p1:alg2" ) );
1529   QVERIFY( creation.get() );
1530   QCOMPARE( creation->provider()->id(), QStringLiteral( "p1" ) );
1531   QCOMPARE( creation->id(), QStringLiteral( "p1:alg2" ) );
1532 
1533   //test that loading a provider triggers an algorithm refresh
1534   DummyProvider *p2 = new DummyProvider( "p2" );
1535   QVERIFY( p2->algorithms().isEmpty() );
1536   p2->load();
1537   QCOMPARE( p2->algorithms().size(), 2 );
1538   delete p2;
1539 
1540   // test that adding a provider to the registry automatically refreshes algorithms (via load)
1541   DummyProvider *p3 = new DummyProvider( "p3" );
1542   QVERIFY( p3->algorithms().isEmpty() );
1543   QVERIFY( r.addProvider( p3 ) );
1544   QCOMPARE( p3->algorithms().size(), 2 );
1545 }
1546 
features()1547 void TestQgsProcessing::features()
1548 {
1549   QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" );
1550   for ( int i = 1; i < 6; ++i )
1551   {
1552     QgsFeature f( i );
1553     f.setGeometry( QgsGeometry( new QgsPoint( 1, 2 ) ) );
1554     layer->dataProvider()->addFeatures( QgsFeatureList() << f );
1555   }
1556 
1557   QgsProject p;
1558   p.addMapLayer( layer );
1559 
1560   QgsProcessingContext context;
1561   context.setProject( &p );
1562   // disable check for geometry validity
1563   context.setFlags( QgsProcessingContext::Flags() );
1564 
1565   const std::function< QgsFeatureIds( QgsFeatureIterator it ) > getIds = []( QgsFeatureIterator it )
1566   {
1567     QgsFeature f;
1568     QgsFeatureIds ids;
1569     while ( it.nextFeature( f ) )
1570     {
1571       ids << f.id();
1572     }
1573     return ids;
1574   };
1575 
1576   const std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
1577   QVariantMap params;
1578   params.insert( QStringLiteral( "layer" ), layer->id() );
1579 
1580   std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1581 
1582   // test with all features
1583   QgsFeatureIds ids = getIds( source->getFeatures() );
1584   QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 );
1585   QCOMPARE( source->featureCount(), 5L );
1586 
1587   // test with selected features
1588   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
1589   layer->selectByIds( QgsFeatureIds() << 2 << 4 );
1590   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1591   ids = getIds( source->getFeatures() );
1592   QCOMPARE( ids, QgsFeatureIds() << 2 << 4 );
1593   QCOMPARE( source->featureCount(), 2L );
1594 
1595   // selection, but not using selected features
1596   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
1597   layer->selectByIds( QgsFeatureIds() << 2 << 4 );
1598   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1599   ids = getIds( source->getFeatures() );
1600   QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 );
1601   QCOMPARE( source->featureCount(), 5L );
1602 
1603   // using selected features, but no selection
1604   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
1605   layer->removeSelection();
1606   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1607   ids = getIds( source->getFeatures() );
1608   QVERIFY( ids.isEmpty() );
1609   QCOMPARE( source->featureCount(), 0L );
1610 
1611   // feature limit
1612   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false, 3 ) ) );
1613   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1614   ids = getIds( source->getFeatures() );
1615   QCOMPARE( ids.size(), 3 );
1616   QCOMPARE( source->featureCount(), 3L );
1617 
1618   // test that feature request is honored
1619   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
1620   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1621   ids = getIds( source->getFeatures( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << 1 << 3 << 5 ) ) );
1622   QCOMPARE( ids, QgsFeatureIds() << 1 << 3 << 5 );
1623 
1624   // count is only rough - but we expect (for now) to see full layer count
1625   QCOMPARE( source->featureCount(), 5L );
1626 
1627   //test that feature request is honored when using selections
1628   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
1629   layer->selectByIds( QgsFeatureIds() << 2 << 4 );
1630   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1631   ids = getIds( source->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) ) );
1632   QCOMPARE( ids, QgsFeatureIds() << 2 << 4 );
1633 
1634   // test callback is hit when filtering invalid geoms
1635   bool encountered = false;
1636   const std::function< void( const QgsFeature & ) > callback = [ &encountered ]( const QgsFeature & )
1637   {
1638     encountered = true;
1639   };
1640 
1641   context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid );
1642   context.setInvalidGeometryCallback( callback );
1643   QgsVectorLayer *polyLayer = new QgsVectorLayer( "Polygon", "v2", "memory" );
1644   QgsFeature f;
1645   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 1 0, 0 1, 1 1, 0 0))" ) ) );
1646   polyLayer->dataProvider()->addFeatures( QgsFeatureList() << f );
1647   p.addMapLayer( polyLayer );
1648   params.insert( QStringLiteral( "layer" ), polyLayer->id() );
1649 
1650   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1651   ids = getIds( source->getFeatures() );
1652   QVERIFY( encountered );
1653 
1654   encountered = false;
1655   context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryNoCheck );
1656   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1657   ids = getIds( source->getFeatures() );
1658   QVERIFY( !encountered );
1659 
1660   // context wants to filter, but filtering disabled on source definition
1661   context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid );
1662   context.setInvalidGeometryCallback( callback );
1663   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( polyLayer->id(), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometryNoCheck ) ) );
1664 
1665   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1666   ids = getIds( source->getFeatures() );
1667   QVERIFY( !encountered );
1668 
1669   QgsProcessingContext context2;
1670   // context wants to skip, source wants to abort
1671   context2.setInvalidGeometryCheck( QgsFeatureRequest::GeometrySkipInvalid );
1672   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( polyLayer->id(), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometryAbortOnInvalid ) ) );
1673   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1674   try
1675   {
1676     ids = getIds( source->getFeatures() );
1677     QVERIFY( false );
1678   }
1679   catch ( QgsProcessingException & )
1680   {}
1681 
1682   // equality operator
1683   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) == QgsProcessingFeatureSourceDefinition( layer->id(), true ) );
1684   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( "b", true ) );
1685   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( layer->id(), false ) );
1686   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( layer->id(), true, 5 ) );
1687   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true, 5, QgsProcessingFeatureSourceDefinition::Flags() ) != QgsProcessingFeatureSourceDefinition( layer->id(), true, 5, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck ) );
1688   QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true, 5, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometrySkipInvalid ) != QgsProcessingFeatureSourceDefinition( layer->id(), true, 5, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometryAbortOnInvalid ) );
1689 }
1690 
uniqueValues()1691 void TestQgsProcessing::uniqueValues()
1692 {
1693   QgsVectorLayer *layer = new QgsVectorLayer( "Point?field=a:integer&field=b:string", "v1", "memory" );
1694   for ( int i = 0; i < 6; ++i )
1695   {
1696     QgsFeature f( i );
1697     f.setAttributes( QgsAttributes() << i % 3 + 1 << QString( QChar( ( i % 3 ) + 65 ) ) );
1698     layer->dataProvider()->addFeatures( QgsFeatureList() << f );
1699   }
1700 
1701   QgsProcessingContext context;
1702   context.setFlags( QgsProcessingContext::Flags() );
1703 
1704   QgsProject p;
1705   p.addMapLayer( layer );
1706   context.setProject( &p );
1707 
1708   const std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
1709   QVariantMap params;
1710   params.insert( QStringLiteral( "layer" ), layer->id() );
1711 
1712   std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1713 
1714   // some bad checks
1715   QVERIFY( source->uniqueValues( -1 ).isEmpty() );
1716   QVERIFY( source->uniqueValues( 10001 ).isEmpty() );
1717 
1718   // good checks
1719   QSet< QVariant > vals = source->uniqueValues( 0 );
1720   QCOMPARE( vals.count(), 3 );
1721   QVERIFY( vals.contains( 1 ) );
1722   QVERIFY( vals.contains( 2 ) );
1723   QVERIFY( vals.contains( 3 ) );
1724   vals = source->uniqueValues( 1 );
1725   QCOMPARE( vals.count(), 3 );
1726   QVERIFY( vals.contains( QString( "A" ) ) );
1727   QVERIFY( vals.contains( QString( "B" ) ) );
1728   QVERIFY( vals.contains( QString( "C" ) ) );
1729 
1730   //using only selected features
1731   layer->selectByIds( QgsFeatureIds() << 1 << 2 << 4 );
1732   // but not using selection yet...
1733   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1734   vals = source->uniqueValues( 0 );
1735   QCOMPARE( vals.count(), 3 );
1736   QVERIFY( vals.contains( 1 ) );
1737   QVERIFY( vals.contains( 2 ) );
1738   QVERIFY( vals.contains( 3 ) );
1739   vals = source->uniqueValues( 1 );
1740   QCOMPARE( vals.count(), 3 );
1741   QVERIFY( vals.contains( QString( "A" ) ) );
1742   QVERIFY( vals.contains( QString( "B" ) ) );
1743   QVERIFY( vals.contains( QString( "C" ) ) );
1744 
1745   // selection and using selection
1746   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
1747   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1748   QVERIFY( source->uniqueValues( -1 ).isEmpty() );
1749   QVERIFY( source->uniqueValues( 10001 ).isEmpty() );
1750   vals = source->uniqueValues( 0 );
1751   QCOMPARE( vals.count(), 2 );
1752   QVERIFY( vals.contains( 1 ) );
1753   QVERIFY( vals.contains( 2 ) );
1754   vals = source->uniqueValues( 1 );
1755   QCOMPARE( vals.count(), 2 );
1756   QVERIFY( vals.contains( QString( "A" ) ) );
1757   QVERIFY( vals.contains( QString( "B" ) ) );
1758 }
1759 
createIndex()1760 void TestQgsProcessing::createIndex()
1761 {
1762   QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" );
1763   for ( int i = 1; i < 6; ++i )
1764   {
1765     QgsFeature f( i );
1766     f.setGeometry( QgsGeometry( new QgsPoint( i, 2 ) ) );
1767     layer->dataProvider()->addFeatures( QgsFeatureList() << f );
1768   }
1769 
1770   QgsProcessingContext context;
1771   QgsProject p;
1772   p.addMapLayer( layer );
1773   context.setProject( &p );
1774 
1775   const std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
1776   QVariantMap params;
1777   params.insert( QStringLiteral( "layer" ), layer->id() );
1778 
1779   // disable selected features check
1780   std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1781   QVERIFY( source.get() );
1782   QgsSpatialIndex index( *source );
1783   QList<QgsFeatureId> ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
1784   QCOMPARE( ids, QList<QgsFeatureId>() << 2 );
1785 
1786   // selected features check, but none selected
1787   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
1788   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1789   index = QgsSpatialIndex( *source );
1790   ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
1791   QCOMPARE( ids, QList<QgsFeatureId>() );
1792 
1793   // create selection
1794   layer->selectByIds( QgsFeatureIds() << 4 << 5 );
1795   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1796   index = QgsSpatialIndex( *source );
1797   ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
1798   QCOMPARE( ids, QList<QgsFeatureId>() << 4 );
1799 
1800   // selection but not using selection mode
1801   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
1802   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
1803   index = QgsSpatialIndex( *source );
1804   ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
1805   QCOMPARE( ids, QList<QgsFeatureId>() << 2 );
1806 }
1807 
generateTemporaryDestination()1808 void TestQgsProcessing::generateTemporaryDestination()
1809 {
1810   // setup a context
1811   QgsProject p;
1812   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
1813   QgsProcessingContext context;
1814   context.setProject( &p );
1815 
1816   // destination vector with "." in it's name
1817   std::unique_ptr< QgsProcessingParameterVectorDestination > def( new QgsProcessingParameterVectorDestination( "with.inside", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
1818 
1819   // check that temporary destination does not have dot at the end when there is no extension
1820   QVERIFY( !def->generateTemporaryDestination().endsWith( QLatin1Char( '.' ) ) );
1821   // check that temporary destination starts with tempFolder
1822   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
1823   // check that extension with QFileInfo::completeSuffix is "gpkg"
1824   QFileInfo f = QFileInfo( def->generateTemporaryDestination() );
1825   QCOMPARE( f.completeSuffix(), QString( "gpkg" ) );
1826 
1827   // destination raster with "." in it's name
1828   std::unique_ptr< QgsProcessingParameterRasterDestination > def2( new QgsProcessingParameterRasterDestination( "with.inside", QString(), QString(), false ) );
1829 
1830   // check that temporary destination does not have dot at the end when there is no extension
1831   QVERIFY( !def2->generateTemporaryDestination().endsWith( QLatin1Char( '.' ) ) );
1832   // check that temporary destination starts with tempFolder
1833   QVERIFY( def2->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
1834   // check that extension with QFileInfo::completeSuffix is "tif"
1835   f = QFileInfo( def2->generateTemporaryDestination() );
1836   QCOMPARE( f.completeSuffix(), QString( "tif" ) );
1837 
1838   // destination vector without "." in it's name
1839   std::unique_ptr< QgsProcessingParameterVectorDestination > def3( new QgsProcessingParameterVectorDestination( "without_inside", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
1840 
1841   // check that temporary destination does not have dot at the end when there is no extension
1842   QVERIFY( !def3->generateTemporaryDestination().endsWith( QLatin1Char( '.' ) ) );
1843   // check that temporary destination starts with tempFolder
1844   QVERIFY( def3->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
1845   // check that extension with QFileInfo::completeSuffix is "gpkg"
1846   f = QFileInfo( def3->generateTemporaryDestination() );
1847   QCOMPARE( f.completeSuffix(), QString( "gpkg" ) );
1848 
1849 }
1850 
parseDestinationString()1851 void TestQgsProcessing::parseDestinationString()
1852 {
1853   QString providerKey;
1854   QString uri;
1855   QString layerName;
1856   QString format;
1857   QVariantMap options;
1858   QString extension;
1859   bool useWriter = false;
1860 
1861   // simple shapefile output
1862   QString destination = QStringLiteral( "d:/test.shp" );
1863   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1864   QCOMPARE( destination, QStringLiteral( "d:/test.shp" ) );
1865   QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
1866   QCOMPARE( uri, QStringLiteral( "d:/test.shp" ) );
1867   QCOMPARE( format, QStringLiteral( "ESRI Shapefile" ) );
1868   QCOMPARE( extension, QStringLiteral( "shp" ) );
1869   QVERIFY( useWriter );
1870 
1871   // postgis output
1872   destination = QStringLiteral( "postgis:dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1873   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1874   QCOMPARE( providerKey, QStringLiteral( "postgres" ) );
1875   QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
1876   QVERIFY( !useWriter );
1877   QVERIFY( extension.isEmpty() );
1878   // postgres key should also work
1879   destination = QStringLiteral( "postgres:dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1880   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1881   QCOMPARE( providerKey, QStringLiteral( "postgres" ) );
1882   QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
1883   QVERIFY( !useWriter );
1884   QVERIFY( extension.isEmpty() );
1885 
1886   // newer format
1887   destination = QStringLiteral( "postgres://dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1888   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1889   QCOMPARE( providerKey, QStringLiteral( "postgres" ) );
1890   QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
1891   QVERIFY( !useWriter );
1892   QVERIFY( extension.isEmpty() );
1893   //mssql
1894   destination = QStringLiteral( "mssql://dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1895   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1896   QCOMPARE( providerKey, QStringLiteral( "mssql" ) );
1897   QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
1898   QVERIFY( !useWriter );
1899   QVERIFY( extension.isEmpty() );
1900 
1901   // full uri shp output
1902   options.clear();
1903   destination = QStringLiteral( "ogr:d:/test.shp" );
1904   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1905   QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
1906   QCOMPARE( uri, QStringLiteral( "d:/test.shp" ) );
1907   QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
1908   QCOMPARE( options.value( QStringLiteral( "driverName" ) ).toString(), QStringLiteral( "ESRI Shapefile" ) );
1909   QVERIFY( !options.contains( QStringLiteral( "layerName" ) ) );
1910   QVERIFY( !useWriter );
1911   QCOMPARE( extension, QStringLiteral( "shp" ) );
1912 
1913   // full uri geopackage output
1914   options.clear();
1915   destination = QStringLiteral( "ogr:d:/test.gpkg" );
1916   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1917   QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
1918   QCOMPARE( uri, QStringLiteral( "d:/test.gpkg" ) );
1919   QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
1920   QVERIFY( !options.contains( QStringLiteral( "layerName" ) ) );
1921   QCOMPARE( options.value( QStringLiteral( "driverName" ) ).toString(), QStringLiteral( "GPKG" ) );
1922   QVERIFY( !useWriter );
1923   QCOMPARE( extension, QStringLiteral( "gpkg" ) );
1924 
1925   // full uri geopackage table output with layer name
1926   options.clear();
1927   destination = QStringLiteral( "ogr:dbname='d:/package.gpkg' table=\"mylayer\" (geom) sql=" );
1928   QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
1929   QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
1930   QCOMPARE( uri, QStringLiteral( "d:/package.gpkg" ) );
1931   QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
1932   QCOMPARE( options.value( QStringLiteral( "layerName" ) ).toString(), QStringLiteral( "mylayer" ) );
1933   QCOMPARE( options.value( QStringLiteral( "driverName" ) ).toString(), QStringLiteral( "GPKG" ) );
1934   QVERIFY( !useWriter );
1935   QCOMPARE( extension, QStringLiteral( "gpkg" ) );
1936 }
1937 
createFeatureSink()1938 void TestQgsProcessing::createFeatureSink()
1939 {
1940   QgsProcessingContext context;
1941 
1942   // empty destination
1943   QString destination;
1944   destination = QString();
1945   QgsVectorLayer *layer = nullptr;
1946 
1947   // should create a memory layer
1948   std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( destination, context, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem() ) );
1949   QVERIFY( sink.get() );
1950   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) );
1951   QVERIFY( layer );
1952   QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() );
1953   QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) );
1954   QCOMPARE( destination, layer->id() );
1955   QVERIFY( !layer->customProperty( QStringLiteral( "OnConvertFormatRegeneratePrimaryKey" ) ).toBool() );
1956   QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store
1957   QgsFeature f;
1958   QCOMPARE( layer->featureCount(), 0L );
1959   QVERIFY( sink->addFeature( f ) );
1960   QCOMPARE( layer->featureCount(), 1L );
1961   context.temporaryLayerStore()->removeAllMapLayers();
1962   layer = nullptr;
1963 
1964   // specific memory layer output
1965   destination = QStringLiteral( "memory:mylayer" );
1966   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem() ) );
1967   QVERIFY( sink.get() );
1968   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) );
1969   QVERIFY( layer );
1970   QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() );
1971   QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) );
1972   QCOMPARE( layer->name(), QStringLiteral( "mylayer" ) );
1973   QCOMPARE( destination, layer->id() );
1974   QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store
1975   QCOMPARE( layer->featureCount(), 0L );
1976   QVERIFY( sink->addFeature( f ) );
1977   QCOMPARE( layer->featureCount(), 1L );
1978   context.temporaryLayerStore()->removeAllMapLayers();
1979   layer = nullptr;
1980 
1981   // nameless memory layer
1982   destination = QStringLiteral( "memory:" );
1983   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem() ) );
1984   QVERIFY( sink.get() );
1985   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) );
1986   QVERIFY( layer );
1987   QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() );
1988   QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) );
1989   QCOMPARE( layer->name(), QString( "output" ) ); // should fall back to "output" name
1990   QCOMPARE( destination, layer->id() );
1991   QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store
1992   QCOMPARE( layer->featureCount(), 0L );
1993   QVERIFY( sink->addFeature( f ) );
1994   QCOMPARE( layer->featureCount(), 1L );
1995   context.temporaryLayerStore()->removeAllMapLayers();
1996   layer = nullptr;
1997 
1998   // memory layer parameters
1999   destination = QStringLiteral( "memory:mylayer" );
2000   QgsFields fields;
2001   fields.append( QgsField( QStringLiteral( "my_field" ), QVariant::String, QString(), 100 ) );
2002   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::PointZM, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ), QVariantMap(), QStringList(), QStringList(), QgsFeatureSink::RegeneratePrimaryKey ) );
2003   QVERIFY( sink.get() );
2004   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) );
2005   QVERIFY( layer );
2006   QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() );
2007   QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) );
2008   QCOMPARE( layer->name(), QStringLiteral( "mylayer" ) );
2009   QVERIFY( layer->customProperty( QStringLiteral( "OnConvertFormatRegeneratePrimaryKey" ) ).toBool() );
2010   QCOMPARE( layer->wkbType(), QgsWkbTypes::PointZM );
2011   QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
2012   QCOMPARE( layer->fields().size(), 1 );
2013   QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "my_field" ) );
2014   QCOMPARE( layer->fields().at( 0 ).type(), QVariant::String );
2015   QCOMPARE( destination, layer->id() );
2016   QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store
2017   QCOMPARE( layer->featureCount(), 0L );
2018   QVERIFY( sink->addFeature( f ) );
2019   QCOMPARE( layer->featureCount(), 1L );
2020   context.temporaryLayerStore()->removeAllMapLayers();
2021 
2022   // non memory layer output
2023   destination = QDir::tempPath() + "/create_feature_sink.tab";
2024   QString prevDest = destination;
2025   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2026   QVERIFY( sink.get() );
2027   f = QgsFeature( fields );
2028   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) );
2029   f.setAttributes( QgsAttributes() << "val" );
2030   QVERIFY( sink->addFeature( f ) );
2031   QCOMPARE( destination, prevDest );
2032   sink.reset( nullptr );
2033   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
2034   QVERIFY( layer->isValid() );
2035   QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
2036   QCOMPARE( layer->fields().size(), 1 );
2037   QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "my_field" ) );
2038   QCOMPARE( layer->fields().at( 0 ).type(), QVariant::String );
2039   QCOMPARE( layer->featureCount(), 1L );
2040 
2041   // no extension, should default to shp
2042   destination = QDir::tempPath() + "/create_feature_sink2";
2043   prevDest = QDir::tempPath() + "/create_feature_sink2.gpkg";
2044   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::PointZ, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2045   QVERIFY( sink.get() );
2046   f = QgsFeature( fields );
2047   f.setAttributes( QgsAttributes() << "val" );
2048   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PointZ(1 2 3)" ) ) );
2049   QVERIFY( sink->addFeature( f ) );
2050   QCOMPARE( destination, prevDest );
2051   sink.reset( nullptr );
2052   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
2053   QCOMPARE( layer->wkbType(), QgsWkbTypes::PointZ );
2054   QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
2055   QCOMPARE( layer->fields().size(), 2 );
2056   QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "fid" ) );
2057   QCOMPARE( layer->fields().at( 1 ).name(), QStringLiteral( "my_field" ) );
2058   QCOMPARE( layer->fields().at( 1 ).type(), QVariant::String );
2059   QCOMPARE( layer->featureCount(), 1L );
2060   // append to existing OGR layer
2061   QgsRemappingSinkDefinition remapDef;
2062   remapDef.setDestinationFields( layer->fields() );
2063   remapDef.setDestinationCrs( layer->crs() );
2064   remapDef.setSourceCrs( QgsCoordinateReferenceSystem( "EPSG:4326" ) );
2065   remapDef.setDestinationWkbType( QgsWkbTypes::Polygon );
2066   remapDef.addMappedField( QStringLiteral( "my_field" ), QgsProperty::fromExpression( QStringLiteral( "field2 || @extra" ) ) );
2067   QgsFields fields2;
2068   fields2.append( QgsField( "field2", QVariant::String ) );
2069   context.expressionContext().appendScope( new QgsExpressionContextScope() );
2070   context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "extra" ), 2 );
2071   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields2, QgsWkbTypes::Point, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QVariantMap(), QStringList(), QStringList(), QgsFeatureSink::SinkFlags(), &remapDef ) );
2072   QVERIFY( sink.get() );
2073   f = QgsFeature( fields2 );
2074   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Point(10 0)" ) ) );
2075   f.setAttributes( QgsAttributes() << "val" );
2076   QVERIFY( sink->addFeature( f ) );
2077   QCOMPARE( destination, prevDest );
2078   sink.reset( nullptr );
2079   layer = new QgsVectorLayer( destination );
2080   QVERIFY( layer->isValid() );
2081   QCOMPARE( layer->featureCount(), 2L );
2082   QgsFeatureIterator it = layer->getFeatures();
2083   QVERIFY( it.nextFeature( f ) );
2084   QCOMPARE( f.attributes().at( 1 ).toString(), QStringLiteral( "val" ) );
2085   QCOMPARE( f.geometry().asWkt( 1 ), QStringLiteral( "PointZ (1 2 3)" ) );
2086   QVERIFY( it.nextFeature( f ) );
2087   QCOMPARE( f.attributes().at( 1 ).toString(), QStringLiteral( "val2" ) );
2088   QCOMPARE( f.geometry().asWkt( 0 ), QStringLiteral( "Point (-10199761 -4017774)" ) );
2089   delete layer;
2090   //windows style path
2091   destination = "d:\\temp\\create_feature_sink.tab";
2092   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2093   QVERIFY( sink.get() );
2094 
2095   // save to geopackage
2096   const QString geopackagePath = QDir::tempPath() + "/packaged.gpkg";
2097   if ( QFileInfo::exists( geopackagePath ) )
2098     QFile::remove( geopackagePath );
2099   destination = QStringLiteral( "ogr:dbname='%1' table=\"polygons\" (geom) sql=" ).arg( geopackagePath );
2100   sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2101   QVERIFY( sink.get() );
2102   QCOMPARE( destination, QStringLiteral( "%1|layername=polygons" ).arg( geopackagePath ) );
2103   f = QgsFeature( fields );
2104   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) );
2105   f.setAttributes( QgsAttributes() << "val" );
2106   QVERIFY( sink->addFeature( f ) );
2107   sink.reset( nullptr );
2108   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
2109   QVERIFY( layer->isValid() );
2110   QCOMPARE( layer->wkbType(), QgsWkbTypes::Polygon );
2111   QVERIFY( layer->getFeatures().nextFeature( f ) );
2112   QCOMPARE( f.attribute( "my_field" ).toString(), QStringLiteral( "val" ) );
2113 
2114   // add another output to the same geopackage
2115   QString destination2 = QStringLiteral( "ogr:dbname='%1' table=\"points\" (geom) sql=" ).arg( geopackagePath );
2116   sink.reset( QgsProcessingUtils::createFeatureSink( destination2, context, fields, QgsWkbTypes::Point, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2117   QVERIFY( sink.get() );
2118   QCOMPARE( destination2, QStringLiteral( "%1|layername=points" ).arg( geopackagePath ) );
2119   f = QgsFeature( fields );
2120   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Point(0 0)" ) ) );
2121   f.setAttributes( QgsAttributes() << "val2" );
2122   QVERIFY( sink->addFeature( f ) );
2123   sink.reset( nullptr );
2124   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination2, context, true ) );
2125   QVERIFY( layer->isValid() );
2126   QCOMPARE( layer->wkbType(), QgsWkbTypes::Point );
2127   QVERIFY( layer->getFeatures().nextFeature( f ) );
2128   QCOMPARE( f.attribute( "my_field" ).toString(), QStringLiteral( "val2" ) );
2129 
2130   // original polygon layer should remain
2131   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
2132   QVERIFY( layer->isValid() );
2133   QCOMPARE( layer->wkbType(), QgsWkbTypes::Polygon );
2134   QVERIFY( layer->getFeatures().nextFeature( f ) );
2135   QCOMPARE( f.attribute( "my_field" ).toString(), QStringLiteral( "val" ) );
2136 
2137   // now append to that second layer
2138   remapDef.setDestinationFields( layer->fields() );
2139   remapDef.setDestinationCrs( layer->crs() );
2140 
2141   remapDef.setSourceCrs( QgsCoordinateReferenceSystem( "EPSG:4326" ) );
2142   remapDef.setDestinationWkbType( QgsWkbTypes::Point );
2143   remapDef.addMappedField( QStringLiteral( "my_field" ), QgsProperty::fromExpression( QStringLiteral( "field2 || @extra" ) ) );
2144   destination2 = QStringLiteral( "ogr:dbname='%1' table=\"points\" (geom) sql=" ).arg( geopackagePath );
2145   sink.reset( QgsProcessingUtils::createFeatureSink( destination2, context, fields2, QgsWkbTypes::PointZ, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QVariantMap(), QStringList(), QStringList(), QgsFeatureSink::SinkFlags(), &remapDef ) );
2146   QVERIFY( sink.get() );
2147   f = QgsFeature( fields );
2148   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PointZ(3 4 5)" ) ) );
2149   f.setAttributes( QgsAttributes() << "v" );
2150   QVERIFY( sink->addFeature( f ) );
2151   sink.reset( nullptr );
2152   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination2, context, true ) );
2153   QVERIFY( layer->isValid() );
2154   QCOMPARE( layer->wkbType(), QgsWkbTypes::Point );
2155   QCOMPARE( layer->featureCount(), 2L );
2156   QVERIFY( layer->getFeatures().nextFeature( f ) );
2157   QCOMPARE( f.attribute( "my_field" ).toString(), QStringLiteral( "val2" ) );
2158 }
2159 
2160 #ifdef ENABLE_PGTEST
createFeatureSinkPostgres()2161 void TestQgsProcessing::createFeatureSinkPostgres()
2162 {
2163   QgsProcessingContext context;
2164 
2165   QgsFields fields;
2166   fields.append( QgsField( QStringLiteral( "my_field" ), QVariant::String, QString(), 100 ) );
2167 
2168   // save to database
2169   QString destination = "postgres://dbname='qgis_test' service='qgis_test' table=\"public\".\"test_feature_sink\" (geom)";
2170   std::unique_ptr< QgsFeatureSink > sink;
2171   try
2172   {
2173     sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
2174   }
2175   catch ( QgsProcessingException &e )
2176   {
2177     QFAIL( "could not create layer -- perhaps postgres test db is not accessible?" );
2178   }
2179   QVERIFY( sink.get() );
2180   QgsFeature f = QgsFeature( fields );
2181   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) );
2182   f.setAttributes( QgsAttributes() << "val" );
2183   QVERIFY( sink->addFeature( f ) );
2184   sink.reset( nullptr );
2185   QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
2186   QVERIFY( layer && layer->isValid() );
2187   QCOMPARE( layer->wkbType(), QgsWkbTypes::Polygon );
2188   QVERIFY( layer->getFeatures().nextFeature( f ) );
2189   QCOMPARE( f.attribute( "my_field" ).toString(), QStringLiteral( "val" ) );
2190 }
2191 #endif
2192 
source()2193 void TestQgsProcessing::source()
2194 {
2195   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
2196   QgsVectorLayer *invalidLayer = new QgsVectorLayer( testDataDir + "invalidgeometries.gml", QString(), "ogr" );
2197   QVERIFY( invalidLayer->isValid() );
2198 
2199   QgsProcessingContext context;
2200   context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid );
2201   QgsProcessingFeatureSource source( invalidLayer, context );
2202   // expect an exception, we should be using the context's "abort on invalid" setting
2203   QgsFeatureIterator it = source.getFeatures();
2204   QgsFeature f;
2205   try
2206   {
2207     it.nextFeature( f );
2208     QVERIFY( false );
2209   }
2210   catch ( QgsProcessingException & )
2211   {
2212 
2213   }
2214 
2215   // now try with a source overriding the context's setting
2216   source.setInvalidGeometryCheck( QgsFeatureRequest::GeometryNoCheck );
2217   it = source.getFeatures();
2218   QVERIFY( it.nextFeature( f ) );
2219   QVERIFY( !f.geometry().isGeosValid() );
2220   // all good!
2221 
2222   QgsVectorLayer *polygonLayer = new QgsVectorLayer( testDataDir + "polys.shp", QString(), "ogr" );
2223   QVERIFY( polygonLayer->isValid() );
2224 
2225   const QgsProcessingFeatureSource source2( polygonLayer, context );
2226   QCOMPARE( source2.featureCount(), 10L );
2227   int i = 0;
2228   it = source2.getFeatures();
2229   while ( it.nextFeature( f ) )
2230     i++;
2231   QCOMPARE( i, 10 );
2232 
2233   // now with a limit on features
2234   const QgsProcessingFeatureSource source3( polygonLayer, context, false, 5 );
2235   QCOMPARE( source3.featureCount(), 5L );
2236   i = 0;
2237   it = source3.getFeatures();
2238   while ( it.nextFeature( f ) )
2239     i++;
2240   QCOMPARE( i, 5 );
2241 
2242   // feature request has a lower limit than source
2243   it = source3.getFeatures( QgsFeatureRequest().setLimit( 2 ) );
2244   i = 0;
2245   while ( it.nextFeature( f ) )
2246     i++;
2247   QCOMPARE( i, 2 );
2248 
2249   // feature request has a higher limit than source
2250   it = source3.getFeatures( QgsFeatureRequest().setLimit( 12 ) );
2251   i = 0;
2252   while ( it.nextFeature( f ) )
2253     i++;
2254   QCOMPARE( i, 5 );
2255 
2256 }
2257 
parameters()2258 void TestQgsProcessing::parameters()
2259 {
2260   // test parameter utilities
2261 
2262   std::unique_ptr< QgsProcessingParameterDefinition > def;
2263 
2264   QVariantMap params;
2265   params.insert( QStringLiteral( "prop" ), QgsProperty::fromField( "a_field" ) );
2266   params.insert( QStringLiteral( "string" ), QStringLiteral( "a string" ) );
2267   params.insert( QStringLiteral( "double" ), 5.2 );
2268   params.insert( QStringLiteral( "int" ), 15 );
2269   params.insert( QStringLiteral( "ints" ), QVariant( QList<QVariant>() << 3 << 2 << 1 ) );
2270   params.insert( QStringLiteral( "bool" ), true );
2271 
2272   QgsProcessingContext context;
2273 
2274   // isDynamic
2275   QVERIFY( QgsProcessingParameters::isDynamic( params, QStringLiteral( "prop" ) ) );
2276   QVERIFY( !QgsProcessingParameters::isDynamic( params, QStringLiteral( "string" ) ) );
2277   QVERIFY( !QgsProcessingParameters::isDynamic( params, QStringLiteral( "bad" ) ) );
2278 
2279   // parameterAsString
2280   def.reset( new QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "desc" ) ) );
2281   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "a string" ) );
2282   def->setName( QStringLiteral( "double" ) );
2283   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ).left( 3 ), QStringLiteral( "5.2" ) );
2284   def->setName( QStringLiteral( "int" ) );
2285   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "15" ) );
2286   def->setName( QStringLiteral( "bool" ) );
2287   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "true" ) );
2288   def->setName( QStringLiteral( "bad" ) );
2289   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
2290 
2291   def->setIsDynamic( true );
2292   QVERIFY( def->isDynamic() );
2293   def->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Distance" ), QObject::tr( "Buffer distance" ), QgsPropertyDefinition::Double ) );
2294   QCOMPARE( def->dynamicPropertyDefinition().name(), QStringLiteral( "Distance" ) );
2295   def->setDynamicLayerParameterName( "parent" );
2296   QCOMPARE( def->dynamicLayerParameterName(), QStringLiteral( "parent" ) );
2297 
2298   // string with dynamic property (feature not set)
2299   def->setName( QStringLiteral( "prop" ) );
2300   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
2301 
2302   // correctly setup feature
2303   QgsFields fields;
2304   fields.append( QgsField( "a_field", QVariant::String, QString(), 30 ) );
2305   QgsFeature f( fields );
2306   f.setAttribute( 0, QStringLiteral( "field value" ) );
2307   context.expressionContext().setFeature( f );
2308   context.expressionContext().setFields( fields );
2309   def->setName( QStringLiteral( "prop" ) );
2310   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "field value" ) );
2311 
2312   // as double
2313   def->setName( QStringLiteral( "double" ) );
2314   QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 5.2 );
2315   def->setName( QStringLiteral( "int" ) );
2316   QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 15.0 );
2317   f.setAttribute( 0, QStringLiteral( "6.2" ) );
2318   context.expressionContext().setFeature( f );
2319   def->setName( QStringLiteral( "prop" ) );
2320   QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 6.2 );
2321 
2322   // as int
2323   def->setName( QStringLiteral( "double" ) );
2324   QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 5 );
2325   def->setName( QStringLiteral( "int" ) );
2326   QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 15 );
2327   def->setName( QStringLiteral( "prop" ) );
2328   QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 6 );
2329 
2330   // as ints
2331   def->setName( QStringLiteral( "int" ) );
2332   QCOMPARE( QgsProcessingParameters::parameterAsInts( def.get(), params, context ), QList<int>() << 15 );
2333   def->setName( QStringLiteral( "ints" ) );
2334   QCOMPARE( QgsProcessingParameters::parameterAsInts( def.get(), params, context ), QList<int>() << 3 << 2 << 1 );
2335 
2336   // as bool
2337   def->setName( QStringLiteral( "double" ) );
2338   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2339   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2340   def->setName( QStringLiteral( "int" ) );
2341   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2342   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2343   def->setName( QStringLiteral( "bool" ) );
2344   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2345   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2346   def->setName( QStringLiteral( "prop" ) );
2347   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2348   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2349   f.setAttribute( 0, false );
2350   context.expressionContext().setFeature( f );
2351   def->setName( QStringLiteral( "prop" ) );
2352   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2353   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2354 
2355   // as layer
2356   def->setName( QStringLiteral( "double" ) );
2357   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
2358   def->setName( QStringLiteral( "int" ) );
2359   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(),  params, context ) );
2360   def->setName( QStringLiteral( "bool" ) );
2361   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
2362   def->setName( QStringLiteral( "prop" ) );
2363   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
2364 
2365   QVERIFY( context.temporaryLayerStore()->mapLayers().isEmpty() );
2366   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
2367   f.setAttribute( 0, QString( testDataDir + "/raster/band1_float32_noct_epsg4326.tif" ) );
2368   context.expressionContext().setFeature( f );
2369   def->setName( QStringLiteral( "prop" ) );
2370   QVERIFY( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
2371   // make sure layer was loaded
2372   QVERIFY( !context.temporaryLayerStore()->mapLayers().isEmpty() );
2373 
2374   // parameters as sinks
2375 
2376   const QgsWkbTypes::Type wkbType = QgsWkbTypes::PolygonM;
2377   QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:3111" ) );
2378   QString destId;
2379   def->setName( QStringLiteral( "string" ) );
2380   params.insert( QStringLiteral( "string" ), QStringLiteral( "memory:mem" ) );
2381   std::unique_ptr< QgsFeatureSink > sink;
2382   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) );
2383   QVERIFY( sink.get() );
2384   QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) );
2385   QVERIFY( layer );
2386   QVERIFY( layer->isValid() );
2387   QCOMPARE( layer->fields().count(), 1 );
2388   QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "a_field" ) );
2389   QCOMPARE( layer->wkbType(), wkbType );
2390   QCOMPARE( layer->crs(), crs );
2391 
2392   // property defined sink destination
2393   params.insert( QStringLiteral( "prop" ), QgsProperty::fromExpression( "'memory:mem2'" ) );
2394   def->setName( QStringLiteral( "prop" ) );
2395   crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:3113" ) );
2396   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) );
2397   QVERIFY( sink.get() );
2398   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) );
2399   QVERIFY( layer );
2400   QVERIFY( layer->isValid() );
2401   QCOMPARE( layer->fields().count(), 1 );
2402   QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "a_field" ) );
2403   QCOMPARE( layer->wkbType(), wkbType );
2404   QCOMPARE( layer->crs(), crs );
2405 
2406   // QgsProcessingFeatureSinkDefinition as parameter
2407   QgsProject p;
2408   QgsProcessingOutputLayerDefinition fs( QStringLiteral( "test.shp" ) );
2409   fs.destinationProject = &p;
2410   QVERIFY( context.layersToLoadOnCompletion().isEmpty() );
2411   params.insert( QStringLiteral( "fs" ), QVariant::fromValue( fs ) );
2412   def->setName( QStringLiteral( "fs" ) );
2413   crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:28356" ) );
2414   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) );
2415   QVERIFY( sink.get() );
2416   QgsVectorFileWriter *writer = dynamic_cast< QgsVectorFileWriter *>( dynamic_cast< QgsProcessingFeatureSink * >( sink.get() )->destinationSink() );
2417   QVERIFY( writer );
2418   layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) );
2419   QVERIFY( layer );
2420   QVERIFY( layer->isValid() );
2421   QCOMPARE( layer->wkbType(), QgsWkbTypes::MultiPolygonM ); // shapefile Polygon[XX] get promoted to Multi
2422   QCOMPARE( layer->crs(), crs );
2423 
2424   // make sure layer was automatically added to list to load on completion
2425   QCOMPARE( context.layersToLoadOnCompletion().size(), 1 );
2426   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), destId );
2427   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "desc" ) );
2428 
2429   // with name overloading
2430   QgsProcessingContext context2;
2431   fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.shp" ) );
2432   fs.destinationProject = &p;
2433   fs.destinationName = QStringLiteral( "my_dest" );
2434   params.insert( QStringLiteral( "fs" ), QVariant::fromValue( fs ) );
2435   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context2, destId ) );
2436   QVERIFY( sink.get() );
2437   QCOMPARE( context2.layersToLoadOnCompletion().size(), 1 );
2438   QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), destId );
2439   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "my_dest" ) );
2440   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).outputName, QStringLiteral( "fs" ) );
2441 
2442   // setting layer name to match...
2443   context2.layersToLoadOnCompletion().values().at( 0 ).setOutputLayerName( nullptr );
2444   std::unique_ptr< QgsVectorLayer > vl = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point" ), QString(), QStringLiteral( "memory" ) );
2445   QVERIFY( vl->isValid() );
2446   context2.layersToLoadOnCompletion().values().at( 0 ).setOutputLayerName( vl.get() );
2447   // temporary layer, must use output name as layer name
2448   QCOMPARE( vl->name(), QStringLiteral( "my_dest" ) );
2449   // otherwise expect to use path
2450   std::unique_ptr< QgsRasterLayer > rl = std::make_unique< QgsRasterLayer >( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif", QString() );
2451   context2.layersToLoadOnCompletion().values().at( 0 ).setOutputLayerName( rl.get() );
2452   QCOMPARE( rl->name(), QStringLiteral( "landsat" ) );
2453   // unless setting prohibits it...
2454   QgsProcessing::settingsPreferFilenameAsLayerName.setValue( false );
2455   context2.layersToLoadOnCompletion().values().at( 0 ).setOutputLayerName( rl.get() );
2456   QCOMPARE( rl->name(), QStringLiteral( "my_dest" ) );
2457   // if layer has a layername, we should use that instead of the base file name...
2458   QgsProcessing::settingsPreferFilenameAsLayerName.setValue( true );
2459   vl = std::make_unique< QgsVectorLayer >( QStringLiteral( TEST_DATA_DIR ) + "/points_gpkg.gpkg|layername=points_small", QString() );
2460   context2.layersToLoadOnCompletion().values().at( 0 ).setOutputLayerName( vl.get() );
2461   QCOMPARE( vl->name(), QStringLiteral( "points_small" ) );
2462   // if forced name is true, that should always be used, regardless of the user's local setting
2463   QgsProcessingContext::LayerDetails details( QStringLiteral( "my name" ), context2.project(), QStringLiteral( "my name" ) );
2464   details.forceName = false;
2465   details.setOutputLayerName( vl.get() );
2466   QCOMPARE( vl->name(), QStringLiteral( "points_small" ) );
2467   details.forceName = true;
2468   details.setOutputLayerName( vl.get() );
2469   QCOMPARE( vl->name(), QStringLiteral( "my name" ) );
2470 }
2471 
algorithmParameters()2472 void TestQgsProcessing::algorithmParameters()
2473 {
2474   DummyAlgorithm *alg = new DummyAlgorithm( "test" );
2475   DummyProvider p( "test" );
2476   alg->runParameterChecks();
2477 
2478   p.addAlgorithm( alg );
2479   alg->runParameterChecks2();
2480 }
2481 
algorithmOutputs()2482 void TestQgsProcessing::algorithmOutputs()
2483 {
2484   DummyAlgorithm alg( "test" );
2485   alg.runOutputChecks();
2486 }
2487 
parameterGeneral()2488 void TestQgsProcessing::parameterGeneral()
2489 {
2490   // test constructor
2491   QgsProcessingParameterBoolean param( "p1", "desc", true, true );
2492   QCOMPARE( param.name(), QString( "p1" ) );
2493   QCOMPARE( param.description(), QString( "desc" ) );
2494   QCOMPARE( param.defaultValue(), QVariant( true ) );
2495   QVERIFY( param.flags() & QgsProcessingParameterDefinition::FlagOptional );
2496   QVERIFY( param.dependsOnOtherParameters().isEmpty() );
2497   QVERIFY( param.help().isEmpty() );
2498 
2499   // test getters and setters
2500   param.setDescription( "p2" );
2501   QCOMPARE( param.description(), QString( "p2" ) );
2502   param.setDefaultValue( false );
2503   QCOMPARE( param.defaultValue(), QVariant( false ) );
2504   param.setFlags( QgsProcessingParameterDefinition::FlagHidden );
2505   QCOMPARE( param.flags(), QgsProcessingParameterDefinition::FlagHidden );
2506   param.setDefaultValue( true );
2507   QCOMPARE( param.defaultValue(), QVariant( true ) );
2508   QCOMPARE( param.defaultValueForGui(), QVariant( true ) );
2509   QVERIFY( !param.guiDefaultValueOverride().isValid() );
2510   param.setGuiDefaultValueOverride( false );
2511   QCOMPARE( param.guiDefaultValueOverride(), QVariant( false ) );
2512   QCOMPARE( param.defaultValueForGui(), QVariant( false ) );
2513 
2514   param.setDefaultValue( QVariant() );
2515   QCOMPARE( param.defaultValue(), QVariant() );
2516   param.setHelp( QStringLiteral( "my help" ) );
2517   QCOMPARE( param.help(), QStringLiteral( "my help" ) );
2518 
2519   QVariantMap metadata;
2520   metadata.insert( "p1", 5 );
2521   metadata.insert( "p2", 7 );
2522   param.setMetadata( metadata );
2523   QCOMPARE( param.metadata(), metadata );
2524   param.metadata().insert( "p3", 9 );
2525   QCOMPARE( param.metadata().value( "p3" ).toInt(), 9 );
2526 
2527   QVERIFY( param.additionalExpressionContextVariables().isEmpty() );
2528   param.setAdditionalExpressionContextVariables( QStringList() << "a" << "b" );
2529   QCOMPARE( param.additionalExpressionContextVariables(), QStringList() << "a" << "b" );
2530   std::unique_ptr< QgsProcessingParameterDefinition > param2( param.clone() );
2531   QCOMPARE( param2->guiDefaultValueOverride(), param.guiDefaultValueOverride() );
2532   QCOMPARE( param2->additionalExpressionContextVariables(), QStringList() << "a" << "b" );
2533 
2534   const QVariantMap map = param.toVariantMap();
2535   QgsProcessingParameterBoolean fromMap( "x" );
2536   QVERIFY( fromMap.fromVariantMap( map ) );
2537   QCOMPARE( fromMap.name(), param.name() );
2538   QCOMPARE( fromMap.description(), param.description() );
2539   QCOMPARE( fromMap.flags(), param.flags() );
2540   QCOMPARE( fromMap.defaultValue(), param.defaultValue() );
2541   QCOMPARE( fromMap.guiDefaultValueOverride(), param.guiDefaultValueOverride() );
2542   QCOMPARE( fromMap.metadata(), param.metadata() );
2543   QCOMPARE( fromMap.help(), QStringLiteral( "my help" ) );
2544 
2545   // escaping quotes
2546   param = QgsProcessingParameterBoolean( "param_name", "Param's name" );
2547   QString pythonCode = param.asPythonString();
2548   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('param_name', \"Param's name\", defaultValue=None)" ) );
2549 
2550   param = QgsProcessingParameterBoolean( "param_name", "Param\"s name" );
2551   pythonCode = param.asPythonString();
2552   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('param_name', 'Param\"s name', defaultValue=None)" ) );
2553 
2554   param = QgsProcessingParameterBoolean( "param_name", "Param's \" name" );
2555   pythonCode = param.asPythonString();
2556   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('param_name', 'Param\\'s \" name', defaultValue=None)" ) );
2557 }
2558 
parameterBoolean()2559 void TestQgsProcessing::parameterBoolean()
2560 {
2561   QgsProcessingContext context;
2562 
2563   // test no def
2564   QVariantMap params;
2565   params.insert( "no_def",  false );
2566   QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false );
2567   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( nullptr, params, context ), false );
2568   params.insert( "no_def",  "false" );
2569   QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false );
2570   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( nullptr, params, context ), false );
2571   params.insert( "no_def",  QVariant() );
2572   QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false );
2573   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( nullptr, params, context ), false );
2574   params.remove( "no_def" );
2575   QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false );
2576   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( nullptr, params, context ), false );
2577 
2578   // with defs
2579 
2580   std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterBoolean( "non_optional_default_false" ) );
2581   QVERIFY( def->checkValueIsAcceptable( false ) );
2582   QVERIFY( def->checkValueIsAcceptable( true ) );
2583   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2584   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2585   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
2586 
2587   params.insert( "non_optional_default_false",  false );
2588   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2589   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2590   params.insert( "non_optional_default_false",  true );
2591   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2592   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2593   params.insert( "non_optional_default_false",  "true" );
2594   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2595   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2596   params.insert( "non_optional_default_false",  "false" );
2597   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2598   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2599 
2600   //non-optional - behavior is undefined, but internally default to false
2601   params.insert( "non_optional_default_false",  QVariant() );
2602   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2603   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2604   params.remove( "non_optional_default_false" );
2605   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2606   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2607 
2608   QCOMPARE( def->valueAsPythonString( false, context ), QStringLiteral( "False" ) );
2609   QCOMPARE( def->valueAsPythonString( true, context ), QStringLiteral( "True" ) );
2610   QCOMPARE( def->valueAsPythonString( "false", context ), QStringLiteral( "False" ) );
2611   QCOMPARE( def->valueAsPythonString( "true", context ), QStringLiteral( "True" ) );
2612   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
2613   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
2614 
2615   QString pythonCode = def->asPythonString();
2616   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('non_optional_default_false', '', defaultValue=None)" ) );
2617 
2618   QString code = def->asScriptCode();
2619   QCOMPARE( code, QStringLiteral( "##non_optional_default_false=boolean false" ) );
2620   std::unique_ptr< QgsProcessingParameterBoolean > fromCode( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2621   QVERIFY( fromCode.get() );
2622   QCOMPARE( fromCode->name(), def->name() );
2623   QCOMPARE( fromCode->description(), QStringLiteral( "non optional default false" ) );
2624   QCOMPARE( fromCode->flags(), def->flags() );
2625   QCOMPARE( fromCode->defaultValue().toBool(), false );
2626 
2627   const QVariantMap map = def->toVariantMap();
2628   QgsProcessingParameterBoolean fromMap( "x" );
2629   QVERIFY( fromMap.fromVariantMap( map ) );
2630   QCOMPARE( fromMap.name(), def->name() );
2631   QCOMPARE( fromMap.description(), def->description() );
2632   QCOMPARE( fromMap.flags(), def->flags() );
2633   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
2634   def.reset( QgsProcessingParameters::parameterFromVariantMap( map ) );
2635   QVERIFY( dynamic_cast< QgsProcessingParameterBoolean *>( def.get() ) );
2636 
2637 
2638   def.reset( new QgsProcessingParameterBoolean( "optional_default_true", QString(), true, true ) );
2639 
2640   QVERIFY( def->checkValueIsAcceptable( false ) );
2641   QVERIFY( def->checkValueIsAcceptable( true ) );
2642   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2643   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2644   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
2645 
2646   params.insert( "optional_default_true",  false );
2647   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2648   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2649   params.insert( "optional_default_true",  true );
2650   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2651   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2652   params.insert( "optional_default_true",  "true" );
2653   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2654   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2655   params.insert( "optional_default_true",  "false" );
2656   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2657   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2658   //optional - should be default
2659   params.insert( "optional_default_true",  QVariant() );
2660   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2661   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2662   params.remove( "optional_default_true" );
2663   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2664   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2665 
2666   pythonCode = def->asPythonString();
2667   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('optional_default_true', '', optional=True, defaultValue=True)" ) );
2668 
2669   code = def->asScriptCode();
2670   QCOMPARE( code, QStringLiteral( "##optional_default_true=optional boolean true" ) );
2671   fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2672   QVERIFY( fromCode.get() );
2673   QCOMPARE( fromCode->name(), def->name() );
2674   QCOMPARE( fromCode->description(), QStringLiteral( "optional default true" ) );
2675   QCOMPARE( fromCode->flags(), def->flags() );
2676   QCOMPARE( fromCode->defaultValue().toBool(), true );
2677 
2678   def.reset( new QgsProcessingParameterBoolean( "optional_default_false", QString(), false, true ) );
2679 
2680   QVERIFY( def->checkValueIsAcceptable( false ) );
2681   QVERIFY( def->checkValueIsAcceptable( true ) );
2682   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2683   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2684   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
2685   QVERIFY( def->checkValueIsAcceptable( false ) );
2686   QVERIFY( def->checkValueIsAcceptable( true ) );
2687   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2688   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2689   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
2690 
2691   params.insert( "optional_default_false",  false );
2692   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2693   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2694   params.insert( "optional_default_false",  true );
2695   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2696   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2697   params.insert( "optional_default_false",  "true" );
2698   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2699   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2700   params.insert( "optional_default_false",  "false" );
2701   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2702   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2703   //optional - should be default
2704   params.insert( "optional_default_false",  QVariant() );
2705   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2706   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2707   params.remove( "optional_default_false" );
2708   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2709   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2710 
2711   pythonCode = def->asPythonString();
2712   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('optional_default_false', '', optional=True, defaultValue=False)" ) );
2713 
2714   code = def->asScriptCode();
2715   fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2716   QVERIFY( fromCode.get() );
2717   QCOMPARE( fromCode->name(), def->name() );
2718   QCOMPARE( fromCode->description(), QStringLiteral( "optional default false" ) );
2719   QCOMPARE( fromCode->flags(), def->flags() );
2720   QCOMPARE( fromCode->defaultValue().toBool(), false );
2721 
2722   def.reset( new QgsProcessingParameterBoolean( "non_optional_default_true", QString(), true, false ) );
2723 
2724   QVERIFY( def->checkValueIsAcceptable( false ) );
2725   QVERIFY( def->checkValueIsAcceptable( true ) );
2726   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2727   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2728   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, because it falls back to default value
2729 
2730   params.insert( "non_optional_default_true",  false );
2731   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2732   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2733   params.insert( "non_optional_default_true",  true );
2734   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2735   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2736   params.insert( "non_optional_default_true",  "true" );
2737   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2738   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2739   params.insert( "non_optional_default_true",  "false" );
2740   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false );
2741   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), false );
2742   //non-optional - behavior is undefined, but internally fallback to default
2743   params.insert( "non_optional_default_true",  QVariant() );
2744   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2745   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2746   params.remove( "non_optional_default_true" );
2747   QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true );
2748   QCOMPARE( QgsProcessingParameters::parameterAsBoolean( def.get(), params, context ), true );
2749 
2750   pythonCode = def->asPythonString();
2751   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBoolean('non_optional_default_true', '', defaultValue=True)" ) );
2752 
2753   code = def->asScriptCode();
2754   fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2755   QVERIFY( fromCode.get() );
2756   QCOMPARE( fromCode->name(), def->name() );
2757   QCOMPARE( fromCode->description(), QStringLiteral( "non optional default true" ) );
2758   QCOMPARE( fromCode->flags(), def->flags() );
2759   QCOMPARE( fromCode->defaultValue().toBool(), true );
2760 
2761   def.reset( new QgsProcessingParameterBoolean( "non_optional_no_default", QString(),  QVariant(), false ) );
2762 
2763   QVERIFY( def->checkValueIsAcceptable( false ) );
2764   QVERIFY( def->checkValueIsAcceptable( true ) );
2765   QVERIFY( def->checkValueIsAcceptable( "false" ) );
2766   QVERIFY( def->checkValueIsAcceptable( "true" ) );
2767   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, because it falls back to invalid default value
2768 }
2769 
parameterCrs()2770 void TestQgsProcessing::parameterCrs()
2771 {
2772   // setup a context
2773   QgsProject p;
2774   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
2775   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
2776   const QString raster1 = testDataDir + "landsat_4326.tif";
2777   const QString raster2 = testDataDir + "landsat.tif";
2778   const QFileInfo fi1( raster1 );
2779   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
2780   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" );
2781   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 );
2782   QgsProcessingContext context;
2783   context.setProject( &p );
2784 
2785   // not optional!
2786   std::unique_ptr< QgsProcessingParameterCrs > def( new QgsProcessingParameterCrs( "non_optional", QString(), QString( "EPSG:3113" ), false ) );
2787   QVERIFY( !def->checkValueIsAcceptable( false ) );
2788   QVERIFY( !def->checkValueIsAcceptable( true ) );
2789   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
2790   QVERIFY( def->checkValueIsAcceptable( "EPSG:12003" ) );
2791   QVERIFY( def->checkValueIsAcceptable( "EPSG:3111" ) );
2792   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
2793   QVERIFY( def->checkValueIsAcceptable( QgsCoordinateReferenceSystem() ) );
2794   QVERIFY( def->checkValueIsAcceptable( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) ) );
2795   QVERIFY( !def->checkValueIsAcceptable( "" ) );
2796   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
2797   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( r1->id() ) ) );
2798   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( QVariant::fromValue( r1 ) ) ) ) );
2799   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( r1->id() ) ) );
2800 
2801   // using map layer
2802   QVariantMap params;
2803   params.insert( "non_optional",  v1->id() );
2804   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) );
2805   QVERIFY( def->checkValueIsAcceptable( v1->id() ) );
2806   params.insert( "non_optional",  QVariant::fromValue( v1 ) );
2807   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) );
2808 
2809   // using QgsCoordinateReferenceSystem
2810   params.insert( "non_optional",  QgsCoordinateReferenceSystem( "EPSG:28356" ) );
2811   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:28356" ) );
2812   params.insert( "non_optional",  QgsCoordinateReferenceSystem() );
2813   QVERIFY( !QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).isValid() );
2814 
2815   // special ProjectCrs string
2816   params.insert( "non_optional",  QStringLiteral( "ProjectCrs" ) );
2817   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:28353" ) );
2818   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "ProjectCrs" ) ) );
2819 
2820   // string representing a project layer source
2821   params.insert( "non_optional", raster1 );
2822   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:4326" ) );
2823   QVERIFY( def->checkValueIsAcceptable( raster1 ) );
2824 
2825   // string representing a non-project layer source
2826   params.insert( "non_optional", raster2 );
2827   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:32633" ) );
2828   QVERIFY( def->checkValueIsAcceptable( raster2 ) );
2829 
2830   // string representation of a crs
2831   params.insert( "non_optional", QString( "EPSG:28355" ) );
2832   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:28355" ) );
2833   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "EPSG:28355" ) ) );
2834 
2835   // nonsense string
2836   params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) );
2837   QVERIFY( !QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).isValid() );
2838 
2839   // using feature source definition
2840   params.insert( "non_optional",  QgsProcessingFeatureSourceDefinition( v1->id() ) );
2841   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) );
2842   params.insert( "non_optional",  QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( QVariant::fromValue( v1 ) ) ) );
2843   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) );
2844   params.insert( "non_optional",  QgsProcessingOutputLayerDefinition( v1->id() ) );
2845   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) );
2846 
2847   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
2848   QCOMPARE( def->valueAsPythonString( QgsCoordinateReferenceSystem( "EPSG:3111" ), context ), QStringLiteral( "QgsCoordinateReferenceSystem('EPSG:3111')" ) );
2849   QCOMPARE( def->valueAsPythonString( QgsCoordinateReferenceSystem(), context ), QStringLiteral( "QgsCoordinateReferenceSystem()" ) );
2850   QCOMPARE( def->valueAsPythonString( "EPSG:12003", context ), QStringLiteral( "'EPSG:12003'" ) );
2851   QCOMPARE( def->valueAsPythonString( "ProjectCrs", context ), QStringLiteral( "'ProjectCrs'" ) );
2852   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
2853   QCOMPARE( def->valueAsPythonString( raster1, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "landsat_4326.tif'" ) ) );
2854   QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "landsat_4326.tif'" ) ) );
2855   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
2856   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
2857 
2858   const QVariantMap map = def->toVariantMap();
2859   QgsProcessingParameterCrs fromMap( "x" );
2860   QVERIFY( fromMap.fromVariantMap( map ) );
2861   QCOMPARE( fromMap.name(), def->name() );
2862   QCOMPARE( fromMap.description(), def->description() );
2863   QCOMPARE( fromMap.flags(), def->flags() );
2864   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
2865   def.reset( dynamic_cast< QgsProcessingParameterCrs *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
2866   QVERIFY( dynamic_cast< QgsProcessingParameterCrs *>( def.get() ) );
2867 
2868   QString pythonCode = def->asPythonString();
2869   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterCrs('non_optional', '', defaultValue='EPSG:3113')" ) );
2870 
2871   QString code = def->asScriptCode();
2872   QCOMPARE( code, QStringLiteral( "##non_optional=crs EPSG:3113" ) );
2873   std::unique_ptr< QgsProcessingParameterCrs > fromCode( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2874   QVERIFY( fromCode.get() );
2875   QCOMPARE( fromCode->name(), def->name() );
2876   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
2877   QCOMPARE( fromCode->flags(), def->flags() );
2878   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
2879 
2880   // optional
2881   def.reset( new QgsProcessingParameterCrs( "optional", QString(), QString( "EPSG:3113" ), true ) );
2882   params.insert( "optional",  QVariant() );
2883   QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3113" ) );
2884   QVERIFY( def->checkValueIsAcceptable( false ) );
2885   QVERIFY( def->checkValueIsAcceptable( true ) );
2886   QVERIFY( def->checkValueIsAcceptable( 5 ) );
2887   QVERIFY( def->checkValueIsAcceptable( "EPSG:12003" ) );
2888   QVERIFY( def->checkValueIsAcceptable( "EPSG:3111" ) );
2889   QVERIFY( def->checkValueIsAcceptable( "" ) );
2890   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
2891 
2892   pythonCode = def->asPythonString();
2893   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterCrs('optional', '', optional=True, defaultValue='EPSG:3113')" ) );
2894 
2895   code = def->asScriptCode();
2896   QCOMPARE( code, QStringLiteral( "##optional=optional crs EPSG:3113" ) );
2897   fromCode.reset( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2898   QVERIFY( fromCode.get() );
2899   QCOMPARE( fromCode->name(), def->name() );
2900   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
2901   QCOMPARE( fromCode->flags(), def->flags() );
2902   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
2903 
2904   code = QStringLiteral( "##optional=optional crs None" );
2905   fromCode.reset( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2906   QVERIFY( fromCode.get() );
2907   QCOMPARE( fromCode->name(), def->name() );
2908   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
2909   QCOMPARE( fromCode->flags(), def->flags() );
2910   QVERIFY( !fromCode->defaultValue().isValid() );
2911 }
2912 
parameterMapLayer()2913 void TestQgsProcessing::parameterMapLayer()
2914 {
2915   // setup a context
2916   QgsProject p;
2917   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
2918   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
2919   const QString raster1 = testDataDir + "tenbytenraster.asc";
2920   const QString raster2 = testDataDir + "landsat.tif";
2921   const QFileInfo fi1( raster1 );
2922   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
2923   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" );
2924   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 );
2925   QgsProcessingContext context;
2926   context.setProject( &p );
2927 
2928   // not optional!
2929   std::unique_ptr< QgsProcessingParameterMapLayer > def( new QgsProcessingParameterMapLayer( "non_optional", QString(), QString(), false ) );
2930   QVERIFY( !def->checkValueIsAcceptable( false ) );
2931   QVERIFY( !def->checkValueIsAcceptable( true ) );
2932   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
2933   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
2934   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
2935   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
2936   QVERIFY( !def->checkValueIsAcceptable( "" ) );
2937   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
2938 
2939   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
2940   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
2941   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
2942   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
2943 
2944   // should be OK
2945   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
2946   // ... unless we use context, when the check that the layer actually exists is performed
2947   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
2948 
2949   // using existing map layer ID
2950   QVariantMap params;
2951   params.insert( "non_optional",  v1->id() );
2952   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), v1->id() );
2953   QVERIFY( def->checkValueIsAcceptable( v1->id() ) );
2954   QVERIFY( def->checkValueIsAcceptable( v1->id(), &context ) );
2955 
2956   // string representing a project layer source
2957   params.insert( "non_optional", raster1 );
2958   QVERIFY( def->checkValueIsAcceptable( raster1 ) );
2959   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), r1->id() );
2960   // string representing a non-project layer source
2961   params.insert( "non_optional", raster2 );
2962   QVERIFY( def->checkValueIsAcceptable( raster2 ) );
2963   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->publicSource(), raster2 );
2964 
2965   // nonsense string
2966   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
2967   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
2968 
2969   // layer
2970   params.insert( "non_optional", QVariant::fromValue( r1 ) );
2971   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ), r1 );
2972   params.insert( "non_optional", QVariant::fromValue( v1 ) );
2973   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ), v1 );
2974 
2975   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
2976   QCOMPARE( def->valueAsPythonString( raster1, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
2977   QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
2978   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
2979   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
2980   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
2981 
2982   QString pythonCode = def->asPythonString();
2983   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='')" ) );
2984 
2985   QString code = def->asScriptCode();
2986   QCOMPARE( code, QStringLiteral( "##non_optional=layer" ) );
2987   std::unique_ptr< QgsProcessingParameterMapLayer > fromCode( dynamic_cast< QgsProcessingParameterMapLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
2988   QVERIFY( fromCode.get() );
2989   QCOMPARE( fromCode->name(), def->name() );
2990   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
2991   QCOMPARE( fromCode->flags(), def->flags() );
2992   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
2993 
2994   const QVariantMap map = def->toVariantMap();
2995   QgsProcessingParameterMapLayer fromMap( "x" );
2996   QVERIFY( fromMap.fromVariantMap( map ) );
2997   QCOMPARE( fromMap.name(), def->name() );
2998   QCOMPARE( fromMap.description(), def->description() );
2999   QCOMPARE( fromMap.flags(), def->flags() );
3000   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3001   def.reset( dynamic_cast< QgsProcessingParameterMapLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3002   QVERIFY( dynamic_cast< QgsProcessingParameterMapLayer *>( def.get() ) );
3003 
3004   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint );
3005   pythonCode = def->asPythonString();
3006   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorPoint])" ) );
3007   code = def->asScriptCode();
3008   QCOMPARE( code, QStringLiteral( "##non_optional=layer point" ) );
3009   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorLine );
3010   pythonCode = def->asPythonString();
3011   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorLine])" ) );
3012   code = def->asScriptCode();
3013   QCOMPARE( code, QStringLiteral( "##non_optional=layer line" ) );
3014   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPolygon );
3015   pythonCode = def->asPythonString();
3016   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorPolygon])" ) );
3017   code = def->asScriptCode();
3018   QCOMPARE( code, QStringLiteral( "##non_optional=layer polygon" ) );
3019   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
3020   pythonCode = def->asPythonString();
3021   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorAnyGeometry])" ) );
3022   code = def->asScriptCode();
3023   QCOMPARE( code, QStringLiteral( "##non_optional=layer hasgeometry" ) );
3024   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
3025   pythonCode = def->asPythonString();
3026   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorPoint,QgsProcessing.TypeVectorLine])" ) );
3027   code = def->asScriptCode();
3028   QCOMPARE( code, QStringLiteral( "##non_optional=layer point line" ) );
3029   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorPolygon );
3030   pythonCode = def->asPythonString();
3031   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeVectorPoint,QgsProcessing.TypeVectorPolygon])" ) );
3032   code = def->asScriptCode();
3033   QCOMPARE( code, QStringLiteral( "##non_optional=layer point polygon" ) );
3034   def->setDataTypes( QList< int >() << QgsProcessing::TypeRaster << QgsProcessing::TypeVectorPoint );
3035   pythonCode = def->asPythonString();
3036   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeRaster,QgsProcessing.TypeVectorPoint])" ) );
3037   code = def->asScriptCode();
3038   QCOMPARE( code, QStringLiteral( "##non_optional=layer raster point" ) );
3039   def->setDataTypes( QList< int >() << QgsProcessing::TypePlugin );
3040   pythonCode = def->asPythonString();
3041   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypePlugin])" ) );
3042   code = def->asScriptCode();
3043   QCOMPARE( code, QStringLiteral( "##non_optional=layer plugin" ) );
3044   def->setDataTypes( QList< int >() << QgsProcessing::TypePointCloud );
3045   pythonCode = def->asPythonString();
3046   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypePointCloud])" ) );
3047   code = def->asScriptCode();
3048   QCOMPARE( code, QStringLiteral( "##non_optional=layer pointcloud" ) );
3049   def->setDataTypes( QList< int >() << QgsProcessing::TypeAnnotation );
3050   pythonCode = def->asPythonString();
3051   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapLayer('non_optional', '', defaultValue='', types=[QgsProcessing.TypeAnnotation])" ) );
3052   code = def->asScriptCode();
3053   QCOMPARE( code, QStringLiteral( "##non_optional=layer annotation" ) );
3054 
3055   // optional
3056   def.reset( new QgsProcessingParameterMapLayer( "optional", QString(), v1->id(), true ) );
3057   params.insert( "optional",  QVariant() );
3058   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), v1->id() );
3059   QVERIFY( def->checkValueIsAcceptable( false ) );
3060   QVERIFY( def->checkValueIsAcceptable( true ) );
3061   QVERIFY( def->checkValueIsAcceptable( 5 ) );
3062   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
3063   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
3064   QVERIFY( def->checkValueIsAcceptable( "" ) );
3065   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3066   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
3067   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
3068 
3069   pythonCode = def->asPythonString();
3070   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterMapLayer('optional', '', optional=True, defaultValue='" ) + v1->id() + "')" ) );
3071 
3072   code = def->asScriptCode();
3073   QCOMPARE( code, QString( QStringLiteral( "##optional=optional layer " ) + v1->id() ) );
3074   fromCode.reset( dynamic_cast< QgsProcessingParameterMapLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3075   QVERIFY( fromCode.get() );
3076   QCOMPARE( fromCode->name(), def->name() );
3077   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3078   QCOMPARE( fromCode->flags(), def->flags() );
3079   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3080 
3081   // check if can manage QgsProcessingOutputLayerDefinition
3082   // as QVariat value in parameters (e.g. coming from an input of
3083   // another algorithm)
3084 
3085   // all ok
3086   def.reset( new QgsProcessingParameterMapLayer( "non_optional", QString(), r1->id(), true ) );
3087   QString sink_name( r1->id() );
3088   const QgsProcessingOutputLayerDefinition val( sink_name );
3089   params.insert( "non_optional", QVariant::fromValue( val ) );
3090   QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), r1->id() );
3091 
3092   // not ok, e.g. source name is not a layer and it's not possible to generate a layer from it source
3093   def.reset( new QgsProcessingParameterMapLayer( "non_optional", QString(), r1->id(), true ) );
3094   sink_name = QString( "i'm not a layer, and nothing you can do will make me one" );
3095   const QgsProcessingOutputLayerDefinition val2( sink_name );
3096   params.insert( "non_optional", QVariant::fromValue( val2 ) );
3097   QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) );
3098 }
3099 
parameterExtent()3100 void TestQgsProcessing::parameterExtent()
3101 {
3102   // setup a context
3103   QgsProject p;
3104   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
3105   const QString raster1 = testDataDir + "landsat_4326.tif";
3106   const QString raster2 = testDataDir + "landsat.tif";
3107   const QFileInfo fi1( raster1 );
3108   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
3109   p.addMapLayers( QList<QgsMapLayer *>() << r1 );
3110   QgsProcessingContext context;
3111   context.setProject( &p );
3112 
3113   // not optional!
3114   std::unique_ptr< QgsProcessingParameterExtent > def( new QgsProcessingParameterExtent( "non_optional", QString(), QString( "1,2,3,4" ), false ) );
3115   QVERIFY( !def->checkValueIsAcceptable( false ) );
3116   QVERIFY( !def->checkValueIsAcceptable( true ) );
3117   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
3118   QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) );
3119   QVERIFY( def->checkValueIsAcceptable( "    1, 2   ,3  , 4  " ) );
3120   QVERIFY( def->checkValueIsAcceptable( "    1, 2   ,3  , 4  ", &context ) );
3121   QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4" ) );
3122   QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4", &context ) );
3123   QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4" ) );
3124   QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4", &context ) );
3125   QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]" ) );
3126   QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]", &context ) );
3127   QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]" ) );
3128   QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]", &context ) );
3129   QVERIFY( def->checkValueIsAcceptable( "  -1.1,   -2,    -3,   -4.4   [EPSG:4326]    " ) );
3130   QVERIFY( def->checkValueIsAcceptable( "  -1.1,   -2,    -3,   -4.4   [EPSG:4326]    ", &context ) );
3131   QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]" ) );
3132   QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]", &context ) );
3133 
3134   QVERIFY( !def->checkValueIsAcceptable( "" ) );
3135   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3136   QVERIFY( def->checkValueIsAcceptable( QgsRectangle( 1, 2, 3, 4 ) ) );
3137   QVERIFY( !def->checkValueIsAcceptable( QgsRectangle() ) );
3138   QVERIFY( def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
3139   QVERIFY( !def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle(), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
3140   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromRect( QgsRectangle( 1, 2, 3, 4 ) ) ) );
3141   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromWkt( QStringLiteral( "LineString(10 10, 20 20)" ) ) ) );
3142 
3143   // these checks require a context - otherwise we could potentially be referring to a layer source
3144   QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) );
3145   QVERIFY( def->checkValueIsAcceptable( "1,2,3,a" ) );
3146   QVERIFY( !def->checkValueIsAcceptable( "1,2,3", &context ) );
3147   QVERIFY( !def->checkValueIsAcceptable( "1,2,3,a", &context ) );
3148 
3149   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( r1->id() ) ) );
3150   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( r1->id() ) ) );
3151 
3152   // using map layer
3153   QVariantMap params;
3154   params.insert( "non_optional",  r1->id() );
3155   QVERIFY( def->checkValueIsAcceptable( r1->id() ) );
3156   QgsRectangle ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3157   QCOMPARE( ext, r1->extent() );
3158 
3159   // string representing a project layer source
3160   params.insert( "non_optional", raster1 );
3161   QVERIFY( def->checkValueIsAcceptable( raster1 ) );
3162   QCOMPARE( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ),  r1->extent() );
3163   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3164   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3165   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3166   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3167   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3168   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3169 
3170   // layer as parameter
3171   params.insert( "non_optional", QVariant::fromValue( r1 ) );
3172   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
3173   QCOMPARE( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ),  r1->extent() );
3174   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3175   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3176   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3177   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3178   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3179   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3180   QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3181   QCOMPARE( gExt.constGet()->vertexCount(), 5 );
3182   ext = gExt.boundingBox();
3183   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3184   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3185   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3186   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3187 
3188   // using feature source definition
3189   params.insert( "non_optional",  QgsProcessingFeatureSourceDefinition( r1->id() ) );
3190   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3191   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3192   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3193   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3194   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3195   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3196   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3197   QCOMPARE( gExt.constGet()->vertexCount(), 5 );
3198   ext = gExt.boundingBox();
3199   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3200   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3201   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3202   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3203   params.insert( "non_optional",  QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( QVariant::fromValue( r1 ) ) ) );
3204   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3205   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3206   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3207   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3208   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3209   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3210   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3211   QCOMPARE( gExt.constGet()->vertexCount(), 5 );
3212   ext = gExt.boundingBox();
3213   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3214   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3215   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3216   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3217 
3218   // using output layer definition, e.g. from a previous model child algorithm
3219   params.insert( "non_optional",  QgsProcessingOutputLayerDefinition( r1->id() ) );
3220   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3221   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3222   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3223   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3224   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3225   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3226   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3227   QCOMPARE( gExt.constGet()->vertexCount(), 5 );
3228   ext = gExt.boundingBox();
3229   QGSCOMPARENEAR( ext.xMinimum(), 17.942777, 0.001 );
3230   QGSCOMPARENEAR( ext.xMaximum(), 17.944704, 0.001 );
3231   QGSCOMPARENEAR( ext.yMinimum(),  30.229681, 0.001 );
3232   QGSCOMPARENEAR( ext.yMaximum(), 30.231616, 0.001 );
3233 
3234   // string representing a non-project layer source
3235   params.insert( "non_optional", raster2 );
3236   QVERIFY( def->checkValueIsAcceptable( raster2 ) );
3237   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:32633" ) );
3238   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3239   QGSCOMPARENEAR( ext.xMinimum(), 781662.375000, 10 );
3240   QGSCOMPARENEAR( ext.xMaximum(), 793062.375000, 10 );
3241   QGSCOMPARENEAR( ext.yMinimum(),  3339523.125000, 10 );
3242   QGSCOMPARENEAR( ext.yMaximum(), 3350923.125000, 10 );
3243   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3244   QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 );
3245   QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 );
3246   QGSCOMPARENEAR( ext.yMinimum(),  30.151856, 0.01 );
3247   QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 );
3248   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
3249   QCOMPARE( gExt.constGet()->vertexCount(), 85 );
3250   ext = gExt.boundingBox();
3251   QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 );
3252   QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 );
3253   QGSCOMPARENEAR( ext.yMinimum(),  30.151856, 0.01 );
3254   QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 );
3255 
3256   // string representation of an extent
3257   params.insert( "non_optional", QString( "1.1,2.2,3.3,4.4" ) );
3258   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "1.1,2.2,3.3,4.4" ) ) );
3259   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3260   QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
3261   QGSCOMPARENEAR( ext.xMaximum(), 2.2, 0.001 );
3262   QGSCOMPARENEAR( ext.yMinimum(),  3.3, 0.001 );
3263   QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
3264 
3265   // with target CRS - should make no difference, because source CRS is unknown
3266   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3267   QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
3268   QGSCOMPARENEAR( ext.xMaximum(), 2.2, 0.001 );
3269   QGSCOMPARENEAR( ext.yMinimum(),  3.3, 0.001 );
3270   QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
3271 
3272   // with crs in string
3273   params.insert( "non_optional", QString( "1.1,3.3,2.2,4.4 [EPSG:4326]" ) );
3274   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3275   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3276   QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
3277   QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 );
3278   QGSCOMPARENEAR( ext.yMinimum(),  2.2, 0.001 );
3279   QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
3280   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3281   QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
3282   QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
3283   QGSCOMPARENEAR( ext.yMinimum(),  244963, 100 );
3284   QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
3285   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3286   QCOMPARE( gExt.constGet()->vertexCount(), 85 );
3287   ext = gExt.boundingBox();
3288   QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
3289   QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
3290   QGSCOMPARENEAR( ext.yMinimum(),  244963, 100 );
3291   QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
3292 
3293   // nonsense string
3294   params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) );
3295   QVERIFY( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ).isNull() );
3296 
3297   // QgsRectangle
3298   params.insert( "non_optional", QgsRectangle( 11.1, 12.2, 13.3, 14.4 ) );
3299   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3300   QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 );
3301   QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 );
3302   QGSCOMPARENEAR( ext.yMinimum(),  12.2, 0.001 );
3303   QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 );
3304 
3305   // with target CRS - should make no difference, because source CRS is unknown
3306   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3307   QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 );
3308   QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 );
3309   QGSCOMPARENEAR( ext.yMinimum(),  12.2, 0.001 );
3310   QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 );
3311 
3312   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3313   QCOMPARE( gExt.asWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) );
3314 
3315   p.setCrs( QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3316   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:3785" ) );
3317 
3318   // QgsGeometry
3319   params.insert( "non_optional", QgsGeometry::fromRect( QgsRectangle( 13, 14, 15, 16 ) ) );
3320   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3321   QGSCOMPARENEAR( ext.xMinimum(), 13, 0.001 );
3322   QGSCOMPARENEAR( ext.xMaximum(), 15, 0.001 );
3323   QGSCOMPARENEAR( ext.yMinimum(),  14, 0.001 );
3324   QGSCOMPARENEAR( ext.yMaximum(), 16, 0.001 );
3325   // with target CRS - should make no difference, because source CRS is unknown
3326   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context,  QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3327   QGSCOMPARENEAR( ext.xMinimum(), 13, 0.001 );
3328   QGSCOMPARENEAR( ext.xMaximum(), 15, 0.001 );
3329   QGSCOMPARENEAR( ext.yMinimum(),  14, 0.001 );
3330   QGSCOMPARENEAR( ext.yMaximum(), 16, 0.001 );
3331 
3332   // QgsReferencedRectangle
3333   params.insert( "non_optional", QgsReferencedRectangle( QgsRectangle( 1.1, 2.2, 3.3, 4.4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3334   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3335   QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
3336   QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 );
3337   QGSCOMPARENEAR( ext.yMinimum(),  2.2, 0.001 );
3338   QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
3339   QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3340 
3341   // with target CRS
3342   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3343   QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
3344   QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
3345   QGSCOMPARENEAR( ext.yMinimum(),  244963, 100 );
3346   QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
3347 
3348   // as reprojected geometry
3349   gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3350   QCOMPARE( gExt.constGet()->vertexCount(), 85 );
3351   ext = gExt.boundingBox();
3352   QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
3353   QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
3354   QGSCOMPARENEAR( ext.yMinimum(),  244963, 100 );
3355   QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
3356 
3357   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
3358   QCOMPARE( def->valueAsPythonString( "1,2,3,4", context ), QStringLiteral( "'1,2,3,4'" ) );
3359   QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "landsat_4326.tif'" ) ) );
3360   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "landsat_4326.tif'" ) ) );
3361   QCOMPARE( def->valueAsPythonString( raster2, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "landsat.tif'" ) ) );
3362   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
3363   QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "'11, 13, 12, 14'" ) );
3364   QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11, 13, 12, 14 [EPSG:4326]'" ) );
3365   QCOMPARE( def->valueAsPythonString( "1,2,3,4 [EPSG:4326]", context ), QStringLiteral( "'1,2,3,4 [EPSG:4326]'" ) );
3366   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
3367   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
3368   QCOMPARE( def->valueAsPythonString( QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 20)" ) ), context ), QStringLiteral( "QgsGeometry.fromWkt('LineString (10 10, 20 20)')" ) );
3369 
3370   QString pythonCode = def->asPythonString();
3371   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExtent('non_optional', '', defaultValue='1,2,3,4')" ) );
3372 
3373   QString code = def->asScriptCode();
3374   QCOMPARE( code, QStringLiteral( "##non_optional=extent 1,2,3,4" ) );
3375   std::unique_ptr< QgsProcessingParameterExtent > fromCode( dynamic_cast< QgsProcessingParameterExtent * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3376   QVERIFY( fromCode.get() );
3377   QCOMPARE( fromCode->name(), def->name() );
3378   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
3379   QCOMPARE( fromCode->flags(), def->flags() );
3380   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3381 
3382   const QVariantMap map = def->toVariantMap();
3383   QgsProcessingParameterExtent fromMap( "x" );
3384   QVERIFY( fromMap.fromVariantMap( map ) );
3385   QCOMPARE( fromMap.name(), def->name() );
3386   QCOMPARE( fromMap.description(), def->description() );
3387   QCOMPARE( fromMap.flags(), def->flags() );
3388   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3389   def.reset( dynamic_cast< QgsProcessingParameterExtent *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3390   QVERIFY( dynamic_cast< QgsProcessingParameterExtent *>( def.get() ) );
3391 
3392   // optional
3393   def.reset( new QgsProcessingParameterExtent( "optional", QString(), QString( "5,6,7,8" ), true ) );
3394   QVERIFY( def->checkValueIsAcceptable( false ) );
3395   QVERIFY( def->checkValueIsAcceptable( true ) );
3396   QVERIFY( def->checkValueIsAcceptable( 5 ) );
3397   QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) );
3398   QVERIFY( def->checkValueIsAcceptable( "" ) );
3399   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3400 
3401   // Extent is unique in that it will let you set invalid, whereas other
3402   // optional parameters become "default" when assigning invalid.
3403   params.insert( "optional",  QVariant() );
3404   ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
3405   QVERIFY( ext.isNull() );
3406 
3407   pythonCode = def->asPythonString();
3408   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExtent('optional', '', optional=True, defaultValue='5,6,7,8')" ) );
3409 
3410   code = def->asScriptCode();
3411   QCOMPARE( code, QStringLiteral( "##optional=optional extent 5,6,7,8" ) );
3412   fromCode.reset( dynamic_cast< QgsProcessingParameterExtent * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3413   QVERIFY( fromCode.get() );
3414   QCOMPARE( fromCode->name(), def->name() );
3415   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3416   QCOMPARE( fromCode->flags(), def->flags() );
3417   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3418 }
3419 
parameterPoint()3420 void TestQgsProcessing::parameterPoint()
3421 {
3422   QgsProcessingContext context;
3423 
3424   // not optional!
3425   std::unique_ptr< QgsProcessingParameterPoint > def( new QgsProcessingParameterPoint( "non_optional", QString(), QString( "1,2" ), false ) );
3426   QVERIFY( !def->checkValueIsAcceptable( false ) );
3427   QVERIFY( !def->checkValueIsAcceptable( true ) );
3428   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
3429   QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
3430   QVERIFY( def->checkValueIsAcceptable( "(1.1,2)" ) );
3431   QVERIFY( def->checkValueIsAcceptable( "    1.1,  2  " ) );
3432   QVERIFY( def->checkValueIsAcceptable( " (    1.1,  2 ) " ) );
3433   QVERIFY( def->checkValueIsAcceptable( "-1.1,2" ) );
3434   QVERIFY( def->checkValueIsAcceptable( "1.1,-2" ) );
3435   QVERIFY( def->checkValueIsAcceptable( "-1.1,-2" ) );
3436   QVERIFY( def->checkValueIsAcceptable( "(-1.1,-2)" ) );
3437   QVERIFY( def->checkValueIsAcceptable( "1.1,2[EPSG:4326]" ) );
3438   QVERIFY( def->checkValueIsAcceptable( "1.1,2 [EPSG:4326]" ) );
3439   QVERIFY( def->checkValueIsAcceptable( "(1.1,2 [EPSG:4326] )" ) );
3440   QVERIFY( def->checkValueIsAcceptable( "  -1.1,   -2   [EPSG:4326]    " ) );
3441   QVERIFY( def->checkValueIsAcceptable( "  (  -1.1,   -2   [EPSG:4326]  )  " ) );
3442   QVERIFY( !def->checkValueIsAcceptable( "1.1,a" ) );
3443   QVERIFY( !def->checkValueIsAcceptable( "(1.1,a)" ) );
3444   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
3445   QVERIFY( !def->checkValueIsAcceptable( "(layer12312312)" ) );
3446   QVERIFY( !def->checkValueIsAcceptable( "" ) );
3447   QVERIFY( !def->checkValueIsAcceptable( "()" ) );
3448   QVERIFY( !def->checkValueIsAcceptable( " (  ) " ) );
3449   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3450   QVERIFY( def->checkValueIsAcceptable( QgsPointXY( 1, 2 ) ) );
3451   QVERIFY( def->checkValueIsAcceptable( QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
3452   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) ) );
3453   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromWkt( QStringLiteral( "LineString(10 10, 20 20)" ) ) ) );
3454 
3455   // string representing a point
3456   QVariantMap params;
3457   params.insert( "non_optional", QString( "1.1,2.2" ) );
3458   QVERIFY( def->checkValueIsAcceptable( "1.1,2.2" ) );
3459   QgsPointXY point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3460   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3461   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3462 
3463   // with target CRS - should make no difference, because source CRS is unknown
3464   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3465   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3466   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3467 
3468   // with optional brackets
3469   params.insert( "non_optional", QString( "(1.1,2.2)" ) );
3470   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3471   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3472   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3473 
3474   params.insert( "non_optional", QString( "  (   -1.1  ,-2.2  )  " ) );
3475   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3476   QGSCOMPARENEAR( point.x(), -1.1, 0.001 );
3477   QGSCOMPARENEAR( point.y(), -2.2, 0.001 );
3478 
3479   // with CRS as string
3480   params.insert( "non_optional", QString( "1.1,2.2[EPSG:4326]" ) );
3481   QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3482   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3483   QGSCOMPARENEAR( point.x(), 122451, 100 );
3484   QGSCOMPARENEAR( point.y(), 244963, 100 );
3485   params.insert( "non_optional", QString( "1.1,2.2 [EPSG:4326]" ) );
3486   QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3487   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3488   QGSCOMPARENEAR( point.x(), 122451, 100 );
3489   QGSCOMPARENEAR( point.y(), 244963, 100 );
3490 
3491   params.insert( "non_optional", QString( "  ( 1.1,2.2   [EPSG:4326]   ) " ) );
3492   QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3493   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3494   QGSCOMPARENEAR( point.x(), 122451, 100 );
3495   QGSCOMPARENEAR( point.y(), 244963, 100 );
3496 
3497   // nonsense string
3498   params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) );
3499   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3500   QVERIFY( point.isEmpty() );
3501   QGSCOMPARENEAR( point.x(), 0.0, 0.001 );
3502   QGSCOMPARENEAR( point.y(), 0.0, 0.001 );
3503 
3504   params.insert( "non_optional", QString( "   (   )  " ) );
3505   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3506   QVERIFY( point.isEmpty() );
3507   QGSCOMPARENEAR( point.x(), 0.0, 0.001 );
3508   QGSCOMPARENEAR( point.y(), 0.0, 0.001 );
3509 
3510   // QgsPointXY
3511   params.insert( "non_optional", QgsPointXY( 11.1, 12.2 ) );
3512   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3513   QGSCOMPARENEAR( point.x(), 11.1, 0.001 );
3514   QGSCOMPARENEAR( point.y(), 12.2, 0.001 );
3515 
3516   // with target CRS - should make no difference, because source CRS is unknown
3517   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3518   QGSCOMPARENEAR( point.x(), 11.1, 0.001 );
3519   QGSCOMPARENEAR( point.y(), 12.2, 0.001 );
3520 
3521   // QgsReferencedPointXY
3522   params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3523   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3524   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3525   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3526   QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3527 
3528   // with target CRS
3529   params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3530   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3531   QGSCOMPARENEAR( point.x(), 122451, 100 );
3532   QGSCOMPARENEAR( point.y(), 244963, 100 );
3533 
3534   // QgsGeometry
3535   params.insert( "non_optional", QgsGeometry::fromPointXY( QgsPointXY( 13.1, 14.2 ) ) );
3536   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3537   QGSCOMPARENEAR( point.x(), 13.1, 0.001 );
3538   QGSCOMPARENEAR( point.y(), 14.2, 0.001 );
3539   // non point geometry should use centroid
3540   params.insert( "non_optional", QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 10)" ) ) );
3541   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3542   QGSCOMPARENEAR( point.x(), 15.0, 0.001 );
3543   QGSCOMPARENEAR( point.y(), 10.0, 0.001 );
3544   // with target CRS - should make no difference, because source CRS is unknown
3545   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3546   QGSCOMPARENEAR( point.x(), 15.0, 0.001 );
3547   QGSCOMPARENEAR( point.y(), 10.0, 0.001 );
3548 
3549   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
3550   QCOMPARE( def->valueAsPythonString( "1,2", context ), QStringLiteral( "'1,2'" ) );
3551   QCOMPARE( def->valueAsPythonString( "1,2 [EPSG:4326]", context ), QStringLiteral( "'1,2 [EPSG:4326]'" ) );
3552   QCOMPARE( def->valueAsPythonString( QgsPointXY( 11, 12 ), context ), QStringLiteral( "'11,12'" ) );
3553   QCOMPARE( def->valueAsPythonString( QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11,12 [EPSG:4326]'" ) );
3554   QCOMPARE( def->valueAsPythonString( QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 20)" ) ), context ), QStringLiteral( "QgsGeometry.fromWkt('LineString (10 10, 20 20)')" ) );
3555 
3556   QString pythonCode = def->asPythonString();
3557   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterPoint('non_optional', '', defaultValue='1,2')" ) );
3558 
3559   QString code = def->asScriptCode();
3560   QCOMPARE( code, QStringLiteral( "##non_optional=point 1,2" ) );
3561   std::unique_ptr< QgsProcessingParameterPoint > fromCode( dynamic_cast< QgsProcessingParameterPoint * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3562   QVERIFY( fromCode.get() );
3563   QCOMPARE( fromCode->name(), def->name() );
3564   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
3565   QCOMPARE( fromCode->flags(), def->flags() );
3566   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3567 
3568   const QVariantMap map = def->toVariantMap();
3569   QgsProcessingParameterPoint fromMap( "x" );
3570   QVERIFY( fromMap.fromVariantMap( map ) );
3571   QCOMPARE( fromMap.name(), def->name() );
3572   QCOMPARE( fromMap.description(), def->description() );
3573   QCOMPARE( fromMap.flags(), def->flags() );
3574   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3575   def.reset( dynamic_cast< QgsProcessingParameterPoint *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3576   QVERIFY( dynamic_cast< QgsProcessingParameterPoint *>( def.get() ) );
3577 
3578   // optional
3579   def.reset( new QgsProcessingParameterPoint( "optional", QString(), QString( "5.1,6.2" ), true ) );
3580   QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
3581   QVERIFY( !def->checkValueIsAcceptable( "1.1,a" ) );
3582   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
3583   QVERIFY( def->checkValueIsAcceptable( "" ) );
3584   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3585 
3586   params.insert( "optional",  QVariant() );
3587   point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context );
3588   QGSCOMPARENEAR( point.x(), 5.1, 0.001 );
3589   QGSCOMPARENEAR( point.y(), 6.2, 0.001 );
3590 
3591   pythonCode = def->asPythonString();
3592   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterPoint('optional', '', optional=True, defaultValue='5.1,6.2')" ) );
3593 
3594   code = def->asScriptCode();
3595   QCOMPARE( code, QStringLiteral( "##optional=optional point 5.1,6.2" ) );
3596   fromCode.reset( dynamic_cast< QgsProcessingParameterPoint * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3597   QVERIFY( fromCode.get() );
3598   QCOMPARE( fromCode->name(), def->name() );
3599   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3600   QCOMPARE( fromCode->flags(), def->flags() );
3601   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3602 }
3603 
parameterGeometry()3604 void TestQgsProcessing::parameterGeometry()
3605 {
3606   QgsProcessingContext context;
3607 
3608   // not optional!
3609   std::unique_ptr< QgsProcessingParameterGeometry > def( new QgsProcessingParameterGeometry( "non_optional", QString(), QString( "Point(1 2)" ), false ) );
3610   QVERIFY( !def->checkValueIsAcceptable( false ) );
3611   QVERIFY( !def->checkValueIsAcceptable( true ) );
3612   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
3613   QVERIFY( !def->checkValueIsAcceptable( "Nonsense string" ) );
3614   QVERIFY( !def->checkValueIsAcceptable( "" ) );
3615   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3616   QVERIFY( !def->checkValueIsAcceptable( QString( "LineString(10 10, 20 a)" ) ) );
3617   QVERIFY( def->checkValueIsAcceptable( QString( "LineString(10 10, 20 20)" ) ) );
3618   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) ) );
3619   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromWkt( QStringLiteral( "LineString(10 10, 20 20)" ) ) ) );
3620   QVERIFY( def->checkValueIsAcceptable( QgsPointXY( 1, 2 ) ) );
3621   QVERIFY( def->checkValueIsAcceptable( QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
3622   QVERIFY( def->checkValueIsAcceptable( QgsRectangle( 10, 10, 20, 20 ) ) );
3623   QVERIFY( def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle( 10, 10, 20, 20 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
3624   QVERIFY( def->checkValueIsAcceptable( QString( "MultiPoint((10 10), (20 20))" ) ) );
3625 
3626   // string representing a geometry
3627   QVariantMap params;
3628   params.insert( "non_optional", QString( "LineString(10 10, 20 20)" ) );
3629   QVERIFY( def->checkValueIsAcceptable( "LineString(10 10, 20 20)" ) );
3630   QgsGeometry geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3631   QCOMPARE( geometry.asWkt(), QStringLiteral( "LineString (10 10, 20 20)" ) );
3632 
3633   // with target CRS - should make no difference, because source CRS is unknown
3634   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3635   QCOMPARE( geometry.asWkt(), QStringLiteral( "LineString (10 10, 20 20)" ) );
3636 
3637   // with CRS as string
3638   params.insert( "non_optional", QString( "CRS=EPSG:4326;Point ( 1.1 2.2 )" ) );
3639   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3640   QPointF point = geometry.asQPointF();
3641   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3642   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3643   QCOMPARE( QgsProcessingParameters::parameterAsGeometryCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3644   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3645   point = geometry.asQPointF();
3646   QGSCOMPARENEAR( point.x(), 122451, 100 );
3647   QGSCOMPARENEAR( point.y(), 244963, 100 );
3648 
3649   // QgsReferencedGeometry
3650   params.insert( "non_optional", QgsReferencedGeometry( QgsGeometry::fromPointXY( QgsPointXY( 1.1, 2.2 ) ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3651   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3652   point = geometry.asQPointF();
3653   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3654   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3655   QCOMPARE( QgsProcessingParameters::parameterAsGeometryCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3656   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3657   point = geometry.asQPointF();
3658   QGSCOMPARENEAR( point.x(), 122451, 100 );
3659   QGSCOMPARENEAR( point.y(), 244963, 100 );
3660 
3661   // QgsPointXY
3662   params.insert( "non_optional", QgsPointXY( 11.1, 12.2 ) );
3663   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3664   point = geometry.asQPointF();
3665   QGSCOMPARENEAR( point.x(), 11.1, 0.001 );
3666   QGSCOMPARENEAR( point.y(), 12.2, 0.001 );
3667 
3668   // with target CRS - should make no difference, because source CRS is unknown
3669   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3670   point = geometry.asQPointF();
3671   QGSCOMPARENEAR( point.x(), 11.1, 0.001 );
3672   QGSCOMPARENEAR( point.y(), 12.2, 0.001 );
3673 
3674   // QgsReferencedPointXY
3675   params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3676   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3677   point = geometry.asQPointF();
3678   QGSCOMPARENEAR( point.x(), 1.1, 0.001 );
3679   QGSCOMPARENEAR( point.y(), 2.2, 0.001 );
3680   QCOMPARE( QgsProcessingParameters::parameterAsGeometryCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3681 
3682   // with target CRS
3683   params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3684   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3685   point = geometry.asQPointF();
3686   QGSCOMPARENEAR( point.x(), 122451, 100 );
3687   QGSCOMPARENEAR( point.y(), 244963, 100 );
3688 
3689   // QgsRectangle
3690   params.insert( "non_optional", QgsRectangle( 11.1, 12.2, 13.3, 14.4 ) );
3691   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3692   QCOMPARE( geometry.asWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) );
3693 
3694   // with target CRS - should make no difference, because source CRS is unknown
3695   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3696   QCOMPARE( geometry.asWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) );
3697 
3698   // QgsReferenced Rectangle
3699   params.insert( "non_optional", QgsReferencedRectangle( QgsRectangle( 11.1, 12.2, 13.3, 14.4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
3700   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3701   QCOMPARE( geometry.asWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) );
3702   QCOMPARE( QgsProcessingParameters::parameterAsGeometryCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
3703 
3704   // with target CRS
3705   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
3706   QCOMPARE( geometry.constGet()->vertexCount(), 85 );
3707   const QgsRectangle ext = geometry.boundingBox();
3708   QGSCOMPARENEAR( ext.xMinimum(), 1235646, 100 );
3709   QGSCOMPARENEAR( ext.xMaximum(), 1480549, 100 );
3710   QGSCOMPARENEAR( ext.yMinimum(), 1368478, 100 );
3711   QGSCOMPARENEAR( ext.yMaximum(), 1620147, 100 );
3712 
3713   // nonsense string
3714   params.insert( "non_optional", QString( "i'm not a geometry, and nothing you can do will make me one" ) );
3715   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3716   QVERIFY( geometry.isNull() );
3717 
3718   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
3719   QCOMPARE( def->valueAsPythonString( "LineString( 10 10, 20 20)", context ), QStringLiteral( "'LineString( 10 10, 20 20)'" ) );
3720   QCOMPARE( def->valueAsPythonString( QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 20)" ) ), context ), QStringLiteral( "'LineString (10 10, 20 20)'" ) );
3721 
3722   // With Srid as string
3723   QCOMPARE( def->valueAsPythonString( QgsReferencedGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 20)" ) ),
3724                                       QgsCoordinateReferenceSystem( "EPSG:4326" ) ), context ),
3725             QStringLiteral( "'CRS=EPSG:4326;LineString (10 10, 20 20)'" ) );
3726 
3727   QString pythonCode = def->asPythonString();
3728   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('non_optional', '', defaultValue='Point(1 2)')" ) );
3729 
3730   QString code = def->asScriptCode();
3731   QCOMPARE( code, QStringLiteral( "##non_optional=geometry Point(1 2)" ) );
3732   std::unique_ptr< QgsProcessingParameterGeometry > fromCode( dynamic_cast< QgsProcessingParameterGeometry * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3733   QVERIFY( fromCode.get() );
3734   QCOMPARE( fromCode->name(), def->name() );
3735   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
3736   QCOMPARE( fromCode->flags(), def->flags() );
3737   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3738 
3739   const QVariantMap map = def->toVariantMap();
3740   QgsProcessingParameterGeometry fromMap( "x" );
3741   QVERIFY( fromMap.fromVariantMap( map ) );
3742   QCOMPARE( fromMap.name(), def->name() );
3743   QCOMPARE( fromMap.description(), def->description() );
3744   QCOMPARE( fromMap.flags(), def->flags() );
3745   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3746   def.reset( dynamic_cast< QgsProcessingParameterGeometry *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3747   QVERIFY( dynamic_cast< QgsProcessingParameterGeometry *>( def.get() ) );
3748 
3749   // optional
3750   def.reset( new QgsProcessingParameterGeometry( "optional", QString(), QString( "Point(-1 3)" ), true ) );
3751   QVERIFY( def->checkValueIsAcceptable( "LineString(10 10, 20 20)" ) );
3752   QVERIFY( !def->checkValueIsAcceptable( "Point(-1 a)" ) );
3753   QVERIFY( def->checkValueIsAcceptable( "" ) );
3754   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3755 
3756   params.insert( "optional",  QVariant() );
3757   geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context );
3758   QCOMPARE( geometry.asWkt(), QStringLiteral( "Point (-1 3)" ) );
3759 
3760   pythonCode = def->asPythonString();
3761   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('optional', '', optional=True, defaultValue='Point(-1 3)')" ) );
3762 
3763   code = def->asScriptCode();
3764   QCOMPARE( code, QStringLiteral( "##optional=optional geometry Point(-1 3)" ) );
3765   fromCode.reset( dynamic_cast< QgsProcessingParameterGeometry * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3766   QVERIFY( fromCode.get() );
3767   QCOMPARE( fromCode->name(), def->name() );
3768   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3769   QCOMPARE( fromCode->flags(), def->flags() );
3770   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3771 
3772   // non optional with filter
3773   def.reset( new QgsProcessingParameterGeometry( "filtered", QString(), QString( "Point(-1 3)" ), false,
3774   { QgsWkbTypes::LineGeometry } ) );
3775   QVERIFY( def->geometryTypes().contains( QgsWkbTypes::LineGeometry ) );
3776   QVERIFY( def->checkValueIsAcceptable( "LineString(10 10, 20 20)" ) );
3777   QVERIFY( !def->checkValueIsAcceptable( "Point(1 2)" ) );
3778   QVERIFY( !def->checkValueIsAcceptable( "" ) );
3779   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3780 
3781   pythonCode = def->asPythonString();
3782   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('filtered', '', geometryTypes=[ QgsWkbTypes.LineGeometry ], defaultValue='Point(-1 3)')" ) );
3783 
3784   const QVariantMap map2 = def->toVariantMap();
3785   QgsProcessingParameterGeometry fromMap2( "x" );
3786   QVERIFY( fromMap2.fromVariantMap( map2 ) );
3787   QCOMPARE( fromMap2.name(), def->name() );
3788   QCOMPARE( fromMap2.description(), def->description() );
3789   QCOMPARE( fromMap2.flags(), def->flags() );
3790   QCOMPARE( fromMap2.defaultValue(), def->defaultValue() );
3791   QCOMPARE( fromMap2.geometryTypes(), def->geometryTypes() );
3792   QCOMPARE( fromMap2.allowMultipart(), def->allowMultipart() );
3793   def.reset( dynamic_cast< QgsProcessingParameterGeometry *>( QgsProcessingParameters::parameterFromVariantMap( map2 ) ) );
3794   QVERIFY( dynamic_cast< QgsProcessingParameterGeometry *>( def.get() ) );
3795 
3796   // not multipart
3797   def.reset( new QgsProcessingParameterGeometry( "not_multipart", QString(), QString( "Point(-1 3)" ), false, {}, false ) );
3798   QVERIFY( !def->allowMultipart() );
3799   QVERIFY( !def->checkValueIsAcceptable( QString( "MultiPoint((10 10), (20 20))" ) ) );
3800   QVERIFY( !def->checkValueIsAcceptable( QgsGeometry::fromWkt( QStringLiteral( "MultiPoint((10 10), (20 20))" ) ) ) );
3801   QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) ) );
3802 
3803   pythonCode = def->asPythonString();
3804   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('not_multipart', '', allowMultipart=False, defaultValue='Point(-1 3)')" ) );
3805 
3806   const QVariantMap map3 = def->toVariantMap();
3807   QgsProcessingParameterGeometry fromMap3( "x" );
3808   QVERIFY( fromMap3.fromVariantMap( map3 ) );
3809   QCOMPARE( fromMap3.allowMultipart(), false );
3810 
3811   std::unique_ptr< QgsProcessingParameterGeometry > cloned( dynamic_cast< QgsProcessingParameterGeometry *>( def->clone() ) );
3812   QCOMPARE( cloned->name(), def->name() );
3813   QCOMPARE( cloned->description(), def->description() );
3814   QCOMPARE( cloned->flags(), def->flags() );
3815   QCOMPARE( cloned->defaultValue(), def->defaultValue() );
3816   QCOMPARE( cloned->geometryTypes(), def->geometryTypes() );
3817   QCOMPARE( cloned->allowMultipart(), def->allowMultipart() );
3818 
3819 }
3820 
3821 
3822 
parameterFile()3823 void TestQgsProcessing::parameterFile()
3824 {
3825   QgsProcessingContext context;
3826 
3827   // not optional!
3828   std::unique_ptr< QgsProcessingParameterFile > def( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QString(), QString( "abc.bmp" ), false ) );
3829   QVERIFY( !def->checkValueIsAcceptable( false ) );
3830   QVERIFY( !def->checkValueIsAcceptable( true ) );
3831   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
3832   QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) );
3833   QVERIFY( !def->checkValueIsAcceptable( "" ) );
3834   QVERIFY( !def->checkValueIsAcceptable( "  " ) );
3835   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3836 
3837   // string representing a file
3838   QVariantMap params;
3839   params.insert( "non_optional", QString( "def.bmp" ) );
3840   QCOMPARE( QgsProcessingParameters::parameterAsFile( def.get(), params, context ), QString( "def.bmp" ) );
3841 
3842   // no extension
3843   def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QString(), QVariant(), false ) );
3844   QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) );
3845   QVERIFY( def->checkValueIsAcceptable( "bricks.BMP" ) );
3846   QVERIFY( def->checkValueIsAcceptable( "bricks.pcx" ) );
3847   QVERIFY( def->checkValueIsAcceptable( "bricks.PCX" ) );
3848   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
3849   QVERIFY( !def->checkValueIsAcceptable( QString( "" ) ) );
3850   def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QString(), QVariant(), true ) );
3851   QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) );
3852   QVERIFY( def->checkValueIsAcceptable( "bricks.BMP" ) );
3853   QVERIFY( def->checkValueIsAcceptable( "bricks.pcx" ) );
3854   QVERIFY( def->checkValueIsAcceptable( "bricks.PCX" ) );
3855   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3856   QVERIFY( def->checkValueIsAcceptable( QString( "" ) ) );
3857 
3858   // with extension
3859   def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QStringLiteral( ".bmp" ), QString( "abc.bmp" ), false ) );
3860   QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) );
3861   QVERIFY( def->checkValueIsAcceptable( "bricks.BMP" ) );
3862   QVERIFY( !def->checkValueIsAcceptable( "bricks.pcx" ) );
3863 
3864   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
3865   QCOMPARE( def->valueAsPythonString( "bricks.bmp", context ), QStringLiteral( "'bricks.bmp'" ) );
3866   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
3867   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
3868   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
3869 
3870   QString pythonCode = def->asPythonString();
3871   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('non_optional', '', behavior=QgsProcessingParameterFile.File, extension='.bmp', defaultValue='abc.bmp')" ) );
3872 
3873   QString code = def->asScriptCode();
3874   QCOMPARE( code, QStringLiteral( "##non_optional=file abc.bmp" ) );
3875   std::unique_ptr< QgsProcessingParameterFile > fromCode( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3876   QVERIFY( fromCode.get() );
3877   QCOMPARE( fromCode->name(), def->name() );
3878   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
3879   QCOMPARE( fromCode->flags(), def->flags() );
3880   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3881   QCOMPARE( fromCode->behavior(), def->behavior() );
3882 
3883   QVariantMap map = def->toVariantMap();
3884   QgsProcessingParameterFile fromMap( "x" );
3885   QVERIFY( fromMap.fromVariantMap( map ) );
3886   QCOMPARE( fromMap.name(), def->name() );
3887   QCOMPARE( fromMap.description(), def->description() );
3888   QCOMPARE( fromMap.flags(), def->flags() );
3889   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3890   QCOMPARE( fromMap.extension(), def->extension() );
3891   QCOMPARE( fromMap.fileFilter(), def->fileFilter() );
3892   QCOMPARE( fromMap.behavior(), def->behavior() );
3893   def.reset( dynamic_cast< QgsProcessingParameterFile *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3894   QVERIFY( dynamic_cast< QgsProcessingParameterFile *>( def.get() ) );
3895 
3896   // with file filter
3897   def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QStringLiteral( ".bmp" ), QString( "abc.bmp" ), false, QStringLiteral( "PNG Files (*.png *.PNG)" ) ) );
3898   QCOMPARE( def->fileFilter(), QStringLiteral( "PNG Files (*.png *.PNG)" ) );
3899   QVERIFY( def->extension().isEmpty() );
3900   QVERIFY( def->checkValueIsAcceptable( "bricks.png" ) );
3901   QVERIFY( def->checkValueIsAcceptable( "bricks.PNG" ) );
3902   QVERIFY( !def->checkValueIsAcceptable( "bricks.pcx" ) );
3903 
3904   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
3905   QCOMPARE( def->valueAsPythonString( "bricks.png", context ), QStringLiteral( "'bricks.png'" ) );
3906   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
3907   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
3908   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
3909 
3910   pythonCode = def->asPythonString();
3911   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('non_optional', '', behavior=QgsProcessingParameterFile.File, fileFilter='PNG Files (*.png *.PNG)', defaultValue='abc.bmp')" ) );
3912 
3913   code = def->asScriptCode();
3914   QCOMPARE( code, QStringLiteral( "##non_optional=file abc.bmp" ) );
3915   fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3916   QVERIFY( fromCode.get() );
3917   QCOMPARE( fromCode->name(), def->name() );
3918   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
3919   QCOMPARE( fromCode->flags(), def->flags() );
3920   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3921   QCOMPARE( fromCode->behavior(), def->behavior() );
3922 
3923   map = def->toVariantMap();
3924   fromMap = QgsProcessingParameterFile( "x" );
3925   QVERIFY( fromMap.fromVariantMap( map ) );
3926   QCOMPARE( fromMap.name(), def->name() );
3927   QCOMPARE( fromMap.description(), def->description() );
3928   QCOMPARE( fromMap.flags(), def->flags() );
3929   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
3930   QCOMPARE( fromMap.extension(), def->extension() );
3931   QCOMPARE( fromMap.fileFilter(), def->fileFilter() );
3932   QCOMPARE( fromMap.behavior(), def->behavior() );
3933   def.reset( dynamic_cast< QgsProcessingParameterFile *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
3934   QVERIFY( dynamic_cast< QgsProcessingParameterFile *>( def.get() ) );
3935 
3936   // with file filter with wildcards
3937   def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QStringLiteral( ".bmp" ), QString( "abc.bmp" ), false, QStringLiteral( "PNG Files (*.png);;Other Files (*.*)" ) ) );
3938   QVERIFY( def->checkValueIsAcceptable( "bricks.png" ) );
3939   QVERIFY( def->checkValueIsAcceptable( "bricks.PNG" ) );
3940   QVERIFY( def->checkValueIsAcceptable( "bricks.pcx" ) );
3941   QVERIFY( def->checkValueIsAcceptable( "bricks.PCX" ) );
3942 
3943   // optional
3944   def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::File, QString(), QString( "gef.bmp" ),  true ) );
3945   QVERIFY( def->checkValueIsAcceptable( false ) );
3946   QVERIFY( def->checkValueIsAcceptable( true ) );
3947   QVERIFY( def->checkValueIsAcceptable( 5 ) );
3948   QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) );
3949   QVERIFY( def->checkValueIsAcceptable( "" ) );
3950   QVERIFY( def->checkValueIsAcceptable( "  " ) );
3951   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
3952 
3953   params.insert( "optional",  QVariant() );
3954   QCOMPARE( QgsProcessingParameters::parameterAsFile( def.get(), params, context ), QString( "gef.bmp" ) );
3955 
3956   pythonCode = def->asPythonString();
3957   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.File, fileFilter='All files (*.*)', defaultValue='gef.bmp')" ) );
3958 
3959   code = def->asScriptCode();
3960   QCOMPARE( code, QStringLiteral( "##optional=optional file gef.bmp" ) );
3961   fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3962   QVERIFY( fromCode.get() );
3963   QCOMPARE( fromCode->name(), def->name() );
3964   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3965   QCOMPARE( fromCode->flags(), def->flags() );
3966   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3967   QCOMPARE( fromCode->behavior(), def->behavior() );
3968 
3969   // folder
3970   def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::Folder, QString(), QString( "/home/me" ),  true ) );
3971   pythonCode = def->asPythonString();
3972   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.Folder, fileFilter='All files (*.*)', defaultValue='/home/me')" ) );
3973   code = def->asScriptCode();
3974   QCOMPARE( code, QStringLiteral( "##optional=optional folder /home/me" ) );
3975   fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
3976   QVERIFY( fromCode.get() );
3977   QCOMPARE( fromCode->name(), def->name() );
3978   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
3979   QCOMPARE( fromCode->flags(), def->flags() );
3980   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
3981   QCOMPARE( fromCode->behavior(), def->behavior() );
3982 
3983   // create file filter
3984   // folder type
3985   QCOMPARE( def->createFileFilter(), QString() );
3986   def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::File, QString(), QString( "/home/me" ),  true ) );
3987   // no filter/extension
3988   QCOMPARE( def->createFileFilter(), QStringLiteral( "All files (*.*)" ) );
3989   def->setExtension( QStringLiteral( "png" ) );
3990   QCOMPARE( def->createFileFilter(), QStringLiteral( "PNG files (*.png);;All files (*.*)" ) );
3991   def->setFileFilter( QStringLiteral( "PNG Files (*.png);;BMP Files (*.bmp)" ) );
3992   QCOMPARE( def->createFileFilter(), QStringLiteral( "PNG Files (*.png);;BMP Files (*.bmp);;All files (*.*)" ) );
3993   def->setFileFilter( QStringLiteral( "All files (*.*)" ) );
3994   QCOMPARE( def->createFileFilter(), QStringLiteral( "All files (*.*)" ) );
3995 }
3996 
parameterMatrix()3997 void TestQgsProcessing::parameterMatrix()
3998 {
3999   QgsProcessingContext context;
4000 
4001   // not optional!
4002   std::unique_ptr< QgsProcessingParameterMatrix > def( new QgsProcessingParameterMatrix( "non_optional", QString(), 3, false, QStringList(), QVariant(), false ) );
4003   QVERIFY( !def->checkValueIsAcceptable( false ) );
4004   QVERIFY( !def->checkValueIsAcceptable( true ) );
4005   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4006   QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) );
4007   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
4008   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 << 2 << 3 ) );
4009   QVERIFY( def->checkValueIsAcceptable( QVariantList() << ( QVariantList() << 1 << 2 << 3 ) << ( QVariantList() << 1 << 2 << 3 ) ) );
4010   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4011   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
4012 
4013   // list
4014   QVariantMap params;
4015   params.insert( "non_optional", QVariantList() << 1 << 2 << 3 );
4016   QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 1 << 2 << 3 );
4017 
4018   //string
4019   params.insert( "non_optional", QString( "4,5,6" ) );
4020   QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 4 << 5 << 6 );
4021 
4022   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4023   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "[5]" ) );
4024   QCOMPARE( def->valueAsPythonString( QVariantList() << 1 << 2.5 << 3, context ), QStringLiteral( "[1,2.5,3]" ) );
4025   QCOMPARE( def->valueAsPythonString( QVariantList() << ( QVariantList() << 1 << 2 << 3 ) << ( QVariantList() << 1 << 2 << 3 ), context ), QStringLiteral( "[1,2,3,1,2,3]" ) );
4026   QCOMPARE( def->valueAsPythonString( QVariantList() << ( QVariantList() << 1 << QStringLiteral( "value" ) << 3 ) << ( QVariantList() << 1 << 2 << QStringLiteral( "it's a value" ) ), context ), QStringLiteral( "[1,'value',3,1,2,\"it's a value\"]" ) );
4027   QCOMPARE( def->valueAsPythonString( QVariantList() << ( QVariantList() << 1 << QVariant() << 3 ) << ( QVariantList() << QVariant() << 2 << 3 ), context ), QStringLiteral( "[1,None,3,None,2,3]" ) );
4028   QCOMPARE( def->valueAsPythonString( QVariantList() << ( QVariantList() << 1 << QString( "" ) << 3 ) << ( QVariantList() << 1 << 2 << QString( "" ) ), context ), QStringLiteral( "[1,'',3,1,2,'']" ) );
4029   QCOMPARE( def->valueAsPythonString( "1,2,3", context ), QStringLiteral( "[1,2,3]" ) );
4030   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4031 
4032   QString pythonCode = def->asPythonString();
4033   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMatrix('non_optional', '', numberRows=3, hasFixedNumberRows=False, headers=[], defaultValue=None)" ) );
4034 
4035   QString code = def->asScriptCode();
4036   QCOMPARE( code, QStringLiteral( "##non_optional=matrix" ) );
4037   std::unique_ptr< QgsProcessingParameterMatrix > fromCode( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4038   QVERIFY( fromCode.get() );
4039   QCOMPARE( fromCode->name(), def->name() );
4040   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
4041   QCOMPARE( fromCode->flags(), def->flags() );
4042   QVERIFY( !fromCode->defaultValue().isValid() );
4043 
4044   const QVariantMap map = def->toVariantMap();
4045   QgsProcessingParameterMatrix fromMap( "x" );
4046   QVERIFY( fromMap.fromVariantMap( map ) );
4047   QCOMPARE( fromMap.name(), def->name() );
4048   QCOMPARE( fromMap.description(), def->description() );
4049   QCOMPARE( fromMap.flags(), def->flags() );
4050   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4051   QCOMPARE( fromMap.headers(), def->headers() );
4052   QCOMPARE( fromMap.numberRows(), def->numberRows() );
4053   QCOMPARE( fromMap.hasFixedNumberRows(), def->hasFixedNumberRows() );
4054   def.reset( dynamic_cast< QgsProcessingParameterMatrix *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4055   QVERIFY( dynamic_cast< QgsProcessingParameterMatrix *>( def.get() ) );
4056 
4057   // optional
4058   def.reset( new QgsProcessingParameterMatrix( "optional", QString(), 3, false, QStringList(), QVariantList() << 4 << 5 << 6,  true ) );
4059   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4060   QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) );
4061   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 << 2 << 3 ) );
4062   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
4063   QVERIFY( def->checkValueIsAcceptable( "" ) );
4064   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4065 
4066   pythonCode = def->asPythonString();
4067   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMatrix('optional', '', optional=True, numberRows=3, hasFixedNumberRows=False, headers=[], defaultValue=[4,5,6])" ) );
4068 
4069   code = def->asScriptCode();
4070   QCOMPARE( code, QStringLiteral( "##optional=optional matrix" ) );
4071   fromCode.reset( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4072   QVERIFY( fromCode.get() );
4073   QCOMPARE( fromCode->name(), def->name() );
4074   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4075   QCOMPARE( fromCode->flags(), def->flags() );
4076   QVERIFY( !fromCode->defaultValue().isValid() );
4077 
4078   params.insert( "optional",  QVariant() );
4079   QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 4 << 5 << 6 );
4080   def.reset( new QgsProcessingParameterMatrix( "optional", QString(), 3, false, QStringList(), QString( "1,2,3" ),  true ) );
4081 
4082   pythonCode = def->asPythonString();
4083   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMatrix('optional', '', optional=True, numberRows=3, hasFixedNumberRows=False, headers=[], defaultValue=[1,2,3])" ) );
4084 
4085   code = def->asScriptCode();
4086   QCOMPARE( code, QStringLiteral( "##optional=optional matrix 1,2,3" ) );
4087   fromCode.reset( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4088   QVERIFY( fromCode.get() );
4089   QCOMPARE( fromCode->name(), def->name() );
4090   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4091   QCOMPARE( fromCode->flags(), def->flags() );
4092   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4093 
4094   params.insert( "optional",  QVariant() );
4095   QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 1 << 2 << 3 );
4096 }
4097 
parameterLayerList()4098 void TestQgsProcessing::parameterLayerList()
4099 {
4100   // setup a context
4101   QgsProject p;
4102   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
4103   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
4104   const QString raster1 = testDataDir + "tenbytenraster.asc";
4105   const QString raster2 = testDataDir + "landsat.tif";
4106   const QFileInfo fi1( raster1 );
4107   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
4108   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" );
4109   QgsVectorLayer *v2 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V5", "memory" );
4110   p.addMapLayers( QList<QgsMapLayer *>() << v1 << v2 << r1 );
4111   QgsProcessingContext context;
4112   context.setProject( &p );
4113 
4114   // not optional!
4115   std::unique_ptr< QgsProcessingParameterMultipleLayers > def( new QgsProcessingParameterMultipleLayers( "non_optional", QString(), QgsProcessing::TypeMapLayer, QString(), false ) );
4116   QVERIFY( !def->checkValueIsAcceptable( false ) );
4117   QVERIFY( !def->checkValueIsAcceptable( true ) );
4118   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4119   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
4120   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4121   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
4122   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
4123   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
4124 
4125   // should be OK
4126   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4127   QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4128   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4129 
4130   // ... unless we use context, when the check that the layer actually exists is performed
4131   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4132   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4133   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4134 
4135   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
4136   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
4137   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
4138   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
4139 
4140   // using existing map layer ID
4141   QVariantMap params;
4142   params.insert( "non_optional",  v1->id() );
4143   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 );
4144   // using existing map layer
4145   params.insert( "non_optional",  QVariant::fromValue( v1 ) );
4146   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 );
4147 
4148   // using two existing map layer ID
4149   params.insert( "non_optional",  QVariantList() << v1->id() << r1->id() );
4150   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 );
4151 
4152   // using two existing map layers
4153   params.insert( "non_optional",  QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( r1 ) );
4154   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 );
4155 
4156   // mix of list and single layers (happens from models)
4157   params.insert( "non_optional",  QVariantList() << QVariant( QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( v2 ) ) << QVariant::fromValue( r1 ) );
4158   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 << r1 );
4159 
4160   // mix of two lists (happens from models)
4161   params.insert( "non_optional",  QVariantList() << QVariant( QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( v2 ) ) << QVariant( QVariantList() << QVariant::fromValue( r1 ) ) );
4162   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 << r1 );
4163 
4164   // mix of existing layers and non project layer string
4165   params.insert( "non_optional",  QVariantList() << v1->id() << raster2 );
4166   const QList< QgsMapLayer *> layers = QgsProcessingParameters::parameterAsLayerList( def.get(), params, context );
4167   QCOMPARE( layers.at( 0 ), v1 );
4168   QCOMPARE( layers.at( 1 )->publicSource(), raster2 );
4169 
4170   // mix of existing layer and ID (and check we keep order)
4171   params.insert( "non_optional",  QVariantList() << QVariant::fromValue( v1 ) << v2->id() );
4172   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 );
4173 
4174   params.insert( "non_optional",  QVariantList() << v1->id() << QVariant::fromValue( v2 ) );
4175   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 );
4176 
4177   // empty string
4178   params.insert( "non_optional",  QString( "" ) );
4179   QVERIFY( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ).isEmpty() );
4180 
4181   // nonsense string
4182   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
4183   QVERIFY( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ).isEmpty() );
4184 
4185   // with 2 min inputs
4186   def->setMinimumNumberInputs( 2 );
4187   QVERIFY( !def->checkValueIsAcceptable( false ) );
4188   QVERIFY( !def->checkValueIsAcceptable( true ) );
4189   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4190   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4191   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
4192   QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
4193   QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) );
4194   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) );
4195   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4196   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
4197   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4198   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4199   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4200   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4201   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4202   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4203 
4204   def->setMinimumNumberInputs( 3 );
4205   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) );
4206   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) );
4207 
4208   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4209   QCOMPARE( def->valueAsPythonString( "layer12312312", context ), QStringLiteral( "'layer12312312'" ) );
4210   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc']" ) ) );
4211   QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc']" ) ) );
4212   QCOMPARE( def->valueAsPythonString( QStringList() << r1->id() << raster2, context ), QString( QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc','" ) + testDataDir + QStringLiteral( "landsat.tif']" ) ) );
4213   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4214   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
4215 
4216   QString pythonCode = def->asPythonString();
4217   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMultipleLayers('non_optional', '', layerType=QgsProcessing.TypeMapLayer, defaultValue='')" ) );
4218 
4219   QString code = def->asScriptCode();
4220   QCOMPARE( code, QStringLiteral( "##non_optional=multiple vector" ) );
4221   std::unique_ptr< QgsProcessingParameterMultipleLayers > fromCode( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4222   QVERIFY( fromCode.get() );
4223   QCOMPARE( fromCode->name(), def->name() );
4224   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
4225   QCOMPARE( fromCode->flags(), def->flags() );
4226   QVERIFY( !fromCode->defaultValue().isValid() );
4227   QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry );
4228 
4229   const QVariantMap map = def->toVariantMap();
4230   QgsProcessingParameterMultipleLayers fromMap( "x" );
4231   QVERIFY( fromMap.fromVariantMap( map ) );
4232   QCOMPARE( fromMap.name(), def->name() );
4233   QCOMPARE( fromMap.description(), def->description() );
4234   QCOMPARE( fromMap.flags(), def->flags() );
4235   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4236   QCOMPARE( fromMap.layerType(), def->layerType() );
4237   QCOMPARE( fromMap.minimumNumberInputs(), def->minimumNumberInputs() );
4238   def.reset( dynamic_cast< QgsProcessingParameterMultipleLayers *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4239   QVERIFY( dynamic_cast< QgsProcessingParameterMultipleLayers *>( def.get() ) );
4240 
4241   // optional with one default layer
4242   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, v1->id(), true ) );
4243   QVERIFY( !def->checkValueIsAcceptable( false ) );
4244   QVERIFY( !def->checkValueIsAcceptable( true ) );
4245   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4246   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
4247   QVERIFY( def->checkValueIsAcceptable( "" ) );
4248   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4249 
4250   // should be OK
4251   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4252   QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4253   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4254 
4255   // ... unless we use context, when the check that the layer actually exists is performed
4256   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4257   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4258   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
4259 
4260   params.insert( "optional",  QVariant() );
4261   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 );
4262 
4263   params.insert( "optional",  QVariantList() << QVariant::fromValue( r1 ) );
4264   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << r1 );
4265 
4266   pythonCode = def->asPythonString();
4267   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterMultipleLayers('optional', '', optional=True, layerType=QgsProcessing.TypeMapLayer, defaultValue='" ) + v1->id() + "')" ) );
4268 
4269   code = def->asScriptCode();
4270   QCOMPARE( code, QString( QStringLiteral( "##optional=optional multiple vector " ) + v1->id() ) );
4271   fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4272   QVERIFY( fromCode.get() );
4273   QCOMPARE( fromCode->name(), def->name() );
4274   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4275   QCOMPARE( fromCode->flags(), def->flags() );
4276   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4277   QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry );
4278 
4279   // optional with two default layers
4280   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariantList() << v1->id() << r1->publicSource(), true ) );
4281   params.insert( "optional",  QVariant() );
4282   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 );
4283   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
4284   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
4285   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
4286   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
4287 
4288   pythonCode = def->asPythonString();
4289   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterMultipleLayers('optional', '', optional=True, layerType=QgsProcessing.TypeMapLayer, defaultValue=['" ) + r1->publicSource() + "'])" ) );
4290 
4291   code = def->asScriptCode();
4292   QCOMPARE( code, QString( QStringLiteral( "##optional=optional multiple vector " ) + v1->id() + "," + r1->publicSource() ) );
4293   fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4294   QVERIFY( fromCode.get() );
4295   QCOMPARE( fromCode->name(), def->name() );
4296   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4297   QCOMPARE( fromCode->flags(), def->flags() );
4298   QCOMPARE( fromCode->defaultValue().toString(), QString( v1->id() + "," + r1->publicSource() ) );
4299   QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry );
4300 
4301   // optional with one default direct layer
4302   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariant::fromValue( v1 ), true ) );
4303   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 );
4304 
4305   // optional with two default direct layers
4306   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( r1 ), true ) );
4307   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 );
4308 
4309   def.reset( new QgsProcessingParameterMultipleLayers( "type", QString(), QgsProcessing::TypeRaster ) );
4310   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
4311   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
4312   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
4313   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
4314 
4315   pythonCode = def->asPythonString();
4316   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMultipleLayers('type', '', layerType=QgsProcessing.TypeRaster, defaultValue=None)" ) );
4317   code = def->asScriptCode();
4318   QCOMPARE( code, QStringLiteral( "##type=multiple raster" ) );
4319   fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4320   QVERIFY( fromCode.get() );
4321   QCOMPARE( fromCode->name(), def->name() );
4322   QCOMPARE( fromCode->description(), QStringLiteral( "type" ) );
4323   QCOMPARE( fromCode->flags(), def->flags() );
4324   QVERIFY( !fromCode->defaultValue().isValid() );
4325   QCOMPARE( fromCode->layerType(), QgsProcessing::TypeRaster );
4326 
4327   def.reset( new QgsProcessingParameterMultipleLayers( "type", QString(), QgsProcessing::TypeFile ) );
4328   QCOMPARE( def->createFileFilter(), QStringLiteral( "All files (*.*)" ) );
4329 
4330   pythonCode = def->asPythonString();
4331   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMultipleLayers('type', '', layerType=QgsProcessing.TypeFile, defaultValue=None)" ) );
4332   code = def->asScriptCode();
4333   QCOMPARE( code, QStringLiteral( "##type=multiple file" ) );
4334   fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4335   QVERIFY( fromCode.get() );
4336   QCOMPARE( fromCode->name(), def->name() );
4337   QCOMPARE( fromCode->description(), QStringLiteral( "type" ) );
4338   QCOMPARE( fromCode->flags(), def->flags() );
4339   QVERIFY( !fromCode->defaultValue().isValid() );
4340   QCOMPARE( fromCode->layerType(), def->layerType() );
4341 
4342   // manage QgsProcessingOutputLayerDefinition as parameter value
4343 
4344   // optional with sink to a QgsMapLayer.id()
4345   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeFile ) );
4346   params.insert( QString( "optional" ), QgsProcessingOutputLayerDefinition( r1->publicSource() ) );
4347   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << r1 );
4348 
4349   // optional with sink to an empty string
4350   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeFile ) );
4351   params.insert( QString( "optional" ), QgsProcessingOutputLayerDefinition( QString() ) );
4352   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() );
4353 
4354   // optional with sink to an nonsense string
4355   def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeFile ) );
4356   params.insert( QString( "optional" ), QgsProcessingOutputLayerDefinition( QString( "i'm not a layer, and nothing you can do will make me one" ) ) );
4357   QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() );
4358 
4359 
4360   // TypeFile
4361   def = std::make_unique< QgsProcessingParameterMultipleLayers >( "non_optional", QString(), QgsProcessing::TypeFile, QString(), false );
4362   QVERIFY( !def->checkValueIsAcceptable( false ) );
4363   QVERIFY( !def->checkValueIsAcceptable( true ) );
4364   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4365   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
4366   QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "a" ) );
4367   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4368   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
4369   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
4370   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
4371   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4372   QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4373   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
4374 
4375   params.clear();
4376   params.insert( "non_optional",  QString( "a" ) );
4377   QCOMPARE( QgsProcessingParameters::parameterAsFileList( def.get(), params, context ), QStringList() << QStringLiteral( "a" ) );
4378   params.insert( "non_optional",  QStringList() << "a" );
4379   QCOMPARE( QgsProcessingParameters::parameterAsFileList( def.get(), params, context ), QStringList() << QStringLiteral( "a" ) );
4380   params.insert( "non_optional",  QStringList() << "a" << "b" );
4381   QCOMPARE( QgsProcessingParameters::parameterAsFileList( def.get(), params, context ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4382   params.insert( "non_optional",  QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "d" ) );
4383   QCOMPARE( QgsProcessingParameters::parameterAsFileList( def.get(), params, context ), QStringList() << QStringLiteral( "c" ) << QStringLiteral( "d" ) );
4384   // mix of two lists (happens from models)
4385   params.insert( "non_optional",  QVariantList() << QVariant( QVariantList() << QStringLiteral( "e" ) << QStringLiteral( "f" ) ) << QVariant( QVariantList() << QStringLiteral( "g" ) ) );
4386   QCOMPARE( QgsProcessingParameters::parameterAsFileList( def.get(), params, context ), QStringList() << QStringLiteral( "e" ) << QStringLiteral( "f" ) << QStringLiteral( "g" ) );
4387 
4388   // with 2 min inputs
4389   def->setMinimumNumberInputs( 2 );
4390   QVERIFY( !def->checkValueIsAcceptable( false ) );
4391   QVERIFY( !def->checkValueIsAcceptable( true ) );
4392   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4393   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4394   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
4395   QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
4396   QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) );
4397   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) );
4398 
4399   def->setMinimumNumberInputs( 3 );
4400   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) );
4401   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) );
4402 
4403   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4404   QCOMPARE( def->valueAsPythonString( "layer12312312", context ), QStringLiteral( "'layer12312312'" ) );
4405   QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "B", context ), QStringLiteral( "['a','B']" ) );
4406   QCOMPARE( def->valueAsPythonString( QVariantList() << "c" << "d", context ), QStringLiteral( "['c','d']" ) );
4407 }
4408 
parameterDistance()4409 void TestQgsProcessing::parameterDistance()
4410 {
4411   QgsProcessingContext context;
4412 
4413   // not optional!
4414   std::unique_ptr< QgsProcessingParameterDistance > def( new QgsProcessingParameterDistance( "non_optional", QString(), 5, QStringLiteral( "parent" ), false ) );
4415   QCOMPARE( def->parentParameterName(), QStringLiteral( "parent" ) );
4416   def->setParentParameterName( QStringLiteral( "parent2" ) );
4417   QCOMPARE( def->defaultUnit(), QgsUnitTypes::DistanceUnknownUnit );
4418   def->setDefaultUnit( QgsUnitTypes::DistanceFeet );
4419   QCOMPARE( def->defaultUnit(), QgsUnitTypes::DistanceFeet );
4420   std::unique_ptr< QgsProcessingParameterDistance > clone( def->clone() );
4421   QCOMPARE( clone->parentParameterName(), QStringLiteral( "parent2" ) );
4422   QCOMPARE( clone->defaultUnit(), QgsUnitTypes::DistanceFeet );
4423 
4424   QCOMPARE( def->parentParameterName(), QStringLiteral( "parent2" ) );
4425   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4426   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4427   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4428   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4429   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4430   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
4431 
4432   // string representing a number
4433   QVariantMap params;
4434   params.insert( "non_optional", QString( "1.1" ) );
4435   double number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4436   QGSCOMPARENEAR( number, 1.1, 0.001 );
4437 
4438   // double
4439   params.insert( "non_optional", 1.1 );
4440   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4441   QGSCOMPARENEAR( number, 1.1, 0.001 );
4442   // int
4443   params.insert( "non_optional", 1 );
4444   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4445   QGSCOMPARENEAR( number, 1, 0.001 );
4446 
4447   // nonsense string
4448   params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) );
4449   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4450   QCOMPARE( number, 5.0 );
4451 
4452   // with min value
4453   def->setMinimum( 11 );
4454   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4455   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
4456   QVERIFY( def->checkValueIsAcceptable( 25 ) );
4457   QVERIFY( def->checkValueIsAcceptable( "21.1" ) );
4458   // with max value
4459   def->setMaximum( 21 );
4460   QVERIFY( !def->checkValueIsAcceptable( 35 ) );
4461   QVERIFY( !def->checkValueIsAcceptable( "31.1" ) );
4462   QVERIFY( def->checkValueIsAcceptable( 15 ) );
4463   QVERIFY( def->checkValueIsAcceptable( "11.1" ) );
4464 
4465   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4466   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
4467   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1.1" ) );
4468   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4469 
4470   const QVariantMap map = def->toVariantMap();
4471   QgsProcessingParameterDistance fromMap( "x" );
4472   QVERIFY( fromMap.fromVariantMap( map ) );
4473   QCOMPARE( fromMap.name(), def->name() );
4474   QCOMPARE( fromMap.description(), def->description() );
4475   QCOMPARE( fromMap.flags(), def->flags() );
4476   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4477   QCOMPARE( fromMap.minimum(), def->minimum() );
4478   QCOMPARE( fromMap.maximum(), def->maximum() );
4479   QCOMPARE( fromMap.dataType(), def->dataType() );
4480   QCOMPARE( fromMap.parentParameterName(), QStringLiteral( "parent2" ) );
4481   QCOMPARE( fromMap.defaultUnit(), QgsUnitTypes::DistanceFeet );
4482   def.reset( dynamic_cast< QgsProcessingParameterDistance *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4483   QVERIFY( dynamic_cast< QgsProcessingParameterDistance *>( def.get() ) );
4484 
4485   // optional
4486   def.reset( new QgsProcessingParameterDistance( "optional", QString(), 5.4, QStringLiteral( "parent" ), true ) );
4487   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4488   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4489   QVERIFY( def->checkValueIsAcceptable( "" ) );
4490   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4491 
4492   params.insert( "optional",  QVariant() );
4493   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4494   QGSCOMPARENEAR( number, 5.4, 0.001 );
4495   // unconvertible string
4496   params.insert( "optional",  QVariant( "aaaa" ) );
4497   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4498   QGSCOMPARENEAR( number, 5.4, 0.001 );
4499 
4500   // non-optional, invalid default
4501   def.reset( new QgsProcessingParameterDistance( "non_optional", QString(), QVariant(), QStringLiteral( "parent" ), false ) );
4502   QCOMPARE( def->parentParameterName(), QStringLiteral( "parent" ) );
4503   def->setParentParameterName( QStringLiteral( "parent2" ) );
4504   QCOMPARE( def->parentParameterName(), QStringLiteral( "parent2" ) );
4505   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4506   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4507   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4508   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4509   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4510   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, falls back to invalid default value
4511 }
4512 
parameterDuration()4513 void TestQgsProcessing::parameterDuration()
4514 {
4515   QgsProcessingContext context;
4516 
4517   // not optional!
4518   std::unique_ptr< QgsProcessingParameterDuration > def( new QgsProcessingParameterDuration( "non_optional", QString(), 5, false ) );
4519   QCOMPARE( def->defaultUnit(), QgsUnitTypes::TemporalMilliseconds );
4520   def->setDefaultUnit( QgsUnitTypes::TemporalDays );
4521   QCOMPARE( def->defaultUnit(), QgsUnitTypes::TemporalDays );
4522   std::unique_ptr< QgsProcessingParameterDuration > clone( def->clone() );
4523   QCOMPARE( clone->defaultUnit(), QgsUnitTypes::TemporalDays );
4524 
4525   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4526   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4527   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4528   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4529   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4530   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
4531 
4532   // string representing a number
4533   QVariantMap params;
4534   params.insert( "non_optional", QString( "1.1" ) );
4535   double number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4536   QGSCOMPARENEAR( number, 1.1, 0.001 );
4537 
4538   // double
4539   params.insert( "non_optional", 1.1 );
4540   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4541   QGSCOMPARENEAR( number, 1.1, 0.001 );
4542   // int
4543   params.insert( "non_optional", 1 );
4544   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4545   QGSCOMPARENEAR( number, 1, 0.001 );
4546 
4547   // nonsense string
4548   params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) );
4549   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4550   QCOMPARE( number, 5.0 );
4551 
4552   // with min value
4553   def->setMinimum( 11 );
4554   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4555   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
4556   QVERIFY( def->checkValueIsAcceptable( 25 ) );
4557   QVERIFY( def->checkValueIsAcceptable( "21.1" ) );
4558   // with max value
4559   def->setMaximum( 21 );
4560   QVERIFY( !def->checkValueIsAcceptable( 35 ) );
4561   QVERIFY( !def->checkValueIsAcceptable( "31.1" ) );
4562   QVERIFY( def->checkValueIsAcceptable( 15 ) );
4563   QVERIFY( def->checkValueIsAcceptable( "11.1" ) );
4564 
4565   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4566   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
4567   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1.1" ) );
4568   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4569 
4570   const QVariantMap map = def->toVariantMap();
4571   QgsProcessingParameterDuration fromMap( "x" );
4572   QVERIFY( fromMap.fromVariantMap( map ) );
4573   QCOMPARE( fromMap.name(), def->name() );
4574   QCOMPARE( fromMap.description(), def->description() );
4575   QCOMPARE( fromMap.flags(), def->flags() );
4576   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4577   QCOMPARE( fromMap.minimum(), def->minimum() );
4578   QCOMPARE( fromMap.maximum(), def->maximum() );
4579   QCOMPARE( fromMap.dataType(), def->dataType() );
4580   QCOMPARE( fromMap.defaultUnit(), QgsUnitTypes::TemporalDays );
4581   def.reset( dynamic_cast< QgsProcessingParameterDuration *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4582   QVERIFY( dynamic_cast< QgsProcessingParameterDuration *>( def.get() ) );
4583 
4584   // optional
4585   def.reset( new QgsProcessingParameterDuration( "optional", QString(), 5.4, true ) );
4586   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4587   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4588   QVERIFY( def->checkValueIsAcceptable( "" ) );
4589   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4590 
4591   params.insert( "optional",  QVariant() );
4592   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4593   QGSCOMPARENEAR( number, 5.4, 0.001 );
4594   // unconvertible string
4595   params.insert( "optional",  QVariant( "aaaa" ) );
4596   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4597   QGSCOMPARENEAR( number, 5.4, 0.001 );
4598 
4599   // non-optional, invalid default
4600   def.reset( new QgsProcessingParameterDuration( "non_optional", QString(), QVariant(), false ) );
4601   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4602   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4603   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4604   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4605   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4606   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, falls back to invalid default value
4607 }
4608 
parameterScale()4609 void TestQgsProcessing::parameterScale()
4610 {
4611   QgsProcessingContext context;
4612 
4613   // not optional!
4614   std::unique_ptr< QgsProcessingParameterScale > def( new QgsProcessingParameterScale( "non_optional", QString(), 5, false ) );
4615 
4616   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4617   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4618   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4619   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4620   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4621   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
4622 
4623   // string representing a number
4624   QVariantMap params;
4625   params.insert( "non_optional", QString( "1.1" ) );
4626   double number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4627   QGSCOMPARENEAR( number, 1.1, 0.001 );
4628 
4629   // double
4630   params.insert( "non_optional", 1.1 );
4631   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4632   QGSCOMPARENEAR( number, 1.1, 0.001 );
4633   // int
4634   params.insert( "non_optional", 1 );
4635   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4636   QGSCOMPARENEAR( number, 1, 0.001 );
4637 
4638   // nonsense string
4639   params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) );
4640   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4641   QCOMPARE( number, 5.0 );
4642 
4643   // with min value
4644   def->setMinimum( 11 );
4645   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4646   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
4647   QVERIFY( def->checkValueIsAcceptable( 25 ) );
4648   QVERIFY( def->checkValueIsAcceptable( "21.1" ) );
4649   // with max value
4650   def->setMaximum( 21 );
4651   QVERIFY( !def->checkValueIsAcceptable( 35 ) );
4652   QVERIFY( !def->checkValueIsAcceptable( "31.1" ) );
4653   QVERIFY( def->checkValueIsAcceptable( 15 ) );
4654   QVERIFY( def->checkValueIsAcceptable( "11.1" ) );
4655 
4656   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4657   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
4658   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1.1" ) );
4659   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4660 
4661   const QVariantMap map = def->toVariantMap();
4662   QgsProcessingParameterScale fromMap( "x" );
4663   QVERIFY( fromMap.fromVariantMap( map ) );
4664   QCOMPARE( fromMap.name(), def->name() );
4665   QCOMPARE( fromMap.description(), def->description() );
4666   QCOMPARE( fromMap.flags(), def->flags() );
4667   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4668   def.reset( dynamic_cast< QgsProcessingParameterScale *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4669   QVERIFY( dynamic_cast< QgsProcessingParameterScale *>( def.get() ) );
4670 
4671   const QString pythonCode = def->asPythonString();
4672   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterScale('non_optional', '', defaultValue=5)" ) );
4673 
4674   const QString code = def->asScriptCode();
4675   QCOMPARE( code, QStringLiteral( "##non_optional=scale 5" ) );
4676   std::unique_ptr< QgsProcessingParameterScale > fromCode( dynamic_cast< QgsProcessingParameterScale * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4677   QVERIFY( fromCode.get() );
4678   QCOMPARE( fromCode->name(), def->name() );
4679   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
4680   QCOMPARE( fromCode->flags(), def->flags() );
4681   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4682 
4683   // optional
4684   def.reset( new QgsProcessingParameterScale( "optional", QString(), 5.4, true ) );
4685   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4686   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4687   QVERIFY( def->checkValueIsAcceptable( "" ) );
4688   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4689 
4690   params.insert( "optional",  QVariant() );
4691   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4692   QGSCOMPARENEAR( number, 5.4, 0.001 );
4693   // unconvertible string
4694   params.insert( "optional",  QVariant( "aaaa" ) );
4695   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4696   QGSCOMPARENEAR( number, 5.4, 0.001 );
4697 
4698   // non-optional, invalid default
4699   def.reset( new QgsProcessingParameterScale( "non_optional", QString(), QVariant(), false ) );
4700   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4701   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4702   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4703   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4704   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4705   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, falls back to invalid default value
4706 }
4707 
parameterNumber()4708 void TestQgsProcessing::parameterNumber()
4709 {
4710   QgsProcessingContext context;
4711 
4712   // not optional!
4713   std::unique_ptr< QgsProcessingParameterNumber > def( new QgsProcessingParameterNumber( "non_optional", QString(), QgsProcessingParameterNumber::Double, 5, false ) );
4714   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4715   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4716   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4717   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4718   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4719   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
4720 
4721   // string representing a number
4722   QVariantMap params;
4723   params.insert( "non_optional", QString( "1.1" ) );
4724   double number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4725   QGSCOMPARENEAR( number, 1.1, 0.001 );
4726   int iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4727   QCOMPARE( iNumber, 1 );
4728 
4729   // double
4730   params.insert( "non_optional", 1.1 );
4731   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4732   QGSCOMPARENEAR( number, 1.1, 0.001 );
4733   iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4734   QCOMPARE( iNumber, 1 );
4735 
4736   // int
4737   params.insert( "non_optional", 1 );
4738   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4739   QGSCOMPARENEAR( number, 1, 0.001 );
4740   iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4741   QCOMPARE( iNumber, 1 );
4742 
4743   // nonsense string
4744   params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) );
4745   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4746   QCOMPARE( number, 5.0 );
4747   iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4748   QCOMPARE( iNumber, 5 );
4749 
4750   // with min value
4751   def->setMinimum( 11 );
4752   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4753   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
4754   QVERIFY( def->checkValueIsAcceptable( 25 ) );
4755   QVERIFY( def->checkValueIsAcceptable( "21.1" ) );
4756   // with max value
4757   def->setMaximum( 21 );
4758   QVERIFY( !def->checkValueIsAcceptable( 35 ) );
4759   QVERIFY( !def->checkValueIsAcceptable( "31.1" ) );
4760   QVERIFY( def->checkValueIsAcceptable( 15 ) );
4761   QVERIFY( def->checkValueIsAcceptable( "11.1" ) );
4762 
4763   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4764   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
4765   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1.1" ) );
4766   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4767 
4768   QString pythonCode = def->asPythonString();
4769   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterNumber('non_optional', '', type=QgsProcessingParameterNumber.Double, minValue=11, maxValue=21, defaultValue=5)" ) );
4770 
4771   QString code = def->asScriptCode();
4772   QCOMPARE( code, QStringLiteral( "##non_optional=number 5" ) );
4773   std::unique_ptr< QgsProcessingParameterNumber > fromCode( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4774   QVERIFY( fromCode.get() );
4775   QCOMPARE( fromCode->name(), def->name() );
4776   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
4777   QCOMPARE( fromCode->flags(), def->flags() );
4778   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4779 
4780 
4781   const QVariantMap map = def->toVariantMap();
4782   QgsProcessingParameterNumber fromMap( "x" );
4783   QVERIFY( fromMap.fromVariantMap( map ) );
4784   QCOMPARE( fromMap.name(), def->name() );
4785   QCOMPARE( fromMap.description(), def->description() );
4786   QCOMPARE( fromMap.flags(), def->flags() );
4787   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4788   QCOMPARE( fromMap.minimum(), def->minimum() );
4789   QCOMPARE( fromMap.maximum(), def->maximum() );
4790   QCOMPARE( fromMap.dataType(), def->dataType() );
4791   def.reset( dynamic_cast< QgsProcessingParameterNumber *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4792   QVERIFY( dynamic_cast< QgsProcessingParameterNumber *>( def.get() ) );
4793 
4794   // optional
4795   def.reset( new QgsProcessingParameterNumber( "optional", QString(), QgsProcessingParameterNumber::Double, 5.4, true ) );
4796   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4797   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4798   QVERIFY( def->checkValueIsAcceptable( "" ) );
4799   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4800 
4801   params.insert( "optional",  QVariant() );
4802   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4803   QGSCOMPARENEAR( number, 5.4, 0.001 );
4804   iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4805   QCOMPARE( iNumber, 5 );
4806   // unconvertible string
4807   params.insert( "optional",  QVariant( "aaaa" ) );
4808   number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context );
4809   QGSCOMPARENEAR( number, 5.4, 0.001 );
4810   iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
4811   QCOMPARE( iNumber, 5 );
4812 
4813   pythonCode = def->asPythonString();
4814   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterNumber('optional', '', optional=True, type=QgsProcessingParameterNumber.Double, defaultValue=5.4)" ) );
4815 
4816   code = def->asScriptCode();
4817   QCOMPARE( code.left( 30 ), QStringLiteral( "##optional=optional number 5.4" ) ); // truncate code to 30, to avoid Qt 5.6 rounding issues
4818   fromCode.reset( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4819   QVERIFY( fromCode.get() );
4820   QCOMPARE( fromCode->name(), def->name() );
4821   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4822   QCOMPARE( fromCode->flags(), def->flags() );
4823   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4824 
4825   fromCode.reset( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional number None" ) ) ) );
4826   QVERIFY( fromCode.get() );
4827   QCOMPARE( fromCode->name(), def->name() );
4828   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4829   QCOMPARE( fromCode->flags(), def->flags() );
4830   QVERIFY( !fromCode->defaultValue().isValid() );
4831 
4832   // non-optional, invalid default
4833   def.reset( new QgsProcessingParameterNumber( "non_optional", QString(), QgsProcessingParameterNumber::Double, QVariant(), false ) );
4834   QVERIFY( def->checkValueIsAcceptable( 5 ) );
4835   QVERIFY( def->checkValueIsAcceptable( "1.1" ) );
4836   QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) );
4837   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4838   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4839   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, falls back to invalid default value
4840 }
4841 
parameterRange()4842 void TestQgsProcessing::parameterRange()
4843 {
4844   QgsProcessingContext context;
4845 
4846   // not optional!
4847   std::unique_ptr< QgsProcessingParameterRange > def( new QgsProcessingParameterRange( "non_optional", QString(), QgsProcessingParameterNumber::Double, QString( "5,6" ), false ) );
4848   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
4849   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
4850   QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
4851   QVERIFY( !def->checkValueIsAcceptable( "1.1,2,3" ) );
4852   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
4853   QVERIFY( !def->checkValueIsAcceptable( "1,a" ) );
4854   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) );
4855   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 << 3 ) );
4856   QVERIFY( !def->checkValueIsAcceptable( "" ) );
4857   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
4858 
4859   // string representing a range of numbers
4860   QVariantMap params;
4861   params.insert( "non_optional", QString( "1.1,1.2" ) );
4862   QList< double > range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4863   QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 );
4864   QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 );
4865 
4866   // list
4867   params.insert( "non_optional", QVariantList() << 1.1 << 1.2 );
4868   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4869   QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 );
4870   QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 );
4871 
4872   // too many elements:
4873   params.insert( "non_optional", QString( "1.1,1.2,1.3" ) );
4874   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4875   QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 );
4876   QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 );
4877   params.insert( "non_optional", QVariantList() << 1.1 << 1.2 << 1.3 );
4878   range = QgsProcessingParameters::parameterAsRange( def.get(), params,  context );
4879   QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 );
4880   QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 );
4881 
4882   // not enough elements - don't care about the result, just don't crash!
4883   params.insert( "non_optional", QString( "1.1" ) );
4884   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4885   params.insert( "non_optional", QVariantList() << 1.1 );
4886   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4887 
4888   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
4889   QCOMPARE( def->valueAsPythonString( "1.1,2", context ), QStringLiteral( "[1.1,2]" ) );
4890   QCOMPARE( def->valueAsPythonString( QVariantList() << 1.1 << 2, context ), QStringLiteral( "[1.1,2]" ) );
4891   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
4892 
4893   QString pythonCode = def->asPythonString();
4894   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRange('non_optional', '', type=QgsProcessingParameterNumber.Double, defaultValue=[5,6])" ) );
4895 
4896   QString code = def->asScriptCode();
4897   QCOMPARE( code, QStringLiteral( "##non_optional=range 5,6" ) );
4898   std::unique_ptr< QgsProcessingParameterRange > fromCode( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4899   QVERIFY( fromCode.get() );
4900   QCOMPARE( fromCode->name(), def->name() );
4901   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
4902   QCOMPARE( fromCode->flags(), def->flags() );
4903   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4904 
4905   const QVariantMap map = def->toVariantMap();
4906   QgsProcessingParameterRange fromMap( "x" );
4907   QVERIFY( fromMap.fromVariantMap( map ) );
4908   QCOMPARE( fromMap.name(), def->name() );
4909   QCOMPARE( fromMap.description(), def->description() );
4910   QCOMPARE( fromMap.flags(), def->flags() );
4911   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
4912   QCOMPARE( fromMap.dataType(), def->dataType() );
4913   def.reset( dynamic_cast< QgsProcessingParameterRange *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
4914   QVERIFY( dynamic_cast< QgsProcessingParameterRange *>( def.get() ) );
4915 
4916   // optional
4917   def.reset( new QgsProcessingParameterRange( "optional", QString(), QgsProcessingParameterNumber::Double, QString( "5.4,7.4" ), true ) );
4918   QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
4919   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) );
4920   QVERIFY( def->checkValueIsAcceptable( "" ) );
4921   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4922 
4923   params.insert( "optional",  QVariant() );
4924   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4925   QGSCOMPARENEAR( range.at( 0 ), 5.4, 0.001 );
4926   QGSCOMPARENEAR( range.at( 1 ), 7.4, 0.001 );
4927 
4928   pythonCode = def->asPythonString();
4929   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRange('optional', '', optional=True, type=QgsProcessingParameterNumber.Double, defaultValue=[5.4,7.4])" ) );
4930 
4931   code = def->asScriptCode();
4932   QCOMPARE( code, QStringLiteral( "##optional=optional range 5.4,7.4" ) );
4933   fromCode.reset( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
4934   QVERIFY( fromCode.get() );
4935   QCOMPARE( fromCode->name(), def->name() );
4936   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4937   QCOMPARE( fromCode->flags(), def->flags() );
4938   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
4939 
4940   // optional, no default value
4941   def.reset( new QgsProcessingParameterRange( "optional", QString(), QgsProcessingParameterNumber::Double, QVariant(), true ) );
4942   QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) );
4943   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) );
4944   QVERIFY( def->checkValueIsAcceptable( "" ) );
4945   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
4946 
4947   params.insert( "optional",  QVariant() );
4948   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4949   QVERIFY( std::isnan( range.at( 0 ) ) );
4950   QVERIFY( std::isnan( range.at( 1 ) ) );
4951 
4952   params.insert( "optional",  QStringLiteral( "None,2" ) );
4953   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4954   QVERIFY( std::isnan( range.at( 0 ) ) );
4955   QGSCOMPARENEAR( range.at( 1 ), 2, 0.001 );
4956 
4957   params.insert( "optional",  QStringLiteral( "1.2,None" ) );
4958   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4959   QGSCOMPARENEAR( range.at( 0 ), 1.2, 0.001 );
4960   QVERIFY( std::isnan( range.at( 1 ) ) );
4961 
4962   params.insert( "optional",  QStringLiteral( "None,None" ) );
4963   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4964   QVERIFY( std::isnan( range.at( 0 ) ) );
4965   QVERIFY( std::isnan( range.at( 1 ) ) );
4966 
4967   params.insert( "optional",  QStringLiteral( "None" ) );
4968   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4969   QVERIFY( std::isnan( range.at( 0 ) ) );
4970   QVERIFY( std::isnan( range.at( 1 ) ) );
4971 
4972   params.insert( "optional",  QVariant() );
4973   range = QgsProcessingParameters::parameterAsRange( def.get(), params, context );
4974   QVERIFY( std::isnan( range.at( 0 ) ) );
4975   QVERIFY( std::isnan( range.at( 1 ) ) );
4976 
4977   pythonCode = def->asPythonString();
4978   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRange('optional', '', optional=True, type=QgsProcessingParameterNumber.Double, defaultValue=None)" ) );
4979 
4980   fromCode.reset( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional range None" ) ) ) );
4981   QVERIFY( fromCode.get() );
4982   QCOMPARE( fromCode->name(), def->name() );
4983   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
4984   QCOMPARE( fromCode->flags(), def->flags() );
4985   QVERIFY( !fromCode->defaultValue().isValid() );
4986 }
4987 
parameterRasterLayer()4988 void TestQgsProcessing::parameterRasterLayer()
4989 {
4990   // setup a context
4991   QgsProject p;
4992   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
4993   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
4994   const QString raster1 = testDataDir + "tenbytenraster.asc";
4995   const QString raster2 = testDataDir + "landsat.tif";
4996   const QFileInfo fi1( raster1 );
4997   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
4998   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" );
4999   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 );
5000   QgsProcessingContext context;
5001   context.setProject( &p );
5002 
5003   // not optional!
5004   std::unique_ptr< QgsProcessingParameterRasterLayer > def( new QgsProcessingParameterRasterLayer( "non_optional", QString(), QVariant(), false ) );
5005   QVERIFY( !def->checkValueIsAcceptable( false ) );
5006   QVERIFY( !def->checkValueIsAcceptable( true ) );
5007   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
5008   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
5009   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5010   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5011   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
5012   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
5013 
5014   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
5015   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
5016   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
5017   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
5018 
5019   // should be OK
5020   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) );
5021   // ... unless we use context, when the check that the layer actually exists is performed
5022   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif", &context ) );
5023 
5024   // using existing map layer ID
5025   QVariantMap params;
5026   params.insert( "non_optional",  r1->id() );
5027   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5028 
5029   // using existing map layer
5030   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
5031   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5032 
5033   // not raster layer
5034   params.insert( "non_optional",  v1->id() );
5035   QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) );
5036 
5037   // using existing vector layer
5038   params.insert( "non_optional",  QVariant::fromValue( v1 ) );
5039   QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) );
5040 
5041   // string representing a project layer source
5042   params.insert( "non_optional", raster1 );
5043   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5044   // string representing a non-project layer source
5045   params.insert( "non_optional", raster2 );
5046   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->publicSource(), raster2 );
5047 
5048   // nonsense string
5049   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
5050   QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) );
5051 
5052   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5053   QCOMPARE( def->valueAsPythonString( raster1, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
5054   QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
5055   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ) );
5056   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
5057   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
5058 
5059   QString pythonCode = def->asPythonString();
5060   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRasterLayer('non_optional', '', defaultValue=None)" ) );
5061 
5062   QString code = def->asScriptCode();
5063   QCOMPARE( code, QStringLiteral( "##non_optional=raster" ) );
5064   std::unique_ptr< QgsProcessingParameterRasterLayer > fromCode( dynamic_cast< QgsProcessingParameterRasterLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5065   QVERIFY( fromCode.get() );
5066   QCOMPARE( fromCode->name(), def->name() );
5067   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5068   QCOMPARE( fromCode->flags(), def->flags() );
5069   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5070 
5071   // optional
5072   def.reset( new QgsProcessingParameterRasterLayer( "optional", QString(), r1->id(), true ) );
5073   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5074   QVERIFY( def->checkValueIsAcceptable( false ) );
5075   QVERIFY( def->checkValueIsAcceptable( true ) );
5076   QVERIFY( def->checkValueIsAcceptable( 5 ) );
5077   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
5078   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) );
5079   QVERIFY( def->checkValueIsAcceptable( "" ) );
5080   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
5081 
5082   params.insert( "optional",  QVariant() );
5083   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5084 
5085   pythonCode = def->asPythonString();
5086   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterRasterLayer('optional', '', optional=True, defaultValue='" ) + r1->id() + "')" ) );
5087 
5088   code = def->asScriptCode();
5089   QCOMPARE( code, QString( QStringLiteral( "##optional=optional raster " ) + r1->id() ) );
5090   fromCode.reset( dynamic_cast< QgsProcessingParameterRasterLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5091   QVERIFY( fromCode.get() );
5092   QCOMPARE( fromCode->name(), def->name() );
5093   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5094   QCOMPARE( fromCode->flags(), def->flags() );
5095   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5096 
5097   const QVariantMap map = def->toVariantMap();
5098   QgsProcessingParameterRasterLayer fromMap( "x" );
5099   QVERIFY( fromMap.fromVariantMap( map ) );
5100   QCOMPARE( fromMap.name(), def->name() );
5101   QCOMPARE( fromMap.description(), def->description() );
5102   QCOMPARE( fromMap.flags(), def->flags() );
5103   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5104   def.reset( dynamic_cast< QgsProcessingParameterRasterLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5105   QVERIFY( dynamic_cast< QgsProcessingParameterRasterLayer *>( def.get() ) );
5106 
5107   // optional with direct layer
5108   def.reset( new QgsProcessingParameterRasterLayer( "optional", QString(), QVariant::fromValue( r1 ), true ) );
5109   QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() );
5110 
5111   // invalidRasterError
5112   params.clear();
5113   QCOMPARE( QgsProcessingAlgorithm::invalidRasterError( params, QStringLiteral( "MISSING" ) ), QStringLiteral( "Could not load source layer for MISSING: no value specified for parameter" ) );
5114   params.insert( QStringLiteral( "INPUT" ), QStringLiteral( "my layer" ) );
5115   QCOMPARE( QgsProcessingAlgorithm::invalidRasterError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my layer not found" ) );
5116   params.insert( QStringLiteral( "INPUT" ), QgsProperty::fromValue( "my prop layer" ) );
5117   QCOMPARE( QgsProcessingAlgorithm::invalidRasterError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my prop layer not found" ) );
5118   params.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( v1 ) );
5119   QCOMPARE( QgsProcessingAlgorithm::invalidRasterError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: invalid value" ) );
5120 }
5121 
parameterEnum()5122 void TestQgsProcessing::parameterEnum()
5123 {
5124   QgsProcessingContext context;
5125 
5126   // not optional!
5127   std::unique_ptr< QgsProcessingParameterEnum > def( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", false, 2, false ) );
5128   QVERIFY( !def->checkValueIsAcceptable( false ) );
5129   QVERIFY( !def->checkValueIsAcceptable( true ) );
5130   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5131   QVERIFY( def->checkValueIsAcceptable( "1" ) );
5132   QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
5133   QVERIFY( def->checkValueIsAcceptable( 0 ) );
5134   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5135   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
5136   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5137   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5138   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5139   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5140   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5141   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, because falls back to default value
5142   QVERIFY( !def->checkValueIsAcceptable( "B" ) ); // should not be acceptable, because static strings flag is not set
5143   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because static strings flag is not set
5144 
5145   // string representing a number
5146   QVariantMap params;
5147   params.insert( "non_optional", QString( "1" ) );
5148   int iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5149   QCOMPARE( iNumber, 1 );
5150 
5151   // double
5152   params.insert( "non_optional", 2.2 );
5153   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5154   QCOMPARE( iNumber, 2 );
5155 
5156   // int
5157   params.insert( "non_optional", 1 );
5158   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5159   QCOMPARE( iNumber, 1 );
5160 
5161   // nonsense string
5162   params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) );
5163   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5164   QCOMPARE( iNumber, 2 );
5165 
5166   // out of range
5167   params.insert( "non_optional", 4 );
5168   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5169   QCOMPARE( iNumber, 2 );
5170 
5171   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5172   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
5173   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1" ) );
5174   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
5175 
5176   QString pythonCode = def->asPythonString();
5177   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('non_optional', '', options=['A','B','C'], allowMultiple=False, usesStaticStrings=False, defaultValue=2)" ) );
5178 
5179   QString code = def->asScriptCode();
5180   QCOMPARE( code, QStringLiteral( "##non_optional=enum A;B;C 2" ) );
5181   std::unique_ptr< QgsProcessingParameterEnum > fromCode( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5182   QVERIFY( fromCode.get() );
5183   QCOMPARE( fromCode->name(), def->name() );
5184   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5185   QCOMPARE( fromCode->flags(), def->flags() );
5186   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5187   QCOMPARE( fromCode->options(), def->options() );
5188   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5189   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5190 
5191   const QVariantMap map = def->toVariantMap();
5192   QgsProcessingParameterEnum fromMap( "x" );
5193   QVERIFY( fromMap.fromVariantMap( map ) );
5194   QCOMPARE( fromMap.name(), def->name() );
5195   QCOMPARE( fromMap.description(), def->description() );
5196   QCOMPARE( fromMap.flags(), def->flags() );
5197   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5198   QCOMPARE( fromMap.options(), def->options() );
5199   QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() );
5200   QCOMPARE( fromMap.usesStaticStrings(), def->usesStaticStrings() );
5201   def.reset( dynamic_cast< QgsProcessingParameterEnum *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5202   QVERIFY( dynamic_cast< QgsProcessingParameterEnum *>( def.get() ) );
5203 
5204   // multiple
5205   def.reset( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", true, 5, false ) );
5206   QVERIFY( !def->checkValueIsAcceptable( false ) );
5207   QVERIFY( !def->checkValueIsAcceptable( true ) );
5208   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5209   QVERIFY( def->checkValueIsAcceptable( "1" ) );
5210   QVERIFY( def->checkValueIsAcceptable( "1,2" ) );
5211   QVERIFY( def->checkValueIsAcceptable( 0 ) );
5212   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); // since non-optional, empty list not allowed
5213   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) );
5214   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5215   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5216   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5217   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5218   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5219   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5220   QVERIFY( !def->checkValueIsAcceptable( "B" ) ); // should not be acceptable, because static strings flag is not set
5221   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because static strings flag is not set
5222 
5223   params.insert( "non_optional", QString( "1,2" ) );
5224   QList< int > iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5225   QCOMPARE( iNumbers, QList<int>() << 1 << 2 );
5226   params.insert( "non_optional", QVariantList() << 0 << 2 );
5227   iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5228   QCOMPARE( iNumbers, QList<int>() << 0 << 2 );
5229 
5230   // empty list
5231   params.insert( "non_optional", QVariantList() );
5232   iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5233   QCOMPARE( iNumbers, QList<int>() );
5234 
5235   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5236   QCOMPARE( def->valueAsPythonString( QVariantList() << 1 << 2, context ), QStringLiteral( "[1,2]" ) );
5237   QCOMPARE( def->valueAsPythonString( QStringLiteral( "1,2" ), context ), QStringLiteral( "[1,2]" ) );
5238 
5239   QCOMPARE( def->valueAsPythonComment( QVariant(), context ), QString() );
5240   QCOMPARE( def->valueAsPythonComment( 2, context ), QStringLiteral( "C" ) );
5241   QCOMPARE( def->valueAsPythonComment( QVariantList() << 1 << 2, context ), QStringLiteral( "B,C" ) );
5242   QCOMPARE( def->valueAsPythonComment( QStringLiteral( "1,2" ), context ), QStringLiteral( "B,C" ) );
5243 
5244   pythonCode = def->asPythonString();
5245   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('non_optional', '', options=['A','B','C'], allowMultiple=True, usesStaticStrings=False, defaultValue=5)" ) );
5246 
5247   code = def->asScriptCode();
5248   QCOMPARE( code, QStringLiteral( "##non_optional=enum multiple A;B;C 5" ) );
5249   fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5250   QVERIFY( fromCode.get() );
5251   QCOMPARE( fromCode->name(), def->name() );
5252   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5253   QCOMPARE( fromCode->flags(), def->flags() );
5254   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5255   QCOMPARE( fromCode->options(), def->options() );
5256   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5257   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5258 
5259   // optional
5260   def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "a" << "b", false, 5, true ) );
5261   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5262   QVERIFY( def->checkValueIsAcceptable( "1" ) );
5263   QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
5264   QVERIFY( def->checkValueIsAcceptable( 0 ) );
5265   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5266   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
5267   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5268   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5269   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5270   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5271   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5272   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5273 
5274   pythonCode = def->asPythonString();
5275   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('optional', '', optional=True, options=['a','b'], allowMultiple=False, usesStaticStrings=False, defaultValue=5)" ) );
5276 
5277   code = def->asScriptCode();
5278   QCOMPARE( code, QStringLiteral( "##optional=optional enum a;b 5" ) );
5279   fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5280   QVERIFY( fromCode.get() );
5281   QCOMPARE( fromCode->name(), def->name() );
5282   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5283   QCOMPARE( fromCode->flags(), def->flags() );
5284   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5285   QCOMPARE( fromCode->options(), def->options() );
5286   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5287   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5288 
5289   params.insert( "optional",  QVariant() );
5290   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5291   QCOMPARE( iNumber, 5 );
5292   // unconvertible string
5293   params.insert( "optional",  QVariant( "aaaa" ) );
5294   iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context );
5295   QCOMPARE( iNumber, 5 );
5296   //optional with multiples
5297   def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "A" << "B" << "C", true, QVariantList() << 1 << 2, true ) );
5298   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5299   QVERIFY( def->checkValueIsAcceptable( "1" ) );
5300   QVERIFY( def->checkValueIsAcceptable( "1,2" ) );
5301   QVERIFY( def->checkValueIsAcceptable( 0 ) );
5302   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
5303   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) );
5304   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5305   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5306   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5307   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5308   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5309   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
5310   QVERIFY( !def->checkValueIsAcceptable( "B" ) ); // should not be acceptable, because static strings flag is not set
5311   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because static strings flag is not set
5312 
5313   params.insert( "optional",  QVariant() );
5314   iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5315   QCOMPARE( iNumbers, QList<int>() << 1 << 2 );
5316   def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "A" << "B" << "C", true, "1,2", true ) );
5317   params.insert( "optional",  QVariant() );
5318   iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5319   QCOMPARE( iNumbers, QList<int>() << 1 << 2 );
5320   // empty list
5321   params.insert( "optional", QVariantList() );
5322   iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context );
5323   QCOMPARE( iNumbers, QList<int>() );
5324 
5325   pythonCode = def->asPythonString();
5326   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('optional', '', optional=True, options=['A','B','C'], allowMultiple=True, usesStaticStrings=False, defaultValue=[1,2])" ) );
5327 
5328   code = def->asScriptCode();
5329   QCOMPARE( code, QStringLiteral( "##optional=optional enum multiple A;B;C 1,2" ) );
5330   fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5331   QVERIFY( fromCode.get() );
5332   QCOMPARE( fromCode->name(), def->name() );
5333   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5334   QCOMPARE( fromCode->flags(), def->flags() );
5335   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5336   QCOMPARE( fromCode->options(), def->options() );
5337   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5338   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5339 
5340   // non optional, no default
5341   def.reset( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", false, QVariant(), false ) );
5342   QVERIFY( !def->checkValueIsAcceptable( false ) );
5343   QVERIFY( !def->checkValueIsAcceptable( true ) );
5344   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5345   QVERIFY( def->checkValueIsAcceptable( "1" ) );
5346   QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
5347   QVERIFY( def->checkValueIsAcceptable( 0 ) );
5348   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5349   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
5350   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5351   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5352   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5353   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5354   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5355   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, because falls back to invalid default value
5356   QVERIFY( !def->checkValueIsAcceptable( "B" ) ); // should not be acceptable, because static strings flag is not set
5357   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because static strings flag is not set
5358 
5359   // not optional with static strings
5360   def.reset( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", false, "B", false, true ) );
5361   QVERIFY( !def->checkValueIsAcceptable( false ) );
5362   QVERIFY( !def->checkValueIsAcceptable( true ) );
5363   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
5364   QVERIFY( !def->checkValueIsAcceptable( "1" ) );
5365   QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
5366   QVERIFY( !def->checkValueIsAcceptable( 0 ) );
5367   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5368   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
5369   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5370   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5371   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5372   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5373   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5374   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, because falls back to default value
5375   QVERIFY( def->checkValueIsAcceptable( "B" ) ); // should be acceptable, because this is a valid enum value
5376   QVERIFY( !def->checkValueIsAcceptable( "b" ) ); // should not be acceptable, because values are case sensitive
5377   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because value is not in the list of enum values
5378 
5379   // valid enum value
5380   params.insert( "non_optional", QString( "A" ) );
5381   QString iString = QgsProcessingParameters::parameterAsEnumString( def.get(), params, context );
5382   QCOMPARE( iString, QStringLiteral( "A" ) );
5383 
5384   // invalid enum value
5385   params.insert( "non_optional", QString( "Z" ) );
5386   iString = QgsProcessingParameters::parameterAsEnumString( def.get(), params, context );
5387   QCOMPARE( iString, QStringLiteral( "B" ) );
5388 
5389   // lowercase
5390   params.insert( "non_optional", QString( "a" ) );
5391   iString = QgsProcessingParameters::parameterAsEnumString( def.get(), params, context );
5392   QCOMPARE( iString, QStringLiteral( "B" ) );
5393 
5394   // empty string
5395   params.insert( "non_optional", QString() );
5396   iString = QgsProcessingParameters::parameterAsEnumString( def.get(), params, context );
5397   QCOMPARE( iString, QStringLiteral( "B" ) );
5398 
5399   // number
5400   params.insert( "non_optional", 1 );
5401   iString = QgsProcessingParameters::parameterAsEnumString( def.get(), params, context );
5402   QCOMPARE( iString, QStringLiteral( "B" ) );
5403 
5404   pythonCode = def->asPythonString();
5405   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('non_optional', '', options=['A','B','C'], allowMultiple=False, usesStaticStrings=True, defaultValue='B')" ) );
5406 
5407   code = def->asScriptCode();
5408   QCOMPARE( code, QStringLiteral( "##non_optional=enum static A;B;C B" ) );
5409   fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5410   QVERIFY( fromCode.get() );
5411   QCOMPARE( fromCode->name(), def->name() );
5412   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5413   QCOMPARE( fromCode->flags(), def->flags() );
5414   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5415   QCOMPARE( fromCode->options(), def->options() );
5416   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5417   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5418 
5419   // multiple with static strings
5420   def.reset( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", true, "B", false, true ) );
5421   QVERIFY( !def->checkValueIsAcceptable( false ) );
5422   QVERIFY( !def->checkValueIsAcceptable( true ) );
5423   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
5424   QVERIFY( !def->checkValueIsAcceptable( "1" ) );
5425   QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
5426   QVERIFY( !def->checkValueIsAcceptable( 0 ) );
5427   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5428   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
5429   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
5430   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "A" ) );
5431   QVERIFY( !def->checkValueIsAcceptable( 15 ) );
5432   QVERIFY( !def->checkValueIsAcceptable( -1 ) );
5433   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
5434   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5435   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, because falls back to default value
5436   QVERIFY( def->checkValueIsAcceptable( "B" ) ); // should be acceptable, because this is a valid enum value
5437   QVERIFY( !def->checkValueIsAcceptable( "b" ) ); // should not be acceptable, because values are case sensitive
5438   QVERIFY( !def->checkValueIsAcceptable( "Z" ) ); // should not be acceptable, because value is not in the list of enum values
5439 
5440   // comma-separated string
5441   params.insert( "non_optional", QString( "A,B" ) );
5442   QStringList iStrings = QgsProcessingParameters::parameterAsEnumStrings( def.get(), params, context );
5443   QCOMPARE( iStrings, QStringList() << "A" << "B" );
5444 
5445   // list
5446   params.insert( "non_optional", QVariantList() << "A" << "C" );
5447   iStrings = QgsProcessingParameters::parameterAsEnumStrings( def.get(), params, context );
5448   QCOMPARE( iStrings, QStringList() << "A" << "C" );
5449 
5450   // empty list
5451   params.insert( "non_optional", QVariantList() );
5452   iStrings = QgsProcessingParameters::parameterAsEnumStrings( def.get(), params, context );
5453   QCOMPARE( iStrings, QStringList() << "B" );
5454 
5455   pythonCode = def->asPythonString();
5456   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterEnum('non_optional', '', options=['A','B','C'], allowMultiple=True, usesStaticStrings=True, defaultValue='B')" ) );
5457 
5458   code = def->asScriptCode();
5459   QCOMPARE( code, QStringLiteral( "##non_optional=enum multiple static A;B;C B" ) );
5460   fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5461   QVERIFY( fromCode.get() );
5462   QCOMPARE( fromCode->name(), def->name() );
5463   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5464   QCOMPARE( fromCode->flags(), def->flags() );
5465   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5466   QCOMPARE( fromCode->options(), def->options() );
5467   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5468   QCOMPARE( fromCode->usesStaticStrings(), def->usesStaticStrings() );
5469 }
5470 
parameterString()5471 void TestQgsProcessing::parameterString()
5472 {
5473   QgsProcessingContext context;
5474 
5475   // not optional!
5476   std::unique_ptr< QgsProcessingParameterString > def( new QgsProcessingParameterString( "non_optional", QString(), QString(), false, false ) );
5477   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5478   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5479   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5480   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5481 
5482   // string
5483   QVariantMap params;
5484   params.insert( "non_optional", QString( "abcdef" ) );
5485   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) );
5486 
5487   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5488   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
5489   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
5490   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
5491   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
5492   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
5493   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
5494 
5495   QString pythonCode = def->asPythonString();
5496   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterString('non_optional', '', multiLine=False, defaultValue=None)" ) );
5497 
5498   QString code = def->asScriptCode();
5499   QCOMPARE( code, QStringLiteral( "##non_optional=string" ) );
5500   std::unique_ptr< QgsProcessingParameterString > fromCode( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5501   QVERIFY( fromCode.get() );
5502   QCOMPARE( fromCode->name(), def->name() );
5503   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5504   QCOMPARE( fromCode->flags(), def->flags() );
5505   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5506   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5507 
5508   const QVariantMap map = def->toVariantMap();
5509   QgsProcessingParameterString fromMap( "x" );
5510   QVERIFY( fromMap.fromVariantMap( map ) );
5511   QCOMPARE( fromMap.name(), def->name() );
5512   QCOMPARE( fromMap.description(), def->description() );
5513   QCOMPARE( fromMap.flags(), def->flags() );
5514   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5515   QCOMPARE( fromMap.multiLine(), def->multiLine() );
5516   def.reset( dynamic_cast< QgsProcessingParameterString *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5517   QVERIFY( dynamic_cast< QgsProcessingParameterString *>( def.get() ) );
5518 
5519   def->setMultiLine( true );
5520 
5521   pythonCode = def->asPythonString();
5522   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterString('non_optional', '', multiLine=True, defaultValue=None)" ) );
5523 
5524   code = def->asScriptCode();
5525   QCOMPARE( code, QStringLiteral( "##non_optional=string long" ) );
5526   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5527   QVERIFY( fromCode.get() );
5528   QCOMPARE( fromCode->name(), def->name() );
5529   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5530   QCOMPARE( fromCode->flags(), def->flags() );
5531   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5532   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5533   def->setMultiLine( false );
5534 
5535   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string None" ) ) ) );
5536   QVERIFY( fromCode.get() );
5537   QCOMPARE( fromCode->name(), def->name() );
5538   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5539   QCOMPARE( fromCode->flags(), def->flags() );
5540   QVERIFY( !fromCode->defaultValue().isValid() );
5541   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5542 
5543   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string it's mario" ) ) ) );
5544   QVERIFY( fromCode.get() );
5545   QCOMPARE( fromCode->name(), def->name() );
5546   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5547   QCOMPARE( fromCode->flags(), def->flags() );
5548   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
5549   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5550 
5551   def->setDefaultValue( QStringLiteral( "it's mario" ) );
5552   pythonCode = def->asPythonString();
5553   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterString('non_optional', '', multiLine=False, defaultValue=\"it's mario\")" ) );
5554   code = def->asScriptCode();
5555   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5556   QVERIFY( fromCode.get() );
5557   QCOMPARE( fromCode->name(), def->name() );
5558   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5559   QCOMPARE( fromCode->flags(), def->flags() );
5560   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5561   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5562 
5563   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string 'my val'" ) ) ) );
5564   QVERIFY( fromCode.get() );
5565   QCOMPARE( fromCode->name(), def->name() );
5566   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5567   QCOMPARE( fromCode->flags(), def->flags() );
5568   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
5569   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5570 
5571   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string \"my val\"" ) ) ) );
5572   QVERIFY( fromCode.get() );
5573   QCOMPARE( fromCode->name(), def->name() );
5574   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5575   QCOMPARE( fromCode->flags(), def->flags() );
5576   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
5577   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5578 
5579   // optional
5580   def.reset( new QgsProcessingParameterString( "optional", QString(), QString( "default" ), false, true ) );
5581   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5582   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5583   QVERIFY( def->checkValueIsAcceptable( "" ) );
5584   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
5585 
5586   params.insert( "optional",  QVariant() );
5587   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) );
5588   params.insert( "optional",  QString() ); //empty string should not result in default value
5589   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
5590 
5591   pythonCode = def->asPythonString();
5592   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterString('optional', '', optional=True, multiLine=False, defaultValue='default')" ) );
5593 
5594   code = def->asScriptCode();
5595   QCOMPARE( code, QStringLiteral( "##optional=optional string default" ) );
5596   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5597   QVERIFY( fromCode.get() );
5598   QCOMPARE( fromCode->name(), def->name() );
5599   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5600   QCOMPARE( fromCode->flags(), def->flags() );
5601   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5602   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5603 
5604   def->setMultiLine( true );
5605   pythonCode = def->asPythonString();
5606   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterString('optional', '', optional=True, multiLine=True, defaultValue='default')" ) );
5607   code = def->asScriptCode();
5608   QCOMPARE( code, QStringLiteral( "##optional=optional string long default" ) );
5609   fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5610   QVERIFY( fromCode.get() );
5611   QCOMPARE( fromCode->name(), def->name() );
5612   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5613   QCOMPARE( fromCode->flags(), def->flags() );
5614   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5615   QCOMPARE( fromCode->multiLine(), def->multiLine() );
5616 
5617   // not optional, valid default!
5618   def.reset( new QgsProcessingParameterString( "non_optional", QString(), QString( "def" ), false, false ) );
5619   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5620   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5621   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5622   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
5623 }
5624 
5625 
parameterAuthConfig()5626 void TestQgsProcessing::parameterAuthConfig()
5627 {
5628   QgsProcessingContext context;
5629 
5630   // not optional!
5631   std::unique_ptr< QgsProcessingParameterAuthConfig > def( new QgsProcessingParameterAuthConfig( "non_optional", QString(), QString(), false ) );
5632   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5633   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5634   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5635   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5636 
5637   // string
5638   QVariantMap params;
5639   params.insert( "non_optional", QString( "abcdef" ) );
5640   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) );
5641 
5642   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5643   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
5644   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
5645   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
5646   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
5647   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
5648 
5649   QString pythonCode = def->asPythonString();
5650   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAuthConfig('non_optional', '', defaultValue='')" ) );
5651 
5652   QString code = def->asScriptCode();
5653   QCOMPARE( code, QStringLiteral( "##non_optional=authcfg" ) );
5654   std::unique_ptr< QgsProcessingParameterAuthConfig > fromCode( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5655   QVERIFY( fromCode.get() );
5656   QCOMPARE( fromCode->name(), def->name() );
5657   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5658   QCOMPARE( fromCode->flags(), def->flags() );
5659   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5660 
5661   const QVariantMap map = def->toVariantMap();
5662   QgsProcessingParameterAuthConfig fromMap( "x" );
5663   QVERIFY( fromMap.fromVariantMap( map ) );
5664   QCOMPARE( fromMap.name(), def->name() );
5665   QCOMPARE( fromMap.description(), def->description() );
5666   QCOMPARE( fromMap.flags(), def->flags() );
5667   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5668   def.reset( dynamic_cast< QgsProcessingParameterAuthConfig *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5669   QVERIFY( dynamic_cast< QgsProcessingParameterAuthConfig *>( def.get() ) );
5670 
5671   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=authcfg None" ) ) ) );
5672   QVERIFY( fromCode.get() );
5673   QCOMPARE( fromCode->name(), def->name() );
5674   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5675   QCOMPARE( fromCode->flags(), def->flags() );
5676   QVERIFY( !fromCode->defaultValue().isValid() );
5677 
5678   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=authcfg it's mario" ) ) ) );
5679   QVERIFY( fromCode.get() );
5680   QCOMPARE( fromCode->name(), def->name() );
5681   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5682   QCOMPARE( fromCode->flags(), def->flags() );
5683   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
5684 
5685   def->setDefaultValue( QStringLiteral( "it's mario" ) );
5686   pythonCode = def->asPythonString();
5687   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAuthConfig('non_optional', '', defaultValue=\"it's mario\")" ) );
5688 
5689   code = def->asScriptCode();
5690   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5691   QVERIFY( fromCode.get() );
5692   QCOMPARE( fromCode->name(), def->name() );
5693   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5694   QCOMPARE( fromCode->flags(), def->flags() );
5695   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5696 
5697   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=authcfg 'my val'" ) ) ) );
5698   QVERIFY( fromCode.get() );
5699   QCOMPARE( fromCode->name(), def->name() );
5700   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5701   QCOMPARE( fromCode->flags(), def->flags() );
5702   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
5703 
5704   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=authcfg \"my val\"" ) ) ) );
5705   QVERIFY( fromCode.get() );
5706   QCOMPARE( fromCode->name(), def->name() );
5707   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5708   QCOMPARE( fromCode->flags(), def->flags() );
5709   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
5710 
5711   // optional
5712   def.reset( new QgsProcessingParameterAuthConfig( "optional", QString(), QString( "default" ), true ) );
5713   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5714   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5715   QVERIFY( def->checkValueIsAcceptable( "" ) );
5716   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
5717 
5718   params.insert( "optional",  QVariant() );
5719   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) );
5720   params.insert( "optional",  QString() ); //empty string should not result in default value
5721   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
5722 
5723   pythonCode = def->asPythonString();
5724   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAuthConfig('optional', '', optional=True, defaultValue='default')" ) );
5725   code = def->asScriptCode();
5726   QCOMPARE( code, QStringLiteral( "##optional=optional authcfg default" ) );
5727   fromCode.reset( dynamic_cast< QgsProcessingParameterAuthConfig * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5728   QVERIFY( fromCode.get() );
5729   QCOMPARE( fromCode->name(), def->name() );
5730   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5731   QCOMPARE( fromCode->flags(), def->flags() );
5732   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5733 
5734   // not optional, valid default!
5735   def.reset( new QgsProcessingParameterAuthConfig( "non_optional", QString(), QString( "def" ), false ) );
5736   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5737   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5738   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5739   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
5740 }
5741 
parameterExpression()5742 void TestQgsProcessing::parameterExpression()
5743 {
5744   QgsProcessingContext context;
5745 
5746   // not optional!
5747   std::unique_ptr< QgsProcessingParameterExpression > def( new QgsProcessingParameterExpression( "non_optional", QString(), QString( "1+1" ), QString(), false ) );
5748   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5749   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5750   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5751   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, because it will fallback to default value
5752 
5753   // string
5754   QVariantMap params;
5755   params.insert( "non_optional", QString( "abcdef" ) );
5756   QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "abcdef" ) );
5757 
5758   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5759   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
5760   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
5761   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
5762   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
5763 
5764   QString pythonCode = def->asPythonString();
5765   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExpression('non_optional', '', parentLayerParameterName='', defaultValue='1+1')" ) );
5766 
5767   QString code = def->asScriptCode();
5768   QCOMPARE( code, QStringLiteral( "##non_optional=expression 1+1" ) );
5769   std::unique_ptr< QgsProcessingParameterExpression > fromCode( dynamic_cast< QgsProcessingParameterExpression * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5770   QVERIFY( fromCode.get() );
5771   QCOMPARE( fromCode->name(), def->name() );
5772   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5773   QCOMPARE( fromCode->flags(), def->flags() );
5774   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5775 
5776   const QVariantMap map = def->toVariantMap();
5777   QgsProcessingParameterExpression fromMap( "x" );
5778   QVERIFY( fromMap.fromVariantMap( map ) );
5779   QCOMPARE( fromMap.name(), def->name() );
5780   QCOMPARE( fromMap.description(), def->description() );
5781   QCOMPARE( fromMap.flags(), def->flags() );
5782   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5783   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
5784   def.reset( dynamic_cast< QgsProcessingParameterExpression *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5785   QVERIFY( dynamic_cast< QgsProcessingParameterExpression *>( def.get() ) );
5786 
5787   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
5788   def->setParentLayerParameterName( QStringLiteral( "test_layer" ) );
5789   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "test_layer" ) );
5790 
5791   // optional
5792   def.reset( new QgsProcessingParameterExpression( "optional", QString(), QString( "default" ), QString(), true ) );
5793   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5794   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5795   QVERIFY( def->checkValueIsAcceptable( "" ) );
5796   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
5797 
5798   params.insert( "optional",  QVariant() );
5799   QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "default" ) );
5800   // valid expression, should not fallback
5801   params.insert( "optional",  QVariant( "1+2" ) );
5802   QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "1+2" ) );
5803   // invalid expression, should fallback
5804   params.insert( "optional",  QVariant( "1+" ) );
5805   QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "default" ) );
5806 
5807   pythonCode = def->asPythonString();
5808   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExpression('optional', '', optional=True, parentLayerParameterName='', defaultValue='default')" ) );
5809 
5810   code = def->asScriptCode();
5811   QCOMPARE( code, QStringLiteral( "##optional=optional expression default" ) );
5812   fromCode.reset( dynamic_cast< QgsProcessingParameterExpression * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5813   QVERIFY( fromCode.get() );
5814   QCOMPARE( fromCode->name(), def->name() );
5815   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
5816   QCOMPARE( fromCode->flags(), def->flags() );
5817   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5818 
5819   // non optional, no default
5820   def.reset( new QgsProcessingParameterExpression( "non_optional", QString(), QString(), QString(), false ) );
5821   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5822   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5823   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5824   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, because it will fallback to invalid default value
5825 
5826 }
5827 
parameterField()5828 void TestQgsProcessing::parameterField()
5829 {
5830   QgsProcessingContext context;
5831 
5832   // not optional!
5833   std::unique_ptr< QgsProcessingParameterField > def( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, false, false ) );
5834   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5835   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5836   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
5837   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
5838   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5839   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5840 
5841   // string
5842   QVariantMap params;
5843   params.insert( "non_optional", QString( "a" ) );
5844   QStringList fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
5845   QCOMPARE( fields, QStringList() << "a" );
5846 
5847   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5848   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
5849   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
5850   QCOMPARE( def->valueAsPythonString( "probably\'invalid\"field", context ), QStringLiteral( "'probably\\'invalid\"field'" ) );
5851 
5852   QString pythonCode = def->asPythonString();
5853   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Any, parentLayerParameterName='', allowMultiple=False, defaultValue=None)" ) );
5854 
5855   QString code = def->asScriptCode();
5856   QCOMPARE( code, QStringLiteral( "##non_optional=field" ) );
5857   std::unique_ptr< QgsProcessingParameterField > fromCode( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5858   QVERIFY( fromCode.get() );
5859   QCOMPARE( fromCode->name(), def->name() );
5860   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5861   QCOMPARE( fromCode->flags(), def->flags() );
5862   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5863   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5864   QCOMPARE( fromCode->dataType(), def->dataType() );
5865   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5866   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5867 
5868   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
5869   def->setParentLayerParameterName( "my_parent" );
5870   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) );
5871 
5872   pythonCode = def->asPythonString();
5873   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Any, parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) );
5874 
5875   code = def->asScriptCode();
5876   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5877   QVERIFY( fromCode.get() );
5878   QCOMPARE( fromCode->name(), def->name() );
5879   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5880   QCOMPARE( fromCode->flags(), def->flags() );
5881   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5882   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5883   QCOMPARE( fromCode->dataType(), def->dataType() );
5884   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5885   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5886 
5887   def->setDataType( QgsProcessingParameterField::Numeric );
5888   pythonCode = def->asPythonString();
5889   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Numeric, parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) );
5890   code = def->asScriptCode();
5891   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5892   QVERIFY( fromCode.get() );
5893   QCOMPARE( fromCode->name(), def->name() );
5894   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5895   QCOMPARE( fromCode->flags(), def->flags() );
5896   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5897   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5898   QCOMPARE( fromCode->dataType(), def->dataType() );
5899   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5900   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5901 
5902   def->setDataType( QgsProcessingParameterField::String );
5903   pythonCode = def->asPythonString();
5904   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.String, parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) );
5905   code = def->asScriptCode();
5906   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5907   QVERIFY( fromCode.get() );
5908   QCOMPARE( fromCode->name(), def->name() );
5909   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5910   QCOMPARE( fromCode->flags(), def->flags() );
5911   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5912   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5913   QCOMPARE( fromCode->dataType(), def->dataType() );
5914   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5915   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5916 
5917   def->setDataType( QgsProcessingParameterField::DateTime );
5918   pythonCode = def->asPythonString();
5919   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.DateTime, parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) );
5920   code = def->asScriptCode();
5921   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5922   QVERIFY( fromCode.get() );
5923   QCOMPARE( fromCode->name(), def->name() );
5924   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5925   QCOMPARE( fromCode->flags(), def->flags() );
5926   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5927   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5928   QCOMPARE( fromCode->dataType(), def->dataType() );
5929   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5930   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5931 
5932   // multiple
5933   def.reset( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, true, false ) );
5934   QVERIFY( def->checkValueIsAcceptable( 1 ) );
5935   QVERIFY( def->checkValueIsAcceptable( "test" ) );
5936   QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
5937   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
5938   QVERIFY( !def->checkValueIsAcceptable( "" ) );
5939   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
5940   QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
5941   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
5942 
5943   params.insert( "non_optional", QString( "a;b" ) );
5944   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
5945   QCOMPARE( fields, QStringList() << "a" << "b" );
5946   params.insert( "non_optional", QVariantList() << "a" << "b" );
5947   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
5948   QCOMPARE( fields, QStringList() << "a" << "b" );
5949 
5950   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
5951   QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "b", context ), QStringLiteral( "['a','b']" ) );
5952   QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "b", context ), QStringLiteral( "['a','b']" ) );
5953 
5954   QVariantMap map = def->toVariantMap();
5955   QgsProcessingParameterField fromMap( "x" );
5956   QVERIFY( fromMap.fromVariantMap( map ) );
5957   QCOMPARE( fromMap.name(), def->name() );
5958   QCOMPARE( fromMap.description(), def->description() );
5959   QCOMPARE( fromMap.flags(), def->flags() );
5960   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5961   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
5962   QCOMPARE( fromMap.dataType(), def->dataType() );
5963   QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() );
5964   QCOMPARE( fromMap.defaultToAllFields(), def->defaultToAllFields() );
5965   def.reset( dynamic_cast< QgsProcessingParameterField *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5966   QVERIFY( dynamic_cast< QgsProcessingParameterField *>( def.get() ) );
5967 
5968   pythonCode = def->asPythonString();
5969   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Any, parentLayerParameterName='', allowMultiple=True, defaultValue=None)" ) );
5970   code = def->asScriptCode();
5971   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
5972   QVERIFY( fromCode.get() );
5973   QCOMPARE( fromCode->name(), def->name() );
5974   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
5975   QCOMPARE( fromCode->flags(), def->flags() );
5976   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
5977   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
5978   QCOMPARE( fromCode->dataType(), def->dataType() );
5979   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
5980   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
5981 
5982   // default to all fields
5983   def.reset( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, true, false, true ) );
5984   map = def->toVariantMap();
5985   QVERIFY( fromMap.fromVariantMap( map ) );
5986   QCOMPARE( fromMap.name(), def->name() );
5987   QCOMPARE( fromMap.description(), def->description() );
5988   QCOMPARE( fromMap.flags(), def->flags() );
5989   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
5990   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
5991   QCOMPARE( fromMap.dataType(), def->dataType() );
5992   QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() );
5993   QCOMPARE( fromMap.defaultToAllFields(), def->defaultToAllFields() );
5994   def.reset( dynamic_cast< QgsProcessingParameterField *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
5995   QVERIFY( dynamic_cast< QgsProcessingParameterField *>( def.get() ) );
5996 
5997   pythonCode = def->asPythonString();
5998   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Any, parentLayerParameterName='', allowMultiple=True, defaultValue=None, defaultToAllFields=True)" ) );
5999   code = def->asScriptCode();
6000   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6001   QVERIFY( fromCode.get() );
6002   QCOMPARE( fromCode->name(), def->name() );
6003   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6004   QCOMPARE( fromCode->flags(), def->flags() );
6005   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6006   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
6007   QCOMPARE( fromCode->dataType(), def->dataType() );
6008   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
6009   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
6010 
6011   // optional
6012   def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "def" ), QString(), QgsProcessingParameterField::Any, false, true ) );
6013   QVERIFY( def->checkValueIsAcceptable( 1 ) );
6014   QVERIFY( def->checkValueIsAcceptable( "test" ) );
6015   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
6016   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
6017   QVERIFY( def->checkValueIsAcceptable( "" ) );
6018   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6019 
6020   params.insert( "optional",  QVariant() );
6021   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6022   QCOMPARE( fields, QStringList() << "def" );
6023 
6024   // optional with string list default
6025   def.reset( new QgsProcessingParameterField( "optional", QString(), QStringList() << QStringLiteral( "def" ) << QStringLiteral( "abc" ), QString(), QgsProcessingParameterField::Any, true, true ) );
6026   QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
6027   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6028   QCOMPARE( fields, QStringList() << "def" << "abc" );
6029   params.insert( "optional",  QVariantList() << "f" << "h" );
6030   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6031   QCOMPARE( fields, QStringList() << "f" << "h" );
6032   params.insert( "optional",  QStringList() << "g" << "h" );
6033   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6034   QCOMPARE( fields, QStringList() << "g" << "h" );
6035 
6036   // optional, no default
6037   def.reset( new QgsProcessingParameterField( "optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, false, true ) );
6038   params.insert( "optional",  QVariant() );
6039   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6040   QVERIFY( fields.isEmpty() );
6041 
6042   pythonCode = def->asPythonString();
6043   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('optional', '', optional=True, type=QgsProcessingParameterField.Any, parentLayerParameterName='', allowMultiple=False, defaultValue=None)" ) );
6044   code = def->asScriptCode();
6045   QCOMPARE( code, QStringLiteral( "##optional=optional field" ) );
6046   fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6047   QVERIFY( fromCode.get() );
6048   QCOMPARE( fromCode->name(), def->name() );
6049   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6050   QCOMPARE( fromCode->flags(), def->flags() );
6051   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6052   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
6053   QCOMPARE( fromCode->dataType(), def->dataType() );
6054   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
6055   QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() );
6056 
6057   //optional with multiples
6058   def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "abc;def" ), QString(), QgsProcessingParameterField::Any, true, true ) );
6059   QVERIFY( def->checkValueIsAcceptable( 1 ) );
6060   QVERIFY( def->checkValueIsAcceptable( "test" ) );
6061   QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
6062   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
6063   QVERIFY( def->checkValueIsAcceptable( "" ) );
6064   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6065 
6066   params.insert( "optional",  QVariant() );
6067   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6068   QCOMPARE( fields, QStringList() << "abc" << "def" );
6069   def.reset( new QgsProcessingParameterField( "optional", QString(), QVariantList() << "abc" << "def", QString(), QgsProcessingParameterField::Any, true, true ) );
6070   params.insert( "optional",  QVariant() );
6071   fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context );
6072   QCOMPARE( fields, QStringList() << "abc" << "def" );
6073 }
6074 
parameterVectorLayer()6075 void TestQgsProcessing::parameterVectorLayer()
6076 {
6077   // setup a context
6078   QgsProject p;
6079   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6080   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
6081   const QString vector1 = testDataDir + "multipoint.shp";
6082   const QString raster = testDataDir + "landsat.tif";
6083   const QFileInfo fi1( raster );
6084   const QFileInfo fi2( vector1 );
6085   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
6086   QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" );
6087   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 );
6088   QgsProcessingContext context;
6089   context.setProject( &p );
6090 
6091   // not optional!
6092   std::unique_ptr< QgsProcessingParameterVectorLayer > def( new QgsProcessingParameterVectorLayer( "non_optional", QString(), QList< int >(), QString( "somelayer" ), false ) );
6093   QVERIFY( !def->checkValueIsAcceptable( false ) );
6094   QVERIFY( !def->checkValueIsAcceptable( true ) );
6095   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6096   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6097   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6098   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6099   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6100   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
6101   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
6102   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
6103   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6104 
6105   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6106   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6107   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6108   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6109 
6110   // should be OK
6111   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6112   // ... unless we use context, when the check that the layer actually exists is performed
6113   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
6114 
6115   // using existing map layer ID
6116   QVariantMap params;
6117   params.insert( "non_optional",  v1->id() );
6118   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() );
6119 
6120   // using existing layer
6121   params.insert( "non_optional",  QVariant::fromValue( v1 ) );
6122   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() );
6123 
6124   // not vector layer
6125   params.insert( "non_optional",  r1->id() );
6126   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6127 
6128   // using existing non-vector layer
6129   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
6130   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6131 
6132   // string representing a layer source
6133   params.insert( "non_optional", vector1 );
6134   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->publicSource(), vector1 );
6135 
6136   // nonsense string
6137   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
6138   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6139 
6140   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6141   QCOMPARE( def->valueAsPythonString( vector1, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ) );
6142   QCOMPARE( def->valueAsPythonString( v1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ) );
6143   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ) );
6144   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6145   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
6146 
6147   QString pythonCode = def->asPythonString();
6148   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorLayer('non_optional', '', defaultValue='somelayer')" ) );
6149 
6150   QString code = def->asScriptCode();
6151   QCOMPARE( code, QStringLiteral( "##non_optional=vector somelayer" ) );
6152   std::unique_ptr< QgsProcessingParameterVectorLayer > fromCode( dynamic_cast< QgsProcessingParameterVectorLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6153   QVERIFY( fromCode.get() );
6154   QCOMPARE( fromCode->name(), def->name() );
6155   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6156   QCOMPARE( fromCode->flags(), def->flags() );
6157   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6158 
6159   const QVariantMap map = def->toVariantMap();
6160   QgsProcessingParameterVectorLayer fromMap( "x" );
6161   QVERIFY( fromMap.fromVariantMap( map ) );
6162   QCOMPARE( fromMap.name(), def->name() );
6163   QCOMPARE( fromMap.description(), def->description() );
6164   QCOMPARE( fromMap.flags(), def->flags() );
6165   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6166   def.reset( dynamic_cast< QgsProcessingParameterVectorLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6167   QVERIFY( dynamic_cast< QgsProcessingParameterVectorLayer *>( def.get() ) );
6168 
6169   // optional
6170   def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), v1->id(), true ) );
6171   params.insert( "optional",  QVariant() );
6172   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params,  context )->id(), v1->id() );
6173   QVERIFY( def->checkValueIsAcceptable( false ) );
6174   QVERIFY( def->checkValueIsAcceptable( true ) );
6175   QVERIFY( def->checkValueIsAcceptable( 5 ) );
6176   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6177   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6178   QVERIFY( def->checkValueIsAcceptable( "" ) );
6179   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6180   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6181 
6182   pythonCode = def->asPythonString();
6183   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterVectorLayer('optional', '', optional=True, defaultValue='" ) + v1->id() + "')" ) );
6184 
6185   code = def->asScriptCode();
6186   QCOMPARE( code, QString( QStringLiteral( "##optional=optional vector " ) + v1->id() ) );
6187   fromCode.reset( dynamic_cast< QgsProcessingParameterVectorLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6188   QVERIFY( fromCode.get() );
6189   QCOMPARE( fromCode->name(), def->name() );
6190   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6191   QCOMPARE( fromCode->flags(), def->flags() );
6192   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6193 
6194   //optional with direct layer default
6195   def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), QVariant::fromValue( v1 ), true ) );
6196   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params,  context )->id(), v1->id() );
6197 }
6198 
parameterMeshLayer()6199 void TestQgsProcessing::parameterMeshLayer()
6200 {
6201   // setup a context
6202   QgsProject p;
6203   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6204   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
6205   const QString vector1 = testDataDir + "multipoint.shp";
6206   const QString raster = testDataDir + "landsat.tif";
6207   const QString mesh = testDataDir + "mesh/quad_and_triangle.2dm";
6208   const QFileInfo fi1( raster );
6209   const QFileInfo fi2( vector1 );
6210   const QFileInfo fi3( mesh );
6211   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
6212   QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" );
6213   QgsMeshLayer *m1 = new QgsMeshLayer( fi3.filePath(), "M1", "mdal" );
6214   Q_ASSERT( m1 );
6215   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 << m1 );
6216   QgsProcessingContext context;
6217   context.setProject( &p );
6218 
6219   // not optional!
6220   std::unique_ptr< QgsProcessingParameterMeshLayer > def( new QgsProcessingParameterMeshLayer( "non_optional", QString(), QString( "somelayer" ), false ) );
6221   QVERIFY( !def->checkValueIsAcceptable( false ) );
6222   QVERIFY( !def->checkValueIsAcceptable( true ) );
6223   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6224   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6225   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6226   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6227   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6228   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( m1 ) ) );
6229   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
6230   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
6231   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
6232   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6233 
6234   // should be OK
6235   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm" ) );
6236   // ... unless we use context, when the check that the layer actually exists is performed
6237   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm", &context ) );
6238 
6239   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6240   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6241   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6242   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6243 
6244   // using existing map layer ID
6245   QVariantMap params;
6246   params.insert( "non_optional",  m1->id() );
6247   QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() );
6248 
6249   // using existing layer
6250   params.insert( "non_optional",  QVariant::fromValue( m1 ) );
6251   QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() );
6252 
6253   // not mesh layer
6254   params.insert( "non_optional",  r1->id() );
6255   QVERIFY( !QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context ) );
6256 
6257   // using existing non-mesh layer
6258   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
6259   QVERIFY( !QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context ) );
6260 
6261   // string representing a layer source
6262   params.insert( "non_optional", mesh );
6263   QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->publicSource(), mesh );
6264 
6265   // nonsense string
6266   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
6267   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6268 
6269   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6270   QCOMPARE( def->valueAsPythonString( mesh, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ) );
6271   QCOMPARE( def->valueAsPythonString( m1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ) );
6272   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( m1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ) );
6273   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6274   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.2dm" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.2dm'" ) );
6275 
6276   QString pythonCode = def->asPythonString();
6277   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshLayer('non_optional', '', defaultValue='somelayer')" ) );
6278 
6279   QString code = def->asScriptCode();
6280   QCOMPARE( code, QStringLiteral( "##non_optional=mesh somelayer" ) );
6281   std::unique_ptr< QgsProcessingParameterMeshLayer > fromCode( dynamic_cast< QgsProcessingParameterMeshLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6282   QVERIFY( fromCode.get() );
6283   QCOMPARE( fromCode->name(), def->name() );
6284   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6285   QCOMPARE( fromCode->flags(), def->flags() );
6286   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6287 
6288   const QVariantMap map = def->toVariantMap();
6289   QgsProcessingParameterMeshLayer fromMap( "x" );
6290   QVERIFY( fromMap.fromVariantMap( map ) );
6291   QCOMPARE( fromMap.name(), def->name() );
6292   QCOMPARE( fromMap.description(), def->description() );
6293   QCOMPARE( fromMap.flags(), def->flags() );
6294   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6295   def.reset( dynamic_cast< QgsProcessingParameterMeshLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6296   QVERIFY( dynamic_cast< QgsProcessingParameterMeshLayer *>( def.get() ) );
6297 
6298   // optional
6299   def.reset( new QgsProcessingParameterMeshLayer( "optional", QString(), m1->id(), true ) );
6300   params.insert( "optional",  QVariant() );
6301   QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params,  context )->id(), m1->id() );
6302   QVERIFY( def->checkValueIsAcceptable( false ) );
6303   QVERIFY( def->checkValueIsAcceptable( true ) );
6304   QVERIFY( def->checkValueIsAcceptable( 5 ) );
6305   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6306   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm" ) );
6307   QVERIFY( def->checkValueIsAcceptable( "" ) );
6308   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6309   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6310 
6311   pythonCode = def->asPythonString();
6312   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterMeshLayer('optional', '', optional=True, defaultValue='" ) + m1->id() + "')" ) );
6313 
6314   code = def->asScriptCode();
6315   QCOMPARE( code, QString( QStringLiteral( "##optional=optional mesh " ) + m1->id() ) );
6316   fromCode.reset( dynamic_cast< QgsProcessingParameterMeshLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6317   QVERIFY( fromCode.get() );
6318   QCOMPARE( fromCode->name(), def->name() );
6319   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6320   QCOMPARE( fromCode->flags(), def->flags() );
6321   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6322 
6323   //optional with direct layer default
6324   def.reset( new QgsProcessingParameterMeshLayer( "optional", QString(), QVariant::fromValue( m1 ), true ) );
6325   QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params,  context )->id(), m1->id() );
6326 }
6327 
parameterFeatureSource()6328 void TestQgsProcessing::parameterFeatureSource()
6329 {
6330   // setup a context
6331   QgsProject p;
6332   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6333   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
6334   const QString vector1 = testDataDir + "multipoint.shp";
6335   const QString vector2 = testDataDir + "lines.shp";
6336   const QString raster = testDataDir + "landsat.tif";
6337   const QFileInfo fi1( raster );
6338   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
6339   QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" );
6340   QgsVectorLayer *v2 = new QgsVectorLayer( vector2, "V5", "ogr" );
6341   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 << v2 );
6342   QgsProcessingContext context;
6343   context.setProject( &p );
6344 
6345   // not optional!
6346   std::unique_ptr< QgsProcessingParameterFeatureSource > def( new QgsProcessingParameterFeatureSource( "non_optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
6347   QVERIFY( !def->checkValueIsAcceptable( false ) );
6348   QVERIFY( !def->checkValueIsAcceptable( true ) );
6349   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6350   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6351   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6352   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6353   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6354   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "" ) ) );
6355   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
6356   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
6357   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
6358   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6359 
6360   // should be OK
6361   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6362   // ... unless we use context, when the check that the layer actually exists is performed
6363   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
6364 
6365   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6366   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6367   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6368   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6369 
6370   // using existing map layer ID
6371   QVariantMap params;
6372   params.insert( "non_optional",  v1->id() );
6373   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() );
6374 
6375   // using existing layer
6376   params.insert( "non_optional",  QVariant::fromValue( v1 ) );
6377   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() );
6378 
6379   // not vector layer
6380   params.insert( "non_optional",  r1->id() );
6381   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6382 
6383   // using existing non-vector layer
6384   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
6385   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6386 
6387   // string representing a layer source
6388   params.insert( "non_optional", vector1 );
6389   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->publicSource(), vector1 );
6390 
6391   // nonsense string
6392   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
6393   QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) );
6394 
6395   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6396   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
6397   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) );
6398   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( "abc'def" ) ), context ), QStringLiteral( "\"abc'def\"" ) );
6399   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( v2->id() ) ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) );
6400   QCOMPARE( def->valueAsPythonString( v2->id(), context ), QStringLiteral( "'%1'" ).arg( vector2 ) );
6401   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), true ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', selectedFeaturesOnly=True, featureLimit=-1, geometryCheck=QgsFeatureRequest.GeometryAbortOnInvalid)" ) );
6402   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "dbname='mydb' host=localhost port=5432 sslmode=disable key='id'" ), true ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(\"dbname='mydb' host=localhost port=5432 sslmode=disable key='id'\", selectedFeaturesOnly=True, featureLimit=-1, geometryCheck=QgsFeatureRequest.GeometryAbortOnInvalid)" ) );
6403   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
6404   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || 'def'" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \\'def\\'')" ) );
6405   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), true ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'), selectedFeaturesOnly=True, featureLimit=-1, geometryCheck=QgsFeatureRequest.GeometryAbortOnInvalid)" ) );
6406   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), false, 11 ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', selectedFeaturesOnly=False, featureLimit=11, geometryCheck=QgsFeatureRequest.GeometryAbortOnInvalid)" ) );
6407   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), false, 11 ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'), selectedFeaturesOnly=False, featureLimit=11, geometryCheck=QgsFeatureRequest.GeometryAbortOnInvalid)" ) );
6408   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), false, -1, QgsProcessingFeatureSourceDefinition::Flags(), QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "'abc'" ) );
6409   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), false, -1, QgsProcessingFeatureSourceDefinition::Flags(), QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
6410   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagOverrideDefaultGeometryCheck, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6411   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'), selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagOverrideDefaultGeometryCheck, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6412   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6413   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'), selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6414   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck | QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagOverrideDefaultGeometryCheck | QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6415   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ), false, -1, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck | QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature, QgsFeatureRequest::GeometrySkipInvalid ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'), selectedFeaturesOnly=False, featureLimit=-1, flags=QgsProcessingFeatureSourceDefinition.FlagOverrideDefaultGeometryCheck | QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature, geometryCheck=QgsFeatureRequest.GeometrySkipInvalid)" ) );
6416   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6417   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"='my val'" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=\\'my val\\'')" ) );
6418   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v2 ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) );
6419   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
6420   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
6421   QCOMPARE( def->valueAsPythonString( QStringLiteral( "postgres://uri='complex' username=\"complex\"" ), context ), QStringLiteral( "'postgres://uri=\\'complex\\' username=\"complex\"'" ) );
6422 
6423   const QVariantMap map = def->toVariantMap();
6424   QgsProcessingParameterFeatureSource fromMap( "x" );
6425   QVERIFY( fromMap.fromVariantMap( map ) );
6426   QCOMPARE( fromMap.name(), def->name() );
6427   QCOMPARE( fromMap.description(), def->description() );
6428   QCOMPARE( fromMap.flags(), def->flags() );
6429   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6430   QCOMPARE( fromMap.dataTypes(), def->dataTypes() );
6431   def.reset( dynamic_cast< QgsProcessingParameterFeatureSource *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6432   QVERIFY( dynamic_cast< QgsProcessingParameterFeatureSource *>( def.get() ) );
6433 
6434   QString pythonCode = def->asPythonString();
6435   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorAnyGeometry], defaultValue='')" ) );
6436 
6437   QString code = def->asScriptCode();
6438   QCOMPARE( code, QStringLiteral( "##non_optional=source" ) );
6439   std::unique_ptr< QgsProcessingParameterFeatureSource > fromCode( dynamic_cast< QgsProcessingParameterFeatureSource * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6440   QVERIFY( fromCode.get() );
6441   QCOMPARE( fromCode->name(), def->name() );
6442   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6443   QCOMPARE( fromCode->flags(), def->flags() );
6444   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6445 
6446   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint );
6447   pythonCode = def->asPythonString();
6448   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorPoint], defaultValue='')" ) );
6449   code = def->asScriptCode();
6450   QCOMPARE( code, QStringLiteral( "##non_optional=source point" ) );
6451   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorLine );
6452   pythonCode = def->asPythonString();
6453   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorLine], defaultValue='')" ) );
6454   code = def->asScriptCode();
6455   QCOMPARE( code, QStringLiteral( "##non_optional=source line" ) );
6456   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPolygon );
6457   pythonCode = def->asPythonString();
6458   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorPolygon], defaultValue='')" ) );
6459   code = def->asScriptCode();
6460   QCOMPARE( code, QStringLiteral( "##non_optional=source polygon" ) );
6461   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
6462   pythonCode = def->asPythonString();
6463   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorPoint,QgsProcessing.TypeVectorLine], defaultValue='')" ) );
6464   code = def->asScriptCode();
6465   QCOMPARE( code, QStringLiteral( "##non_optional=source point line" ) );
6466   def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorPolygon );
6467   pythonCode = def->asPythonString();
6468   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSource('non_optional', '', types=[QgsProcessing.TypeVectorPoint,QgsProcessing.TypeVectorPolygon], defaultValue='')" ) );
6469   code = def->asScriptCode();
6470   QCOMPARE( code, QStringLiteral( "##non_optional=source point polygon" ) );
6471 
6472 
6473   // optional
6474   def.reset( new QgsProcessingParameterFeatureSource( "optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, v1->id(), true ) );
6475   params.insert( "optional",  QVariant() );
6476   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params,  context )->id(), v1->id() );
6477   QVERIFY( def->checkValueIsAcceptable( false ) );
6478   QVERIFY( def->checkValueIsAcceptable( true ) );
6479   QVERIFY( def->checkValueIsAcceptable( 5 ) );
6480   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6481   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6482   QVERIFY( def->checkValueIsAcceptable( "" ) );
6483   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6484   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
6485 
6486   pythonCode = def->asPythonString();
6487   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterFeatureSource('optional', '', optional=True, types=[QgsProcessing.TypeVectorAnyGeometry], defaultValue='" ) + v1->id() + "')" ) );
6488   code = def->asScriptCode();
6489   QCOMPARE( code, QString( QStringLiteral( "##optional=optional source " ) + v1->id() ) );
6490   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSource * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6491   QVERIFY( fromCode.get() );
6492   QCOMPARE( fromCode->name(), def->name() );
6493   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6494   QCOMPARE( fromCode->flags(), def->flags() );
6495   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6496 
6497 
6498   //optional with direct layer default
6499   def.reset( new QgsProcessingParameterFeatureSource( "optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, QVariant::fromValue( v1 ), true ) );
6500   QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params,  context )->id(), v1->id() );
6501 
6502   // invalidSourceError
6503   params.clear();
6504   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "MISSING" ) ), QStringLiteral( "Could not load source layer for MISSING: no value specified for parameter" ) );
6505   params.insert( QStringLiteral( "INPUT" ), QStringLiteral( "my layer" ) );
6506   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my layer not found" ) );
6507   params.insert( QStringLiteral( "INPUT" ), QgsProperty::fromValue( "my prop layer" ) );
6508   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my prop layer not found" ) );
6509   params.insert( QStringLiteral( "INPUT" ), QgsProcessingFeatureSourceDefinition( QStringLiteral( "my prop layer" ) ) );
6510   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my prop layer not found" ) );
6511   params.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( v1 ) );
6512   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: invalid value" ) );
6513   params.insert( QStringLiteral( "INPUT" ), QgsProcessingOutputLayerDefinition( QStringLiteral( "my prop layer" ) ) );
6514   QCOMPARE( QgsProcessingAlgorithm::invalidSourceError( params, QStringLiteral( "INPUT" ) ), QStringLiteral( "Could not load source layer for INPUT: my prop layer not found" ) );
6515 }
6516 
parameterFeatureSink()6517 void TestQgsProcessing::parameterFeatureSink()
6518 {
6519   // setup a context
6520   QgsProject p;
6521   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6522   QgsProcessingContext context;
6523   context.setProject( &p );
6524 
6525   // not optional!
6526   std::unique_ptr< QgsProcessingParameterFeatureSink > def( new QgsProcessingParameterFeatureSink( "non_optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
6527   QVERIFY( !def->checkValueIsAcceptable( false ) );
6528   QVERIFY( !def->checkValueIsAcceptable( true ) );
6529   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6530   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6531   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6532   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6533   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6534   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "" ) ) );
6535   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
6536   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6537 
6538   // should be OK with or without context - it's an output layer!
6539   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6540   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
6541 
6542   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6543   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
6544   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) );
6545   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) );
6546   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
6547   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6548   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
6549   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
6550 
6551   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "gpkg" ) );
6552   QCOMPARE( def->generateTemporaryDestination(), QStringLiteral( "memory:" ) );
6553   def->setSupportsNonFileBasedOutput( false );
6554   QVERIFY( def->generateTemporaryDestination().endsWith( QLatin1String( ".gpkg" ) ) );
6555   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
6556 
6557   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6558   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6559   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6560   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6561 
6562   const QVariantMap map = def->toVariantMap();
6563   QgsProcessingParameterFeatureSink fromMap( "x" );
6564   QVERIFY( fromMap.fromVariantMap( map ) );
6565   QCOMPARE( fromMap.name(), def->name() );
6566   QCOMPARE( fromMap.description(), def->description() );
6567   QCOMPARE( fromMap.flags(), def->flags() );
6568   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6569   QCOMPARE( fromMap.dataType(), def->dataType() );
6570   QCOMPARE( fromMap.supportsNonFileBasedOutput(), def->supportsNonFileBasedOutput() );
6571   def.reset( dynamic_cast< QgsProcessingParameterFeatureSink *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6572   QVERIFY( dynamic_cast< QgsProcessingParameterFeatureSink *>( def.get() ) );
6573 
6574   QString pythonCode = def->asPythonString();
6575   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('non_optional', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue='')" ) );
6576 
6577   QString code = def->asScriptCode();
6578   QCOMPARE( code, QStringLiteral( "##non_optional=sink" ) );
6579   std::unique_ptr< QgsProcessingParameterFeatureSink > fromCode( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6580   QVERIFY( fromCode.get() );
6581   QCOMPARE( fromCode->name(), def->name() );
6582   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6583   QCOMPARE( fromCode->flags(), def->flags() );
6584   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6585   QCOMPARE( fromCode->dataType(), def->dataType() );
6586 
6587   def->setDataType( QgsProcessing::TypeVectorPoint );
6588   pythonCode = def->asPythonString();
6589   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('non_optional', '', type=QgsProcessing.TypeVectorPoint, createByDefault=True, defaultValue='')" ) );
6590   code = def->asScriptCode();
6591   QCOMPARE( code, QStringLiteral( "##non_optional=sink point" ) );
6592   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6593   QCOMPARE( fromCode->dataType(), def->dataType() );
6594   def->setDataType( QgsProcessing::TypeVectorLine );
6595   pythonCode = def->asPythonString();
6596   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('non_optional', '', type=QgsProcessing.TypeVectorLine, createByDefault=True, defaultValue='')" ) );
6597   code = def->asScriptCode();
6598   QCOMPARE( code, QStringLiteral( "##non_optional=sink line" ) );
6599   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6600   QCOMPARE( fromCode->dataType(), def->dataType() );
6601   def->setDataType( QgsProcessing::TypeVectorPolygon );
6602   pythonCode = def->asPythonString();
6603   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('non_optional', '', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, defaultValue='')" ) );
6604   code = def->asScriptCode();
6605   QCOMPARE( code, QStringLiteral( "##non_optional=sink polygon" ) );
6606   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6607   QCOMPARE( fromCode->dataType(), def->dataType() );
6608   def->setDataType( QgsProcessing::TypeVector );
6609   pythonCode = def->asPythonString();
6610   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('non_optional', '', type=QgsProcessing.TypeVector, createByDefault=True, defaultValue='')" ) );
6611   code = def->asScriptCode();
6612   QCOMPARE( code, QStringLiteral( "##non_optional=sink table" ) );
6613   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6614   QCOMPARE( fromCode->dataType(), def->dataType() );
6615 
6616   // optional
6617   def.reset( new QgsProcessingParameterFeatureSink( "optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
6618   QVERIFY( !def->checkValueIsAcceptable( false ) );
6619   QVERIFY( !def->checkValueIsAcceptable( true ) );
6620   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6621   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6622   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6623   QVERIFY( def->checkValueIsAcceptable( "" ) );
6624   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6625   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6626   def->setCreateByDefault( false );
6627   QVERIFY( !def->createByDefault() );
6628 
6629   pythonCode = def->asPythonString();
6630   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('optional', '', optional=True, type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=False, defaultValue='')" ) );
6631   code = def->asScriptCode();
6632   QCOMPARE( code, QStringLiteral( "##optional=optional sink" ) );
6633   fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6634   QCOMPARE( fromCode->name(), def->name() );
6635   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6636   QCOMPARE( fromCode->flags(), def->flags() );
6637   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6638   QCOMPARE( fromCode->dataType(), def->dataType() );
6639   QVERIFY( !def->createByDefault() );
6640 
6641   // test hasGeometry
6642   QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeMapLayer ).hasGeometry() );
6643   QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorAnyGeometry ).hasGeometry() );
6644   QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorPoint ).hasGeometry() );
6645   QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorLine ).hasGeometry() );
6646   QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorPolygon ).hasGeometry() );
6647   QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeRaster ).hasGeometry() );
6648   QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeFile ).hasGeometry() );
6649   QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVector ).hasGeometry() );
6650 
6651   // invalidSinkError
6652   QVariantMap params;
6653   QCOMPARE( QgsProcessingAlgorithm::invalidSinkError( params, QStringLiteral( "MISSING" ) ), QStringLiteral( "Could not create destination layer for MISSING: no value specified for parameter" ) );
6654   params.insert( QStringLiteral( "OUTPUT" ), QStringLiteral( "d:/test.shp" ) );
6655   QCOMPARE( QgsProcessingAlgorithm::invalidSinkError( params, QStringLiteral( "OUTPUT" ) ), QStringLiteral( "Could not create destination layer for OUTPUT: d:/test.shp" ) );
6656   params.insert( QStringLiteral( "OUTPUT" ), QgsProperty::fromValue( QStringLiteral( "d:/test2.shp" ) ) );
6657   QCOMPARE( QgsProcessingAlgorithm::invalidSinkError( params, QStringLiteral( "OUTPUT" ) ), QStringLiteral( "Could not create destination layer for OUTPUT: d:/test2.shp" ) );
6658   params.insert( QStringLiteral( "OUTPUT" ), QgsProcessingOutputLayerDefinition( QStringLiteral( "d:/test3.shp" ) ) );
6659   QCOMPARE( QgsProcessingAlgorithm::invalidSinkError( params, QStringLiteral( "OUTPUT" ) ), QStringLiteral( "Could not create destination layer for OUTPUT: d:/test3.shp" ) );
6660   params.insert( QStringLiteral( "OUTPUT" ), QgsProcessingFeatureSourceDefinition( QStringLiteral( "source" ) ) );
6661   QCOMPARE( QgsProcessingAlgorithm::invalidSinkError( params, QStringLiteral( "OUTPUT" ) ), QStringLiteral( "Could not create destination layer for OUTPUT: invalid value" ) );
6662 
6663   // test supported output vector layer extensions
6664 
6665   def.reset( new QgsProcessingParameterFeatureSink( "with_geom", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
6666   DummyProvider3 provider;
6667   provider.loadAlgorithms();
6668   def->mOriginalProvider = &provider;
6669   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 2 );
6670   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "mif" ) );
6671   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 1 ), QStringLiteral( "tab" ) );
6672   def->mOriginalProvider = nullptr;
6673   def->mAlgorithm = const_cast< QgsProcessingAlgorithm * >( provider.algorithms().at( 0 ) );
6674   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 2 );
6675   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "mif" ) );
6676   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 1 ), QStringLiteral( "tab" ) );
6677 
6678   def.reset( new QgsProcessingParameterFeatureSink( "no_geom", QString(), QgsProcessing::TypeVector, QString(), true ) );
6679   def->mOriginalProvider = &provider;
6680   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 1 );
6681   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "dbf" ) );
6682   def->mOriginalProvider = nullptr;
6683   def->mAlgorithm = const_cast< QgsProcessingAlgorithm * >( provider.algorithms().at( 0 ) );
6684   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 1 );
6685   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "dbf" ) );
6686 }
6687 
parameterVectorOut()6688 void TestQgsProcessing::parameterVectorOut()
6689 {
6690   // setup a context
6691   QgsProject p;
6692   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6693   QgsProcessingContext context;
6694   context.setProject( &p );
6695 
6696   // not optional!
6697   std::unique_ptr< QgsProcessingParameterVectorDestination > def( new QgsProcessingParameterVectorDestination( "non_optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
6698   QVERIFY( !def->checkValueIsAcceptable( false ) );
6699   QVERIFY( !def->checkValueIsAcceptable( true ) );
6700   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6701   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6702   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6703   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6704   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6705   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "" ) ) );
6706   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer1231123" ) ) ) );
6707   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6708 
6709   // should be OK with or without context - it's an output layer!
6710   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6711   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
6712 
6713   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6714   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
6715   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) );
6716   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) );
6717   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
6718   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6719   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
6720   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
6721 
6722   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "gpkg" ) );
6723   QVERIFY( def->generateTemporaryDestination().endsWith( QLatin1String( ".gpkg" ) ) );
6724   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
6725 
6726   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6727   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6728   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6729   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6730 
6731   const QVariantMap map = def->toVariantMap();
6732   QgsProcessingParameterVectorDestination fromMap( "x" );
6733   QVERIFY( fromMap.fromVariantMap( map ) );
6734   QCOMPARE( fromMap.name(), def->name() );
6735   QCOMPARE( fromMap.description(), def->description() );
6736   QCOMPARE( fromMap.flags(), def->flags() );
6737   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6738   QCOMPARE( fromMap.dataType(), def->dataType() );
6739   def.reset( dynamic_cast< QgsProcessingParameterVectorDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6740   QVERIFY( dynamic_cast< QgsProcessingParameterVectorDestination *>( def.get() ) );
6741 
6742   QString pythonCode = def->asPythonString();
6743   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorDestination('non_optional', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue='')" ) );
6744   QString code = def->asScriptCode();
6745   QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination" ) );
6746   std::unique_ptr< QgsProcessingParameterVectorDestination > fromCode( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6747   QVERIFY( fromCode.get() );
6748   QCOMPARE( fromCode->name(), def->name() );
6749   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6750   QCOMPARE( fromCode->flags(), def->flags() );
6751   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6752   QCOMPARE( fromCode->dataType(), def->dataType() );
6753 
6754   def->setDataType( QgsProcessing::TypeVectorPoint );
6755   pythonCode = def->asPythonString();
6756   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorDestination('non_optional', '', type=QgsProcessing.TypeVectorPoint, createByDefault=True, defaultValue='')" ) );
6757   code = def->asScriptCode();
6758   QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination point" ) );
6759   fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6760   QCOMPARE( fromCode->dataType(), def->dataType() );
6761   def->setDataType( QgsProcessing::TypeVectorLine );
6762   pythonCode = def->asPythonString();
6763   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorDestination('non_optional', '', type=QgsProcessing.TypeVectorLine, createByDefault=True, defaultValue='')" ) );
6764   code = def->asScriptCode();
6765   QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination line" ) );
6766   fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6767   QCOMPARE( fromCode->dataType(), def->dataType() );
6768   def->setDataType( QgsProcessing::TypeVectorPolygon );
6769   pythonCode = def->asPythonString();
6770   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorDestination('non_optional', '', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, defaultValue='')" ) );
6771   code = def->asScriptCode();
6772   QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination polygon" ) );
6773   fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6774   QCOMPARE( fromCode->dataType(), def->dataType() );
6775 
6776   // optional
6777   def.reset( new QgsProcessingParameterVectorDestination( "optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
6778   QVERIFY( !def->checkValueIsAcceptable( false ) );
6779   QVERIFY( !def->checkValueIsAcceptable( true ) );
6780   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6781   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6782   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
6783   QVERIFY( def->checkValueIsAcceptable( "" ) );
6784   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6785   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6786 
6787   pythonCode = def->asPythonString();
6788   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterVectorDestination('optional', '', optional=True, type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue='')" ) );
6789   code = def->asScriptCode();
6790   QCOMPARE( code, QStringLiteral( "##optional=optional vectorDestination" ) );
6791   fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6792   QCOMPARE( fromCode->name(), def->name() );
6793   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6794   QCOMPARE( fromCode->flags(), def->flags() );
6795   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6796   QCOMPARE( fromCode->dataType(), def->dataType() );
6797 
6798   // test hasGeometry
6799   QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeMapLayer ).hasGeometry() );
6800   QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorAnyGeometry ).hasGeometry() );
6801   QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorPoint ).hasGeometry() );
6802   QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorLine ).hasGeometry() );
6803   QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorPolygon ).hasGeometry() );
6804   QVERIFY( !QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeRaster ).hasGeometry() );
6805   QVERIFY( !QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeFile ).hasGeometry() );
6806   QVERIFY( !QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVector ).hasGeometry() );
6807 
6808   // test layers to load on completion
6809   def.reset( new QgsProcessingParameterVectorDestination( "x", QStringLiteral( "desc" ), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
6810   QgsProcessingOutputLayerDefinition fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.shp" ) );
6811   fs.destinationProject = &p;
6812   QVariantMap params;
6813   params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) );
6814   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.shp" ) );
6815 
6816   // make sure layer was automatically added to list to load on completion
6817   QCOMPARE( context.layersToLoadOnCompletion().size(), 1 );
6818   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.shp" ) );
6819   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "desc" ) );
6820   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).layerTypeHint, QgsProcessingUtils::LayerHint::Vector );
6821 
6822   // with name overloading
6823   QgsProcessingContext context2;
6824   fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.shp" ) );
6825   fs.destinationProject = &p;
6826   fs.destinationName = QStringLiteral( "my_dest" );
6827   params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) );
6828   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context2 ), QStringLiteral( "test.shp" ) );
6829   QCOMPARE( context2.layersToLoadOnCompletion().size(), 1 );
6830   QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.shp" ) );
6831   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "my_dest" ) );
6832   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).outputName, QStringLiteral( "x" ) );
6833   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).layerTypeHint, QgsProcessingUtils::LayerHint::Vector );
6834 
6835   QgsProcessingContext context3;
6836   params.insert( QStringLiteral( "x" ), QgsProcessing::TEMPORARY_OUTPUT );
6837   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context3 ).right( 6 ), QStringLiteral( "x.gpkg" ) );
6838 
6839   QgsProcessingContext context4;
6840   fs.sink = QgsProperty::fromValue( QgsProcessing::TEMPORARY_OUTPUT );
6841   params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) );
6842   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context4 ).right( 6 ), QStringLiteral( "x.gpkg" ) );
6843 
6844   // test supported output vector layer extensions
6845 
6846   def.reset( new QgsProcessingParameterVectorDestination( "with_geom", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
6847   DummyProvider3 provider;
6848   QString error;
6849   QVERIFY( provider.isSupportedOutputValue( QVariant(), def.get(), context, error ) ); // optional
6850   QVERIFY( provider.isSupportedOutputValue( QString(), def.get(), context, error ) ); // optional
6851   QVERIFY( !provider.isSupportedOutputValue( "d:/test.shp", def.get(), context, error ) );
6852   QVERIFY( !provider.isSupportedOutputValue( "d:/test.SHP", def.get(), context, error ) );
6853   QVERIFY( !provider.isSupportedOutputValue( "ogr:d:/test.shp", def.get(), context, error ) );
6854   QVERIFY( !provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.SHP" ), def.get(), context, error ) );
6855   QVERIFY( provider.isSupportedOutputValue( "d:/test.mif", def.get(), context, error ) );
6856   QVERIFY( provider.isSupportedOutputValue( "d:/test.MIF", def.get(), context, error ) );
6857   QVERIFY( provider.isSupportedOutputValue( "ogr:d:/test.MIF", def.get(), context, error ) );
6858   QVERIFY( provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.MIF" ), def.get(), context, error ) );
6859   def.reset( new QgsProcessingParameterVectorDestination( "with_geom", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) );
6860   QVERIFY( !provider.isSupportedOutputValue( QVariant(), def.get(), context, error ) ); // non-optional
6861   QVERIFY( !provider.isSupportedOutputValue( QString(), def.get(), context, error ) ); // non-optional
6862 
6863   provider.loadAlgorithms();
6864   def->mOriginalProvider = &provider;
6865   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 2 );
6866   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "mif" ) );
6867   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 1 ), QStringLiteral( "tab" ) );
6868   def->mOriginalProvider = nullptr;
6869   def->mAlgorithm = const_cast< QgsProcessingAlgorithm * >( provider.algorithms().at( 0 ) );
6870   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 2 );
6871   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "mif" ) );
6872   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 1 ), QStringLiteral( "tab" ) );
6873 
6874   def.reset( new QgsProcessingParameterVectorDestination( "no_geom", QString(), QgsProcessing::TypeVector, QString(), true ) );
6875   def->mOriginalProvider = &provider;
6876   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 1 );
6877   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "dbf" ) );
6878   def->mOriginalProvider = nullptr;
6879   def->mAlgorithm = const_cast< QgsProcessingAlgorithm * >( provider.algorithms().at( 0 ) );
6880   QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 1 );
6881   QCOMPARE( def->supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "dbf" ) );
6882 }
6883 
parameterRasterOut()6884 void TestQgsProcessing::parameterRasterOut()
6885 {
6886   // setup a context
6887   QgsProject p;
6888   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
6889   QgsProcessingContext context;
6890   context.setProject( &p );
6891 
6892   // not optional!
6893   std::unique_ptr< QgsProcessingParameterRasterDestination > def( new QgsProcessingParameterRasterDestination( "non_optional", QString(), QVariant(), false ) );
6894   QVERIFY( !def->checkValueIsAcceptable( false ) );
6895   QVERIFY( !def->checkValueIsAcceptable( true ) );
6896   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6897   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6898   QVERIFY( !def->checkValueIsAcceptable( "" ) );
6899   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
6900   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6901   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "" ) ) );
6902   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
6903   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
6904 
6905   // should be OK with or without context - it's an output layer!
6906   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) );
6907   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif", &context ) );
6908 
6909   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
6910   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "tif" ) );
6911   QVERIFY( def->generateTemporaryDestination().endsWith( QLatin1String( ".tif" ) ) );
6912   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
6913 
6914   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
6915   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
6916   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.2dm" ) ) );
6917   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
6918 
6919   QVariantMap params;
6920   params.insert( "non_optional", "test.tif" );
6921   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) );
6922   params.insert( "non_optional", QgsProcessingOutputLayerDefinition( "test.tif" ) );
6923   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) );
6924 
6925   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
6926   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) );
6927   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) );
6928   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
6929   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
6930   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
6931   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
6932 
6933   const QVariantMap map = def->toVariantMap();
6934   QgsProcessingParameterRasterDestination fromMap( "x" );
6935   QVERIFY( fromMap.fromVariantMap( map ) );
6936   QCOMPARE( fromMap.name(), def->name() );
6937   QCOMPARE( fromMap.description(), def->description() );
6938   QCOMPARE( fromMap.flags(), def->flags() );
6939   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
6940   QCOMPARE( fromMap.supportsNonFileBasedOutput(), def->supportsNonFileBasedOutput() );
6941   def.reset( dynamic_cast< QgsProcessingParameterRasterDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
6942   QVERIFY( dynamic_cast< QgsProcessingParameterRasterDestination *>( def.get() ) );
6943 
6944   QString pythonCode = def->asPythonString();
6945   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRasterDestination('non_optional', '', createByDefault=True, defaultValue=None)" ) );
6946   QString code = def->asScriptCode();
6947   QCOMPARE( code, QStringLiteral( "##non_optional=rasterDestination" ) );
6948   std::unique_ptr< QgsProcessingParameterRasterDestination > fromCode( dynamic_cast< QgsProcessingParameterRasterDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6949   QVERIFY( fromCode.get() );
6950   QCOMPARE( fromCode->name(), def->name() );
6951   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
6952   QCOMPARE( fromCode->flags(), def->flags() );
6953   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6954 
6955   // optional
6956   def.reset( new QgsProcessingParameterRasterDestination( "optional", QString(), QString( "default.tif" ), true ) );
6957   QVERIFY( !def->checkValueIsAcceptable( false ) );
6958   QVERIFY( !def->checkValueIsAcceptable( true ) );
6959   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
6960   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
6961   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) );
6962   QVERIFY( def->checkValueIsAcceptable( "" ) );
6963   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
6964   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
6965 
6966   params.insert( "optional", QVariant() );
6967   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "default.tif" ) );
6968 
6969   pythonCode = def->asPythonString();
6970   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterRasterDestination('optional', '', optional=True, createByDefault=True, defaultValue='default.tif')" ) );
6971   code = def->asScriptCode();
6972   QCOMPARE( code, QStringLiteral( "##optional=optional rasterDestination default.tif" ) );
6973   fromCode.reset( dynamic_cast< QgsProcessingParameterRasterDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
6974   QVERIFY( fromCode.get() );
6975   QCOMPARE( fromCode->name(), def->name() );
6976   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
6977   QCOMPARE( fromCode->flags(), def->flags() );
6978   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
6979 
6980   const DummyProvider3 provider;
6981   QString error;
6982   QVERIFY( !provider.isSupportedOutputValue( QVariant(), def.get(), context, error ) );
6983   QVERIFY( !provider.isSupportedOutputValue( QString(), def.get(), context, error ) );
6984   QVERIFY( !provider.isSupportedOutputValue( "d:/test.tif", def.get(), context, error ) );
6985   QVERIFY( !provider.isSupportedOutputValue( "d:/test.TIF", def.get(), context, error ) );
6986   QVERIFY( !provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.tif" ), def.get(), context, error ) );
6987   QVERIFY( provider.isSupportedOutputValue( "d:/test.mig", def.get(), context, error ) );
6988   QVERIFY( provider.isSupportedOutputValue( "d:/test.MIG", def.get(), context, error ) );
6989   QVERIFY( provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.MIG" ), def.get(), context, error ) );
6990 
6991   // test layers to load on completion
6992   def.reset( new QgsProcessingParameterRasterDestination( "x", QStringLiteral( "desc" ), QStringLiteral( "default.tif" ), true ) );
6993   QgsProcessingOutputLayerDefinition fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.tif" ) );
6994   fs.destinationProject = &p;
6995   params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) );
6996   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) );
6997 
6998   // make sure layer was automatically added to list to load on completion
6999   QCOMPARE( context.layersToLoadOnCompletion().size(), 1 );
7000   QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.tif" ) );
7001   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "desc" ) );
7002   QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).layerTypeHint, QgsProcessingUtils::LayerHint::Raster );
7003 
7004   // with name overloading
7005   QgsProcessingContext context2;
7006   fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.tif" ) );
7007   fs.destinationProject = &p;
7008   fs.destinationName = QStringLiteral( "my_dest" );
7009   params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) );
7010   QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context2 ), QStringLiteral( "test.tif" ) );
7011   QCOMPARE( context2.layersToLoadOnCompletion().size(), 1 );
7012   QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.tif" ) );
7013   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "my_dest" ) );
7014   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).outputName, QStringLiteral( "x" ) );
7015   QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).layerTypeHint, QgsProcessingUtils::LayerHint::Raster );
7016 }
7017 
parameterFileOut()7018 void TestQgsProcessing::parameterFileOut()
7019 {
7020   // setup a context
7021   QgsProject p;
7022   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
7023   QgsProcessingContext context;
7024   context.setProject( &p );
7025 
7026   // not optional!
7027   std::unique_ptr< QgsProcessingParameterFileDestination > def( new QgsProcessingParameterFileDestination( "non_optional", QString(), QStringLiteral( "BMP files (*.bmp)" ), QVariant(), false ) );
7028   QCOMPARE( def->fileFilter(), QStringLiteral( "BMP files (*.bmp)" ) );
7029   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "bmp" ) );
7030   QVERIFY( def->generateTemporaryDestination().endsWith( QLatin1String( ".bmp" ) ) );
7031   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
7032   def->setFileFilter( QStringLiteral( "PCX files (*.pcx)" ) );
7033   QCOMPARE( def->fileFilter(), QStringLiteral( "PCX files (*.pcx)" ) );
7034   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) );
7035   def->setFileFilter( QStringLiteral( "PCX files (*.pcx *.picx)" ) );
7036   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) );
7037   def->setFileFilter( QStringLiteral( "PCX files (*.pcx *.picx);;BMP files (*.bmp)" ) );
7038   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) );
7039   QCOMPARE( def->createFileFilter(), QStringLiteral( "PCX files (*.pcx *.picx);;BMP files (*.bmp);;All files (*.*)" ) );
7040 
7041   def->setFileFilter( QString() );
7042   QCOMPARE( def->defaultFileExtension(), QStringLiteral( "file" ) );
7043   QVERIFY( def->generateTemporaryDestination().endsWith( QLatin1String( ".file" ) ) );
7044   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
7045 
7046   QVERIFY( !def->checkValueIsAcceptable( false ) );
7047   QVERIFY( !def->checkValueIsAcceptable( true ) );
7048   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
7049   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
7050   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7051   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7052   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
7053   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "" ) ) );
7054   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
7055   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
7056 
7057   // should be OK with or without context - it's an output file!
7058   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt" ) );
7059   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt", &context ) );
7060 
7061   QCOMPARE( def->createFileFilter(), QStringLiteral( "All files (*.*)" ) );
7062 
7063   QVariantMap params;
7064   params.insert( "non_optional", "test.txt" );
7065   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "test.txt" ) );
7066   params.insert( "non_optional", QgsProcessingOutputLayerDefinition( "test.txt" ) );
7067   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "test.txt" ) );
7068 
7069   params.insert( QStringLiteral( "non_optional" ), QgsProcessing::TEMPORARY_OUTPUT );
7070   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ).right( 18 ), QStringLiteral( "/non_optional.file" ) );
7071 
7072   QgsProcessingOutputLayerDefinition fs;
7073   fs.sink = QgsProperty::fromValue( QgsProcessing::TEMPORARY_OUTPUT );
7074   params.insert( QStringLiteral( "non_optional" ), QVariant::fromValue( fs ) );
7075   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ).right( 18 ), QStringLiteral( "/non_optional.file" ) );
7076 
7077   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7078   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7079   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) );
7080   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) );
7081   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) );
7082   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
7083   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
7084   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
7085 
7086   const QVariantMap map = def->toVariantMap();
7087   QgsProcessingParameterFileDestination fromMap( "x" );
7088   QVERIFY( fromMap.fromVariantMap( map ) );
7089   QCOMPARE( fromMap.name(), def->name() );
7090   QCOMPARE( fromMap.description(), def->description() );
7091   QCOMPARE( fromMap.flags(), def->flags() );
7092   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7093   QCOMPARE( fromMap.fileFilter(), def->fileFilter() );
7094   QCOMPARE( fromMap.supportsNonFileBasedOutput(), def->supportsNonFileBasedOutput() );
7095   def.reset( dynamic_cast< QgsProcessingParameterFileDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7096   QVERIFY( dynamic_cast< QgsProcessingParameterFileDestination *>( def.get() ) );
7097 
7098   QString pythonCode = def->asPythonString();
7099   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFileDestination('non_optional', '', fileFilter='', createByDefault=True, defaultValue=None)" ) );
7100   QString code = def->asScriptCode();
7101   QCOMPARE( code, QStringLiteral( "##non_optional=fileDestination" ) );
7102   std::unique_ptr< QgsProcessingParameterFileDestination > fromCode( dynamic_cast< QgsProcessingParameterFileDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7103   QVERIFY( fromCode.get() );
7104   QCOMPARE( fromCode->name(), def->name() );
7105   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7106   QCOMPARE( fromCode->flags(), def->flags() );
7107   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7108 
7109   // optional
7110   def.reset( new QgsProcessingParameterFileDestination( "optional", QString(), QString(), QString( "default.txt" ), true ) );
7111   QVERIFY( !def->checkValueIsAcceptable( false ) );
7112   QVERIFY( !def->checkValueIsAcceptable( true ) );
7113   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
7114   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
7115   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt" ) );
7116   QVERIFY( def->checkValueIsAcceptable( "" ) );
7117   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7118   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) );
7119 
7120   params.insert( "optional", QVariant() );
7121   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "default.txt" ) );
7122 
7123   pythonCode = def->asPythonString();
7124   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFileDestination('optional', '', optional=True, fileFilter='All files (*.*)', createByDefault=True, defaultValue='default.txt')" ) );
7125   code = def->asScriptCode();
7126   QCOMPARE( code, QStringLiteral( "##optional=optional fileDestination default.txt" ) );
7127   fromCode.reset( dynamic_cast< QgsProcessingParameterFileDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7128   QVERIFY( fromCode.get() );
7129   QCOMPARE( fromCode->name(), def->name() );
7130   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7131   QCOMPARE( fromCode->flags(), def->flags() );
7132   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7133 
7134   // outputs definition test
7135   def.reset( new QgsProcessingParameterFileDestination( "html", QString(), QString( "HTML files" ), QString(), false ) );
7136   std::unique_ptr< QgsProcessingOutputDefinition > outputDef( def->toOutputDefinition() );
7137   QVERIFY( dynamic_cast< QgsProcessingOutputHtml *>( outputDef.get() ) );
7138   def.reset( new QgsProcessingParameterFileDestination( "html", QString(), QString( "Text files (*.htm)" ), QString(), false ) );
7139   outputDef.reset( def->toOutputDefinition() );
7140   QVERIFY( dynamic_cast< QgsProcessingOutputHtml *>( outputDef.get() ) );
7141   def.reset( new QgsProcessingParameterFileDestination( "file", QString(), QString( "Text files (*.txt)" ), QString(), false ) );
7142   outputDef.reset( def->toOutputDefinition() );
7143   QVERIFY( dynamic_cast< QgsProcessingOutputFile *>( outputDef.get() ) );
7144   def.reset( new QgsProcessingParameterFileDestination( "file", QString(), QString(), QString(), false ) );
7145   outputDef.reset( def->toOutputDefinition() );
7146   QVERIFY( dynamic_cast< QgsProcessingOutputFile *>( outputDef.get() ) );
7147 }
7148 
parameterFolderOut()7149 void TestQgsProcessing::parameterFolderOut()
7150 {
7151   // setup a context
7152   QgsProject p;
7153   QgsProcessingContext context;
7154   context.setProject( &p );
7155 
7156   // not optional!
7157   std::unique_ptr< QgsProcessingParameterFolderDestination > def( new QgsProcessingParameterFolderDestination( "non_optional", QString(), QVariant(), false ) );
7158 
7159   QVERIFY( !def->checkValueIsAcceptable( false ) );
7160   QVERIFY( !def->checkValueIsAcceptable( true ) );
7161   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
7162   QVERIFY( def->checkValueIsAcceptable( "asdasd" ) );
7163   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7164   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7165   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "asdasdas" ) ) ) );
7166   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
7167 
7168   // should be OK with or without context - it's an output folder!
7169   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/" ) );
7170   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/", &context ) );
7171 
7172   // check that temporary destination does not have dot at the end when there is no extension
7173   QVERIFY( !def->generateTemporaryDestination().endsWith( QLatin1Char( '.' ) ) );
7174   QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) );
7175 
7176   QVariantMap params;
7177   params.insert( "non_optional", "c:/mine" );
7178   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "c:/mine" ) );
7179 
7180   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7181   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7182   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
7183   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
7184   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\'" ) );
7185 
7186   const QVariantMap map = def->toVariantMap();
7187   QgsProcessingParameterFolderDestination fromMap( "x" );
7188   QVERIFY( fromMap.fromVariantMap( map ) );
7189   QCOMPARE( fromMap.name(), def->name() );
7190   QCOMPARE( fromMap.description(), def->description() );
7191   QCOMPARE( fromMap.flags(), def->flags() );
7192   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7193   QCOMPARE( fromMap.supportsNonFileBasedOutput(), def->supportsNonFileBasedOutput() );
7194   def.reset( dynamic_cast< QgsProcessingParameterFolderDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7195   QVERIFY( dynamic_cast< QgsProcessingParameterFolderDestination *>( def.get() ) );
7196 
7197   QString pythonCode = def->asPythonString();
7198   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFolderDestination('non_optional', '', createByDefault=True, defaultValue=None)" ) );
7199   QString code = def->asScriptCode();
7200   QCOMPARE( code, QStringLiteral( "##non_optional=folderDestination" ) );
7201   std::unique_ptr< QgsProcessingParameterFolderDestination > fromCode( dynamic_cast< QgsProcessingParameterFolderDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7202   QVERIFY( fromCode.get() );
7203   QCOMPARE( fromCode->name(), def->name() );
7204   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7205   QCOMPARE( fromCode->flags(), def->flags() );
7206   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7207 
7208   // optional
7209   def.reset( new QgsProcessingParameterFolderDestination( "optional", QString(), QString( "c:/junk" ), true ) );
7210   QVERIFY( !def->checkValueIsAcceptable( false ) );
7211   QVERIFY( !def->checkValueIsAcceptable( true ) );
7212   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
7213   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
7214   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/" ) );
7215   QVERIFY( def->checkValueIsAcceptable( "" ) );
7216   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7217 
7218   params.insert( "optional", QVariant() );
7219   QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "c:/junk" ) );
7220 
7221   pythonCode = def->asPythonString();
7222   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFolderDestination('optional', '', optional=True, createByDefault=True, defaultValue='c:/junk')" ) );
7223   code = def->asScriptCode();
7224   QCOMPARE( code, QStringLiteral( "##optional=optional folderDestination c:/junk" ) );
7225   fromCode.reset( dynamic_cast< QgsProcessingParameterFolderDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7226   QVERIFY( fromCode.get() );
7227   QCOMPARE( fromCode->name(), def->name() );
7228   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7229   QCOMPARE( fromCode->flags(), def->flags() );
7230   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7231 
7232   // temporary directory
7233   def.reset( new QgsProcessingParameterFolderDestination( "junkdir", QString(), QgsProcessing::TEMPORARY_OUTPUT ) );
7234   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ).right( 8 ), QStringLiteral( "/junkdir" ) );
7235 }
7236 
parameterBand()7237 void TestQgsProcessing::parameterBand()
7238 {
7239   QgsProcessingContext context;
7240 
7241   // not optional!
7242   std::unique_ptr< QgsProcessingParameterBand > def( new QgsProcessingParameterBand( "non_optional", QString(), QVariant(), QString(), false ) );
7243   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7244   QVERIFY( def->checkValueIsAcceptable( "1" ) );
7245   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7246   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7247 
7248   // string representing a band
7249   QVariantMap params;
7250   params.insert( "non_optional", "1" );
7251   int band = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
7252   QCOMPARE( band, 1 );
7253 
7254   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7255   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
7256 
7257   QString pythonCode = def->asPythonString();
7258   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBand('non_optional', '', parentLayerParameterName='', allowMultiple=False, defaultValue=None)" ) );
7259   QString code = def->asScriptCode();
7260   QCOMPARE( code, QStringLiteral( "##non_optional=band" ) );
7261   std::unique_ptr< QgsProcessingParameterBand > fromCode( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7262   QVERIFY( fromCode.get() );
7263   QCOMPARE( fromCode->name(), def->name() );
7264   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7265   QCOMPARE( fromCode->flags(), def->flags() );
7266   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7267   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
7268 
7269   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
7270   def->setParentLayerParameterName( "my_parent" );
7271   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) );
7272 
7273   pythonCode = def->asPythonString();
7274   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBand('non_optional', '', parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) );
7275   code = def->asScriptCode();
7276   fromCode.reset( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7277   QVERIFY( fromCode.get() );
7278   QCOMPARE( fromCode->name(), def->name() );
7279   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7280   QCOMPARE( fromCode->flags(), def->flags() );
7281   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7282   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
7283 
7284   // multiple
7285   def.reset( new QgsProcessingParameterBand( "non_optional", QString(), QVariant(), QString(), false, true ) );
7286   QVERIFY( def->checkValueIsAcceptable( QStringList() << "1" << "2" ) );
7287   QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 << 2 ) );
7288   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7289   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7290   QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
7291   QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
7292 
7293   params.insert( "non_optional", QString( "1;2" ) );
7294   QList<int> bands = QgsProcessingParameters::parameterAsInts( def.get(), params, context );
7295   QCOMPARE( bands, QList<int>() << 1 << 2 );
7296   params.insert( "non_optional", QVariantList() << 1 << 2 );
7297   bands = QgsProcessingParameters::parameterAsInts( def.get(), params, context );
7298   QCOMPARE( bands, QList<int>() << 1 << 2 );
7299 
7300   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7301   QCOMPARE( def->valueAsPythonString( QStringList() << "1" << "2", context ), QStringLiteral( "[1,2]" ) );
7302   QCOMPARE( def->valueAsPythonString( QVariantList() << 1 << 2, context ), QStringLiteral( "[1,2]" ) );
7303 
7304   const QVariantMap map = def->toVariantMap();
7305   QgsProcessingParameterBand fromMap( "x" );
7306   QVERIFY( fromMap.fromVariantMap( map ) );
7307   QCOMPARE( fromMap.name(), def->name() );
7308   QCOMPARE( fromMap.description(), def->description() );
7309   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7310   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
7311   QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() );
7312   def.reset( dynamic_cast< QgsProcessingParameterBand *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7313   QVERIFY( dynamic_cast< QgsProcessingParameterBand *>( def.get() ) );
7314 
7315   pythonCode = def->asPythonString();
7316   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBand('non_optional', '', parentLayerParameterName='', allowMultiple=True, defaultValue=None)" ) );
7317   code = def->asScriptCode();
7318   fromCode.reset( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7319   QVERIFY( fromCode.get() );
7320   QCOMPARE( fromCode->name(), def->name() );
7321   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7322   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7323   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
7324   QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() );
7325 
7326   // optional
7327   def.reset( new QgsProcessingParameterBand( "optional", QString(), 1, QString(), true ) );
7328   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7329   QVERIFY( def->checkValueIsAcceptable( "1" ) );
7330   QVERIFY( def->checkValueIsAcceptable( "" ) );
7331   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7332 
7333   params.insert( "optional",  QVariant() );
7334   band = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
7335   QCOMPARE( band, 1 );
7336 
7337   // optional, no default
7338   def.reset( new QgsProcessingParameterBand( "optional", QString(), QVariant(), QString(), true ) );
7339   params.insert( "optional",  QVariant() );
7340   band = QgsProcessingParameters::parameterAsInt( def.get(), params, context );
7341   QCOMPARE( band, 0 );
7342 
7343   pythonCode = def->asPythonString();
7344   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterBand('optional', '', optional=True, parentLayerParameterName='', allowMultiple=False, defaultValue=None)" ) );
7345   code = def->asScriptCode();
7346   QCOMPARE( code, QStringLiteral( "##optional=optional band" ) );
7347   fromCode.reset( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7348   QVERIFY( fromCode.get() );
7349   QCOMPARE( fromCode->name(), def->name() );
7350   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7351   QCOMPARE( fromCode->flags(), def->flags() );
7352   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7353   QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() );
7354 }
7355 
parameterLayout()7356 void TestQgsProcessing::parameterLayout()
7357 {
7358   QgsProcessingContext context;
7359 
7360   QgsProject p;
7361   QgsPrintLayout *l = new QgsPrintLayout( &p );
7362   l->setName( "l1" );
7363   QgsPrintLayout *l2 = new QgsPrintLayout( &p );
7364   l2->setName( "l2" );
7365   p.layoutManager()->addLayout( l );
7366   p.layoutManager()->addLayout( l2 );
7367 
7368   // not optional!
7369   std::unique_ptr< QgsProcessingParameterLayout > def( new QgsProcessingParameterLayout( "non_optional", QString(), QString(), false ) );
7370   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7371   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7372   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7373   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7374 
7375   // string
7376   QVariantMap params;
7377   params.insert( "non_optional", QString( "abcdef" ) );
7378   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) );
7379   QVERIFY( !QgsProcessingParameters::parameterAsLayout( def.get(), params, context ) );
7380   params.insert( "non_optional", QString( "l1" ) );
7381   QVERIFY( !QgsProcessingParameters::parameterAsLayout( def.get(), params, context ) );
7382   context.setProject( &p );
7383   params.insert( "non_optional", QString( "abcdef" ) );
7384   QVERIFY( !QgsProcessingParameters::parameterAsLayout( def.get(), params, context ) );
7385   params.insert( "non_optional", QString( "l1" ) );
7386   QCOMPARE( QgsProcessingParameters::parameterAsLayout( def.get(), params, context ), l );
7387   params.insert( "non_optional", QString( "l2" ) );
7388   QCOMPARE( QgsProcessingParameters::parameterAsLayout( def.get(), params, context ), l2 );
7389 
7390   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7391   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
7392   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7393   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
7394   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
7395   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
7396 
7397   QString pythonCode = def->asPythonString();
7398   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('non_optional', '', defaultValue=None)" ) );
7399 
7400   QString code = def->asScriptCode();
7401   QCOMPARE( code, QStringLiteral( "##non_optional=layout" ) );
7402   std::unique_ptr< QgsProcessingParameterLayout > fromCode( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7403   QVERIFY( fromCode.get() );
7404   QCOMPARE( fromCode->name(), def->name() );
7405   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7406   QCOMPARE( fromCode->flags(), def->flags() );
7407   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7408 
7409   const QVariantMap map = def->toVariantMap();
7410   QgsProcessingParameterLayout fromMap( "x" );
7411   QVERIFY( fromMap.fromVariantMap( map ) );
7412   QCOMPARE( fromMap.name(), def->name() );
7413   QCOMPARE( fromMap.description(), def->description() );
7414   QCOMPARE( fromMap.flags(), def->flags() );
7415   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7416   def.reset( dynamic_cast< QgsProcessingParameterLayout *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7417   QVERIFY( dynamic_cast< QgsProcessingParameterLayout *>( def.get() ) );
7418 
7419   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout None" ) ) ) );
7420   QVERIFY( fromCode.get() );
7421   QCOMPARE( fromCode->name(), def->name() );
7422   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7423   QCOMPARE( fromCode->flags(), def->flags() );
7424   QVERIFY( !fromCode->defaultValue().isValid() );
7425 
7426   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout it's mario" ) ) ) );
7427   QVERIFY( fromCode.get() );
7428   QCOMPARE( fromCode->name(), def->name() );
7429   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7430   QCOMPARE( fromCode->flags(), def->flags() );
7431   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
7432 
7433   def->setDefaultValue( QStringLiteral( "it's mario" ) );
7434   pythonCode = def->asPythonString();
7435   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('non_optional', '', defaultValue=\"it's mario\")" ) );
7436 
7437   code = def->asScriptCode();
7438   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7439   QVERIFY( fromCode.get() );
7440   QCOMPARE( fromCode->name(), def->name() );
7441   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7442   QCOMPARE( fromCode->flags(), def->flags() );
7443   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7444 
7445   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout 'my val'" ) ) ) );
7446   QVERIFY( fromCode.get() );
7447   QCOMPARE( fromCode->name(), def->name() );
7448   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7449   QCOMPARE( fromCode->flags(), def->flags() );
7450   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7451 
7452   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout \"my val\"" ) ) ) );
7453   QVERIFY( fromCode.get() );
7454   QCOMPARE( fromCode->name(), def->name() );
7455   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7456   QCOMPARE( fromCode->flags(), def->flags() );
7457   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7458 
7459   // optional
7460   def.reset( new QgsProcessingParameterLayout( "optional", QString(), QString( "default" ), true ) );
7461   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7462   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7463   QVERIFY( def->checkValueIsAcceptable( "" ) );
7464   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7465 
7466   params.insert( "optional",  QVariant() );
7467   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) );
7468   params.insert( "optional",  QString() ); //empty string should not result in default value
7469   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
7470 
7471   pythonCode = def->asPythonString();
7472   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('optional', '', optional=True, defaultValue='default')" ) );
7473   code = def->asScriptCode();
7474   QCOMPARE( code, QStringLiteral( "##optional=optional layout default" ) );
7475   fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7476   QVERIFY( fromCode.get() );
7477   QCOMPARE( fromCode->name(), def->name() );
7478   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7479   QCOMPARE( fromCode->flags(), def->flags() );
7480   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7481 
7482   // not optional, valid default!
7483   def.reset( new QgsProcessingParameterLayout( "non_optional", QString(), QString( "def" ), false ) );
7484   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7485   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7486   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7487   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
7488 }
7489 
parameterLayoutItem()7490 void TestQgsProcessing::parameterLayoutItem()
7491 {
7492   QgsProcessingContext context;
7493 
7494   QgsProject p;
7495   QgsPrintLayout *l = new QgsPrintLayout( &p );
7496   l->setName( "l1" );
7497   QgsLayoutItemLabel *label1 = new QgsLayoutItemLabel( l );
7498   label1->setId( "a" );
7499   l->addLayoutItem( label1 );
7500   QgsLayoutItemLabel *label2 = new QgsLayoutItemLabel( l );
7501   label2->setId( "b" );
7502   l->addLayoutItem( label2 );
7503 
7504   QgsPrintLayout *l2 = new QgsPrintLayout( &p );
7505 
7506   // not optional!
7507   std::unique_ptr< QgsProcessingParameterLayoutItem > def( new QgsProcessingParameterLayoutItem( "non_optional", QString(), QVariant(), QString(), -1, false ) );
7508   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7509   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7510   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7511   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7512 
7513   // string
7514   QVariantMap params;
7515   params.insert( "non_optional", QString( "aaaa" ) );
7516   QString f = QgsProcessingParameters::parameterAsString( def.get(), params, context );
7517   QCOMPARE( f, QStringLiteral( "aaaa" ) );
7518 
7519   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, nullptr ) );
7520   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ) );
7521   params.insert( "non_optional", label1->uuid() );
7522   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, nullptr ) );
7523   params.insert( "non_optional", QString( "abcdef" ) );
7524   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, nullptr ) );
7525   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ) );
7526   params.insert( "non_optional", label1->uuid() );
7527   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, nullptr ) );
7528   QVERIFY( !QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l2 ) );
7529   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label1 );
7530   params.insert( "non_optional", label1->id() );
7531   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label1 );
7532   params.insert( "non_optional", label2->uuid() );
7533   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label2 );
7534   params.insert( "non_optional", label2->id() );
7535   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label2 );
7536   // UUID matching must take precedence
7537   label1->setId( label2->uuid() );
7538   params.insert( "non_optional", label2->uuid() );
7539   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label2 );
7540   label2->setId( label1->uuid() );
7541   params.insert( "non_optional", label1->uuid() );
7542   QCOMPARE( QgsProcessingParameters::parameterAsLayoutItem( def.get(), params, context, l ), label1 );
7543 
7544   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7545   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7546   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
7547   QCOMPARE( def->valueAsPythonString( "probably\'invalid\"item", context ), QStringLiteral( "'probably\\'invalid\"item'" ) );
7548 
7549   QString pythonCode = def->asPythonString();
7550   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayoutItem('non_optional', '', parentLayoutParameterName='', defaultValue=None)" ) );
7551 
7552   QString code = def->asScriptCode();
7553   QCOMPARE( code, QStringLiteral( "##non_optional=layoutitem" ) );
7554   std::unique_ptr< QgsProcessingParameterLayoutItem > fromCode( dynamic_cast< QgsProcessingParameterLayoutItem * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7555   QVERIFY( fromCode.get() );
7556   QCOMPARE( fromCode->name(), def->name() );
7557   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7558   QCOMPARE( fromCode->flags(), def->flags() );
7559   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7560   QCOMPARE( fromCode->parentLayoutParameterName(), def->parentLayoutParameterName() );
7561   QCOMPARE( fromCode->itemType(), def->itemType() );
7562 
7563   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
7564   def->setParentLayoutParameterName( "my_parent" );
7565   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) );
7566 
7567   pythonCode = def->asPythonString();
7568   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayoutItem('non_optional', '', parentLayoutParameterName='my_parent', defaultValue=None)" ) );
7569 
7570   code = def->asScriptCode();
7571   fromCode.reset( dynamic_cast< QgsProcessingParameterLayoutItem * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7572   QVERIFY( fromCode.get() );
7573   QCOMPARE( fromCode->name(), def->name() );
7574   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7575   QCOMPARE( fromCode->flags(), def->flags() );
7576   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7577   QCOMPARE( fromCode->parentLayoutParameterName(), def->parentLayoutParameterName() );
7578   QCOMPARE( fromCode->itemType(), def->itemType() );
7579 
7580   def->setItemType( 100 );
7581   pythonCode = def->asPythonString();
7582   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayoutItem('non_optional', '', itemType=100, parentLayoutParameterName='my_parent', defaultValue=None)" ) );
7583   code = def->asScriptCode();
7584   fromCode.reset( dynamic_cast< QgsProcessingParameterLayoutItem * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7585   QVERIFY( fromCode.get() );
7586   QCOMPARE( fromCode->name(), def->name() );
7587   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7588   QCOMPARE( fromCode->flags(), def->flags() );
7589   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7590   QCOMPARE( fromCode->parentLayoutParameterName(), def->parentLayoutParameterName() );
7591   QCOMPARE( fromCode->itemType(), def->itemType() );
7592 
7593   def->setItemType( 101 );
7594   pythonCode = def->asPythonString();
7595   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayoutItem('non_optional', '', itemType=101, parentLayoutParameterName='my_parent', defaultValue=None)" ) );
7596   code = def->asScriptCode();
7597   fromCode.reset( dynamic_cast< QgsProcessingParameterLayoutItem * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7598   QVERIFY( fromCode.get() );
7599   QCOMPARE( fromCode->name(), def->name() );
7600   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7601   QCOMPARE( fromCode->flags(), def->flags() );
7602   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7603   QCOMPARE( fromCode->parentLayoutParameterName(), def->parentLayoutParameterName() );
7604   QCOMPARE( fromCode->itemType(), def->itemType() );
7605 
7606   // optional
7607   def.reset( new QgsProcessingParameterLayoutItem( "optional", QString(), QString( "def" ), QString(), -1, true ) );
7608   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7609   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7610   QVERIFY( def->checkValueIsAcceptable( "" ) );
7611   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7612 
7613   params.insert( "optional",  QVariant() );
7614   f = QgsProcessingParameters::parameterAsString( def.get(), params, context );
7615   QCOMPARE( f, QStringLiteral( "def" ) );
7616 
7617   // optional, no default
7618   def.reset( new QgsProcessingParameterLayoutItem( "optional", QString(), QVariant(), QString(), -1, true ) );
7619   params.insert( "optional",  QVariant() );
7620   f = QgsProcessingParameters::parameterAsString( def.get(), params, context );
7621   QVERIFY( f.isEmpty() );
7622 
7623   pythonCode = def->asPythonString();
7624   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayoutItem('optional', '', optional=True, parentLayoutParameterName='', defaultValue=None)" ) );
7625   code = def->asScriptCode();
7626   QCOMPARE( code, QStringLiteral( "##optional=optional layoutitem" ) );
7627   fromCode.reset( dynamic_cast< QgsProcessingParameterLayoutItem * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7628   QVERIFY( fromCode.get() );
7629   QCOMPARE( fromCode->name(), def->name() );
7630   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7631   QCOMPARE( fromCode->flags(), def->flags() );
7632   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7633   QCOMPARE( fromCode->parentLayoutParameterName(), def->parentLayoutParameterName() );
7634   QCOMPARE( fromCode->itemType(), def->itemType() );
7635 
7636 }
7637 
parameterColor()7638 void TestQgsProcessing::parameterColor()
7639 {
7640   QgsProcessingContext context;
7641 
7642   // not optional!
7643   std::unique_ptr< QgsProcessingParameterColor > def( new QgsProcessingParameterColor( "non_optional", QString(), QString(), true, false ) );
7644   QVERIFY( def->opacityEnabled() );
7645   def->setOpacityEnabled( false );
7646   QVERIFY( !def->opacityEnabled() );
7647   def->setOpacityEnabled( true );
7648 
7649   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
7650   QVERIFY( def->checkValueIsAcceptable( "#ff0000" ) );
7651   QVERIFY( !def->checkValueIsAcceptable( "bbbbbbbbbbbbbbbbbbbb" ) );
7652   QVERIFY( def->checkValueIsAcceptable( QColor( 255, 0, 0 ) ) );
7653   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7654   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7655 
7656   // string
7657   QVariantMap params;
7658   params.insert( "non_optional", QString( "xxx" ) );
7659   QVERIFY( !QgsProcessingParameters::parameterAsColor( def.get(), params, context ).isValid() );
7660   params.insert( "non_optional", QString( "#ff0000" ) );
7661   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ).name(), QString( "#ff0000" ) );
7662   params.insert( "non_optional", QString( "rgba(255,0,0,0.1" ) );
7663   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ).name(), QString( "#ff0000" ) );
7664   params.insert( "non_optional", QColor( 255, 255, 0 ) );
7665   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ).name(), QString( "#ffff00" ) );
7666   params.insert( "non_optional", QColor( 255, 255, 0, 100 ) );
7667   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ), QColor( 255, 255, 0, 100 ) );
7668 
7669   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7670   QCOMPARE( def->valueAsPythonString( QStringLiteral( "#ff0000" ), context ), QStringLiteral( "QColor(255, 0, 0)" ) );
7671   QCOMPARE( def->valueAsPythonString( QColor(), context ), QStringLiteral( "QColor()" ) );
7672   QCOMPARE( def->valueAsPythonString( QColor( 255, 0, 0 ), context ), QStringLiteral( "QColor(255, 0, 0)" ) );
7673   QCOMPARE( def->valueAsPythonString( QColor( 255, 0, 0, 100 ), context ), QStringLiteral( "QColor(255, 0, 0, 100)" ) );
7674 
7675   QString pythonCode = def->asPythonString();
7676   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterColor('non_optional', '', opacityEnabled=True, defaultValue=None)" ) );
7677 
7678   QString code = def->asScriptCode();
7679   QCOMPARE( code, QStringLiteral( "##non_optional=color withopacity" ) );
7680   std::unique_ptr< QgsProcessingParameterColor > fromCode( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7681   QVERIFY( fromCode.get() );
7682   QCOMPARE( fromCode->name(), def->name() );
7683   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7684   QCOMPARE( fromCode->flags(), def->flags() );
7685   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7686   QVERIFY( fromCode->opacityEnabled() );
7687 
7688   const QVariantMap map = def->toVariantMap();
7689   QgsProcessingParameterColor fromMap( "x" );
7690   QVERIFY( fromMap.fromVariantMap( map ) );
7691   QCOMPARE( fromMap.name(), def->name() );
7692   QCOMPARE( fromMap.description(), def->description() );
7693   QCOMPARE( fromMap.flags(), def->flags() );
7694   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7695   QVERIFY( fromMap.opacityEnabled() );
7696   def.reset( dynamic_cast< QgsProcessingParameterColor *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7697   QVERIFY( dynamic_cast< QgsProcessingParameterColor *>( def.get() ) );
7698 
7699   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=color withopacity None" ) ) ) );
7700   QVERIFY( fromCode.get() );
7701   QCOMPARE( fromCode->name(), def->name() );
7702   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7703   QCOMPARE( fromCode->flags(), def->flags() );
7704   QVERIFY( !fromCode->defaultValue().isValid() );
7705   QVERIFY( fromCode->opacityEnabled() );
7706 
7707   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=color #aabbcc" ) ) ) );
7708   QVERIFY( fromCode.get() );
7709   QCOMPARE( fromCode->name(), def->name() );
7710   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7711   QCOMPARE( fromCode->flags(), def->flags() );
7712   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "#aabbcc" ) );
7713   QVERIFY( !fromCode->opacityEnabled() );
7714 
7715   def->setDefaultValue( QColor( 10, 20, 30 ) );
7716   pythonCode = def->asPythonString();
7717   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterColor('non_optional', '', opacityEnabled=True, defaultValue=QColor(10, 20, 30))" ) );
7718 
7719   def->setOpacityEnabled( false );
7720   pythonCode = def->asPythonString();
7721   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterColor('non_optional', '', opacityEnabled=False, defaultValue=QColor(10, 20, 30))" ) );
7722   def->setOpacityEnabled( true );
7723 
7724   code = def->asScriptCode();
7725   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7726   QVERIFY( fromCode.get() );
7727   QCOMPARE( fromCode->name(), def->name() );
7728   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7729   QCOMPARE( fromCode->flags(), def->flags() );
7730   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7731   QVERIFY( fromCode->opacityEnabled() );
7732 
7733   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=color 'my val'" ) ) ) );
7734   QVERIFY( fromCode.get() );
7735   QCOMPARE( fromCode->name(), def->name() );
7736   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7737   QCOMPARE( fromCode->flags(), def->flags() );
7738   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7739   QVERIFY( !fromCode->opacityEnabled() );
7740 
7741   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=color withopacity \"my val\"" ) ) ) );
7742   QVERIFY( fromCode.get() );
7743   QCOMPARE( fromCode->name(), def->name() );
7744   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7745   QCOMPARE( fromCode->flags(), def->flags() );
7746   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7747   QVERIFY( fromCode->opacityEnabled() );
7748 
7749   // optional
7750   def.reset( new QgsProcessingParameterColor( "optional", QString(), QString( "#ff00ff" ), false, true ) );
7751   QVERIFY( def->checkValueIsAcceptable( "#ff0000" ) );
7752   QVERIFY( def->checkValueIsAcceptable( QColor( 255, 0, 0 ) ) );
7753   QVERIFY( def->checkValueIsAcceptable( "" ) );
7754   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7755   QVERIFY( !def->opacityEnabled() );
7756 
7757   params.insert( "optional",  QVariant() );
7758   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ).name(), QString( "#ff00ff" ) );
7759   params.insert( "optional",  QColor() ); //invalid color should not result in default value
7760   QVERIFY( !QgsProcessingParameters::parameterAsColor( def.get(), params, context ).isValid() );
7761   params.insert( "optional",  QString() ); //empty string should not result in default value
7762   QVERIFY( !QgsProcessingParameters::parameterAsColor( def.get(), params, context ).isValid() );
7763 
7764   // not opacity enabled, should be stripped off
7765   params.insert( "optional", QColor( 255, 255, 0, 100 ) );
7766   QCOMPARE( QgsProcessingParameters::parameterAsColor( def.get(), params, context ), QColor( 255, 255, 0 ) );
7767 
7768   pythonCode = def->asPythonString();
7769   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterColor('optional', '', optional=True, opacityEnabled=False, defaultValue=QColor(255, 0, 255))" ) );
7770   code = def->asScriptCode();
7771   QCOMPARE( code, QStringLiteral( "##optional=optional color #ff00ff" ) );
7772   fromCode.reset( dynamic_cast< QgsProcessingParameterColor * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7773   QVERIFY( fromCode.get() );
7774   QCOMPARE( fromCode->name(), def->name() );
7775   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7776   QCOMPARE( fromCode->flags(), def->flags() );
7777   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7778   QVERIFY( !fromCode->opacityEnabled() );
7779 
7780   // not optional, valid default!
7781   def.reset( new QgsProcessingParameterColor( "non_optional", QString(), QString( "#ff00ff" ), true, false ) );
7782   QVERIFY( def->checkValueIsAcceptable( "#dddddd" ) );
7783   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7784   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
7785 }
7786 
parameterCoordinateOperation()7787 void TestQgsProcessing::parameterCoordinateOperation()
7788 {
7789   QgsProcessingContext context;
7790 
7791   // not optional!
7792   std::unique_ptr< QgsProcessingParameterCoordinateOperation > def( new QgsProcessingParameterCoordinateOperation( "non_optional", QString(), QString(), QString(), QString(), QVariant(), QVariant(), false ) );
7793   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7794   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7795   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7796   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7797   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
7798   def->setSourceCrsParameterName( QStringLiteral( "src" ) );
7799   QCOMPARE( def->sourceCrsParameterName(), QStringLiteral( "src" ) );
7800   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "src" ) );
7801   def->setDestinationCrsParameterName( QStringLiteral( "dest" ) );
7802   QCOMPARE( def->destinationCrsParameterName(), QStringLiteral( "dest" ) );
7803   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "src" ) << QStringLiteral( "dest" ) );
7804   def->setSourceCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:7855" ) ) );
7805   QCOMPARE( def->sourceCrs().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:7855" ) );
7806   def->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28355" ) ) );
7807   QCOMPARE( def->destinationCrs().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:28355" ) );
7808 
7809   // string value
7810   QVariantMap params;
7811   params.insert( "non_optional", QStringLiteral( "abcdef" ) );
7812   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) );
7813 
7814   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7815   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
7816   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7817   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
7818   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
7819   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
7820   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
7821 
7822   QString pythonCode = def->asPythonString();
7823   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterCoordinateOperation('non_optional', '', sourceCrsParameterName='src', destinationCrsParameterName='dest', staticSourceCrs=QgsCoordinateReferenceSystem('EPSG:7855'), staticDestinationCrs=QgsCoordinateReferenceSystem('EPSG:28355'), defaultValue=None)" ) );
7824 
7825   QString code = def->asScriptCode();
7826   QCOMPARE( code, QStringLiteral( "##non_optional=coordinateoperation" ) );
7827   std::unique_ptr< QgsProcessingParameterCoordinateOperation > fromCode( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7828   QVERIFY( fromCode.get() );
7829   QCOMPARE( fromCode->name(), def->name() );
7830   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7831   QCOMPARE( fromCode->flags(), def->flags() );
7832   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7833 
7834   const QVariantMap map = def->toVariantMap();
7835   QgsProcessingParameterCoordinateOperation fromMap( "x" );
7836   QVERIFY( fromMap.fromVariantMap( map ) );
7837   QCOMPARE( fromMap.name(), def->name() );
7838   QCOMPARE( fromMap.description(), def->description() );
7839   QCOMPARE( fromMap.flags(), def->flags() );
7840   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7841   QCOMPARE( fromMap.sourceCrsParameterName(), def->sourceCrsParameterName() );
7842   QCOMPARE( fromMap.destinationCrsParameterName(), def->destinationCrsParameterName() );
7843   QCOMPARE( fromMap.sourceCrs().value< QgsCoordinateReferenceSystem >().authid(), def->sourceCrs().value< QgsCoordinateReferenceSystem >().authid() );
7844   QCOMPARE( fromMap.destinationCrs().value< QgsCoordinateReferenceSystem >().authid(), def->destinationCrs().value< QgsCoordinateReferenceSystem >().authid() );
7845   def.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7846   QVERIFY( dynamic_cast< QgsProcessingParameterCoordinateOperation *>( def.get() ) );
7847 
7848   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=coordinateoperation None" ) ) ) );
7849   QVERIFY( fromCode.get() );
7850   QCOMPARE( fromCode->name(), def->name() );
7851   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7852   QCOMPARE( fromCode->flags(), def->flags() );
7853   QVERIFY( !fromCode->defaultValue().isValid() );
7854 
7855   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=coordinateoperation it's mario" ) ) ) );
7856   QVERIFY( fromCode.get() );
7857   QCOMPARE( fromCode->name(), def->name() );
7858   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7859   QCOMPARE( fromCode->flags(), def->flags() );
7860   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
7861 
7862   def->setDefaultValue( QStringLiteral( "it's mario" ) );
7863   pythonCode = def->asPythonString();
7864   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterCoordinateOperation('non_optional', '', sourceCrsParameterName='src', destinationCrsParameterName='dest', staticSourceCrs=QgsCoordinateReferenceSystem('EPSG:7855'), staticDestinationCrs=QgsCoordinateReferenceSystem('EPSG:28355'), defaultValue=\"it's mario\")" ) );
7865   code = def->asScriptCode();
7866   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7867   QVERIFY( fromCode.get() );
7868   QCOMPARE( fromCode->name(), def->name() );
7869   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7870   QCOMPARE( fromCode->flags(), def->flags() );
7871   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7872 
7873   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=coordinateoperation 'my val'" ) ) ) );
7874   QVERIFY( fromCode.get() );
7875   QCOMPARE( fromCode->name(), def->name() );
7876   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7877   QCOMPARE( fromCode->flags(), def->flags() );
7878   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7879 
7880   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=coordinateoperation \"my val\"" ) ) ) );
7881   QVERIFY( fromCode.get() );
7882   QCOMPARE( fromCode->name(), def->name() );
7883   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7884   QCOMPARE( fromCode->flags(), def->flags() );
7885   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7886 
7887   // optional
7888   def.reset( new QgsProcessingParameterCoordinateOperation( "optional", QString(), QString( "default" ), QString(), QString(), QVariant(), QVariant(), true ) );
7889   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7890   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7891   QVERIFY( def->checkValueIsAcceptable( "" ) );
7892   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
7893 
7894   params.insert( "optional",  QVariant() );
7895   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) );
7896   params.insert( "optional",  QString() ); //empty string should not result in default value
7897   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
7898 
7899   pythonCode = def->asPythonString();
7900   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterCoordinateOperation('optional', '', optional=True, defaultValue='default')" ) );
7901 
7902   code = def->asScriptCode();
7903   QCOMPARE( code, QStringLiteral( "##optional=optional coordinateoperation default" ) );
7904   fromCode.reset( dynamic_cast< QgsProcessingParameterCoordinateOperation * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7905   QVERIFY( fromCode.get() );
7906   QCOMPARE( fromCode->name(), def->name() );
7907   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
7908   QCOMPARE( fromCode->flags(), def->flags() );
7909   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7910 
7911   // not optional, valid default!
7912   def.reset( new QgsProcessingParameterCoordinateOperation( "non_optional", QString(), QString( "def" ), QString(), QString(), QVariant(), QVariant(), false ) );
7913   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7914   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7915   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7916   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
7917 }
7918 
parameterMapTheme()7919 void TestQgsProcessing::parameterMapTheme()
7920 {
7921   QgsProcessingContext context;
7922 
7923   // not optional!
7924   std::unique_ptr< QgsProcessingParameterMapTheme > def( new QgsProcessingParameterMapTheme( "non_optional", QString(), QVariant(), false ) );
7925   QVERIFY( def->checkValueIsAcceptable( 1 ) );
7926   QVERIFY( def->checkValueIsAcceptable( "test" ) );
7927   QVERIFY( !def->checkValueIsAcceptable( "" ) );
7928   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
7929 
7930   // string
7931   QVariantMap params;
7932   params.insert( "non_optional", QString( "abcdef" ) );
7933   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) );
7934 
7935   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
7936   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
7937   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
7938   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
7939   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
7940   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
7941   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
7942 
7943   QString pythonCode = def->asPythonString();
7944   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapTheme('non_optional', '', defaultValue=None)" ) );
7945 
7946   QString code = def->asScriptCode();
7947   QCOMPARE( code, QStringLiteral( "##non_optional=maptheme" ) );
7948   std::unique_ptr< QgsProcessingParameterMapTheme > fromCode( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7949   QVERIFY( fromCode.get() );
7950   QCOMPARE( fromCode->name(), def->name() );
7951   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7952   QCOMPARE( fromCode->flags(), def->flags() );
7953   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7954 
7955   const QVariantMap map = def->toVariantMap();
7956   QgsProcessingParameterMapTheme fromMap( "x" );
7957   QVERIFY( fromMap.fromVariantMap( map ) );
7958   QCOMPARE( fromMap.name(), def->name() );
7959   QCOMPARE( fromMap.description(), def->description() );
7960   QCOMPARE( fromMap.flags(), def->flags() );
7961   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
7962   def.reset( dynamic_cast< QgsProcessingParameterMapTheme *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
7963   QVERIFY( dynamic_cast< QgsProcessingParameterMapTheme *>( def.get() ) );
7964 
7965   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=maptheme None" ) ) ) );
7966   QVERIFY( fromCode.get() );
7967   QCOMPARE( fromCode->name(), def->name() );
7968   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7969   QCOMPARE( fromCode->flags(), def->flags() );
7970   QVERIFY( !fromCode->defaultValue().isValid() );
7971 
7972   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=maptheme it's mario" ) ) ) );
7973   QVERIFY( fromCode.get() );
7974   QCOMPARE( fromCode->name(), def->name() );
7975   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7976   QCOMPARE( fromCode->flags(), def->flags() );
7977   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
7978 
7979   def->setDefaultValue( QStringLiteral( "it's mario" ) );
7980   pythonCode = def->asPythonString();
7981   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapTheme('non_optional', '', defaultValue=\"it's mario\")" ) );
7982   code = def->asScriptCode();
7983   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
7984   QVERIFY( fromCode.get() );
7985   QCOMPARE( fromCode->name(), def->name() );
7986   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7987   QCOMPARE( fromCode->flags(), def->flags() );
7988   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
7989 
7990   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=maptheme 'my val'" ) ) ) );
7991   QVERIFY( fromCode.get() );
7992   QCOMPARE( fromCode->name(), def->name() );
7993   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
7994   QCOMPARE( fromCode->flags(), def->flags() );
7995   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
7996 
7997   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=maptheme \"my val\"" ) ) ) );
7998   QVERIFY( fromCode.get() );
7999   QCOMPARE( fromCode->name(), def->name() );
8000   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8001   QCOMPARE( fromCode->flags(), def->flags() );
8002   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
8003 
8004   // optional
8005   def.reset( new QgsProcessingParameterMapTheme( "optional", QString(), QString( "default" ), true ) );
8006   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8007   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8008   QVERIFY( def->checkValueIsAcceptable( "" ) );
8009   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8010 
8011   params.insert( "optional",  QVariant() );
8012   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) );
8013   params.insert( "optional",  QString() ); //empty string should not result in default value
8014   QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() );
8015 
8016   pythonCode = def->asPythonString();
8017   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMapTheme('optional', '', optional=True, defaultValue='default')" ) );
8018 
8019   code = def->asScriptCode();
8020   QCOMPARE( code, QStringLiteral( "##optional=optional maptheme default" ) );
8021   fromCode.reset( dynamic_cast< QgsProcessingParameterMapTheme * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8022   QVERIFY( fromCode.get() );
8023   QCOMPARE( fromCode->name(), def->name() );
8024   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8025   QCOMPARE( fromCode->flags(), def->flags() );
8026   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8027 
8028   // not optional, valid default!
8029   def.reset( new QgsProcessingParameterMapTheme( "non_optional", QString(), QString( "def" ), false ) );
8030   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8031   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8032   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8033   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
8034 }
8035 
parameterProviderConnection()8036 void TestQgsProcessing::parameterProviderConnection()
8037 {
8038   QgsProcessingContext context;
8039 
8040   // not optional!
8041   std::unique_ptr< QgsProcessingParameterProviderConnection > def( new QgsProcessingParameterProviderConnection( "non_optional", QString(), QStringLiteral( "ogr" ), QVariant(), false ) );
8042   QCOMPARE( def->providerId(), QStringLiteral( "ogr" ) );
8043   def->setProviderId( QStringLiteral( "postgres" ) );
8044   QCOMPARE( def->providerId(), QStringLiteral( "postgres" ) );
8045   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8046   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8047   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8048   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
8049 
8050   // string value
8051   QVariantMap params;
8052   params.insert( "non_optional", QString( "abcdef" ) );
8053   QCOMPARE( QgsProcessingParameters::parameterAsConnectionName( def.get(), params, context ), QString( "abcdef" ) );
8054 
8055   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8056   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) );
8057   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
8058   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
8059   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8060   QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\"complex\"'" ) );
8061   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) );
8062 
8063   QString pythonCode = def->asPythonString();
8064   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterProviderConnection('non_optional', '', 'postgres', defaultValue=None)" ) );
8065 
8066   QString code = def->asScriptCode();
8067   QCOMPARE( code, QStringLiteral( "##non_optional=providerconnection postgres" ) );
8068   std::unique_ptr< QgsProcessingParameterProviderConnection > fromCode( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8069   QVERIFY( fromCode.get() );
8070   QCOMPARE( fromCode->name(), def->name() );
8071   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8072   QCOMPARE( fromCode->flags(), def->flags() );
8073   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8074   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8075 
8076   const QVariantMap map = def->toVariantMap();
8077   QgsProcessingParameterProviderConnection fromMap( "x", QString(), QString() );
8078   QVERIFY( fromMap.fromVariantMap( map ) );
8079   QCOMPARE( fromMap.name(), def->name() );
8080   QCOMPARE( fromMap.description(), def->description() );
8081   QCOMPARE( fromMap.flags(), def->flags() );
8082   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8083   QCOMPARE( fromMap.providerId(), QStringLiteral( "postgres" ) );
8084   def.reset( dynamic_cast< QgsProcessingParameterProviderConnection *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
8085   QVERIFY( dynamic_cast< QgsProcessingParameterProviderConnection *>( def.get() ) );
8086 
8087   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=providerconnection postgres None" ) ) ) );
8088   QVERIFY( fromCode.get() );
8089   QCOMPARE( fromCode->name(), def->name() );
8090   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8091   QCOMPARE( fromCode->flags(), def->flags() );
8092   QVERIFY( !fromCode->defaultValue().isValid() );
8093   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8094 
8095   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=providerconnection postgres it's mario" ) ) ) );
8096   QVERIFY( fromCode.get() );
8097   QCOMPARE( fromCode->name(), def->name() );
8098   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8099   QCOMPARE( fromCode->flags(), def->flags() );
8100   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) );
8101   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8102 
8103   def->setDefaultValue( QStringLiteral( "it's mario" ) );
8104   pythonCode = def->asPythonString();
8105   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterProviderConnection('non_optional', '', 'postgres', defaultValue=\"it's mario\")" ) );
8106   code = def->asScriptCode();
8107   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8108   QVERIFY( fromCode.get() );
8109   QCOMPARE( fromCode->name(), def->name() );
8110   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8111   QCOMPARE( fromCode->flags(), def->flags() );
8112   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8113   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8114 
8115   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=providerconnection postgres 'my val'" ) ) ) );
8116   QVERIFY( fromCode.get() );
8117   QCOMPARE( fromCode->name(), def->name() );
8118   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8119   QCOMPARE( fromCode->flags(), def->flags() );
8120   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
8121   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8122 
8123   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=providerconnection postgres \"my val\"" ) ) ) );
8124   QVERIFY( fromCode.get() );
8125   QCOMPARE( fromCode->name(), def->name() );
8126   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8127   QCOMPARE( fromCode->flags(), def->flags() );
8128   QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) );
8129   QCOMPARE( fromCode->providerId(), QStringLiteral( "postgres" ) );
8130 
8131   // optional
8132   def.reset( new QgsProcessingParameterProviderConnection( "optional", QString(), QStringLiteral( "ogr" ), QString( "default" ), true ) );
8133   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8134   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8135   QVERIFY( def->checkValueIsAcceptable( "" ) );
8136   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8137 
8138   params.insert( "optional",  QVariant() );
8139   QCOMPARE( QgsProcessingParameters::parameterAsConnectionName( def.get(), params, context ), QString( "default" ) );
8140   params.insert( "optional",  QString() ); //empty string should not result in default value
8141   QCOMPARE( QgsProcessingParameters::parameterAsConnectionName( def.get(), params, context ), QString() );
8142 
8143   pythonCode = def->asPythonString();
8144   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterProviderConnection('optional', '', 'ogr', optional=True, defaultValue='default')" ) );
8145 
8146   code = def->asScriptCode();
8147   QCOMPARE( code, QStringLiteral( "##optional=optional providerconnection ogr default" ) );
8148   fromCode.reset( dynamic_cast< QgsProcessingParameterProviderConnection * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8149   QVERIFY( fromCode.get() );
8150   QCOMPARE( fromCode->name(), def->name() );
8151   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8152   QCOMPARE( fromCode->flags(), def->flags() );
8153   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8154   QCOMPARE( fromCode->providerId(), QStringLiteral( "ogr" ) );
8155 
8156   // not optional, valid default!
8157   def.reset( new QgsProcessingParameterProviderConnection( "non_optional", QString(), QStringLiteral( "ogr" ), QString( "def" ), false ) );
8158   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8159   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8160   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8161   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default
8162 }
8163 
parameterDatabaseSchema()8164 void TestQgsProcessing::parameterDatabaseSchema()
8165 {
8166   QgsProcessingContext context;
8167 
8168   // not optional!
8169   std::unique_ptr< QgsProcessingParameterDatabaseSchema > def( new QgsProcessingParameterDatabaseSchema( "non_optional", QString(), QString(), QVariant(), false ) );
8170   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8171   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8172   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8173   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
8174 
8175   // string
8176   QVariantMap params;
8177   params.insert( "non_optional", QString( "a" ) );
8178   QCOMPARE( QgsProcessingParameters::parameterAsSchema( def.get(), params, context ), QStringLiteral( "a" ) );
8179 
8180   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8181   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
8182   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8183   QCOMPARE( def->valueAsPythonString( "probably\'invalid\"schema", context ), QStringLiteral( "'probably\\'invalid\"schema'" ) );
8184 
8185   QString pythonCode = def->asPythonString();
8186   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseSchema('non_optional', '', connectionParameterName='', defaultValue=None)" ) );
8187 
8188   QString code = def->asScriptCode();
8189   QCOMPARE( code, QStringLiteral( "##non_optional=databaseschema" ) );
8190   std::unique_ptr< QgsProcessingParameterDatabaseSchema > fromCode( dynamic_cast< QgsProcessingParameterDatabaseSchema * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8191   QVERIFY( fromCode.get() );
8192   QCOMPARE( fromCode->name(), def->name() );
8193   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8194   QCOMPARE( fromCode->flags(), def->flags() );
8195   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8196   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8197 
8198   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8199   def->setParentConnectionParameterName( "my_parent" );
8200   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) );
8201 
8202   pythonCode = def->asPythonString();
8203   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseSchema('non_optional', '', connectionParameterName='my_parent', defaultValue=None)" ) );
8204 
8205   code = def->asScriptCode();
8206   fromCode.reset( dynamic_cast< QgsProcessingParameterDatabaseSchema * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8207   QVERIFY( fromCode.get() );
8208   QCOMPARE( fromCode->name(), def->name() );
8209   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8210   QCOMPARE( fromCode->flags(), def->flags() );
8211   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8212   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8213 
8214   // optional
8215   def.reset( new QgsProcessingParameterDatabaseSchema( "optional", QString(), QString(), QStringLiteral( "def" ), true ) );
8216   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8217   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8218   QVERIFY( def->checkValueIsAcceptable( "" ) );
8219   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8220 
8221   params.insert( "optional",  QVariant() );
8222   QCOMPARE( QgsProcessingParameters::parameterAsSchema( def.get(), params, context ), QStringLiteral( "def" ) );
8223 
8224   // optional, no default
8225   def.reset( new QgsProcessingParameterDatabaseSchema( "optional", QString(), QString(), QVariant(), true ) );
8226   params.insert( "optional",  QVariant() );
8227   QVERIFY( QgsProcessingParameters::parameterAsSchema( def.get(), params, context ).isEmpty() );
8228 
8229   pythonCode = def->asPythonString();
8230   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseSchema('optional', '', optional=True, connectionParameterName='', defaultValue=None)" ) );
8231   code = def->asScriptCode();
8232   QCOMPARE( code, QStringLiteral( "##optional=optional databaseschema" ) );
8233   fromCode.reset( dynamic_cast< QgsProcessingParameterDatabaseSchema * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8234   QVERIFY( fromCode.get() );
8235   QCOMPARE( fromCode->name(), def->name() );
8236   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8237   QCOMPARE( fromCode->flags(), def->flags() );
8238   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8239   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8240 }
8241 
parameterDatabaseTable()8242 void TestQgsProcessing::parameterDatabaseTable()
8243 {
8244   QgsProcessingContext context;
8245 
8246   // not optional!
8247   std::unique_ptr< QgsProcessingParameterDatabaseTable > def( new QgsProcessingParameterDatabaseTable( "non_optional", QString(), QString(), QString(), QVariant(), false ) );
8248   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8249   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8250   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8251   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
8252 
8253   // string
8254   QVariantMap params;
8255   params.insert( "non_optional", QString( "a" ) );
8256   QCOMPARE( QgsProcessingParameters::parameterAsDatabaseTableName( def.get(), params, context ), QStringLiteral( "a" ) );
8257 
8258   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8259   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
8260   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8261   QCOMPARE( def->valueAsPythonString( "probably\'invalid\"schema", context ), QStringLiteral( "'probably\\'invalid\"schema'" ) );
8262 
8263   QString pythonCode = def->asPythonString();
8264   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseTable('non_optional', '', connectionParameterName='', schemaParameterName='', defaultValue=None)" ) );
8265 
8266   QString code = def->asScriptCode();
8267   QCOMPARE( code, QStringLiteral( "##non_optional=databasetable none none" ) );
8268   std::unique_ptr< QgsProcessingParameterDatabaseTable > fromCode( dynamic_cast< QgsProcessingParameterDatabaseTable * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8269   QVERIFY( fromCode.get() );
8270   QCOMPARE( fromCode->name(), def->name() );
8271   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8272   QCOMPARE( fromCode->flags(), def->flags() );
8273   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8274   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8275   QCOMPARE( fromCode->parentSchemaParameterName(), def->parentSchemaParameterName() );
8276 
8277   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8278   def->setParentConnectionParameterName( "my_parent" );
8279   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) );
8280   def->setParentSchemaParameterName( "my_schema" );
8281   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) << QStringLiteral( "my_schema" ) );
8282 
8283   pythonCode = def->asPythonString();
8284   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseTable('non_optional', '', connectionParameterName='my_parent', schemaParameterName='my_schema', defaultValue=None)" ) );
8285 
8286   code = def->asScriptCode();
8287   fromCode.reset( dynamic_cast< QgsProcessingParameterDatabaseTable * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8288   QVERIFY( fromCode.get() );
8289   QCOMPARE( fromCode->name(), def->name() );
8290   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8291   QCOMPARE( fromCode->flags(), def->flags() );
8292   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8293   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8294   QCOMPARE( fromCode->parentSchemaParameterName(), def->parentSchemaParameterName() );
8295 
8296   // optional
8297   def.reset( new QgsProcessingParameterDatabaseTable( "optional", QString(), QString(), QString(), QStringLiteral( "def" ), true ) );
8298   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8299   QVERIFY( def->checkValueIsAcceptable( "test" ) );
8300   QVERIFY( def->checkValueIsAcceptable( "" ) );
8301   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8302 
8303   params.insert( "optional",  QVariant() );
8304   QCOMPARE( QgsProcessingParameters::parameterAsDatabaseTableName( def.get(), params, context ), QStringLiteral( "def" ) );
8305 
8306   // optional, no default
8307   def.reset( new QgsProcessingParameterDatabaseTable( "optional", QString(), QString(), QString(), QVariant(), true ) );
8308   params.insert( "optional",  QVariant() );
8309   QVERIFY( QgsProcessingParameters::parameterAsDatabaseTableName( def.get(), params, context ).isEmpty() );
8310 
8311   pythonCode = def->asPythonString();
8312   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseTable('optional', '', optional=True, connectionParameterName='', schemaParameterName='', defaultValue=None)" ) );
8313   code = def->asScriptCode();
8314   QCOMPARE( code, QStringLiteral( "##optional=optional databasetable none none" ) );
8315   fromCode.reset( dynamic_cast< QgsProcessingParameterDatabaseTable * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8316   QVERIFY( fromCode.get() );
8317   QCOMPARE( fromCode->name(), def->name() );
8318   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8319   QCOMPARE( fromCode->flags(), def->flags() );
8320   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8321   QCOMPARE( fromCode->parentConnectionParameterName(), def->parentConnectionParameterName() );
8322   QCOMPARE( fromCode->parentSchemaParameterName(), def->parentSchemaParameterName() );
8323 
8324   // allow new table names
8325   def.reset( new QgsProcessingParameterDatabaseTable( "new", QString(), QStringLiteral( "con" ), QStringLiteral( "schema" ), QVariant(), false, true ) );
8326 
8327   pythonCode = def->asPythonString();
8328   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDatabaseTable('new', '', allowNewTableNames=True, connectionParameterName='con', schemaParameterName='schema', defaultValue=None)" ) );
8329   const QVariantMap var = def->toVariantMap();
8330   def.reset( dynamic_cast<QgsProcessingParameterDatabaseTable *>( QgsProcessingParameters::parameterFromVariantMap( var ) ) );
8331   QCOMPARE( def->parentConnectionParameterName(), QStringLiteral( "con" ) );
8332   QCOMPARE( def->parentSchemaParameterName(), QStringLiteral( "schema" ) );
8333   QVERIFY( def->allowNewTableNames() );
8334 }
8335 
parameterFieldMapping()8336 void TestQgsProcessing::parameterFieldMapping()
8337 {
8338   QgsProcessingContext context;
8339 
8340   // not optional!
8341   std::unique_ptr< QgsProcessingParameterFieldMapping > def( new QgsProcessingParameterFieldMapping( "non_optional", QString(), QStringLiteral( "parent" ), false ) );
8342   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8343   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8344   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8345   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
8346   QVariantMap map;
8347   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8348   map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8349   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8350   map.insert( QStringLiteral( "type" ), QStringLiteral( "t" ) );
8351   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8352   map.insert( QStringLiteral( "expression" ), QStringLiteral( "e" ) );
8353   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map ) );
8354   QVariantMap map2;
8355   map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8356   map2.insert( QStringLiteral( "type" ), QStringLiteral( "t2" ) );
8357   map2.insert( QStringLiteral( "expression" ), QStringLiteral( "e2" ) );
8358   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map << map2 ) );
8359 
8360   QVariantMap params;
8361   params.insert( "non_optional", QVariantList() << map << map2 );
8362 
8363   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8364   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
8365   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
8366   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
8367   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8368   QCOMPARE( def->valueAsPythonString( QVariant( QVariantList() << map << map2 ), context ), QStringLiteral( "[{'expression': 'e','name': 'n','type': 't'},{'expression': 'e2','name': 'n2','type': 't2'}]" ) );
8369 
8370   QString pythonCode = def->asPythonString();
8371   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFieldMapping('non_optional', '', parentLayerParameterName='parent')" ) );
8372 
8373   const QVariantMap mapDef = def->toVariantMap();
8374   QgsProcessingParameterFieldMapping fromMap( "x" );
8375   QVERIFY( fromMap.fromVariantMap( mapDef ) );
8376   QCOMPARE( fromMap.name(), def->name() );
8377   QCOMPARE( fromMap.description(), def->description() );
8378   QCOMPARE( fromMap.flags(), def->flags() );
8379   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8380   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
8381   def.reset( dynamic_cast< QgsProcessingParameterFieldMapping *>( QgsProcessingParameters::parameterFromVariantMap( mapDef ) ) );
8382   QVERIFY( dynamic_cast< QgsProcessingParameterFieldMapping *>( def.get() ) );
8383 
8384   def->setParentLayerParameterName( QString() );
8385   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8386   def->setParentLayerParameterName( QStringLiteral( "test_layer" ) );
8387   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "test_layer" ) );
8388 
8389 
8390   // optional
8391   def.reset( new QgsProcessingParameterFieldMapping( "non_optional", QString(), QStringLiteral( "parent" ), true ) );
8392   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8393   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8394   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8395   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
8396   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map << map2 ) );
8397   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8398 
8399   pythonCode = def->asPythonString();
8400   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFieldMapping('non_optional', '', parentLayerParameterName='parent', optional=True)" ) );
8401 }
8402 
parameterAggregate()8403 void TestQgsProcessing::parameterAggregate()
8404 {
8405   QgsProcessingContext context;
8406 
8407   // not optional!
8408   std::unique_ptr< QgsProcessingParameterAggregate > def( new QgsProcessingParameterAggregate( "non_optional", QString(), QStringLiteral( "parent" ), false ) );
8409   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8410   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8411   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8412   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
8413   QVariantMap map;
8414   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8415   map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8416   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8417   map.insert( QStringLiteral( "type" ), QStringLiteral( "t" ) );
8418   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8419   map.insert( QStringLiteral( "aggregate" ), QStringLiteral( "e" ) );
8420   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << map ) );
8421   map.insert( QStringLiteral( "input" ), QStringLiteral( "i" ) );
8422   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map ) );
8423   QVariantMap map2;
8424   map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8425   map2.insert( QStringLiteral( "type" ), QStringLiteral( "t2" ) );
8426   map2.insert( QStringLiteral( "aggregate" ), QStringLiteral( "e2" ) );
8427   map2.insert( QStringLiteral( "input" ), QStringLiteral( "i2" ) );
8428   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map << map2 ) );
8429 
8430   QVariantMap params;
8431   params.insert( "non_optional", QVariantList() << map << map2 );
8432 
8433   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8434   QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) );
8435   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) );
8436   QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) );
8437   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8438   QCOMPARE( def->valueAsPythonString( QVariant( QVariantList() << map << map2 ), context ), QStringLiteral( "[{'aggregate': 'e','input': 'i','name': 'n','type': 't'},{'aggregate': 'e2','input': 'i2','name': 'n2','type': 't2'}]" ) );
8439 
8440   QString pythonCode = def->asPythonString();
8441   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAggregate('non_optional', '', parentLayerParameterName='parent')" ) );
8442 
8443   const QVariantMap mapDef = def->toVariantMap();
8444   QgsProcessingParameterAggregate fromMap( "x" );
8445   QVERIFY( fromMap.fromVariantMap( mapDef ) );
8446   QCOMPARE( fromMap.name(), def->name() );
8447   QCOMPARE( fromMap.description(), def->description() );
8448   QCOMPARE( fromMap.flags(), def->flags() );
8449   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8450   QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() );
8451   def.reset( dynamic_cast< QgsProcessingParameterAggregate *>( QgsProcessingParameters::parameterFromVariantMap( mapDef ) ) );
8452   QVERIFY( dynamic_cast< QgsProcessingParameterAggregate *>( def.get() ) );
8453 
8454   def->setParentLayerParameterName( QString() );
8455   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8456   def->setParentLayerParameterName( QStringLiteral( "test_layer" ) );
8457   QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "test_layer" ) );
8458 
8459 
8460   // optional
8461   def.reset( new QgsProcessingParameterAggregate( "non_optional", QString(), QStringLiteral( "parent" ), true ) );
8462   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8463   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8464   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8465   QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
8466   QVERIFY( def->checkValueIsAcceptable( QVariantList() << map << map2 ) );
8467   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8468 
8469   pythonCode = def->asPythonString();
8470   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAggregate('non_optional', '', parentLayerParameterName='parent', optional=True)" ) );
8471 }
8472 
parameterTinInputLayers()8473 void TestQgsProcessing::parameterTinInputLayers()
8474 {
8475   QgsProcessingContext context;
8476   QgsProject project;
8477   context.setProject( &project );
8478   QgsVectorLayer *vectorLayer = new QgsVectorLayer( QStringLiteral( "Point" ),
8479       QStringLiteral( "PointLayerForTin" ),
8480       QStringLiteral( "memory" ) );
8481   project.addMapLayer( vectorLayer );
8482 
8483   std::unique_ptr< QgsProcessingParameterTinInputLayers > def( new QgsProcessingParameterTinInputLayers( "tin input layer" ) );
8484   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8485   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8486   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8487   QVariantList layerList;
8488   QVERIFY( !def->checkValueIsAcceptable( layerList ) );
8489   QVariantMap layerMap;
8490   layerList.append( layerMap );
8491   QVERIFY( !def->checkValueIsAcceptable( layerList ) );
8492   layerMap["source"] = "layerName";
8493   layerMap["type"] = 0;
8494   layerMap["attributeIndex"] = -1;
8495   layerList[0] = layerMap;
8496   QVERIFY( def->checkValueIsAcceptable( layerList ) );
8497   QVERIFY( !def->checkValueIsAcceptable( layerList, &context ) ); //no corresponding layer in the context's project
8498   layerMap["source"] = "PointLayerForTin";
8499   layerMap["attributeIndex"] = 1; //change for invalid attribute index
8500   layerList[0] = layerMap;
8501   QVERIFY( !def->checkValueIsAcceptable( layerList, &context ) );
8502 
8503   layerMap["attributeIndex"] = -1; //valid attribute index (-1 is for Z coordinate of features)
8504   layerList[0] = layerMap;
8505   QVERIFY( def->checkValueIsAcceptable( layerList, &context ) );
8506 
8507   const QString valueAsPythonString = def->valueAsPythonString( layerList, context );
8508   QCOMPARE( valueAsPythonString, QStringLiteral( "[{'source': 'PointLayerForTin','type': 0,'attributeIndex': -1}]" ) );
8509 
8510   const QString pythonCode = def->asPythonString();
8511   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterTinInputLayers('tin input layer', '')" ) );
8512 }
8513 
parameterMeshDatasetGroups()8514 void TestQgsProcessing::parameterMeshDatasetGroups()
8515 {
8516   QgsProcessingContext context;
8517   QgsProject project;
8518   context.setProject( &project );
8519 
8520   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariant() ), QList<int>( {0} ) );
8521   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariantList() ), QList<int>( {0} ) );
8522   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( 3 ), QList<int>( {3} ) );
8523   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariant( "3" ) ), QList<int>( {3} ) );
8524   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariantList( { "3", "4", "5"} ) ), QList<int>( {3, 4, 5 } ) );
8525   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariantList( { 3, 4, 5} ) ), QList<int>( {3, 4, 5 } ) );
8526   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( QVariantList( { 3.0, 4.0, 5.0} ) ), QList<int>( {3, 4, 5 } ) );
8527 
8528   QSet<int> supportedData;
8529   supportedData << QgsMeshDatasetGroupMetadata::DataOnVertices;
8530   std::unique_ptr< QgsProcessingParameterMeshDatasetGroups> def(
8531     new QgsProcessingParameterMeshDatasetGroups( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ), QString(), supportedData ) );
8532 
8533   QVERIFY( def->type() == QLatin1String( "meshdatasetgroups" ) );
8534   QVERIFY( def->isDataTypeSupported( QgsMeshDatasetGroupMetadata::DataOnVertices ) );
8535   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8536   QVERIFY( def->checkValueIsAcceptable( 1.0 ) );
8537   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8538   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
8539   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
8540   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8541   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); //not optional
8542 
8543   QVariantList groupsList;
8544   QVERIFY( !def->checkValueIsAcceptable( groupsList ) ); //not optional
8545 
8546   groupsList.append( 0 );
8547   QVERIFY( def->checkValueIsAcceptable( groupsList ) );
8548   groupsList.append( 5 );
8549   QVERIFY( def->checkValueIsAcceptable( groupsList ) );
8550 
8551   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8552 
8553   QString valueAsPythonString = def->valueAsPythonString( groupsList, context );
8554   QCOMPARE( valueAsPythonString, QStringLiteral( "[0,5]" ) );
8555   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsList ), QList<int>() << 0 << 5 );
8556 
8557   QString pythonCode = def->asPythonString();
8558   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetGroups('dataset groups', 'groups', dataType=[QgsMeshDatasetGroupMetadata.DataOnVertices])" ) );
8559 
8560   // optional, layer parameter and data on faces
8561   supportedData << QgsMeshDatasetGroupMetadata::DataOnFaces;
8562   def.reset( new QgsProcessingParameterMeshDatasetGroups(
8563                QStringLiteral( "dataset groups" ),
8564                QStringLiteral( "groups" ),
8565                QStringLiteral( "layer parameter" ),
8566                supportedData, true ) );
8567   QVERIFY( def->isDataTypeSupported( QgsMeshDatasetGroupMetadata::DataOnFaces ) );
8568   QVERIFY( def->checkValueIsAcceptable( 1 ) );
8569   QVERIFY( def->checkValueIsAcceptable( 1.0 ) );
8570   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8571   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8572   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
8573   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
8574   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8575   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8576   groupsList = QVariantList();
8577   QVERIFY( def->checkValueIsAcceptable( groupsList ) );
8578   groupsList.append( 2 );
8579   QVERIFY( def->checkValueIsAcceptable( groupsList ) );
8580   groupsList.append( 6 );
8581   QVERIFY( def->checkValueIsAcceptable( groupsList ) );
8582 
8583   valueAsPythonString = def->valueAsPythonString( groupsList, context );
8584   QCOMPARE( valueAsPythonString, QStringLiteral( "[2,6]" ) );
8585   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsList ), QList<int>() << 2 << 6 );
8586 
8587   QVERIFY( !def->dependsOnOtherParameters().isEmpty() );
8588   QCOMPARE( def->meshLayerParameterName(), QStringLiteral( "layer parameter" ) );
8589 
8590   pythonCode = def->asPythonString();
8591   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetGroups('dataset groups', 'groups', meshLayerParameterName='layer parameter', dataType=[QgsMeshDatasetGroupMetadata.DataOnFaces,QgsMeshDatasetGroupMetadata.DataOnVertices], optional=True)" ) );
8592 }
8593 
parameterMeshDatasetTime()8594 void TestQgsProcessing::parameterMeshDatasetTime()
8595 {
8596   QgsProcessingContext context;
8597   QgsProject project;
8598   context.setProject( &project );
8599 
8600   std::unique_ptr< QgsProcessingParameterMeshDatasetTime> def( new QgsProcessingParameterMeshDatasetTime( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ) ) );
8601   QVERIFY( def->type() == QLatin1String( "meshdatasettime" ) );
8602   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ) ) ) );
8603   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ) ) ).toString() ) );
8604   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
8605   QVERIFY( !def->checkValueIsAcceptable( 1.0 ) );
8606   QVERIFY( !def->checkValueIsAcceptable( "test" ) );
8607   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) );
8608   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) );
8609   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8610   QVERIFY( !def->checkValueIsAcceptable( QStringList() ) );
8611   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
8612 
8613   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ) ) ),
8614             QStringLiteral( "defined-date-time" ) );
8615   QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ) ) ),
8616             QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ), Qt::UTC ) );
8617   QCOMPARE( def->valueAsPythonString( QDateTime( QDate( 2020, 01, 01 ), QTime( 10, 0, 0 ) ), context ),
8618             QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2020, 1, 1), QTime(10, 0, 0))}" ) );
8619 
8620   QVariantMap value;
8621   QVERIFY( !def->checkValueIsAcceptable( value ) );
8622   value[QStringLiteral( "test" )] = QStringLiteral( "test" );
8623   QVERIFY( !def->checkValueIsAcceptable( value ) );
8624 
8625   value.clear();
8626   value[QStringLiteral( "type" )] = QStringLiteral( "test" );
8627   QVERIFY( !def->checkValueIsAcceptable( value ) );
8628 
8629   value[QStringLiteral( "type" )] = QStringLiteral( "static" );
8630   QVERIFY( def->checkValueIsAcceptable( value ) );
8631   QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'static'}" ) );
8632   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "static" ) );
8633 
8634   value[QStringLiteral( "type" )] = QStringLiteral( "current-context-time" );
8635   QVERIFY( def->checkValueIsAcceptable( value ) );
8636   QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'current-context-time'}" ) );
8637   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "current-context-time" ) );
8638 
8639   value[QStringLiteral( "type" )] = QStringLiteral( "defined-date-time" );
8640   QVERIFY( !def->checkValueIsAcceptable( value ) );
8641   value[QStringLiteral( "value" )] = QDateTime( QDate( 2123, 1, 2 ), QTime( 1, 2, 3 ) );
8642   QVERIFY( def->checkValueIsAcceptable( value ) );
8643   QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2123, 1, 2), QTime(1, 2, 3))}" ) );
8644   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "defined-date-time" ) );
8645   QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( value ), QDateTime( QDate( 2123, 1, 2 ), QTime( 1, 2, 3 ) ) );
8646   QVERIFY( !QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( value ).isValid() );
8647 
8648   value.clear();
8649   value[QStringLiteral( "type" )] = QStringLiteral( "dataset-time-step" );
8650   QVERIFY( !def->checkValueIsAcceptable( value ) );
8651   value[QStringLiteral( "value" )] = QVariantList() << 1 << 5;
8652   QVERIFY( def->checkValueIsAcceptable( value ) );
8653   QCOMPARE( def->valueAsPythonString( value, context ), QStringLiteral( "{'type': 'dataset-time-step','value': [1,5]}" ) );
8654   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( value ), QStringLiteral( "dataset-time-step" ) );
8655   QVERIFY( !QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( value ).isValid() );
8656   QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( value ) == QgsMeshDatasetIndex( 1, 5 ) );
8657 
8658   QString pythonCode = def->asPythonString();
8659   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetTime('dataset groups', 'groups')" ) );
8660 
8661   QVERIFY( def->dependsOnOtherParameters().isEmpty() );
8662 
8663   def.reset( new QgsProcessingParameterMeshDatasetTime( QStringLiteral( "dataset groups" ), QStringLiteral( "groups" ), QStringLiteral( "layer parameter" ), QStringLiteral( "dataset group parameter" ) ) );
8664   pythonCode = def->asPythonString();
8665   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterMeshDatasetTime('dataset groups', 'groups', meshLayerParameterName='layer parameter', datasetGroupParameterName='dataset group parameter')" ) );
8666 
8667   QVERIFY( !def->dependsOnOtherParameters().isEmpty() );
8668   QCOMPARE( def->meshLayerParameterName(), QStringLiteral( "layer parameter" ) );
8669   QCOMPARE( def->datasetGroupParameterName(), QStringLiteral( "dataset group parameter" ) );
8670 }
8671 
parameterDateTime()8672 void TestQgsProcessing::parameterDateTime()
8673 {
8674   QgsProcessingContext context;
8675 
8676   // not optional!
8677   std::unique_ptr< QgsProcessingParameterDateTime > def( new QgsProcessingParameterDateTime( "non_optional", QString(), QgsProcessingParameterDateTime::DateTime, QDateTime( QDate( 2010, 4, 3 ), QTime( 12, 11, 10 ) ), false ) );
8678   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
8679   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
8680   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 2, 2 ), QTime( 0, 0, 0 ) ) ) );
8681   QVERIFY( def->checkValueIsAcceptable( QDate( 2020, 2, 2 ) ) );
8682   QVERIFY( !def->checkValueIsAcceptable( QTime( 13, 14, 15 ) ) );
8683   QVERIFY( def->checkValueIsAcceptable( "2020-02-03" ) );
8684   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) ) );
8685   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
8686   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8687   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
8688 
8689   // QDateTime value
8690   QVariantMap params;
8691   params.insert( "non_optional", QDateTime( QDate( 2010, 3, 4 ), QTime( 12, 11, 10 ) ) );
8692   QDateTime d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8693   QCOMPARE( d, QDateTime( QDate( 2010, 3, 4 ), QTime( 12, 11, 10 ) ) );
8694   QTime t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8695   QCOMPARE( t, QTime( 12, 11, 10 ) );
8696 
8697   params.insert( "non_optional", QDateTime( QDate( 2010, 3, 4 ), QTime( 0, 0, 0 ) ) );
8698   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8699   QCOMPARE( d, QDateTime( QDate( 2010, 3, 4 ), QTime() ) );
8700 
8701   // string representing a datetime
8702   params.insert( "non_optional", QString( "2010-03-04" ) );
8703   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8704   QCOMPARE( d, QDateTime( QDate( 2010, 3, 4 ), QTime() ) );
8705 
8706   // expression
8707   params.insert( "non_optional", QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) );
8708   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8709   QCOMPARE( d, QDateTime( QDate( 2010, 2, 5 ), QTime() ) );
8710 
8711   // nonsense string
8712   params.insert( "non_optional", QString( "i'm not a datetime, and nothing you can do will make me one" ) );
8713   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8714   QCOMPARE( d, QDateTime( QDate( 2010, 4, 3 ), QTime( 12, 11, 10 ) ) );
8715 
8716   // with min value
8717   def->setMinimum( QDateTime( QDate( 2015, 1, 1 ), QTime( 0, 0, 0 ) ) );
8718   QVERIFY( !def->checkValueIsAcceptable( QDateTime( QDate( 2014, 12, 31 ), QTime( 0, 0, 0 ) ) ) );
8719   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2014-12-31" ) ) );
8720   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 12, 31 ), QTime( 0, 0, 0 ) ) ) );
8721   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "2020-12-31" ) ) );
8722   // with max value
8723   def->setMaximum( QDateTime( QDate( 2015, 12, 31 ), QTime( 0, 0, 0 ) ) );
8724   QVERIFY( !def->checkValueIsAcceptable( QDateTime( QDate( 2014, 12, 31 ), QTime( 0, 0, 0 ) ) ) );
8725   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2014-12-31" ) ) );
8726   QVERIFY( !def->checkValueIsAcceptable( QDateTime( QDate( 2016, 1, 1 ), QTime( 0, 0, 0 ) ) ) );
8727   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2016-01-01" ) ) );
8728   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2015, 12, 31 ), QTime( 0, 0, 0 ) ) ) );
8729   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "2015-12-31" ) ) );
8730 
8731   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
8732   QCOMPARE( def->valueAsPythonString( QDateTime( QDate( 2014, 12, 31 ), QTime( 0, 0, 0 ) ), context ), QStringLiteral( "QDateTime(QDate(2014, 12, 31), QTime(0, 0, 0))" ) );
8733   QCOMPARE( def->valueAsPythonString( QDateTime( QDate( 2014, 12, 31 ), QTime( 12, 11, 10 ) ), context ), QStringLiteral( "QDateTime(QDate(2014, 12, 31), QTime(12, 11, 10))" ) );
8734   QCOMPARE( def->valueAsPythonString( QStringLiteral( "2015-12-31" ), context ), QStringLiteral( "2015-12-31" ) );
8735   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
8736 
8737   QString pythonCode = def->asPythonString();
8738   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('non_optional', '', type=QgsProcessingParameterDateTime.DateTime, minValue=QDateTime(QDate(2015, 1, 1), QTime(0, 0, 0)), maxValue=QDateTime(QDate(2015, 12, 31), QTime(0, 0, 0)), defaultValue=QDateTime(QDate(2010, 4, 3), QTime(12, 11, 10)))" ) );
8739 
8740   QString code = def->asScriptCode();
8741   QCOMPARE( code.left( 43 ), QStringLiteral( "##non_optional=datetime 2010-04-03T12:11:10" ) );
8742   std::unique_ptr< QgsProcessingParameterDateTime > fromCode( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8743   QVERIFY( fromCode.get() );
8744   QCOMPARE( fromCode->name(), def->name() );
8745   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
8746   QCOMPARE( fromCode->flags(), def->flags() );
8747   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8748 
8749   QVariantMap map = def->toVariantMap();
8750   QgsProcessingParameterDateTime fromMap( "x" );
8751   QVERIFY( fromMap.fromVariantMap( map ) );
8752   QCOMPARE( fromMap.name(), def->name() );
8753   QCOMPARE( fromMap.description(), def->description() );
8754   QCOMPARE( fromMap.flags(), def->flags() );
8755   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8756   QCOMPARE( fromMap.minimum(), def->minimum() );
8757   QCOMPARE( fromMap.maximum(), def->maximum() );
8758   QCOMPARE( fromMap.dataType(), def->dataType() );
8759   def.reset( dynamic_cast< QgsProcessingParameterDateTime *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
8760   QVERIFY( dynamic_cast< QgsProcessingParameterDateTime *>( def.get() ) );
8761 
8762   // optional
8763   def.reset( new QgsProcessingParameterDateTime( "optional", QString(), QgsProcessingParameterDateTime::DateTime, QDateTime( QDate( 2018, 5, 6 ), QTime( 4, 5, 6 ) ), true ) );
8764   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
8765   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
8766   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 2, 2 ), QTime( 0, 0, 0 ) ) ) );
8767   QVERIFY( def->checkValueIsAcceptable( QDate( 2020, 2, 2 ) ) );
8768   QVERIFY( def->checkValueIsAcceptable( "2020-02-03" ) );
8769   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) ) );
8770   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
8771   QVERIFY( def->checkValueIsAcceptable( "" ) );
8772   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
8773 
8774   params.insert( "optional",  QVariant() );
8775   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8776   QCOMPARE( d, QDateTime( QDate( 2018, 5, 6 ), QTime( 4, 5, 6 ) ) );
8777   params.insert( "optional",  QString() );
8778   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8779   QCOMPARE( d, QDateTime( QDate( 2018, 5, 6 ), QTime( 4, 5, 6 ) ) );
8780   params.insert( "optional",  QVariant( "aaaa" ) );
8781   d = QgsProcessingParameters::parameterAsDateTime( def.get(), params, context );
8782   QCOMPARE( d, QDateTime( QDate( 2018, 5, 6 ), QTime( 4, 5, 6 ) ) );
8783 
8784   pythonCode = def->asPythonString();
8785   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('optional', '', optional=True, type=QgsProcessingParameterDateTime.DateTime, defaultValue=QDateTime(QDate(2018, 5, 6), QTime(4, 5, 6)))" ) );
8786 
8787   code = def->asScriptCode();
8788   QCOMPARE( code.left( 48 ), QStringLiteral( "##optional=optional datetime 2018-05-06T04:05:06" ) );
8789   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8790   QVERIFY( fromCode.get() );
8791   QCOMPARE( fromCode->name(), def->name() );
8792   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8793   QCOMPARE( fromCode->flags(), def->flags() );
8794   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8795 
8796   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional datetime None" ) ) ) );
8797   QVERIFY( fromCode.get() );
8798   QCOMPARE( fromCode->name(), def->name() );
8799   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8800   QCOMPARE( fromCode->flags(), def->flags() );
8801   QVERIFY( !fromCode->defaultValue().isValid() );
8802 
8803   // non-optional, invalid default
8804   def.reset( new QgsProcessingParameterDateTime( "non_optional", QString(), QgsProcessingParameterDateTime::DateTime, QVariant(), false ) );
8805   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
8806   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
8807   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 2, 2 ), QTime( 0, 0, 0 ) ) ) );
8808   QVERIFY( def->checkValueIsAcceptable( QDate( 2020, 2, 2 ) ) );
8809   QVERIFY( def->checkValueIsAcceptable( "2020-02-03" ) );
8810   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) ) );
8811   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
8812   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8813   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should NOT be acceptable, falls back to invalid default value
8814 
8815 
8816   // date only mode
8817 
8818   // not optional!
8819   def.reset( new QgsProcessingParameterDateTime( "non_optional", QString(), QgsProcessingParameterDateTime::Date, QDate( 2010, 4, 3 ), false ) );
8820   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
8821   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
8822   QVERIFY( def->checkValueIsAcceptable( QDateTime( QDate( 2020, 2, 2 ), QTime( 0, 0, 0 ) ) ) );
8823   QVERIFY( def->checkValueIsAcceptable( QDate( 2020, 2, 2 ) ) );
8824   QVERIFY( !def->checkValueIsAcceptable( QTime( 13, 14, 15 ) ) );
8825   QVERIFY( def->checkValueIsAcceptable( "2020-02-03" ) );
8826   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) ) );
8827   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
8828   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8829   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
8830 
8831   // QDateTime value
8832   params.insert( "non_optional", QDateTime( QDate( 2010, 3, 4 ), QTime( 12, 11, 10 ) ) );
8833   QDate dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8834   QCOMPARE( dt, QDate( 2010, 3, 4 ) );
8835 
8836   params.insert( "non_optional", QDate( 2010, 3, 4 ) );
8837   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8838   QCOMPARE( dt, QDate( 2010, 3, 4 ) );
8839 
8840   // string representing a date
8841   params.insert( "non_optional", QString( "2010-03-04" ) );
8842   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8843   QCOMPARE( dt, QDate( 2010, 3, 4 ) );
8844 
8845   // expression
8846   params.insert( "non_optional", QgsProperty::fromExpression( "to_date( '2010-02-03') +  to_interval( '2 days')" ) );
8847   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8848   QCOMPARE( dt, QDate( 2010, 2, 5 ) );
8849 
8850   // nonsense string
8851   params.insert( "non_optional", QString( "i'm not a datetime, and nothing you can do will make me one" ) );
8852   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8853   QCOMPARE( dt, QDate( QDate( 2010, 4, 3 ) ) );
8854 
8855   // with min value
8856   def->setMinimum( QDateTime( QDate( 2015, 1, 1 ), QTime( 0, 0, 0 ) ) );
8857   QVERIFY( !def->checkValueIsAcceptable( QDate( 2014, 12, 31 ) ) );
8858   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2014-12-31" ) ) );
8859   QVERIFY( def->checkValueIsAcceptable( QDate( 2020, 12, 31 ) ) );
8860   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "2020-12-31" ) ) );
8861   // with max value
8862   def->setMaximum( QDateTime( QDate( 2015, 12, 31 ), QTime( 0, 0, 0 ) ) );
8863   QVERIFY( !def->checkValueIsAcceptable( QDate( 2014, 12, 31 ) ) );
8864   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2014-12-31" ) ) );
8865   QVERIFY( !def->checkValueIsAcceptable( QDate( 2016, 1, 1 ) ) );
8866   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "2016-01-01" ) ) );
8867   QVERIFY( def->checkValueIsAcceptable( QDate( 2015, 12, 31 ) ) );
8868   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "2015-12-31" ) ) );
8869 
8870   QCOMPARE( def->valueAsPythonString( QDate( 2014, 12, 31 ), context ), QStringLiteral( "QDate(2014, 12, 31)" ) );
8871 
8872   pythonCode = def->asPythonString();
8873   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('non_optional', '', type=QgsProcessingParameterDateTime.Date, minValue=QDateTime(QDate(2015, 1, 1), QTime(0, 0, 0)), maxValue=QDateTime(QDate(2015, 12, 31), QTime(0, 0, 0)), defaultValue=QDate(2010, 4, 3))" ) );
8874 
8875   map = def->toVariantMap();
8876   fromMap =  QgsProcessingParameterDateTime( "x" );
8877   QVERIFY( fromMap.fromVariantMap( map ) );
8878   QCOMPARE( fromMap.name(), def->name() );
8879   QCOMPARE( fromMap.description(), def->description() );
8880   QCOMPARE( fromMap.flags(), def->flags() );
8881   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8882   QCOMPARE( fromMap.minimum(), def->minimum() );
8883   QCOMPARE( fromMap.maximum(), def->maximum() );
8884   QCOMPARE( fromMap.dataType(), def->dataType() );
8885   def.reset( dynamic_cast< QgsProcessingParameterDateTime *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
8886   QVERIFY( dynamic_cast< QgsProcessingParameterDateTime *>( def.get() ) );
8887 
8888   // optional
8889   def.reset( new QgsProcessingParameterDateTime( "optional", QString(), QgsProcessingParameterDateTime::Date, QDate( 2018, 5, 6 ), true ) );
8890 
8891   params.insert( "optional",  QVariant() );
8892   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8893   QCOMPARE( dt, QDate( 2018, 5, 6 ) );
8894   params.insert( "optional",  QString() );
8895   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8896   QCOMPARE( dt, QDate( 2018, 5, 6 ) );
8897   params.insert( "optional",  QVariant( "aaaa" ) );
8898   dt = QgsProcessingParameters::parameterAsDate( def.get(), params, context );
8899   QCOMPARE( dt, QDate( 2018, 5, 6 ) );
8900 
8901   pythonCode = def->asPythonString();
8902   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('optional', '', optional=True, type=QgsProcessingParameterDateTime.Date, defaultValue=QDate(2018, 5, 6))" ) );
8903 
8904   code = def->asScriptCode();
8905   QCOMPARE( code, QStringLiteral( "##optional=optional datetime 2018-05-06" ) );
8906   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
8907   QVERIFY( fromCode.get() );
8908   QCOMPARE( fromCode->name(), def->name() );
8909   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8910   QCOMPARE( fromCode->flags(), def->flags() );
8911   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
8912 
8913   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional datetime None" ) ) ) );
8914   QVERIFY( fromCode.get() );
8915   QCOMPARE( fromCode->name(), def->name() );
8916   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
8917   QCOMPARE( fromCode->flags(), def->flags() );
8918   QVERIFY( !fromCode->defaultValue().isValid() );
8919 
8920   // time only mode
8921 
8922   // not optional!
8923   def.reset( new QgsProcessingParameterDateTime( "non_optional", QString(), QgsProcessingParameterDateTime::Time, QTime( 12, 11, 13 ), false ) );
8924   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
8925   QVERIFY( !def->checkValueIsAcceptable( "1.1" ) );
8926   QVERIFY( !def->checkValueIsAcceptable( QDateTime( QDate( 2020, 2, 2 ), QTime( 0, 0, 0 ) ) ) );
8927   QVERIFY( !def->checkValueIsAcceptable( QDate( 2020, 2, 2 ) ) );
8928   QVERIFY( def->checkValueIsAcceptable( QTime( 13, 14, 15 ) ) );
8929   QVERIFY( def->checkValueIsAcceptable( "13:14:15" ) );
8930   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromExpression( "to_time('12:30:01') +  to_interval( '2 minutes')" ) ) );
8931   QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) );
8932   QVERIFY( !def->checkValueIsAcceptable( "" ) );
8933   QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be acceptable, falls back to default value
8934 
8935   // QTime value
8936   params.insert( "non_optional", QTime( 12, 11, 10 ) );
8937   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8938   QCOMPARE( t, QTime( 12, 11, 10 ) );
8939 
8940   params.insert( "non_optional", QDate( 2010, 3, 4 ) );
8941   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8942   QCOMPARE( t, QTime( 12, 11, 13 ) );
8943 
8944   // string representing a time
8945   params.insert( "non_optional", QString( "13:14:15" ) );
8946   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8947   QCOMPARE( t, QTime( 13, 14, 15 ) );
8948 
8949   // expression
8950   params.insert( "non_optional", QgsProperty::fromExpression( "to_time('12:30:01') +  to_interval( '2 minutes')" ) );
8951   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8952   QCOMPARE( t, QTime( 12, 32, 1 ) );
8953 
8954   // nonsense string
8955   params.insert( "non_optional", QString( "i'm not a datetime, and nothing you can do will make me one" ) );
8956   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8957   QCOMPARE( t, QTime( 12, 11, 13 ) );
8958 
8959   // with min value
8960   def->setMinimum( QDateTime( QDate( 1, 1, 1 ), QTime( 10, 0, 0 ) ) );
8961   QVERIFY( !def->checkValueIsAcceptable( QTime( 9, 0, 0 ) ) );
8962   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "9:00:00" ) ) );
8963   QVERIFY( def->checkValueIsAcceptable( QTime( 12, 0, 0 ) ) );
8964   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "12:00:00" ) ) );
8965   // with max value
8966   def->setMaximum( QDateTime( QDate( 1, 1, 1 ), QTime( 11, 0, 0 ) ) );
8967   QVERIFY( !def->checkValueIsAcceptable( QTime( 9, 0, 0 ) ) );
8968   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "9:00:00" ) ) );
8969   QVERIFY( !def->checkValueIsAcceptable( QTime( 11, 0, 1 ) ) );
8970   QVERIFY( !def->checkValueIsAcceptable( QStringLiteral( "11:00:01" ) ) );
8971   QVERIFY( def->checkValueIsAcceptable( QTime( 10, 40, 1 ) ) );
8972   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "10:40:01" ) ) );
8973 
8974   QCOMPARE( def->valueAsPythonString( QTime( 13, 14, 15 ), context ), QStringLiteral( "QTime(13, 14, 15)" ) );
8975 
8976   pythonCode = def->asPythonString();
8977   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('non_optional', '', type=QgsProcessingParameterDateTime.Time, minValue=QDateTime(QDate(1, 1, 1), QTime(10, 0, 0)), maxValue=QDateTime(QDate(1, 1, 1), QTime(11, 0, 0)), defaultValue=QTime(12, 11, 13))" ) );
8978 
8979   map = def->toVariantMap();
8980   fromMap =  QgsProcessingParameterDateTime( "x" );
8981   QVERIFY( fromMap.fromVariantMap( map ) );
8982   QCOMPARE( fromMap.name(), def->name() );
8983   QCOMPARE( fromMap.description(), def->description() );
8984   QCOMPARE( fromMap.flags(), def->flags() );
8985   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
8986   QCOMPARE( fromMap.minimum(), def->minimum() );
8987   QCOMPARE( fromMap.maximum(), def->maximum() );
8988   QCOMPARE( fromMap.dataType(), def->dataType() );
8989   def.reset( dynamic_cast< QgsProcessingParameterDateTime *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
8990   QVERIFY( dynamic_cast< QgsProcessingParameterDateTime *>( def.get() ) );
8991 
8992   // optional
8993   def.reset( new QgsProcessingParameterDateTime( "optional", QString(), QgsProcessingParameterDateTime::Time, QTime( 14, 15, 16 ), true ) );
8994 
8995   params.insert( "optional",  QVariant() );
8996   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
8997   QCOMPARE( t, QTime( 14, 15, 16 ) );
8998   params.insert( "optional",  QString() );
8999   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
9000   QCOMPARE( t, QTime( 14, 15, 16 ) );
9001   params.insert( "optional",  QVariant( "aaaa" ) );
9002   t = QgsProcessingParameters::parameterAsTime( def.get(), params, context );
9003   QCOMPARE( t, QTime( 14, 15, 16 ) );
9004 
9005   pythonCode = def->asPythonString();
9006   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDateTime('optional', '', optional=True, type=QgsProcessingParameterDateTime.Time, defaultValue=QTime(14, 15, 16))" ) );
9007 
9008   code = def->asScriptCode();
9009   QCOMPARE( code.left( 37 ), QStringLiteral( "##optional=optional datetime 14:15:16" ) );
9010   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
9011   QVERIFY( fromCode.get() );
9012   QCOMPARE( fromCode->name(), def->name() );
9013   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
9014   QCOMPARE( fromCode->flags(), def->flags() );
9015   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
9016 
9017   fromCode.reset( dynamic_cast< QgsProcessingParameterDateTime * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional datetime None" ) ) ) );
9018   QVERIFY( fromCode.get() );
9019   QCOMPARE( fromCode->name(), def->name() );
9020   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
9021   QCOMPARE( fromCode->flags(), def->flags() );
9022   QVERIFY( !fromCode->defaultValue().isValid() );
9023 }
9024 
parameterDxfLayers()9025 void TestQgsProcessing::parameterDxfLayers()
9026 {
9027   QgsProcessingContext context;
9028   QgsProject project;
9029   context.setProject( &project );
9030   QgsVectorLayer *vectorLayer = new QgsVectorLayer( QStringLiteral( "Point" ),
9031       QStringLiteral( "PointLayer" ),
9032       QStringLiteral( "memory" ) );
9033   project.addMapLayer( vectorLayer );
9034 
9035   std::unique_ptr< QgsProcessingParameterDxfLayers > def( new QgsProcessingParameterDxfLayers( "dxf input layer" ) );
9036   QVERIFY( !def->checkValueIsAcceptable( 1 ) );
9037   QVERIFY( def->checkValueIsAcceptable( "test" ) );
9038   QVERIFY( !def->checkValueIsAcceptable( "" ) );
9039   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
9040   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( vectorLayer ) ) );
9041 
9042   // should also be OK
9043   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
9044   QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
9045   QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) );
9046   // ... unless we use context, when the check that the layer actually exists is performed
9047   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
9048   QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
9049   QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) );
9050 
9051   QVariantList layerList;
9052   QVERIFY( !def->checkValueIsAcceptable( layerList ) );
9053   QVariantMap layerMap;
9054   layerList.append( layerMap );
9055   QVERIFY( !def->checkValueIsAcceptable( layerList ) );
9056   layerMap["layer"] = "layerName";
9057   layerMap["attributeIndex"] = -1;
9058   layerList[0] = layerMap;
9059   QVERIFY( def->checkValueIsAcceptable( layerList ) );
9060   QVERIFY( !def->checkValueIsAcceptable( layerList, &context ) ); //no corresponding layer in the context's project
9061   layerMap["layer"] = "PointLayer";
9062   layerMap["attributeIndex"] = 1; //change for invalid attribute index
9063   layerList[0] = layerMap;
9064   QVERIFY( !def->checkValueIsAcceptable( layerList, &context ) );
9065 
9066   layerMap["attributeIndex"] = -1;
9067   layerList[0] = layerMap;
9068   QVERIFY( def->checkValueIsAcceptable( layerList, &context ) );
9069 
9070   const QString valueAsPythonString = def->valueAsPythonString( layerList, context );
9071   QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1}]" ).arg( vectorLayer->source() ) );
9072 
9073   const QString pythonCode = def->asPythonString();
9074   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDxfLayers('dxf input layer', '')" ) );
9075 
9076   const QgsDxfExport::DxfLayer dxfLayer( vectorLayer );
9077   QList<QgsDxfExport::DxfLayer> dxfList = def->parameterAsLayers( QVariant( vectorLayer->source() ), context );
9078   QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() );
9079   QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() );
9080   dxfList = def->parameterAsLayers( QVariant( QStringList() << vectorLayer->source() ), context );
9081   QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() );
9082   QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() );
9083   dxfList = def->parameterAsLayers( layerList, context );
9084   QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() );
9085   QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() );
9086 }
9087 
parameterAnnotationLayer()9088 void TestQgsProcessing::parameterAnnotationLayer()
9089 {
9090   // setup a context
9091   QgsProject p;
9092   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
9093   QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
9094   QString vector1 = testDataDir + "multipoint.shp";
9095   QString raster = testDataDir + "landsat.tif";
9096   QFileInfo fi1( raster );
9097   QFileInfo fi2( vector1 );
9098   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
9099   QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" );
9100 
9101   QgsAnnotationLayer *al = new QgsAnnotationLayer( QStringLiteral( "secondary annotation layer" ), QgsAnnotationLayer::LayerOptions( p.transformContext() ) );
9102   Q_ASSERT( al );
9103   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 << al );
9104   QgsProcessingContext context;
9105   context.setProject( &p );
9106 
9107   // not optional!
9108   std::unique_ptr< QgsProcessingParameterAnnotationLayer > def( new QgsProcessingParameterAnnotationLayer( "non_optional", QString(), QString( "somelayer" ), false ) );
9109   QVERIFY( !def->checkValueIsAcceptable( false ) );
9110   QVERIFY( !def->checkValueIsAcceptable( true ) );
9111   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
9112   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
9113   QVERIFY( !def->checkValueIsAcceptable( "" ) );
9114   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
9115   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
9116   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( al ) ) );
9117   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( p.mainAnnotationLayer() ) ) );
9118   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
9119   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
9120   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
9121   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
9122 
9123   // should be OK
9124   QVERIFY( def->checkValueIsAcceptable( "tertiary annotation layer" ) );
9125   // ... unless we use context, when the check that the layer actually exists is performed
9126   QVERIFY( !def->checkValueIsAcceptable( "tertiary annotation layer", &context ) );
9127 
9128   // using existing map layer ID
9129   QVariantMap params;
9130   params.insert( "non_optional",  al->id() );
9131   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context )->id(), al->id() );
9132 
9133   // using existing layer
9134   params.insert( "non_optional",  QVariant::fromValue( al ) );
9135   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context )->id(), al->id() );
9136 
9137   // not annotation layer
9138   params.insert( "non_optional",  r1->id() );
9139   QVERIFY( !QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context ) );
9140 
9141   // using existing non-annotation layer
9142   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
9143   QVERIFY( !QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context ) );
9144 
9145   // string representing a layer source
9146   params.insert( "non_optional", QStringLiteral( "secondary annotation layer" ) );
9147   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context ), al );
9148 
9149   // main annotation layer
9150   params.insert( "non_optional", QStringLiteral( "main" ) );
9151   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context ), p.mainAnnotationLayer() );
9152 
9153   // nonsense string
9154   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
9155   QVERIFY( !QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params, context ) );
9156 
9157   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
9158   QCOMPARE( def->valueAsPythonString( QStringLiteral( "main" ), context ), QStringLiteral( "'main'" ) );
9159   QCOMPARE( def->valueAsPythonString( al->id(), context ), QStringLiteral( "'%1'" ).arg( al->id() ) );
9160   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( al ), context ), QStringLiteral( "'%1'" ).arg( al->id() ) );
9161   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
9162 
9163   QString pythonCode = def->asPythonString();
9164   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterAnnotationLayer('non_optional', '', defaultValue='somelayer')" ) );
9165 
9166   QString code = def->asScriptCode();
9167   QCOMPARE( code, QStringLiteral( "##non_optional=annotation somelayer" ) );
9168   std::unique_ptr< QgsProcessingParameterAnnotationLayer > fromCode( dynamic_cast< QgsProcessingParameterAnnotationLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
9169   QVERIFY( fromCode.get() );
9170   QCOMPARE( fromCode->name(), def->name() );
9171   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
9172   QCOMPARE( fromCode->flags(), def->flags() );
9173   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
9174 
9175   QVariantMap map = def->toVariantMap();
9176   QgsProcessingParameterAnnotationLayer fromMap( "x" );
9177   QVERIFY( fromMap.fromVariantMap( map ) );
9178   QCOMPARE( fromMap.name(), def->name() );
9179   QCOMPARE( fromMap.description(), def->description() );
9180   QCOMPARE( fromMap.flags(), def->flags() );
9181   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
9182   def.reset( dynamic_cast< QgsProcessingParameterAnnotationLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
9183   QVERIFY( dynamic_cast< QgsProcessingParameterAnnotationLayer *>( def.get() ) );
9184 
9185   // optional
9186   def.reset( new QgsProcessingParameterAnnotationLayer( "optional", QString(), al->id(), true ) );
9187   params.insert( "optional",  QVariant() );
9188   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params,  context )->id(), al->id() );
9189   QVERIFY( def->checkValueIsAcceptable( false ) );
9190   QVERIFY( def->checkValueIsAcceptable( true ) );
9191   QVERIFY( def->checkValueIsAcceptable( 5 ) );
9192   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
9193   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.las" ) );
9194   QVERIFY( def->checkValueIsAcceptable( "" ) );
9195   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
9196   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
9197 
9198   pythonCode = def->asPythonString();
9199   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterAnnotationLayer('optional', '', optional=True, defaultValue='" ) + al->id() + "')" ) );
9200 
9201   code = def->asScriptCode();
9202   QCOMPARE( code, QString( QStringLiteral( "##optional=optional annotation " ) + al->id() ) );
9203   fromCode.reset( dynamic_cast< QgsProcessingParameterAnnotationLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
9204   QVERIFY( fromCode.get() );
9205   QCOMPARE( fromCode->name(), def->name() );
9206   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
9207   QCOMPARE( fromCode->flags(), def->flags() );
9208   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
9209 
9210   //optional with direct layer default
9211   def.reset( new QgsProcessingParameterAnnotationLayer( "optional", QString(), QVariant::fromValue( al ), true ) );
9212   QCOMPARE( QgsProcessingParameters::parameterAsAnnotationLayer( def.get(), params,  context )->id(), al->id() );
9213 }
9214 
9215 #ifdef HAVE_EPT
parameterPointCloudLayer()9216 void TestQgsProcessing::parameterPointCloudLayer()
9217 {
9218   // setup a context
9219   QgsProject p;
9220   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) );
9221   QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
9222   QString vector1 = testDataDir + "multipoint.shp";
9223   QString raster = testDataDir + "landsat.tif";
9224   QString pointCloud = testDataDir + "point_clouds/ept/sunshine-coast/ept.json";
9225   QFileInfo fi1( raster );
9226   QFileInfo fi2( vector1 );
9227   QFileInfo fi3( pointCloud );
9228   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
9229   QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" );
9230   QgsPointCloudLayer *pc1 = new QgsPointCloudLayer( fi3.filePath(), "PC1", "ept" );
9231   Q_ASSERT( pc1 );
9232   p.addMapLayers( QList<QgsMapLayer *>() << v1 << r1 << pc1 );
9233   QgsProcessingContext context;
9234   context.setProject( &p );
9235 
9236   // not optional!
9237   std::unique_ptr< QgsProcessingParameterPointCloudLayer > def( new QgsProcessingParameterPointCloudLayer( "non_optional", QString(), QString( "somelayer" ), false ) );
9238   QVERIFY( !def->checkValueIsAcceptable( false ) );
9239   QVERIFY( !def->checkValueIsAcceptable( true ) );
9240   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
9241   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
9242   QVERIFY( !def->checkValueIsAcceptable( "" ) );
9243   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
9244   QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
9245   QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( pc1 ) ) );
9246   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) );
9247   QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) );
9248   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) );
9249   QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) );
9250 
9251   // should be OK
9252   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.las" ) );
9253   // ... unless we use context, when the check that the layer actually exists is performed
9254   QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.las", &context ) );
9255 
9256   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.shp" ) ) );
9257   QVERIFY( !def->createFileFilter().contains( QStringLiteral( "*.tif" ) ) );
9258   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.las" ) ) );
9259   QVERIFY( def->createFileFilter().contains( QStringLiteral( "*.*" ) ) );
9260 
9261   // using existing map layer ID
9262   QVariantMap params;
9263   params.insert( "non_optional",  pc1->id() );
9264   QCOMPARE( QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context )->id(), pc1->id() );
9265 
9266   // using existing layer
9267   params.insert( "non_optional",  QVariant::fromValue( pc1 ) );
9268   QCOMPARE( QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context )->id(), pc1->id() );
9269 
9270   // not mesh layer
9271   params.insert( "non_optional",  r1->id() );
9272   QVERIFY( !QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context ) );
9273 
9274   // using existing non-mesh layer
9275   params.insert( "non_optional",  QVariant::fromValue( r1 ) );
9276   QVERIFY( !QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context ) );
9277 
9278   // string representing a layer source
9279   params.insert( "non_optional", pointCloud );
9280   QCOMPARE( QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context )->publicSource(), pointCloud );
9281 
9282   // nonsense string
9283   params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) );
9284   QVERIFY( !QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params, context ) );
9285 
9286   QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
9287   QCOMPARE( def->valueAsPythonString( pointCloud, context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "point_clouds/ept/sunshine-coast/ept.json'" ) ) );
9288   QCOMPARE( def->valueAsPythonString( pc1->id(), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "point_clouds/ept/sunshine-coast/ept.json'" ) ) );
9289   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( pc1 ), context ), QString( QString( "'" ) + testDataDir + QStringLiteral( "point_clouds/ept/sunshine-coast/ept.json'" ) ) );
9290   QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
9291   QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.las" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.las'" ) );
9292 
9293   QString pythonCode = def->asPythonString();
9294   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterPointCloudLayer('non_optional', '', defaultValue='somelayer')" ) );
9295 
9296   QString code = def->asScriptCode();
9297   QCOMPARE( code, QStringLiteral( "##non_optional=pointcloud somelayer" ) );
9298   std::unique_ptr< QgsProcessingParameterPointCloudLayer > fromCode( dynamic_cast< QgsProcessingParameterPointCloudLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
9299   QVERIFY( fromCode.get() );
9300   QCOMPARE( fromCode->name(), def->name() );
9301   QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) );
9302   QCOMPARE( fromCode->flags(), def->flags() );
9303   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
9304 
9305   QVariantMap map = def->toVariantMap();
9306   QgsProcessingParameterPointCloudLayer fromMap( "x" );
9307   QVERIFY( fromMap.fromVariantMap( map ) );
9308   QCOMPARE( fromMap.name(), def->name() );
9309   QCOMPARE( fromMap.description(), def->description() );
9310   QCOMPARE( fromMap.flags(), def->flags() );
9311   QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
9312   def.reset( dynamic_cast< QgsProcessingParameterPointCloudLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
9313   QVERIFY( dynamic_cast< QgsProcessingParameterPointCloudLayer *>( def.get() ) );
9314 
9315   // optional
9316   def.reset( new QgsProcessingParameterPointCloudLayer( "optional", QString(), pc1->id(), true ) );
9317   params.insert( "optional",  QVariant() );
9318   QCOMPARE( QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params,  context )->id(), pc1->id() );
9319   QVERIFY( def->checkValueIsAcceptable( false ) );
9320   QVERIFY( def->checkValueIsAcceptable( true ) );
9321   QVERIFY( def->checkValueIsAcceptable( 5 ) );
9322   QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) );
9323   QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.las" ) );
9324   QVERIFY( def->checkValueIsAcceptable( "" ) );
9325   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
9326   QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) );
9327 
9328   pythonCode = def->asPythonString();
9329   QCOMPARE( pythonCode, QString( QStringLiteral( "QgsProcessingParameterPointCloudLayer('optional', '', optional=True, defaultValue='" ) + pc1->id() + "')" ) );
9330 
9331   code = def->asScriptCode();
9332   QCOMPARE( code, QString( QStringLiteral( "##optional=optional pointcloud " ) + pc1->id() ) );
9333   fromCode.reset( dynamic_cast< QgsProcessingParameterPointCloudLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
9334   QVERIFY( fromCode.get() );
9335   QCOMPARE( fromCode->name(), def->name() );
9336   QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) );
9337   QCOMPARE( fromCode->flags(), def->flags() );
9338   QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
9339 
9340   //optional with direct layer default
9341   def.reset( new QgsProcessingParameterPointCloudLayer( "optional", QString(), QVariant::fromValue( pc1 ), true ) );
9342   QCOMPARE( QgsProcessingParameters::parameterAsPointCloudLayer( def.get(), params,  context )->id(), pc1->id() );
9343 }
9344 #endif
9345 
checkParamValues()9346 void TestQgsProcessing::checkParamValues()
9347 {
9348   DummyAlgorithm a( "asd" );
9349   a.checkParameterVals();
9350 }
9351 
combineLayerExtent()9352 void TestQgsProcessing::combineLayerExtent()
9353 {
9354   QgsProcessingContext context;
9355   QgsRectangle ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>(), QgsCoordinateReferenceSystem(), context );
9356   QVERIFY( ext.isNull() );
9357 
9358   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
9359 
9360   const QString raster1 = testDataDir + "tenbytenraster.asc";
9361   const QString raster2 = testDataDir + "landsat.tif";
9362   const QFileInfo fi1( raster1 );
9363   const std::unique_ptr< QgsRasterLayer > r1( new QgsRasterLayer( fi1.filePath(), "R1" ) );
9364   const QFileInfo fi2( raster2 );
9365   const std::unique_ptr< QgsRasterLayer > r2( new QgsRasterLayer( fi2.filePath(), "R2" ) );
9366 
9367   ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1.get(), QgsCoordinateReferenceSystem(), context );
9368   QGSCOMPARENEAR( ext.xMinimum(), 1535375.000000, 10 );
9369   QGSCOMPARENEAR( ext.xMaximum(), 1535475, 10 );
9370   QGSCOMPARENEAR( ext.yMinimum(), 5083255, 10 );
9371   QGSCOMPARENEAR( ext.yMaximum(), 5083355, 10 );
9372 
9373   ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1.get() << r2.get(), QgsCoordinateReferenceSystem(), context );
9374   QGSCOMPARENEAR( ext.xMinimum(), 781662, 10 );
9375   QGSCOMPARENEAR( ext.xMaximum(), 1535475, 10 );
9376   QGSCOMPARENEAR( ext.yMinimum(), 3339523, 10 );
9377   QGSCOMPARENEAR( ext.yMaximum(), 5083355, 10 );
9378 
9379   // with reprojection
9380   ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1.get() << r2.get(), QgsCoordinateReferenceSystem::fromEpsgId( 3785 ), context );
9381   QGSCOMPARENEAR( ext.xMinimum(), 1535375.0, 10 );
9382   QGSCOMPARENEAR( ext.xMaximum(), 2008833, 10 );
9383   QGSCOMPARENEAR( ext.yMinimum(), 3523084, 10 );
9384   QGSCOMPARENEAR( ext.yMaximum(), 5083355, 10 );
9385 }
9386 
processingFeatureSource()9387 void TestQgsProcessing::processingFeatureSource()
9388 {
9389   const QString sourceString = QStringLiteral( "test.shp" );
9390   const QgsProcessingFeatureSourceDefinition fs( sourceString, true, 21, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck, QgsFeatureRequest::GeometrySkipInvalid );
9391   QCOMPARE( fs.source.staticValue().toString(), sourceString );
9392   QVERIFY( fs.selectedFeaturesOnly );
9393   QCOMPARE( fs.featureLimit, 21LL );
9394   QCOMPARE( fs.flags, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck );
9395   QCOMPARE( fs.geometryCheck, QgsFeatureRequest::GeometrySkipInvalid );
9396 
9397   // test storing QgsProcessingFeatureSource in variant and retrieving
9398   const QVariant fsInVariant = QVariant::fromValue( fs );
9399   QVERIFY( fsInVariant.isValid() );
9400 
9401   // test converting to variant map and back
9402   const QVariant res = fs.toVariant();
9403   QgsProcessingFeatureSourceDefinition dd;
9404   QVERIFY( dd.loadVariant( res.toMap() ) );
9405   QCOMPARE( dd.source.staticValue().toString(), sourceString );
9406   QVERIFY( dd.selectedFeaturesOnly );
9407   QCOMPARE( dd.featureLimit, 21LL );
9408   QCOMPARE( dd.flags, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck );
9409   QCOMPARE( dd.geometryCheck, QgsFeatureRequest::GeometrySkipInvalid );
9410 
9411   const QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( fsInVariant );
9412   QCOMPARE( fromVar.source.staticValue().toString(), sourceString );
9413   QVERIFY( fromVar.selectedFeaturesOnly );
9414   QCOMPARE( fromVar.featureLimit, 21LL );
9415   QCOMPARE( fromVar.flags, QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck );
9416   QCOMPARE( fromVar.geometryCheck, QgsFeatureRequest::GeometrySkipInvalid );
9417 
9418   // test evaluating parameter as source
9419   QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" );
9420   QgsFeature f( 10001 );
9421   f.setGeometry( QgsGeometry( new QgsPoint( 1, 2 ) ) );
9422   layer->dataProvider()->addFeatures( QgsFeatureList() << f );
9423 
9424   QgsProject p;
9425   p.addMapLayer( layer );
9426   QgsProcessingContext context;
9427   context.setProject( &p );
9428 
9429   // first using static string definition
9430   const std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
9431   QVariantMap params;
9432   params.insert( QStringLiteral( "layer" ), QgsProcessingFeatureSourceDefinition( layer->id(), false ) );
9433   std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
9434   // can't directly match it to layer, so instead just get the feature and test that it matches what we expect
9435   QgsFeature f2;
9436   QVERIFY( source.get() );
9437   QVERIFY( source->getFeatures().nextFeature( f2 ) );
9438   QCOMPARE( f2.geometry().asWkt(), f.geometry().asWkt() );
9439 
9440   // direct map layer
9441   params.insert( QStringLiteral( "layer" ), QVariant::fromValue( layer ) );
9442   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
9443   // can't directly match it to layer, so instead just get the feature and test that it matches what we expect
9444   QVERIFY( source.get() );
9445   QVERIFY( source->getFeatures().nextFeature( f2 ) );
9446   QCOMPARE( f2.geometry().asWkt(), f.geometry().asWkt() );
9447 
9448   // next using property based definition
9449   params.insert( QStringLiteral( "layer" ), QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('%1' + ' ')" ).arg( layer->id() ) ), false ) );
9450   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
9451   // can't directly match it to layer, so instead just get the feature and test that it matches what we expect
9452   QVERIFY( source.get() );
9453   QVERIFY( source->getFeatures().nextFeature( f2 ) );
9454   QCOMPARE( f2.geometry().asWkt(), f.geometry().asWkt() );
9455 
9456   // we also must accept QgsProcessingOutputLayerDefinition - e.g. to allow outputs from earlier child algorithms in models
9457   params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( layer->id() ) ) );
9458   source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) );
9459   // can't directly match it to layer, so instead just get the feature and test that it matches what we expect
9460   QVERIFY( source.get() );
9461   QVERIFY( source->getFeatures().nextFeature( f2 ) );
9462   QCOMPARE( f2.geometry().asWkt(), f.geometry().asWkt() );
9463 }
9464 
processingFeatureSink()9465 void TestQgsProcessing::processingFeatureSink()
9466 {
9467   const QString sinkString( QStringLiteral( "test.shp" ) );
9468   QgsProject p;
9469   QgsProcessingOutputLayerDefinition fs( sinkString, &p );
9470   QgsRemappingSinkDefinition remap;
9471   QVERIFY( !fs.useRemapping() );
9472   remap.setDestinationWkbType( QgsWkbTypes::Point );
9473   fs.setRemappingDefinition( remap );
9474   QVERIFY( fs.useRemapping() );
9475 
9476   QCOMPARE( fs.sink.staticValue().toString(), sinkString );
9477   QCOMPARE( fs.destinationProject, &p );
9478   QCOMPARE( fs.remappingDefinition().destinationWkbType(), QgsWkbTypes::Point );
9479 
9480   // test storing QgsProcessingFeatureSink in variant and retrieving
9481   const QVariant fsInVariant = QVariant::fromValue( fs );
9482   QVERIFY( fsInVariant.isValid() );
9483 
9484   const QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( fsInVariant );
9485   QCOMPARE( fromVar.sink.staticValue().toString(), sinkString );
9486   QCOMPARE( fromVar.destinationProject, &p );
9487   QCOMPARE( fromVar.remappingDefinition().destinationWkbType(), QgsWkbTypes::Point );
9488 
9489   // test evaluating parameter as sink
9490   QgsProcessingContext context;
9491   context.setProject( &p );
9492 
9493   // first using static string definition
9494   std::unique_ptr< QgsProcessingParameterFeatureSink > def( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ) ) );
9495   QVariantMap params;
9496   params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( "memory:test", nullptr ) );
9497   QString dest;
9498   std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) );
9499   QVERIFY( sink.get() );
9500   QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
9501   QVERIFY( layer );
9502   QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
9503 
9504   // next using property based definition
9505   params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('memory' + ':test2')" ) ), nullptr ) );
9506   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
9507   QVERIFY( sink.get() );
9508   QgsVectorLayer *layer2 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
9509   QVERIFY( layer2 );
9510   QCOMPARE( layer2->crs().authid(), QStringLiteral( "EPSG:3113" ) );
9511 
9512   // temporary sink
9513   params.insert( QStringLiteral( "layer" ), QgsProcessing::TEMPORARY_OUTPUT );
9514   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:28356" ), context, dest ) );
9515   QVERIFY( sink.get() );
9516   QgsVectorLayer *layer3 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
9517   QVERIFY( layer3 );
9518   QCOMPARE( layer3->crs().authid(), QStringLiteral( "EPSG:28356" ) );
9519   QCOMPARE( layer3->dataProvider()->name(), QStringLiteral( "memory" ) );
9520 
9521   params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( QgsProcessing::TEMPORARY_OUTPUT ), nullptr ) );
9522   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:28354" ), context, dest ) );
9523   QVERIFY( sink.get() );
9524   QgsVectorLayer *layer4 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
9525   QVERIFY( layer4 );
9526   QCOMPARE( layer4->crs().authid(), QStringLiteral( "EPSG:28354" ) );
9527   QCOMPARE( layer4->dataProvider()->name(), QStringLiteral( "memory" ) );
9528 
9529   // non optional sink
9530   def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QVariant(), false ) );
9531   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
9532   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
9533   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
9534   QVERIFY( !def->checkValueIsAcceptable( QString() ) );
9535   QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
9536   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
9537   params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
9538   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
9539   QVERIFY( sink.get() );
9540 
9541   // optional sink
9542   def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QVariant(), true ) );
9543   QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
9544   QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
9545   QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
9546   QVERIFY( def->checkValueIsAcceptable( QString() ) );
9547   QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
9548   QVERIFY( !def->checkValueIsAcceptable( 5 ) );
9549   params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
9550   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
9551   QVERIFY( sink.get() );
9552   // optional sink, not set - should be no sink
9553   params.insert( QStringLiteral( "layer" ), QVariant() );
9554   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
9555   QVERIFY( !sink.get() );
9556 
9557   //.... unless there's a default set
9558   def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QStringLiteral( "memory:defaultlayer" ), true ) );
9559   params.insert( QStringLiteral( "layer" ), QVariant() );
9560   sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
9561   QVERIFY( sink.get() );
9562 
9563   // appendable
9564   def->setSupportsAppend( true );
9565   QVERIFY( def->supportsAppend() );
9566   QString pythonCode = def->asPythonString();
9567   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('layer', '', optional=True, type=QgsProcessing.TypeMapLayer, createByDefault=True, supportsAppend=True, defaultValue='memory:defaultlayer')" ) );
9568 
9569   const QVariantMap val = def->toVariantMap();
9570   QgsProcessingParameterFeatureSink fromMap( "x" );
9571   QVERIFY( fromMap.fromVariantMap( val ) );
9572   QVERIFY( fromMap.supportsAppend() );
9573 
9574   def->setSupportsAppend( false );
9575   QVERIFY( !def->supportsAppend() );
9576   pythonCode = def->asPythonString();
9577   QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFeatureSink('layer', '', optional=True, type=QgsProcessing.TypeMapLayer, createByDefault=True, defaultValue='memory:defaultlayer')" ) );
9578 }
9579 
algorithmScope()9580 void TestQgsProcessing::algorithmScope()
9581 {
9582   QgsProcessingContext pc;
9583 
9584   // no alg
9585   std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::processingAlgorithmScope( nullptr, QVariantMap(), pc ) );
9586   QVERIFY( scope.get() );
9587 
9588   // with alg
9589   std::unique_ptr< QgsProcessingAlgorithm > alg( new DummyAlgorithm( "alg1" ) );
9590   QVariantMap params;
9591   params.insert( QStringLiteral( "a_param" ), 5 );
9592   scope.reset( QgsExpressionContextUtils::processingAlgorithmScope( alg.get(), params, pc ) );
9593   QVERIFY( scope.get() );
9594   QCOMPARE( scope->variable( QStringLiteral( "algorithm_id" ) ).toString(), alg->id() );
9595 
9596   QgsExpressionContext context;
9597   context.appendScope( scope.release() );
9598   QgsExpression exp( "parameter('bad')" );
9599   QVERIFY( !exp.evaluate( &context ).isValid() );
9600   QgsExpression exp2( "parameter('a_param')" );
9601   QCOMPARE( exp2.evaluate( &context ).toInt(), 5 );
9602 }
9603 
modelScope()9604 void TestQgsProcessing::modelScope()
9605 {
9606   QgsProcessingContext pc;
9607 
9608   QgsProcessingModelAlgorithm alg( "test", "testGroup" );
9609 
9610   QVariantMap variables;
9611   variables.insert( QStringLiteral( "v1" ), 5 );
9612   variables.insert( QStringLiteral( "v2" ), QStringLiteral( "aabbccd" ) );
9613   alg.setVariables( variables );
9614 
9615   QVariantMap params;
9616   params.insert( QStringLiteral( "a_param" ), 5 );
9617   std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
9618   QVERIFY( scope.get() );
9619   QCOMPARE( scope->variable( QStringLiteral( "model_name" ) ).toString(), QStringLiteral( "test" ) );
9620   QCOMPARE( scope->variable( QStringLiteral( "model_group" ) ).toString(), QStringLiteral( "testGroup" ) );
9621   QVERIFY( scope->hasVariable( QStringLiteral( "model_path" ) ) );
9622   QVERIFY( scope->hasVariable( QStringLiteral( "model_folder" ) ) );
9623   QCOMPARE( scope->variable( QStringLiteral( "model_path" ) ).toString(), QString() );
9624   QCOMPARE( scope->variable( QStringLiteral( "model_folder" ) ).toString(), QString() );
9625   QCOMPARE( scope->variable( QStringLiteral( "v1" ) ).toInt(), 5 );
9626   QCOMPARE( scope->variable( QStringLiteral( "v2" ) ).toString(), QStringLiteral( "aabbccd" ) );
9627 
9628   QgsProject p;
9629   pc.setProject( &p );
9630   p.setFileName( TEST_DATA_DIR + QStringLiteral( "/test_file.qgs" ) );
9631   scope.reset( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
9632   QCOMPARE( scope->variable( QStringLiteral( "model_path" ) ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/test_file.qgs" ) ) );
9633   QCOMPARE( scope->variable( QStringLiteral( "model_folder" ) ).toString(), QStringLiteral( TEST_DATA_DIR ) );
9634 
9635   alg.setSourceFilePath( TEST_DATA_DIR + QStringLiteral( "/processing/my_model.model3" ) );
9636   scope.reset( QgsExpressionContextUtils::processingModelAlgorithmScope( &alg, params, pc ) );
9637   QCOMPARE( scope->variable( QStringLiteral( "model_path" ) ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/processing/my_model.model3" ) ) );
9638   QCOMPARE( scope->variable( QStringLiteral( "model_folder" ) ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/processing" ) ) );
9639 
9640   const QgsExpressionContext ctx = alg.createExpressionContext( QVariantMap(), pc );
9641   QVERIFY( scope->hasVariable( QStringLiteral( "model_path" ) ) );
9642   QVERIFY( scope->hasVariable( QStringLiteral( "model_folder" ) ) );
9643 }
9644 
validateInputCrs()9645 void TestQgsProcessing::validateInputCrs()
9646 {
9647   DummyAlgorithm alg( "test" );
9648   alg.runValidateInputCrsChecks();
9649 }
9650 
generateIteratingDestination()9651 void TestQgsProcessing::generateIteratingDestination()
9652 {
9653   QgsProcessingContext context;
9654   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "memory:x", 1, context ).toString(), QStringLiteral( "memory:x_1" ) );
9655   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "memory:x", 2, context ).toString(), QStringLiteral( "memory:x_2" ) );
9656   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "ape.shp", 1, context ).toString(), QStringLiteral( "ape_1.shp" ) );
9657   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "ape.shp", 2, context ).toString(), QStringLiteral( "ape_2.shp" ) );
9658   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "/home/bif.o/ape.shp", 2, context ).toString(), QStringLiteral( "/home/bif.o/ape_2.shp" ) );
9659   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( QgsProcessing::TEMPORARY_OUTPUT, 2, context ).toString(), QgsProcessing::TEMPORARY_OUTPUT );
9660   QCOMPARE( QgsProcessingUtils::generateIteratingDestination( QgsProperty::fromValue( QgsProcessing::TEMPORARY_OUTPUT ), 2, context ).toString(), QgsProcessing::TEMPORARY_OUTPUT );
9661 
9662   QgsProject p;
9663   QgsProcessingOutputLayerDefinition def;
9664   def.sink = QgsProperty::fromValue( "ape.shp" );
9665   def.destinationProject = &p;
9666   QVariant res = QgsProcessingUtils::generateIteratingDestination( def, 2, context );
9667   QVERIFY( res.canConvert<QgsProcessingOutputLayerDefinition>() );
9668   QgsProcessingOutputLayerDefinition fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( res );
9669   QCOMPARE( fromVar.sink.staticValue().toString(), QStringLiteral( "ape_2.shp" ) );
9670   QCOMPARE( fromVar.destinationProject, &p );
9671 
9672   def.sink = QgsProperty::fromExpression( "'ape' || '.shp'" );
9673   res = QgsProcessingUtils::generateIteratingDestination( def, 2, context );
9674   QVERIFY( res.canConvert<QgsProcessingOutputLayerDefinition>() );
9675   fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( res );
9676   QCOMPARE( fromVar.sink.staticValue().toString(), QStringLiteral( "ape_2.shp" ) );
9677   QCOMPARE( fromVar.destinationProject, &p );
9678 
9679   QgsProcessingOutputLayerDefinition def2;
9680   def2.sink = QgsProperty::fromValue( QgsProcessing::TEMPORARY_OUTPUT );
9681   def2.destinationProject = &p;
9682   res = QgsProcessingUtils::generateIteratingDestination( def2, 2, context );
9683   QVERIFY( res.canConvert<QgsProcessingOutputLayerDefinition>() );
9684   fromVar = qvariant_cast<QgsProcessingOutputLayerDefinition>( res );
9685   QCOMPARE( fromVar.sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
9686   QCOMPARE( fromVar.destinationProject, &p );
9687 }
9688 
asPythonCommand()9689 void TestQgsProcessing::asPythonCommand()
9690 {
9691   DummyAlgorithm alg( "test" );
9692   alg.runAsPythonCommandChecks();
9693 }
9694 
modelerAlgorithm()9695 void TestQgsProcessing::modelerAlgorithm()
9696 {
9697   //static value source
9698   QgsProcessingModelChildParameterSource svSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 );
9699   QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9700   QCOMPARE( svSource.staticValue().toInt(), 5 );
9701   svSource.setStaticValue( 7 );
9702   QCOMPARE( svSource.staticValue().toInt(), 7 );
9703   QMap< QString, QString > friendlyNames;
9704   QCOMPARE( svSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "7" ) );
9705   svSource = QgsProcessingModelChildParameterSource::fromModelParameter( "a" );
9706   // check that calling setStaticValue flips source to StaticValue
9707   QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::ModelParameter );
9708   QCOMPARE( svSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "parameters['a']" ) );
9709   svSource.setStaticValue( 7 );
9710   QCOMPARE( svSource.staticValue().toInt(), 7 );
9711   QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9712   QCOMPARE( svSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "7" ) );
9713 
9714   // model parameter source
9715   QgsProcessingModelChildParameterSource mpSource = QgsProcessingModelChildParameterSource::fromModelParameter( "a" );
9716   QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::ModelParameter );
9717   QCOMPARE( mpSource.parameterName(), QStringLiteral( "a" ) );
9718   QCOMPARE( mpSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "parameters['a']" ) );
9719   mpSource.setParameterName( "b" );
9720   QCOMPARE( mpSource.parameterName(), QStringLiteral( "b" ) );
9721   QCOMPARE( mpSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "parameters['b']" ) );
9722   mpSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 );
9723   // check that calling setParameterName flips source to ModelParameter
9724   QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9725   QCOMPARE( mpSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "5" ) );
9726   mpSource.setParameterName( "c" );
9727   QCOMPARE( mpSource.parameterName(), QStringLiteral( "c" ) );
9728   QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::ModelParameter );
9729   QCOMPARE( mpSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "parameters['c']" ) );
9730 
9731   // child alg output source
9732   QgsProcessingModelChildParameterSource oSource = QgsProcessingModelChildParameterSource::fromChildOutput( "a", "b" );
9733   QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput );
9734   QCOMPARE( oSource.outputChildId(), QStringLiteral( "a" ) );
9735   QCOMPARE( oSource.outputName(), QStringLiteral( "b" ) );
9736   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "outputs['a']['b']" ) );
9737   // with friendly name
9738   friendlyNames.insert( QStringLiteral( "a" ), QStringLiteral( "alga" ) );
9739   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "outputs['alga']['b']" ) );
9740   oSource.setOutputChildId( "c" );
9741   QCOMPARE( oSource.outputChildId(), QStringLiteral( "c" ) );
9742   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "outputs['c']['b']" ) );
9743   oSource.setOutputName( "d" );
9744   QCOMPARE( oSource.outputName(), QStringLiteral( "d" ) );
9745   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "outputs['c']['d']" ) );
9746   oSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 );
9747   // check that calling setOutputChildId flips source to ChildOutput
9748   QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9749   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "5" ) );
9750   oSource.setOutputChildId( "c" );
9751   QCOMPARE( oSource.outputChildId(), QStringLiteral( "c" ) );
9752   QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput );
9753   oSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 );
9754   // check that calling setOutputName flips source to ChildOutput
9755   QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9756   QCOMPARE( oSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "5" ) );
9757   oSource.setOutputName( "d" );
9758   QCOMPARE( oSource.outputName(), QStringLiteral( "d" ) );
9759   QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput );
9760 
9761   // expression source
9762   QgsProcessingModelChildParameterSource expSource = QgsProcessingModelChildParameterSource::fromExpression( "1+2" );
9763   QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::Expression );
9764   QCOMPARE( expSource.expression(), QStringLiteral( "1+2" ) );
9765   QCOMPARE( expSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "QgsExpression('1+2').evaluate()" ) );
9766   expSource.setExpression( "1+3" );
9767   QCOMPARE( expSource.expression(), QStringLiteral( "1+3" ) );
9768   QCOMPARE( expSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "QgsExpression('1+3').evaluate()" ) );
9769   expSource.setExpression( "'a' || 'b\\'c'" );
9770   QCOMPARE( expSource.expression(), QStringLiteral( "'a' || 'b\\'c'" ) );
9771   QCOMPARE( expSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "QgsExpression(\"'a' || 'b\\\\'c'\").evaluate()" ) );
9772   expSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 );
9773   // check that calling setExpression flips source to Expression
9774   QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::StaticValue );
9775   QCOMPARE( expSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "5" ) );
9776   expSource.setExpression( "1+4" );
9777   QCOMPARE( expSource.expression(), QStringLiteral( "1+4" ) );
9778   QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::Expression );
9779   QCOMPARE( expSource.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, nullptr, friendlyNames ), QStringLiteral( "QgsExpression('1+4').evaluate()" ) );
9780 
9781   // source equality operator
9782   QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) ==
9783            QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) );
9784   QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) !=
9785            QgsProcessingModelChildParameterSource::fromStaticValue( 7 ) );
9786   QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) !=
9787            QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) );
9788   QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) ==
9789            QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) );
9790   QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) !=
9791            QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "b" ) ) );
9792   QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) !=
9793            QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) );
9794   QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) ==
9795            QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) );
9796   QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) !=
9797            QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg2" ), QStringLiteral( "out" ) ) );
9798   QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) !=
9799            QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out2" ) ) );
9800   QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) ==
9801            QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) );
9802   QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) !=
9803            QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "b" ) ) );
9804   QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) !=
9805            QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "b" ) ) );
9806 
9807   // a comment
9808   QgsProcessingModelComment comment;
9809   comment.setSize( QSizeF( 9, 8 ) );
9810   QCOMPARE( comment.size(), QSizeF( 9, 8 ) );
9811   comment.setPosition( QPointF( 11, 14 ) );
9812   QCOMPARE( comment.position(), QPointF( 11, 14 ) );
9813   comment.setDescription( QStringLiteral( "a comment" ) );
9814   QCOMPARE( comment.description(), QStringLiteral( "a comment" ) );
9815   comment.setColor( QColor( 123, 45, 67 ) );
9816   QCOMPARE( comment.color(), QColor( 123, 45, 67 ) );
9817   std::unique_ptr< QgsProcessingModelComment > commentClone( comment.clone() );
9818   QCOMPARE( commentClone->toVariant(), comment.toVariant() );
9819   QCOMPARE( commentClone->size(), QSizeF( 9, 8 ) );
9820   QCOMPARE( commentClone->position(), QPointF( 11, 14 ) );
9821   QCOMPARE( commentClone->description(), QStringLiteral( "a comment" ) );
9822   QCOMPARE( commentClone->color(), QColor( 123, 45, 67 ) );
9823   QgsProcessingModelComment comment2;
9824   comment2.loadVariant( comment.toVariant().toMap() );
9825   QCOMPARE( comment2.size(), QSizeF( 9, 8 ) );
9826   QCOMPARE( comment2.position(), QPointF( 11, 14 ) );
9827   QCOMPARE( comment2.description(), QStringLiteral( "a comment" ) );
9828   QCOMPARE( comment2.color(), QColor( 123, 45, 67 ) );
9829 
9830   // group boxes
9831   QgsProcessingModelGroupBox groupBox;
9832   groupBox.setSize( QSizeF( 9, 8 ) );
9833   QCOMPARE( groupBox.size(), QSizeF( 9, 8 ) );
9834   groupBox.setPosition( QPointF( 11, 14 ) );
9835   QCOMPARE( groupBox.position(), QPointF( 11, 14 ) );
9836   groupBox.setDescription( QStringLiteral( "a comment" ) );
9837   QCOMPARE( groupBox.description(), QStringLiteral( "a comment" ) );
9838   groupBox.setColor( QColor( 123, 45, 67 ) );
9839   QCOMPARE( groupBox.color(), QColor( 123, 45, 67 ) );
9840   std::unique_ptr< QgsProcessingModelGroupBox > groupClone( groupBox.clone() );
9841   QCOMPARE( groupClone->toVariant(), groupBox.toVariant() );
9842   QCOMPARE( groupClone->size(), QSizeF( 9, 8 ) );
9843   QCOMPARE( groupClone->position(), QPointF( 11, 14 ) );
9844   QCOMPARE( groupClone->description(), QStringLiteral( "a comment" ) );
9845   QCOMPARE( groupClone->color(), QColor( 123, 45, 67 ) );
9846   QCOMPARE( groupClone->uuid(), groupBox.uuid() );
9847   QgsProcessingModelGroupBox groupBox2;
9848   groupBox2.loadVariant( groupBox.toVariant().toMap() );
9849   QCOMPARE( groupBox2.size(), QSizeF( 9, 8 ) );
9850   QCOMPARE( groupBox2.position(), QPointF( 11, 14 ) );
9851   QCOMPARE( groupBox2.description(), QStringLiteral( "a comment" ) );
9852   QCOMPARE( groupBox2.color(), QColor( 123, 45, 67 ) );
9853   QCOMPARE( groupBox2.uuid(), groupBox.uuid() );
9854 
9855   const QMap< QString, QString > friendlyOutputNames;
9856   QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) );
9857   QCOMPARE( child.algorithmId(), QStringLiteral( "some_id" ) );
9858   QVERIFY( !child.algorithm() );
9859   QVERIFY( !child.setAlgorithmId( QStringLiteral( "blah" ) ) );
9860   QVERIFY( !child.reattach() );
9861   QVERIFY( child.setAlgorithmId( QStringLiteral( "native:centroids" ) ) );
9862   QVERIFY( child.algorithm() );
9863   QCOMPARE( child.algorithm()->id(), QStringLiteral( "native:centroids" ) );
9864   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, QgsStringMap(), 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    alg_params = {\n    }\n    outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
9865   QgsStringMap extraParams;
9866   extraParams[QStringLiteral( "SOMETHING" )] = QStringLiteral( "SOMETHING_ELSE" );
9867   extraParams[QStringLiteral( "SOMETHING2" )] = QStringLiteral( "SOMETHING_ELSE2" );
9868   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    alg_params = {\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs[''] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
9869   // bit of a hack -- but try to simulate an algorithm not originally available!
9870   child.mAlgorithm.reset();
9871   QVERIFY( !child.algorithm() );
9872   QVERIFY( child.reattach() );
9873   QVERIFY( child.algorithm() );
9874   QCOMPARE( child.algorithm()->id(), QStringLiteral( "native:centroids" ) );
9875 
9876   QVariantMap myConfig;
9877   myConfig.insert( QStringLiteral( "some_key" ), 11 );
9878   child.setConfiguration( myConfig );
9879   QCOMPARE( child.configuration(), myConfig );
9880 
9881   child.setDescription( QStringLiteral( "desc" ) );
9882   QCOMPARE( child.description(), QStringLiteral( "desc" ) );
9883   QVERIFY( child.isActive() );
9884   child.setActive( false );
9885   QVERIFY( !child.isActive() );
9886   child.setPosition( QPointF( 1, 2 ) );
9887   QCOMPARE( child.position(), QPointF( 1, 2 ) );
9888   child.setSize( QSizeF( 3, 4 ) );
9889   QCOMPARE( child.size(), QSizeF( 3, 4 ) );
9890   QVERIFY( child.linksCollapsed( Qt::TopEdge ) );
9891   child.setLinksCollapsed( Qt::TopEdge, false );
9892   QVERIFY( !child.linksCollapsed( Qt::TopEdge ) );
9893   QVERIFY( child.linksCollapsed( Qt::BottomEdge ) );
9894   child.setLinksCollapsed( Qt::BottomEdge, false );
9895   QVERIFY( !child.linksCollapsed( Qt::BottomEdge ) );
9896   child.comment()->setDescription( QStringLiteral( "com" ) );
9897   QCOMPARE( child.comment()->description(), QStringLiteral( "com" ) );
9898   child.comment()->setSize( QSizeF( 56, 78 ) );
9899   child.comment()->setPosition( QPointF( 111, 122 ) );
9900 
9901   QgsProcessingModelChildAlgorithm other;
9902   other.setChildId( QStringLiteral( "diff" ) );
9903   other.setDescription( QStringLiteral( "d2" ) );
9904   other.setAlgorithmId( QStringLiteral( "alg33" ) );
9905   other.setLinksCollapsed( Qt::BottomEdge, true );
9906   other.setLinksCollapsed( Qt::TopEdge, true );
9907   other.comment()->setDescription( QStringLiteral( "other comment" ) );
9908   other.copyNonDefinitionProperties( child );
9909   // only subset of properties should have been copied!
9910   QCOMPARE( other.description(), QStringLiteral( "d2" ) );
9911   QCOMPARE( other.position(), QPointF( 1, 2 ) );
9912   QCOMPARE( other.size(), QSizeF( 3, 4 ) );
9913   QVERIFY( !other.linksCollapsed( Qt::TopEdge ) );
9914   QVERIFY( !other.linksCollapsed( Qt::BottomEdge ) );
9915   QCOMPARE( other.comment()->description(), QStringLiteral( "other comment" ) );
9916   QCOMPARE( other.comment()->position(), QPointF( 111, 122 ) );
9917   QCOMPARE( other.comment()->size(), QSizeF( 56, 78 ) );
9918 
9919   child.comment()->setDescription( QString() );
9920 
9921   child.setChildId( QStringLiteral( "my_id" ) );
9922   QCOMPARE( child.childId(), QStringLiteral( "my_id" ) );
9923 
9924   child.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "a" ) << QgsProcessingModelChildDependency( "b" ) );
9925   QCOMPARE( child.dependencies(), QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "a" ) << QgsProcessingModelChildDependency( "b" ) );
9926 
9927   QMap< QString, QgsProcessingModelChildParameterSources > sources;
9928   sources.insert( QStringLiteral( "a" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) );
9929   child.setParameterSources( sources );
9930   QCOMPARE( child.parameterSources().value( QStringLiteral( "a" ) ).at( 0 ).staticValue().toInt(), 5 );
9931   child.addParameterSources( QStringLiteral( "b" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 7 ) << QgsProcessingModelChildParameterSource::fromStaticValue( 9 ) );
9932   QCOMPARE( child.parameterSources().value( QStringLiteral( "a" ) ).at( 0 ).staticValue().toInt(), 5 );
9933   QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).count(), 2 );
9934   QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 0 ).staticValue().toInt(), 7 );
9935   QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 1 ).staticValue().toInt(), 9 );
9936 
9937   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    # desc\n    alg_params = {\n      'a': 5,\n      'b': [7,9],\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
9938   child.comment()->setDescription( QStringLiteral( "do something useful" ) );
9939   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    # desc\n    # do something useful\n    alg_params = {\n      'a': 5,\n      'b': [7,9],\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)" ) );
9940 
9941   std::unique_ptr< QgsProcessingModelChildAlgorithm > childClone( child.clone() );
9942   QCOMPARE( childClone->toVariant(), child.toVariant() );
9943   QCOMPARE( childClone->comment()->description(), QStringLiteral( "do something useful" ) );
9944 
9945   QgsProcessingModelOutput testModelOut;
9946   testModelOut.setChildId( QStringLiteral( "my_id" ) );
9947   QCOMPARE( testModelOut.childId(), QStringLiteral( "my_id" ) );
9948   testModelOut.setChildOutputName( QStringLiteral( "my_output" ) );
9949   QCOMPARE( testModelOut.childOutputName(), QStringLiteral( "my_output" ) );
9950   testModelOut.setDefaultValue( QStringLiteral( "my_value" ) );
9951   QCOMPARE( testModelOut.defaultValue().toString(), QStringLiteral( "my_value" ) );
9952   testModelOut.setMandatory( true );
9953   QVERIFY( testModelOut.isMandatory() );
9954   testModelOut.comment()->setDescription( QStringLiteral( "my comm" ) );
9955   QCOMPARE( testModelOut.comment()->description(), QStringLiteral( "my comm" ) );
9956   std::unique_ptr< QgsProcessingModelOutput > outputClone( testModelOut.clone() );
9957   QCOMPARE( outputClone->toVariant(), testModelOut.toVariant() );
9958   QCOMPARE( outputClone->comment()->description(), QStringLiteral( "my comm" ) );
9959   QgsProcessingModelOutput testModelOutV;
9960   testModelOutV.loadVariant( testModelOut.toVariant().toMap() );
9961   QCOMPARE( testModelOutV.comment()->description(), QStringLiteral( "my comm" ) );
9962 
9963   QgsProcessingOutputLayerDefinition layerDef( QStringLiteral( "my_path" ) );
9964   layerDef.createOptions["fileEncoding"] = QStringLiteral( "my_encoding" );
9965   testModelOut.setDefaultValue( layerDef );
9966   QCOMPARE( testModelOut.defaultValue().value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "my_path" ) );
9967   QVariantMap map = testModelOut.toVariant().toMap();
9968   QCOMPARE( map["default_value"].toMap()["sink"].toMap()["val"].toString(), QStringLiteral( "my_path" ) );
9969   QCOMPARE( map["default_value"].toMap()["create_options"].toMap()["fileEncoding"].toString(), QStringLiteral( "my_encoding" ) );
9970   QgsProcessingModelOutput out;
9971   out.loadVariant( map );
9972   QVERIFY( out.defaultValue().canConvert<QgsProcessingOutputLayerDefinition>() );
9973   layerDef = out.defaultValue().value<QgsProcessingOutputLayerDefinition>();
9974   QCOMPARE( layerDef.sink.staticValue().toString(), QStringLiteral( "my_path" ) );
9975   QCOMPARE( layerDef.createOptions["fileEncoding"].toString(), QStringLiteral( "my_encoding" ) );
9976 
9977   QMap<QString, QgsProcessingModelOutput> outputs;
9978   QgsProcessingModelOutput out1;
9979   out1.setDescription( QStringLiteral( "my output" ) );
9980   outputs.insert( QStringLiteral( "a" ), out1 );
9981   child.setModelOutputs( outputs );
9982   QCOMPARE( child.modelOutputs().count(), 1 );
9983   QCOMPARE( child.modelOutputs().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "my output" ) );
9984   QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output" ) );
9985   child.modelOutput( "a" ).setDescription( QStringLiteral( "my output 2" ) );
9986   QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output 2" ) );
9987   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    # desc\n    # do something useful\n    alg_params = {\n      'a': 5,\n      'b': [7,9],\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n    results['my_id:a'] = outputs['my_id']['']" ) );
9988 
9989   // ensure friendly name is used if present
9990   child.addParameterSources( QStringLiteral( "b" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "a", "out" ) );
9991   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    # desc\n    # do something useful\n    alg_params = {\n      'a': 5,\n      'b': outputs['alga']['out'],\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n    results['my_id:a'] = outputs['my_id']['']" ) );
9992   friendlyNames.remove( "a" );
9993   QCOMPARE( child.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, extraParams, 4, 2, friendlyNames, friendlyOutputNames ).join( '\n' ), QStringLiteral( "    # desc\n    # do something useful\n    alg_params = {\n      'a': 5,\n      'b': outputs['a']['out'],\n      'SOMETHING': SOMETHING_ELSE,\n      'SOMETHING2': SOMETHING_ELSE2\n    }\n    outputs['my_id'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n    results['my_id:a'] = outputs['my_id']['']" ) );
9994 
9995   // no existent
9996   child.modelOutput( "b" ).setDescription( QStringLiteral( "my output 3" ) );
9997   QCOMPARE( child.modelOutput( "b" ).description(), QStringLiteral( "my output 3" ) );
9998   QCOMPARE( child.modelOutputs().count(), 2 );
9999   child.removeModelOutput( QStringLiteral( "a" ) );
10000   QCOMPARE( child.modelOutputs().count(), 1 );
10001 
10002   // model algorithm tests
10003 
10004   QgsProcessingModelAlgorithm alg( "test", "testGroup" );
10005   QCOMPARE( alg.name(), QStringLiteral( "test" ) );
10006   QCOMPARE( alg.displayName(), QStringLiteral( "test" ) );
10007   QCOMPARE( alg.group(), QStringLiteral( "testGroup" ) );
10008   alg.setName( QStringLiteral( "test2" ) );
10009   QCOMPARE( alg.name(), QStringLiteral( "test2" ) );
10010   QCOMPARE( alg.displayName(), QStringLiteral( "test2" ) );
10011   alg.setGroup( QStringLiteral( "group2" ) );
10012   QCOMPARE( alg.group(), QStringLiteral( "group2" ) );
10013 
10014   QVariantMap help;
10015   alg.setHelpContent( help );
10016   QVERIFY( alg.helpContent().isEmpty() );
10017   QVERIFY( alg.helpUrl().isEmpty() );
10018   QVERIFY( alg.shortDescription().isEmpty() );
10019   help.insert( QStringLiteral( "SHORT_DESCRIPTION" ), QStringLiteral( "short" ) );
10020   help.insert( QStringLiteral( "HELP_URL" ), QStringLiteral( "url" ) );
10021   alg.setHelpContent( help );
10022   QCOMPARE( alg.helpContent(), help );
10023   QCOMPARE( alg.shortDescription(), QStringLiteral( "short" ) );
10024   QCOMPARE( alg.helpUrl(), QStringLiteral( "url" ) );
10025 
10026   QVERIFY( alg.groupBoxes().isEmpty() );
10027   alg.addGroupBox( groupBox );
10028   QCOMPARE( alg.groupBoxes().size(), 1 );
10029   QCOMPARE( alg.groupBoxes().at( 0 ).uuid(), groupBox.uuid() );
10030   QCOMPARE( alg.groupBoxes().at( 0 ).uuid(), groupBox.uuid() );
10031   alg.removeGroupBox( QStringLiteral( "a" ) );
10032   QCOMPARE( alg.groupBoxes().size(), 1 );
10033   alg.removeGroupBox( groupBox.uuid() );
10034   QVERIFY( alg.groupBoxes().isEmpty() );
10035 
10036 
10037   QVariantMap lastParams;
10038   lastParams.insert( QStringLiteral( "a" ), 2 );
10039   lastParams.insert( QStringLiteral( "b" ), 4 );
10040   alg.setDesignerParameterValues( lastParams );
10041   QCOMPARE( alg.designerParameterValues(), lastParams );
10042 
10043   // child algorithms
10044   QMap<QString, QgsProcessingModelChildAlgorithm> algs;
10045   QgsProcessingModelChildAlgorithm a1;
10046   a1.setDescription( QStringLiteral( "alg1" ) );
10047   QgsProcessingModelChildAlgorithm a2;
10048   a2.setDescription( QStringLiteral( "alg2" ) );
10049   a2.setPosition( QPointF( 112, 131 ) );
10050   a2.setSize( QSizeF( 44, 55 ) );
10051   a2.comment()->setSize( QSizeF( 111, 222 ) );
10052   a2.comment()->setPosition( QPointF( 113, 114 ) );
10053   a2.comment()->setDescription( QStringLiteral( "c" ) );
10054   a2.comment()->setColor( QColor( 255, 254, 253 ) );
10055   QgsProcessingModelOutput oo;
10056   oo.setPosition( QPointF( 312, 331 ) );
10057   oo.setSize( QSizeF( 344, 355 ) );
10058   oo.comment()->setSize( QSizeF( 311, 322 ) );
10059   oo.comment()->setPosition( QPointF( 313, 314 ) );
10060   oo.comment()->setDescription( QStringLiteral( "c3" ) );
10061   oo.comment()->setColor( QColor( 155, 14, 353 ) );
10062   QMap< QString, QgsProcessingModelOutput > a2Outs;
10063   a2Outs.insert( QStringLiteral( "out1" ), oo );
10064   a2.setModelOutputs( a2Outs );
10065 
10066   algs.insert( QStringLiteral( "a" ), a1 );
10067   algs.insert( QStringLiteral( "b" ), a2 );
10068   alg.setChildAlgorithms( algs );
10069   QCOMPARE( alg.childAlgorithms().count(), 2 );
10070   QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "alg1" ) );
10071   QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "b" ) ).description(), QStringLiteral( "alg2" ) );
10072 
10073   QgsProcessingModelChildAlgorithm a2other;
10074   a2other.setChildId( QStringLiteral( "b" ) );
10075   a2other.setDescription( QStringLiteral( "alg2 other" ) );
10076   const QgsProcessingModelOutput oo2;
10077   QMap< QString, QgsProcessingModelOutput > a2Outs2;
10078   a2Outs2.insert( QStringLiteral( "out1" ), oo2 );
10079   a2other.setModelOutputs( a2Outs2 );
10080 
10081   a2other.copyNonDefinitionPropertiesFromModel( &alg );
10082   QCOMPARE( a2other.description(), QStringLiteral( "alg2 other" ) );
10083   QCOMPARE( a2other.position(), QPointF( 112, 131 ) );
10084   QCOMPARE( a2other.size(), QSizeF( 44, 55 ) );
10085   QCOMPARE( a2other.comment()->size(), QSizeF( 111, 222 ) );
10086   QCOMPARE( a2other.comment()->position(), QPointF( 113, 114 ) );
10087   // should not be copied
10088   QCOMPARE( a2other.comment()->description(), QString() );
10089   QVERIFY( !a2other.comment()->color().isValid() );
10090 
10091   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).position(), QPointF( 312, 331 ) );
10092   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).size(), QSizeF( 344, 355 ) );
10093   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->size(), QSizeF( 311, 322 ) );
10094   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->position(), QPointF( 313, 314 ) );
10095   // should be copied for outputs
10096   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->description(), QStringLiteral( "c3" ) );
10097   QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->color(), QColor( 155, 14, 353 ) );
10098 
10099   QgsProcessingModelChildAlgorithm a3;
10100   a3.setChildId( QStringLiteral( "c" ) );
10101   a3.setDescription( QStringLiteral( "alg3" ) );
10102   QCOMPARE( alg.addChildAlgorithm( a3 ), QStringLiteral( "c" ) );
10103   QCOMPARE( alg.childAlgorithms().count(), 3 );
10104   QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "alg1" ) );
10105   QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "b" ) ).description(), QStringLiteral( "alg2" ) );
10106   QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "c" ) ).description(), QStringLiteral( "alg3" ) );
10107   QCOMPARE( alg.childAlgorithm( "a" ).description(), QStringLiteral( "alg1" ) );
10108   QCOMPARE( alg.childAlgorithm( "b" ).description(), QStringLiteral( "alg2" ) );
10109   QCOMPARE( alg.childAlgorithm( "c" ).description(), QStringLiteral( "alg3" ) );
10110   // initially non-existent
10111   QVERIFY( alg.childAlgorithm( "d" ).description().isEmpty() );
10112   alg.childAlgorithm( "d" ).setDescription( QStringLiteral( "alg4" ) );
10113   QCOMPARE( alg.childAlgorithm( "d" ).description(), QStringLiteral( "alg4" ) );
10114   // overwrite existing
10115   QgsProcessingModelChildAlgorithm a4a;
10116   a4a.setChildId( "d" );
10117   a4a.setDescription( "new" );
10118   alg.setChildAlgorithm( a4a );
10119   QCOMPARE( alg.childAlgorithm( "d" ).description(), QStringLiteral( "new" ) );
10120 
10121 
10122   // generating child ids
10123   QgsProcessingModelChildAlgorithm c1;
10124   c1.setAlgorithmId( QStringLiteral( "buffer" ) );
10125   c1.generateChildId( alg );
10126   QCOMPARE( c1.childId(), QStringLiteral( "buffer_1" ) );
10127   QCOMPARE( alg.addChildAlgorithm( c1 ), QStringLiteral( "buffer_1" ) );
10128   QgsProcessingModelChildAlgorithm c2;
10129   c2.setAlgorithmId( QStringLiteral( "buffer" ) );
10130   c2.generateChildId( alg );
10131   QCOMPARE( c2.childId(), QStringLiteral( "buffer_2" ) );
10132   QCOMPARE( alg.addChildAlgorithm( c2 ), QStringLiteral( "buffer_2" ) );
10133   QgsProcessingModelChildAlgorithm c3;
10134   c3.setAlgorithmId( QStringLiteral( "centroid" ) );
10135   c3.generateChildId( alg );
10136   QCOMPARE( c3.childId(), QStringLiteral( "centroid_1" ) );
10137   QCOMPARE( alg.addChildAlgorithm( c3 ), QStringLiteral( "centroid_1" ) );
10138   QgsProcessingModelChildAlgorithm c4;
10139   c4.setAlgorithmId( QStringLiteral( "centroid" ) );
10140   c4.setChildId( QStringLiteral( "centroid_1" ) );// dupe id
10141   QCOMPARE( alg.addChildAlgorithm( c4 ), QStringLiteral( "centroid_2" ) );
10142   QCOMPARE( alg.childAlgorithm( QStringLiteral( "centroid_2" ) ).childId(), QStringLiteral( "centroid_2" ) );
10143 
10144   // parameter components
10145   QMap<QString, QgsProcessingModelParameter> pComponents;
10146   QgsProcessingModelParameter pc1;
10147   pc1.setParameterName( QStringLiteral( "my_param" ) );
10148   QCOMPARE( pc1.parameterName(), QStringLiteral( "my_param" ) );
10149   pc1.comment()->setDescription( QStringLiteral( "my comment" ) );
10150   QCOMPARE( pc1.comment()->description(), QStringLiteral( "my comment" ) );
10151   std::unique_ptr< QgsProcessingModelParameter > paramClone( pc1.clone() );
10152   QCOMPARE( paramClone->toVariant(), pc1.toVariant() );
10153   QCOMPARE( paramClone->comment()->description(), QStringLiteral( "my comment" ) );
10154   QgsProcessingModelParameter pcc1;
10155   pcc1.loadVariant( pc1.toVariant().toMap() );
10156   QCOMPARE( pcc1.comment()->description(), QStringLiteral( "my comment" ) );
10157   pComponents.insert( QStringLiteral( "my_param" ), pc1 );
10158   alg.setParameterComponents( pComponents );
10159   QCOMPARE( alg.parameterComponents().count(), 1 );
10160   QCOMPARE( alg.parameterComponents().value( QStringLiteral( "my_param" ) ).parameterName(), QStringLiteral( "my_param" ) );
10161   QCOMPARE( alg.parameterComponent( "my_param" ).parameterName(), QStringLiteral( "my_param" ) );
10162   alg.parameterComponent( "my_param" ).setDescription( QStringLiteral( "my param 2" ) );
10163   QCOMPARE( alg.parameterComponent( "my_param" ).description(), QStringLiteral( "my param 2" ) );
10164   // no existent
10165   alg.parameterComponent( "b" ).setDescription( QStringLiteral( "my param 3" ) );
10166   QCOMPARE( alg.parameterComponent( "b" ).description(), QStringLiteral( "my param 3" ) );
10167   QCOMPARE( alg.parameterComponent( "b" ).parameterName(), QStringLiteral( "b" ) );
10168   QCOMPARE( alg.parameterComponents().count(), 2 );
10169 
10170   // parameter definitions
10171   QgsProcessingModelAlgorithm alg1a( "test", "testGroup" );
10172   QgsProcessingModelParameter bool1;
10173   bool1.setPosition( QPointF( 1, 2 ) );
10174   bool1.setSize( QSizeF( 11, 12 ) );
10175   alg1a.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool1 );
10176   QCOMPARE( alg1a.parameterDefinitions().count(), 1 );
10177   QCOMPARE( alg1a.parameterDefinition( "p1" )->type(), QStringLiteral( "boolean" ) );
10178   QCOMPARE( alg1a.parameterComponent( "p1" ).position().x(), 1.0 );
10179   QCOMPARE( alg1a.parameterComponent( "p1" ).position().y(), 2.0 );
10180   QCOMPARE( alg1a.parameterComponent( "p1" ).size().width(), 11.0 );
10181   QCOMPARE( alg1a.parameterComponent( "p1" ).size().height(), 12.0 );
10182   alg1a.updateModelParameter( new QgsProcessingParameterBoolean( "p1", "descx" ) );
10183   QCOMPARE( alg1a.parameterDefinition( "p1" )->description(), QStringLiteral( "descx" ) );
10184   alg1a.removeModelParameter( "bad" );
10185   QCOMPARE( alg1a.parameterDefinitions().count(), 1 );
10186   alg1a.removeModelParameter( "p1" );
10187   QVERIFY( alg1a.parameterDefinitions().isEmpty() );
10188   QVERIFY( alg1a.parameterComponents().isEmpty() );
10189 
10190 
10191   // test canExecute
10192   QgsProcessingModelAlgorithm alg2( "test", "testGroup" );
10193   QVERIFY( alg2.canExecute() );
10194   QgsProcessingModelChildAlgorithm c5;
10195   c5.setAlgorithmId( "native:centroids" );
10196   alg2.addChildAlgorithm( c5 );
10197   QVERIFY( alg2.canExecute() );
10198   // non-existing alg
10199   QgsProcessingModelChildAlgorithm c6;
10200   c6.setAlgorithmId( "i'm not an alg" );
10201   alg2.addChildAlgorithm( c6 );
10202   QVERIFY( !alg2.canExecute() );
10203 
10204   // test that children are re-attached before testing for canExecute
10205   QgsProcessingModelAlgorithm alg2a( "test", "testGroup" );
10206   QgsProcessingModelChildAlgorithm c5a;
10207   c5a.setAlgorithmId( "native:centroids" );
10208   alg2a.addChildAlgorithm( c5a );
10209   // simulate initially missing provider or algorithm (e.g. another model as a child algorithm)
10210   alg2a.mChildAlgorithms.begin().value().mAlgorithm.reset();
10211   QVERIFY( alg2a.canExecute() );
10212 
10213   // dependencies
10214   QgsProcessingModelAlgorithm alg3( "test", "testGroup" );
10215   QVERIFY( alg3.dependentChildAlgorithms( "notvalid" ).isEmpty() );
10216   QVERIFY( alg3.dependsOnChildAlgorithms( "notvalid" ).isEmpty() );
10217   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "notvalid" ) ).isEmpty() );
10218 
10219   // add a child
10220   QgsProcessingModelChildAlgorithm c7;
10221   c7.setChildId( "c7" );
10222   alg3.addChildAlgorithm( c7 );
10223   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).isEmpty() );
10224   QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() );
10225   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c7" ) ).isEmpty() );
10226 
10227   // direct dependency
10228   QgsProcessingModelChildAlgorithm c8;
10229   c8.setChildId( "c8" );
10230   c8.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "c7" ) );
10231   alg3.addChildAlgorithm( c8 );
10232   QVERIFY( alg3.dependentChildAlgorithms( "c8" ).isEmpty() );
10233   QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() );
10234   QCOMPARE( alg3.dependentChildAlgorithms( "c7" ).count(), 1 );
10235   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c8" ) );
10236   QCOMPARE( alg3.dependsOnChildAlgorithms( "c8" ).count(), 1 );
10237   QVERIFY( alg3.dependsOnChildAlgorithms( "c8" ).contains( "c7" ) );
10238   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c7" ) ).isEmpty() );
10239   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).size(), 1 );
10240   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).at( 0 ).childId, QStringLiteral( "c7" ) );
10241 
10242   // dependency via parameter source
10243   QgsProcessingModelChildAlgorithm c9;
10244   c9.setChildId( "c9" );
10245   c9.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "c8", "x" ) );
10246   alg3.addChildAlgorithm( c9 );
10247   QVERIFY( alg3.dependentChildAlgorithms( "c9" ).isEmpty() );
10248   QCOMPARE( alg3.dependentChildAlgorithms( "c8" ).count(), 1 );
10249   QVERIFY( alg3.dependentChildAlgorithms( "c8" ).contains( "c9" ) );
10250   QCOMPARE( alg3.dependentChildAlgorithms( "c7" ).count(), 2 );
10251   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c8" ) );
10252   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c9" ) );
10253 
10254   QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() );
10255   QCOMPARE( alg3.dependsOnChildAlgorithms( "c8" ).count(), 1 );
10256   QVERIFY( alg3.dependsOnChildAlgorithms( "c8" ).contains( "c7" ) );
10257   QCOMPARE( alg3.dependsOnChildAlgorithms( "c9" ).count(), 2 );
10258   QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c7" ) );
10259   QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c8" ) );
10260 
10261   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c7" ) ).isEmpty() );
10262   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).size(), 1 );
10263   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).at( 0 ).childId, QStringLiteral( "c7" ) );
10264   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).size(), 2 );
10265   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c7" ) ) ) );
10266   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c8" ) ) ) );
10267 
10268   QgsProcessingModelChildAlgorithm c9b;
10269   c9b.setChildId( "c9b" );
10270   c9b.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "c9", "x" ) );
10271   alg3.addChildAlgorithm( c9b );
10272 
10273   QCOMPARE( alg3.dependentChildAlgorithms( "c9" ).count(), 1 );
10274   QCOMPARE( alg3.dependentChildAlgorithms( "c8" ).count(), 2 );
10275   QVERIFY( alg3.dependentChildAlgorithms( "c8" ).contains( "c9" ) );
10276   QVERIFY( alg3.dependentChildAlgorithms( "c8" ).contains( "c9b" ) );
10277   QCOMPARE( alg3.dependentChildAlgorithms( "c7" ).count(), 3 );
10278   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c8" ) );
10279   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c9" ) );
10280   QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c9b" ) );
10281 
10282   QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() );
10283   QCOMPARE( alg3.dependsOnChildAlgorithms( "c8" ).count(), 1 );
10284   QVERIFY( alg3.dependsOnChildAlgorithms( "c8" ).contains( "c7" ) );
10285   QCOMPARE( alg3.dependsOnChildAlgorithms( "c9" ).count(), 2 );
10286   QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c7" ) );
10287   QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c8" ) );
10288   QCOMPARE( alg3.dependsOnChildAlgorithms( "c9b" ).count(), 3 );
10289   QVERIFY( alg3.dependsOnChildAlgorithms( "c9b" ).contains( "c7" ) );
10290   QVERIFY( alg3.dependsOnChildAlgorithms( "c9b" ).contains( "c8" ) );
10291   QVERIFY( alg3.dependsOnChildAlgorithms( "c9b" ).contains( "c9" ) );
10292 
10293   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c7" ) ).isEmpty() );
10294   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).size(), 1 );
10295   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c8" ) ).at( 0 ).childId, QStringLiteral( "c7" ) );
10296   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).size(), 2 );
10297   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c7" ) ) ) );
10298   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c8" ) ) ) );
10299   QCOMPARE( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9b" ) ).size(), 3 );
10300   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9b" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c7" ) ) ) );
10301   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9b" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c8" ) ) ) );
10302   QVERIFY( alg3.availableDependenciesForChildAlgorithm( QStringLiteral( "c9b" ) ).contains( QgsProcessingModelChildDependency( QStringLiteral( "c9" ) ) ) );
10303 
10304   alg3.removeChildAlgorithm( "c9b" );
10305 
10306 
10307   // (de)activate child algorithm
10308   alg3.deactivateChildAlgorithm( "c9" );
10309   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10310   QVERIFY( alg3.activateChildAlgorithm( "c9" ) );
10311   QVERIFY( alg3.childAlgorithm( "c9" ).isActive() );
10312   alg3.deactivateChildAlgorithm( "c8" );
10313   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10314   QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() );
10315   QVERIFY( !alg3.activateChildAlgorithm( "c9" ) );
10316   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10317   QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() );
10318   QVERIFY( alg3.activateChildAlgorithm( "c8" ) );
10319   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10320   QVERIFY( alg3.childAlgorithm( "c8" ).isActive() );
10321   QVERIFY( alg3.activateChildAlgorithm( "c9" ) );
10322   QVERIFY( alg3.childAlgorithm( "c9" ).isActive() );
10323   QVERIFY( alg3.childAlgorithm( "c8" ).isActive() );
10324   alg3.deactivateChildAlgorithm( "c7" );
10325   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10326   QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() );
10327   QVERIFY( !alg3.childAlgorithm( "c7" ).isActive() );
10328   QVERIFY( !alg3.activateChildAlgorithm( "c9" ) );
10329   QVERIFY( !alg3.activateChildAlgorithm( "c8" ) );
10330   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10331   QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() );
10332   QVERIFY( !alg3.childAlgorithm( "c7" ).isActive() );
10333   QVERIFY( !alg3.activateChildAlgorithm( "c8" ) );
10334   QVERIFY( alg3.activateChildAlgorithm( "c7" ) );
10335   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10336   QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() );
10337   QVERIFY( alg3.childAlgorithm( "c7" ).isActive() );
10338   QVERIFY( !alg3.activateChildAlgorithm( "c9" ) );
10339   QVERIFY( alg3.activateChildAlgorithm( "c8" ) );
10340   QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() );
10341   QVERIFY( alg3.childAlgorithm( "c8" ).isActive() );
10342   QVERIFY( alg3.childAlgorithm( "c7" ).isActive() );
10343   QVERIFY( alg3.activateChildAlgorithm( "c9" ) );
10344   QVERIFY( alg3.childAlgorithm( "c9" ).isActive() );
10345   QVERIFY( alg3.childAlgorithm( "c8" ).isActive() );
10346   QVERIFY( alg3.childAlgorithm( "c7" ).isActive() );
10347 
10348 
10349 
10350   //remove child algorithm
10351   QVERIFY( !alg3.removeChildAlgorithm( "c7" ) );
10352   QVERIFY( !alg3.removeChildAlgorithm( "c8" ) );
10353   QVERIFY( alg3.removeChildAlgorithm( "c9" ) );
10354   QCOMPARE( alg3.childAlgorithms().count(), 2 );
10355   QVERIFY( alg3.childAlgorithms().contains( "c7" ) );
10356   QVERIFY( alg3.childAlgorithms().contains( "c8" ) );
10357   QVERIFY( !alg3.removeChildAlgorithm( "c7" ) );
10358   QVERIFY( alg3.removeChildAlgorithm( "c8" ) );
10359   QCOMPARE( alg3.childAlgorithms().count(), 1 );
10360   QVERIFY( alg3.childAlgorithms().contains( "c7" ) );
10361   QVERIFY( alg3.removeChildAlgorithm( "c7" ) );
10362   QVERIFY( alg3.childAlgorithms().isEmpty() );
10363 
10364   // parameter dependencies
10365   QgsProcessingModelAlgorithm alg4( "test", "testGroup" );
10366   QVERIFY( !alg4.childAlgorithmsDependOnParameter( "not a param" ) );
10367   QgsProcessingModelChildAlgorithm c10;
10368   c10.setChildId( "c10" );
10369   alg4.addChildAlgorithm( c10 );
10370   QVERIFY( !alg4.childAlgorithmsDependOnParameter( "not a param" ) );
10371   const QgsProcessingModelParameter bool2;
10372   alg4.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool2 );
10373   QVERIFY( !alg4.childAlgorithmsDependOnParameter( "p1" ) );
10374   c10.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p2" ) );
10375   alg4.setChildAlgorithm( c10 );
10376   QVERIFY( !alg4.childAlgorithmsDependOnParameter( "p1" ) );
10377   c10.addParameterSources( "y", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p1" ) );
10378   alg4.setChildAlgorithm( c10 );
10379   QVERIFY( alg4.childAlgorithmsDependOnParameter( "p1" ) );
10380 
10381   const QgsProcessingModelParameter vlP;
10382   alg4.addModelParameter( new QgsProcessingParameterVectorLayer( "layer" ), vlP );
10383   const QgsProcessingModelParameter field;
10384   alg4.addModelParameter( new QgsProcessingParameterField( "field", QString(), QVariant(), QStringLiteral( "layer" ) ), field );
10385   QVERIFY( !alg4.otherParametersDependOnParameter( "p1" ) );
10386   QVERIFY( !alg4.otherParametersDependOnParameter( "field" ) );
10387   QVERIFY( alg4.otherParametersDependOnParameter( "layer" ) );
10388 
10389 
10390 
10391 
10392 
10393   // to/from XML
10394   QgsProcessingModelAlgorithm alg5( "test", "testGroup" );
10395   alg5.helpContent().insert( "author", "me" );
10396   alg5.helpContent().insert( "usage", "run" );
10397   alg5.addGroupBox( groupBox );
10398   QVariantMap variables;
10399   variables.insert( QStringLiteral( "v1" ), 5 );
10400   variables.insert( QStringLiteral( "v2" ), QStringLiteral( "aabbccd" ) );
10401   alg5.setVariables( variables );
10402   QCOMPARE( alg5.variables(), variables );
10403   QgsProcessingModelChildAlgorithm alg5c1;
10404   alg5c1.setChildId( "cx1" );
10405   alg5c1.setAlgorithmId( "buffer" );
10406   alg5c1.setConfiguration( myConfig );
10407   alg5c1.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p1" ) );
10408   alg5c1.addParameterSources( "y", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx2", "out3" ) );
10409   alg5c1.addParameterSources( "z", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) );
10410   alg5c1.addParameterSources( "a", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromExpression( "2*2" ) );
10411   alg5c1.addParameterSources( "zm", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 6 )
10412                               << QgsProcessingModelChildParameterSource::fromModelParameter( "p2" )
10413                               << QgsProcessingModelChildParameterSource::fromChildOutput( "cx2", "out4" )
10414                               << QgsProcessingModelChildParameterSource::fromExpression( "1+2" )
10415                               << QgsProcessingModelChildParameterSource::fromStaticValue( QgsProperty::fromExpression( "1+8" ) ) );
10416   alg5c1.setActive( true );
10417   alg5c1.setLinksCollapsed( Qt::BottomEdge, true );
10418   alg5c1.setLinksCollapsed( Qt::TopEdge, true );
10419   alg5c1.setDescription( "child 1" );
10420   alg5c1.setPosition( QPointF( 1, 2 ) );
10421   alg5c1.setSize( QSizeF( 11, 21 ) );
10422   QMap<QString, QgsProcessingModelOutput> alg5c1outputs;
10423   QgsProcessingModelOutput alg5c1out1;
10424   alg5c1out1.setDescription( QStringLiteral( "my output" ) );
10425   alg5c1out1.setPosition( QPointF( 3, 4 ) );
10426   alg5c1out1.setSize( QSizeF( 31, 41 ) );
10427   alg5c1outputs.insert( QStringLiteral( "a" ), alg5c1out1 );
10428   alg5c1.setModelOutputs( alg5c1outputs );
10429   alg5.addChildAlgorithm( alg5c1 );
10430 
10431   QgsProcessingModelChildAlgorithm alg5c2;
10432   alg5c2.setChildId( "cx2" );
10433   alg5c2.setAlgorithmId( QStringLiteral( "native:centroids" ) );
10434   alg5c2.setActive( false );
10435   alg5c2.setLinksCollapsed( Qt::BottomEdge, false );
10436   alg5c2.setLinksCollapsed( Qt::TopEdge, false );
10437   alg5c2.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "a" ) << QgsProcessingModelChildDependency( "b" ) );
10438   alg5.addChildAlgorithm( alg5c2 );
10439 
10440   QgsProcessingModelParameter alg5pc1;
10441   alg5pc1.setParameterName( QStringLiteral( "my_param" ) );
10442   alg5pc1.setPosition( QPointF( 11, 12 ) );
10443   alg5pc1.setSize( QSizeF( 21, 22 ) );
10444   alg5.addModelParameter( new QgsProcessingParameterBoolean( QStringLiteral( "my_param" ) ), alg5pc1 );
10445   alg5.setDesignerParameterValues( lastParams );
10446 
10447   QDomDocument doc = QDomDocument( "model" );
10448   alg5.initAlgorithm();
10449   const QVariant v = alg5.toVariant();
10450   // make sure private parameters weren't included in the definition
10451   QVERIFY( !v.toMap().value( QStringLiteral( "parameterDefinitions" ) ).toMap().contains( QStringLiteral( "VERBOSE_LOG" ) ) );
10452 
10453   const QDomElement elem = QgsXmlUtils::writeVariant( v, doc );
10454   doc.appendChild( elem );
10455 
10456   QgsProcessingModelAlgorithm alg6;
10457   QVERIFY( alg6.loadVariant( QgsXmlUtils::readVariant( doc.firstChildElement() ) ) );
10458   QCOMPARE( alg6.name(), QStringLiteral( "test" ) );
10459   QCOMPARE( alg6.group(), QStringLiteral( "testGroup" ) );
10460   QCOMPARE( alg6.helpContent(), alg5.helpContent() );
10461   QCOMPARE( alg6.variables(), variables );
10462   QCOMPARE( alg6.designerParameterValues(), lastParams );
10463 
10464   QCOMPARE( alg6.groupBoxes().size(), 1 );
10465   QCOMPARE( alg6.groupBoxes().at( 0 ).size(), QSizeF( 9, 8 ) );
10466   QCOMPARE( alg6.groupBoxes().at( 0 ).position(), QPointF( 11, 14 ) );
10467   QCOMPARE( alg6.groupBoxes().at( 0 ).description(), QStringLiteral( "a comment" ) );
10468   QCOMPARE( alg6.groupBoxes().at( 0 ).color(), QColor( 123, 45, 67 ) );
10469 
10470   QgsProcessingModelChildAlgorithm alg6c1 = alg6.childAlgorithm( "cx1" );
10471   QCOMPARE( alg6c1.childId(), QStringLiteral( "cx1" ) );
10472   QCOMPARE( alg6c1.algorithmId(), QStringLiteral( "buffer" ) );
10473   QCOMPARE( alg6c1.configuration(), myConfig );
10474   QVERIFY( alg6c1.isActive() );
10475   QVERIFY( alg6c1.linksCollapsed( Qt::BottomEdge ) );
10476   QVERIFY( alg6c1.linksCollapsed( Qt::TopEdge ) );
10477   QCOMPARE( alg6c1.description(), QStringLiteral( "child 1" ) );
10478   QCOMPARE( alg6c1.position().x(), 1.0 );
10479   QCOMPARE( alg6c1.position().y(), 2.0 );
10480   QCOMPARE( alg6c1.size().width(), 11.0 );
10481   QCOMPARE( alg6c1.size().height(), 21.0 );
10482   QCOMPARE( alg6c1.parameterSources().count(), 5 );
10483   QCOMPARE( alg6c1.parameterSources().value( "x" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::ModelParameter );
10484   QCOMPARE( alg6c1.parameterSources().value( "x" ).at( 0 ).parameterName(), QStringLiteral( "p1" ) );
10485   QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::ChildOutput );
10486   QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).outputChildId(), QStringLiteral( "cx2" ) );
10487   QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).outputName(), QStringLiteral( "out3" ) );
10488   QCOMPARE( alg6c1.parameterSources().value( "z" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::StaticValue );
10489   QCOMPARE( alg6c1.parameterSources().value( "z" ).at( 0 ).staticValue().toInt(), 5 );
10490   QCOMPARE( alg6c1.parameterSources().value( "a" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::Expression );
10491   QCOMPARE( alg6c1.parameterSources().value( "a" ).at( 0 ).expression(), QStringLiteral( "2*2" ) );
10492   QCOMPARE( alg6c1.parameterSources().value( "zm" ).count(), 5 );
10493   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::StaticValue );
10494   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 0 ).staticValue().toInt(), 6 );
10495   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 1 ).source(), QgsProcessingModelChildParameterSource::ModelParameter );
10496   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 1 ).parameterName(), QStringLiteral( "p2" ) );
10497   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).source(), QgsProcessingModelChildParameterSource::ChildOutput );
10498   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).outputChildId(), QStringLiteral( "cx2" ) );
10499   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).outputName(), QStringLiteral( "out4" ) );
10500   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 3 ).source(), QgsProcessingModelChildParameterSource::Expression );
10501   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 3 ).expression(), QStringLiteral( "1+2" ) );
10502   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 4 ).source(), QgsProcessingModelChildParameterSource::StaticValue );
10503   QVERIFY( alg6c1.parameterSources().value( "zm" ).at( 4 ).staticValue().canConvert< QgsProperty >() );
10504   QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 4 ).staticValue().value< QgsProperty >().expressionString(), QStringLiteral( "1+8" ) );
10505 
10506   QCOMPARE( alg6c1.modelOutputs().count(), 1 );
10507   QCOMPARE( alg6c1.modelOutputs().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "my output" ) );
10508   QCOMPARE( alg6c1.modelOutput( "a" ).description(), QStringLiteral( "my output" ) );
10509   QCOMPARE( alg6c1.modelOutput( "a" ).position().x(), 3.0 );
10510   QCOMPARE( alg6c1.modelOutput( "a" ).position().y(), 4.0 );
10511   QCOMPARE( alg6c1.modelOutput( "a" ).size().width(), 31.0 );
10512   QCOMPARE( alg6c1.modelOutput( "a" ).size().height(), 41.0 );
10513 
10514   const QgsProcessingModelChildAlgorithm alg6c2 = alg6.childAlgorithm( "cx2" );
10515   QCOMPARE( alg6c2.childId(), QStringLiteral( "cx2" ) );
10516   QVERIFY( !alg6c2.isActive() );
10517   QVERIFY( !alg6c2.linksCollapsed( Qt::BottomEdge ) );
10518   QVERIFY( !alg6c2.linksCollapsed( Qt::TopEdge ) );
10519   QCOMPARE( alg6c2.dependencies(), QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "a" ) << QgsProcessingModelChildDependency( "b" ) );
10520 
10521   QCOMPARE( alg6.parameterComponents().count(), 1 );
10522   QCOMPARE( alg6.parameterComponents().value( QStringLiteral( "my_param" ) ).parameterName(), QStringLiteral( "my_param" ) );
10523   QCOMPARE( alg6.parameterComponent( "my_param" ).parameterName(), QStringLiteral( "my_param" ) );
10524   QCOMPARE( alg6.parameterComponent( "my_param" ).position().x(), 11.0 );
10525   QCOMPARE( alg6.parameterComponent( "my_param" ).position().y(), 12.0 );
10526   QCOMPARE( alg6.parameterComponent( "my_param" ).size().width(), 21.0 );
10527   QCOMPARE( alg6.parameterComponent( "my_param" ).size().height(), 22.0 );
10528   QCOMPARE( alg6.parameterDefinitions().count(), 1 );
10529   QCOMPARE( alg6.parameterDefinitions().at( 0 )->type(), QStringLiteral( "boolean" ) );
10530 
10531   // destination parameters
10532   QgsProcessingModelAlgorithm alg7( "test", "testGroup" );
10533   QgsProcessingModelChildAlgorithm alg7c1;
10534   alg7c1.setChildId( "cx1" );
10535   alg7c1.setAlgorithmId( "native:centroids" );
10536   QMap<QString, QgsProcessingModelOutput> alg7c1outputs;
10537   QgsProcessingModelOutput alg7c1out1( QStringLiteral( "my_output" ) );
10538   alg7c1out1.setChildId( "cx1" );
10539   alg7c1out1.setChildOutputName( "OUTPUT" );
10540   alg7c1out1.setDescription( QStringLiteral( "my output" ) );
10541   alg7c1outputs.insert( QStringLiteral( "my_output" ), alg7c1out1 );
10542   alg7c1.setModelOutputs( alg7c1outputs );
10543   alg7.addChildAlgorithm( alg7c1 );
10544   // verify that model has destination parameter created
10545   QCOMPARE( alg7.destinationParameterDefinitions().count(), 1 );
10546   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) );
10547   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) );
10548   QCOMPARE( static_cast< const QgsProcessingDestinationParameter * >( alg7.destinationParameterDefinitions().at( 0 ) )->originalProvider()->id(), QStringLiteral( "native" ) );
10549   QCOMPARE( alg7.outputDefinitions().count(), 1 );
10550   QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) );
10551   QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) );
10552   QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) );
10553 
10554   QgsProcessingModelChildAlgorithm alg7c2;
10555   alg7c2.setChildId( "cx2" );
10556   alg7c2.setAlgorithmId( "native:centroids" );
10557   QMap<QString, QgsProcessingModelOutput> alg7c2outputs;
10558   QgsProcessingModelOutput alg7c2out1( QStringLiteral( "my_output2" ) );
10559   alg7c2out1.setChildId( "cx2" );
10560   alg7c2out1.setChildOutputName( "OUTPUT" );
10561   alg7c2out1.setDescription( QStringLiteral( "my output2" ) );
10562   alg7c2out1.setDefaultValue( QStringLiteral( "my value" ) );
10563   alg7c2out1.setMandatory( true );
10564   alg7c2outputs.insert( QStringLiteral( "my_output2" ), alg7c2out1 );
10565   alg7c2.setModelOutputs( alg7c2outputs );
10566   alg7.addChildAlgorithm( alg7c2 );
10567 
10568   QCOMPARE( alg7.destinationParameterDefinitions().count(), 2 );
10569   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) );
10570   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) );
10571   QVERIFY( alg7.destinationParameterDefinitions().at( 0 )->defaultValue().isNull() );
10572   QVERIFY( !( alg7.destinationParameterDefinitions().at( 0 )->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
10573   QCOMPARE( alg7.destinationParameterDefinitions().at( 1 )->name(), QStringLiteral( "cx2:my_output2" ) );
10574   QCOMPARE( alg7.destinationParameterDefinitions().at( 1 )->description(), QStringLiteral( "my output2" ) );
10575   QCOMPARE( alg7.destinationParameterDefinitions().at( 1 )->defaultValue().toString(), QStringLiteral( "my value" ) );
10576   QVERIFY( !( alg7.destinationParameterDefinitions().at( 1 )->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
10577   QCOMPARE( alg7.outputDefinitions().count(), 2 );
10578   QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) );
10579   QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) );
10580   QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) );
10581   QCOMPARE( alg7.outputDefinitions().at( 1 )->name(), QStringLiteral( "cx2:my_output2" ) );
10582   QCOMPARE( alg7.outputDefinitions().at( 1 )->type(), QStringLiteral( "outputVector" ) );
10583   QCOMPARE( alg7.outputDefinitions().at( 1 )->description(), QStringLiteral( "my output2" ) );
10584 
10585   alg7.removeChildAlgorithm( "cx1" );
10586   QCOMPARE( alg7.destinationParameterDefinitions().count(), 1 );
10587   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx2:my_output2" ) );
10588   QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output2" ) );
10589   QCOMPARE( alg7.outputDefinitions().count(), 1 );
10590   QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx2:my_output2" ) );
10591   QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) );
10592   QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output2" ) );
10593 
10594   // mandatory model output with optional child algorithm parameter
10595   QgsProcessingModelChildAlgorithm alg7c3;
10596   alg7c3.setChildId( "cx3" );
10597   alg7c3.setAlgorithmId( "native:extractbyexpression" );
10598   QMap<QString, QgsProcessingModelOutput> alg7c3outputs;
10599   QgsProcessingModelOutput alg7c3out1;
10600   alg7c3out1.setChildId( "cx3" );
10601   alg7c3out1.setChildOutputName( "FAIL_OUTPUT" );
10602   alg7c3out1.setDescription( QStringLiteral( "my_output3" ) );
10603   alg7c3outputs.insert( QStringLiteral( "my_output3" ), alg7c3out1 );
10604   alg7c3.setModelOutputs( alg7c3outputs );
10605   alg7.addChildAlgorithm( alg7c3 );
10606   QVERIFY( alg7.destinationParameterDefinitions().at( 1 )->flags() & QgsProcessingParameterDefinition::FlagOptional );
10607   alg7.childAlgorithm( alg7c3.childId() ).modelOutput( QStringLiteral( "my_output3" ) ).setMandatory( true );
10608   alg7.updateDestinationParameters();
10609   QVERIFY( !( alg7.destinationParameterDefinitions().at( 1 )->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
10610 }
10611 
modelExecution()10612 void TestQgsProcessing::modelExecution()
10613 {
10614   // test childOutputIsRequired
10615   QgsProcessingModelAlgorithm model1;
10616   QgsProcessingModelChildAlgorithm algc1;
10617   algc1.setChildId( "cx1" );
10618   algc1.setAlgorithmId( "native:centroids" );
10619   model1.addChildAlgorithm( algc1 );
10620   QgsProcessingModelChildAlgorithm algc2;
10621   algc2.setChildId( "cx2" );
10622   algc2.setAlgorithmId( "native:centroids" );
10623   algc2.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "p1" ) );
10624   model1.addChildAlgorithm( algc2 );
10625   QgsProcessingModelChildAlgorithm algc3;
10626   algc3.setChildId( "cx3" );
10627   algc3.setAlgorithmId( "native:centroids" );
10628   algc3.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "p2" ) );
10629   algc3.setActive( false );
10630   model1.addChildAlgorithm( algc3 );
10631 
10632   QVERIFY( model1.childOutputIsRequired( "cx1", "p1" ) ); // cx2 depends on p1
10633   QVERIFY( !model1.childOutputIsRequired( "cx1", "p2" ) ); // cx3 depends on p2, but cx3 is not active
10634   QVERIFY( !model1.childOutputIsRequired( "cx1", "p3" ) ); // nothing requires p3
10635   QVERIFY( !model1.childOutputIsRequired( "cx2", "p1" ) );
10636   QVERIFY( !model1.childOutputIsRequired( "cx3", "p1" ) );
10637 
10638   // test parametersForChildAlgorithm
10639   QgsProcessingModelAlgorithm model2;
10640   QgsProcessingModelParameter sourceParam( "SOURCE_LAYER" );
10641   sourceParam.comment()->setDescription( QStringLiteral( "an input" ) );
10642   model2.addModelParameter( new QgsProcessingParameterFeatureSource( "SOURCE_LAYER" ), sourceParam );
10643   model2.addModelParameter( new QgsProcessingParameterNumber( "DIST", QString(), QgsProcessingParameterNumber::Double ), QgsProcessingModelParameter( "DIST" ) );
10644   QgsProcessingParameterCrs *p = new QgsProcessingParameterCrs( "CRS", QString(), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28355" ) ) );
10645   p->setFlags( p->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
10646   model2.addModelParameter( p, QgsProcessingModelParameter( "CRS" ) );
10647   QgsProcessingModelChildAlgorithm alg2c1;
10648   QgsExpressionContext expContext;
10649   QgsExpressionContextScope *scope = new QgsExpressionContextScope();
10650   scope->setVariable( "myvar", 8 );
10651   expContext.appendScope( scope );
10652   alg2c1.setChildId( "cx1" );
10653   alg2c1.setAlgorithmId( "native:buffer" );
10654   alg2c1.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "SOURCE_LAYER" ) );
10655   alg2c1.addParameterSources( "DISTANCE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "DIST" ) );
10656   alg2c1.addParameterSources( "SEGMENTS", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "@myvar*2" ) ) );
10657   alg2c1.addParameterSources( "END_CAP_STYLE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 1 ) );
10658   alg2c1.addParameterSources( "JOIN_STYLE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 2 ) );
10659   alg2c1.addParameterSources( "DISSOLVE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( false ) );
10660   QMap<QString, QgsProcessingModelOutput> outputs1;
10661   QgsProcessingModelOutput out1( "MODEL_OUT_LAYER" );
10662   out1.setChildOutputName( "OUTPUT" );
10663   outputs1.insert( QStringLiteral( "MODEL_OUT_LAYER" ), out1 );
10664   alg2c1.setModelOutputs( outputs1 );
10665   model2.addChildAlgorithm( alg2c1 );
10666 
10667   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
10668   const QString vector = testDataDir + "points.shp";
10669 
10670   QVariantMap modelInputs;
10671   modelInputs.insert( "SOURCE_LAYER", vector );
10672   modelInputs.insert( "DIST", 271 );
10673   modelInputs.insert( "cx1:MODEL_OUT_LAYER", "dest.shp" );
10674   QgsProcessingOutputLayerDefinition layerDef( "memory:" );
10675   layerDef.destinationName = "my_dest";
10676   modelInputs.insert( "cx3:MY_OUT", QVariant::fromValue( layerDef ) );
10677   QVariantMap childResults;
10678   QVariantMap params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx1" ), modelInputs, childResults, expContext );
10679   QCOMPARE( params.value( "DISSOLVE" ).toBool(), false );
10680   QCOMPARE( params.value( "DISTANCE" ).toInt(), 271 );
10681   QCOMPARE( params.value( "SEGMENTS" ).toInt(), 16 );
10682   QCOMPARE( params.value( "END_CAP_STYLE" ).toInt(), 1 );
10683   QCOMPARE( params.value( "JOIN_STYLE" ).toInt(), 2 );
10684   QCOMPARE( params.value( "INPUT" ).toString(), vector );
10685   QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "dest.shp" ) );
10686   QCOMPARE( params.count(), 7 );
10687 
10688   QgsProcessingContext context;
10689 
10690   // Check variables for child algorithm
10691   // without values
10692   QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> variables = model2.variablesForChildAlgorithm( "cx1", context );
10693   QCOMPARE( variables.count(), 7 );
10694   QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10695   QCOMPARE( variables.value( "CRS" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10696   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10697   QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10698   QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10699   QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10700   QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10701 
10702   // with values
10703   variables = model2.variablesForChildAlgorithm( "cx1", context, modelInputs, childResults );
10704   QCOMPARE( variables.count(), 7 );
10705   QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 );
10706   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.parameterName(), QString( "SOURCE_LAYER" ) );
10707   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 );
10708   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 );
10709   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
10710   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
10711 
10712   std::unique_ptr< QgsExpressionContextScope > childScope( model2.createExpressionContextScopeForChildAlgorithm( "cx1", context, modelInputs, childResults ) );
10713   QCOMPARE( childScope->name(), QStringLiteral( "algorithm_inputs" ) );
10714   QCOMPARE( childScope->variableCount(), 7 );
10715   QCOMPARE( childScope->variable( "DIST" ).toInt(), 271 );
10716   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.parameterName(), QString( "SOURCE_LAYER" ) );
10717   QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_minx" ).toDouble(), -118.8888, 0.001 );
10718   QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_miny" ).toDouble(), 22.8002, 0.001 );
10719   QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_maxx" ).toDouble(), -83.3333, 0.001 );
10720   QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_maxy" ).toDouble(), 46.8719, 0.001 );
10721 
10722 
10723   QVariantMap results;
10724   results.insert( "OUTPUT", QStringLiteral( "dest.shp" ) );
10725   childResults.insert( "cx1", results );
10726 
10727   // a child who uses an output from another alg as a parameter value
10728   QgsProcessingModelChildAlgorithm alg2c2;
10729   alg2c2.setChildId( "cx2" );
10730   alg2c2.setAlgorithmId( "native:centroids" );
10731   alg2c2.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) );
10732   model2.addChildAlgorithm( alg2c2 );
10733   params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx2" ), modelInputs, childResults, expContext );
10734   QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) );
10735   QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "memory:Centroids" ) );
10736   QCOMPARE( params.count(), 2 );
10737 
10738   variables = model2.variablesForChildAlgorithm( "cx2", context );
10739   QCOMPARE( variables.count(), 12 );
10740   QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10741   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10742   QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10743   QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10744   QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10745   QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10746   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10747   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10748   QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10749   QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10750   QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10751   QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10752   QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10753   QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10754   QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10755   QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10756 
10757   // with values
10758   variables = model2.variablesForChildAlgorithm( "cx2", context, modelInputs, childResults );
10759   QCOMPARE( variables.count(), 12 );
10760   QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 );
10761   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.parameterName(), QString( "SOURCE_LAYER" ) );
10762   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.outputChildId(), QString( "cx1" ) );
10763   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.parameterName(), QString( "" ) );
10764   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 );
10765   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 );
10766   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
10767   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
10768 
10769   // a child with an optional output
10770   QgsProcessingModelChildAlgorithm alg2c3;
10771   alg2c3.setChildId( "cx3" );
10772   alg2c3.setAlgorithmId( "native:extractbyexpression" );
10773   alg2c3.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) );
10774   alg2c3.addParameterSources( "EXPRESSION", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( "true" ) );
10775   alg2c3.addParameterSources( "OUTPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "MY_OUT" ) );
10776   alg2c3.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "cx2" ) );
10777   QMap<QString, QgsProcessingModelOutput> outputs3;
10778   QgsProcessingModelOutput out2( "MY_OUT" );
10779   out2.setChildOutputName( "OUTPUT" );
10780   outputs3.insert( QStringLiteral( "MY_OUT" ), out2 );
10781   alg2c3.setModelOutputs( outputs3 );
10782 
10783   model2.addChildAlgorithm( alg2c3 );
10784   params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx3" ), modelInputs, childResults, expContext );
10785   QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) );
10786   QCOMPARE( params.value( "EXPRESSION" ).toString(), QStringLiteral( "true" ) );
10787   QVERIFY( params.value( "OUTPUT" ).canConvert<QgsProcessingOutputLayerDefinition>() );
10788   const QgsProcessingOutputLayerDefinition outDef = qvariant_cast<QgsProcessingOutputLayerDefinition>( params.value( "OUTPUT" ) );
10789   QCOMPARE( outDef.destinationName, QStringLiteral( "MY_OUT" ) );
10790   QCOMPARE( outDef.sink.staticValue().toString(), QStringLiteral( "memory:" ) );
10791   QCOMPARE( params.count(), 3 ); // don't want FAIL_OUTPUT set!
10792 
10793   // a child with an static output value
10794   QgsProcessingModelChildAlgorithm alg2c4;
10795   alg2c4.setChildId( "cx4" );
10796   alg2c4.setAlgorithmId( "native:extractbyexpression" );
10797   alg2c4.addParameterSources( "OUTPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( "STATIC" ) );
10798   model2.addChildAlgorithm( alg2c4 );
10799   params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx4" ), modelInputs, childResults, expContext );
10800   QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "STATIC" ) );
10801   model2.removeChildAlgorithm( "cx4" );
10802   // expression based output value
10803   alg2c4.addParameterSources( "OUTPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromExpression( "'A' || 'B'" ) );
10804   model2.addChildAlgorithm( alg2c4 );
10805   params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx4" ), modelInputs, childResults, expContext );
10806   QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "AB" ) );
10807   model2.removeChildAlgorithm( "cx4" );
10808 
10809 
10810   variables = model2.variablesForChildAlgorithm( "cx3", context );
10811   QCOMPARE( variables.count(), 17 );
10812   QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10813   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10814   QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10815   QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10816   QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10817   QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter );
10818   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10819   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10820   QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10821   QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10822   QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10823   QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10824   QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10825   QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10826   QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10827   QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx1" ) );
10828   QCOMPARE( variables.value( "cx2_OUTPUT" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10829   QCOMPARE( variables.value( "cx2_OUTPUT" ).source.outputChildId(), QStringLiteral( "cx2" ) );
10830   QCOMPARE( variables.value( "cx2_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10831   QCOMPARE( variables.value( "cx2_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx2" ) );
10832   QCOMPARE( variables.value( "cx2_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10833   QCOMPARE( variables.value( "cx2_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx2" ) );
10834   QCOMPARE( variables.value( "cx2_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10835   QCOMPARE( variables.value( "cx2_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx2" ) );
10836   QCOMPARE( variables.value( "cx2_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput );
10837   QCOMPARE( variables.value( "cx2_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx2" ) );
10838   // with values
10839   variables = model2.variablesForChildAlgorithm( "cx3", context, modelInputs, childResults );
10840   QCOMPARE( variables.count(), 17 );
10841   QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 );
10842   QCOMPARE( variables.value( "SOURCE_LAYER" ).source.parameterName(), QString( "SOURCE_LAYER" ) );
10843   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.outputChildId(), QString( "cx1" ) );
10844   QCOMPARE( variables.value( "cx1_OUTPUT" ).source.parameterName(), QString( "" ) );
10845   QCOMPARE( variables.value( "cx2_OUTPUT" ).source.outputChildId(), QString( "cx2" ) );
10846   QCOMPARE( variables.value( "cx2_OUTPUT" ).source.parameterName(), QString( "" ) );
10847   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 );
10848   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 );
10849   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 );
10850   QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 );
10851 
10852   // test safe name of the child alg parameter as source to another algorithm
10853   // parameter name should have [\s ' ( ) : .] chars changed to "_" (regexp [\\s'\"\\(\\):\.])
10854   // this case is esecially important in case of grass algs where name algorithm contains "."
10855   // name of the variable is get from childDescription or childId. Refs https://github.com/qgis/QGIS/issues/36377
10856   QgsProcessingModelChildAlgorithm &cx1 = model2.childAlgorithm( "cx1" );
10857   const QString oldDescription = cx1.description();
10858   cx1.setDescription( "cx '():.1" );
10859   variables = model2.variablesForChildAlgorithm( "cx3", context );
10860   QVERIFY( !variables.contains( "cx1_OUTPUT" ) );
10861   QVERIFY( !variables.contains( "cx '():.1_OUTPUT" ) );
10862   QVERIFY( variables.contains( "cx______1_OUTPUT" ) );
10863   cx1.setDescription( oldDescription ); // set descrin back to avoid fail of following tests
10864 
10865   // test model to python conversion
10866   model2.setName( QStringLiteral( "2my model" ) );
10867   model2.childAlgorithm( "cx1" ).modelOutput( QStringLiteral( "MODEL_OUT_LAYER" ) ).setDescription( "my model output" );
10868   model2.updateDestinationParameters();
10869   model2.childAlgorithm( "cx1" ).setDescription( "first step in my model" );
10870   const QStringList actualParts = model2.asPythonCode( QgsProcessing::PythonQgsProcessingAlgorithmSubclass, 2 );
10871   QgsDebugMsg( actualParts.join( '\n' ) );
10872   const QStringList expectedParts = QStringLiteral( "\"\"\"\n"
10873                                     "Model exported as python.\n"
10874                                     "Name : 2my model\n"
10875                                     "Group : \n"
10876                                     "With QGIS : %1\n"
10877                                     "\"\"\"\n\n"
10878                                     "from qgis.core import QgsProcessing\n"
10879                                     "from qgis.core import QgsProcessingAlgorithm\n"
10880                                     "from qgis.core import QgsProcessingMultiStepFeedback\n"
10881                                     "from qgis.core import QgsProcessingParameterFeatureSource\n"
10882                                     "from qgis.core import QgsProcessingParameterNumber\n"
10883                                     "from qgis.core import QgsProcessingParameterCrs\n"
10884                                     "from qgis.core import QgsProcessingParameterFeatureSink\n"
10885                                     "from qgis.core import QgsProcessingParameterDefinition\n"
10886                                     "from qgis.core import QgsCoordinateReferenceSystem\n"
10887                                     "from qgis.core import QgsExpression\n"
10888                                     "import processing\n"
10889                                     "\n"
10890                                     "\n"
10891                                     "class MyModel(QgsProcessingAlgorithm):\n"
10892                                     "\n"
10893                                     "  def initAlgorithm(self, config=None):\n"
10894                                     "    # an input\n"
10895                                     "    self.addParameter(QgsProcessingParameterFeatureSource('SOURCE_LAYER', '', defaultValue=None))\n"
10896                                     "    self.addParameter(QgsProcessingParameterNumber('DIST', '', type=QgsProcessingParameterNumber.Double, defaultValue=None))\n"
10897                                     "    param = QgsProcessingParameterCrs('CRS', '', defaultValue=QgsCoordinateReferenceSystem('EPSG:28355'))\n"
10898                                     "    param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)\n"
10899                                     "    self.addParameter(param)\n"
10900                                     "    self.addParameter(QgsProcessingParameterFeatureSink('MyModelOutput', 'my model output', type=QgsProcessing.TypeVectorPolygon, createByDefault=True, supportsAppend=True, defaultValue=None))\n"
10901                                     "    self.addParameter(QgsProcessingParameterFeatureSink('cx3:MY_OUT', '', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))\n"
10902                                     "\n"
10903                                     "  def processAlgorithm(self, parameters, context, model_feedback):\n"
10904                                     "    # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the\n"
10905                                     "    # overall progress through the model\n"
10906                                     "    feedback = QgsProcessingMultiStepFeedback(3, model_feedback)\n"
10907                                     "    results = {}\n"
10908                                     "    outputs = {}\n"
10909                                     "\n"
10910                                     "    # first step in my model\n"
10911                                     "    alg_params = {\n"
10912                                     "      'DISSOLVE': False,\n"
10913                                     "      'DISTANCE': parameters['DIST'],\n"
10914                                     "      'END_CAP_STYLE': 1,  # Flat\n"
10915                                     "      'INPUT': parameters['SOURCE_LAYER'],\n"
10916                                     "      'JOIN_STYLE': 2,  # Bevel\n"
10917                                     "      'SEGMENTS': QgsExpression('@myvar*2').evaluate(),\n"
10918                                     "      'OUTPUT': parameters['MyModelOutput']\n"
10919                                     "    }\n"
10920                                     "    outputs['FirstStepInMyModel'] = processing.run('native:buffer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
10921                                     "    results['MyModelOutput'] = outputs['FirstStepInMyModel']['OUTPUT']\n"
10922                                     "\n"
10923                                     "    feedback.setCurrentStep(1)\n"
10924                                     "    if feedback.isCanceled():\n"
10925                                     "      return {}\n"
10926                                     "\n"
10927                                     "    alg_params = {\n"
10928                                     "      'INPUT': outputs['FirstStepInMyModel']['OUTPUT'],\n"
10929                                     "      'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT\n"
10930                                     "    }\n"
10931                                     "    outputs['cx2'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
10932                                     "\n"
10933                                     "    feedback.setCurrentStep(2)\n"
10934                                     "    if feedback.isCanceled():\n"
10935                                     "      return {}\n"
10936                                     "\n"
10937                                     "    alg_params = {\n"
10938                                     "      'EXPRESSION': 'true',\n"
10939                                     "      'INPUT': outputs['FirstStepInMyModel']['OUTPUT'],\n"
10940                                     "      'OUTPUT': parameters['MY_OUT'],\n"
10941                                     "      'OUTPUT': parameters['cx3:MY_OUT']\n"
10942                                     "    }\n"
10943                                     "    outputs['cx3'] = processing.run('native:extractbyexpression', alg_params, context=context, feedback=feedback, is_child_algorithm=True)\n"
10944                                     "    results['cx3:MY_OUT'] = outputs['cx3']['OUTPUT']\n"
10945                                     "    return results\n"
10946                                     "\n"
10947                                     "  def name(self):\n"
10948                                     "    return '2my model'\n"
10949                                     "\n"
10950                                     "  def displayName(self):\n"
10951                                     "    return '2my model'\n"
10952                                     "\n"
10953                                     "  def group(self):\n"
10954                                     "    return ''\n"
10955                                     "\n"
10956                                     "  def groupId(self):\n"
10957                                     "    return ''\n"
10958                                     "\n"
10959                                     "  def createInstance(self):\n"
10960                                     "    return MyModel()\n" ).arg( Qgis::versionInt() ).split( '\n' );
10961   QCOMPARE( actualParts, expectedParts );
10962 }
10963 
modelBranchPruning()10964 void TestQgsProcessing::modelBranchPruning()
10965 {
10966   QgsVectorLayer *layer3111 = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" );
10967   QgsProject p;
10968   p.addMapLayer( layer3111 );
10969 
10970   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
10971   const QString raster1 = testDataDir + "landsat_4326.tif";
10972   const QFileInfo fi1( raster1 );
10973   QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
10974   QVERIFY( r1->isValid() );
10975   p.addMapLayer( r1 );
10976 
10977   QgsProcessingContext context;
10978   context.setProject( &p );
10979 
10980   // test that model branches are trimmed for algorithms which return the FlagPruneModelBranchesBasedOnAlgorithmResults flag
10981   QgsProcessingModelAlgorithm model1;
10982 
10983   // first add the filter by layer type alg
10984   QgsProcessingModelChildAlgorithm algc1;
10985   algc1.setChildId( "filter" );
10986   algc1.setAlgorithmId( "native:filterlayersbytype" );
10987   QgsProcessingModelParameter param;
10988   param.setParameterName( QStringLiteral( "LAYER" ) );
10989   model1.addModelParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "LAYER" ) ), param );
10990   algc1.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "LAYER" ) ) );
10991   model1.addChildAlgorithm( algc1 );
10992 
10993   //then create some branches which come off this, depending on the layer type
10994   QgsProcessingModelChildAlgorithm algc2;
10995   algc2.setChildId( "buffer" );
10996   algc2.setAlgorithmId( "native:buffer" );
10997   algc2.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "filter" ), QStringLiteral( "VECTOR" ) ) );
10998   QMap<QString, QgsProcessingModelOutput> outputsc2;
10999   QgsProcessingModelOutput outc2( "BUFFER_OUTPUT" );
11000   outc2.setChildOutputName( "OUTPUT" );
11001   outputsc2.insert( QStringLiteral( "BUFFER_OUTPUT" ), outc2 );
11002   algc2.setModelOutputs( outputsc2 );
11003   model1.addChildAlgorithm( algc2 );
11004   // ...we want a complex branch, so add some more bits to the branch
11005   QgsProcessingModelChildAlgorithm algc3;
11006   algc3.setChildId( "buffer2" );
11007   algc3.setAlgorithmId( "native:buffer" );
11008   algc3.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "buffer" ), QStringLiteral( "OUTPUT" ) ) );
11009   QMap<QString, QgsProcessingModelOutput> outputsc3;
11010   QgsProcessingModelOutput outc3( "BUFFER2_OUTPUT" );
11011   outc3.setChildOutputName( "OUTPUT" );
11012   outputsc3.insert( QStringLiteral( "BUFFER2_OUTPUT" ), outc3 );
11013   algc3.setModelOutputs( outputsc3 );
11014   model1.addChildAlgorithm( algc3 );
11015   QgsProcessingModelChildAlgorithm algc4;
11016   algc4.setChildId( "buffer3" );
11017   algc4.setAlgorithmId( "native:buffer" );
11018   algc4.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "buffer" ), QStringLiteral( "OUTPUT" ) ) );
11019   QMap<QString, QgsProcessingModelOutput> outputsc4;
11020   QgsProcessingModelOutput outc4( "BUFFER3_OUTPUT" );
11021   outc4.setChildOutputName( "OUTPUT" );
11022   outputsc4.insert( QStringLiteral( "BUFFER3_OUTPUT" ), outc4 );
11023   algc4.setModelOutputs( outputsc4 );
11024   model1.addChildAlgorithm( algc4 );
11025 
11026   // now add some bits to the raster branch
11027   QgsProcessingModelChildAlgorithm algr2;
11028   algr2.setChildId( "fill2" );
11029   algr2.setAlgorithmId( "native:fillnodata" );
11030   algr2.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "filter" ), QStringLiteral( "RASTER" ) ) );
11031   QMap<QString, QgsProcessingModelOutput> outputsr2;
11032   QgsProcessingModelOutput outr2( "RASTER_OUTPUT" );
11033   outr2.setChildOutputName( "OUTPUT" );
11034   outputsr2.insert( QStringLiteral( "RASTER_OUTPUT" ), outr2 );
11035   algr2.setModelOutputs( outputsr2 );
11036   model1.addChildAlgorithm( algr2 );
11037 
11038   // some more bits on the raster branch
11039   QgsProcessingModelChildAlgorithm algr3;
11040   algr3.setChildId( "fill3" );
11041   algr3.setAlgorithmId( "native:fillnodata" );
11042   algr3.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "fill2" ), QStringLiteral( "OUTPUT" ) ) );
11043   QMap<QString, QgsProcessingModelOutput> outputsr3;
11044   QgsProcessingModelOutput outr3( "RASTER_OUTPUT2" );
11045   outr3.setChildOutputName( "OUTPUT" );
11046   outputsr3.insert( QStringLiteral( "RASTER_OUTPUT2" ), outr3 );
11047   algr3.setModelOutputs( outputsr3 );
11048   model1.addChildAlgorithm( algr3 );
11049 
11050   QgsProcessingModelChildAlgorithm algr4;
11051   algr4.setChildId( "fill4" );
11052   algr4.setAlgorithmId( "native:fillnodata" );
11053   algr4.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "fill2" ), QStringLiteral( "OUTPUT" ) ) );
11054   QMap<QString, QgsProcessingModelOutput> outputsr4;
11055   QgsProcessingModelOutput outr4( "RASTER_OUTPUT3" );
11056   outr4.setChildOutputName( "OUTPUT" );
11057   outputsr4.insert( QStringLiteral( "RASTER_OUTPUT3" ), outr4 );
11058   algr4.setModelOutputs( outputsr4 );
11059   model1.addChildAlgorithm( algr4 );
11060 
11061   QgsProcessingFeedback feedback;
11062   QVariantMap params;
11063   // vector input
11064   params.insert( QStringLiteral( "LAYER" ), QStringLiteral( "v1" ) );
11065   params.insert( QStringLiteral( "buffer:BUFFER_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
11066   params.insert( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
11067   params.insert( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
11068   params.insert( QStringLiteral( "fill2:RASTER_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
11069   params.insert( QStringLiteral( "fill3:RASTER_OUTPUT2" ), QgsProcessing::TEMPORARY_OUTPUT );
11070   params.insert( QStringLiteral( "fill4:RASTER_OUTPUT3" ), QgsProcessing::TEMPORARY_OUTPUT );
11071   QVariantMap results = model1.run( params, context, &feedback );
11072   // we should get the vector branch outputs only
11073   QVERIFY( !results.value( QStringLiteral( "buffer:BUFFER_OUTPUT" ) ).toString().isEmpty() );
11074   QVERIFY( !results.value( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ) ).toString().isEmpty() );
11075   QVERIFY( !results.value( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ) ).toString().isEmpty() );
11076   QVERIFY( !results.contains( QStringLiteral( "fill2:RASTER_OUTPUT" ) ) );
11077   QVERIFY( !results.contains( QStringLiteral( "fill3:RASTER_OUTPUT2" ) ) );
11078   QVERIFY( !results.contains( QStringLiteral( "fill4:RASTER_OUTPUT3" ) ) );
11079 
11080   // raster input
11081   params.insert( QStringLiteral( "LAYER" ), QStringLiteral( "R1" ) );
11082   results = model1.run( params, context, &feedback );
11083   // we should get the raster branch outputs only
11084   QVERIFY( !results.value( QStringLiteral( "fill2:RASTER_OUTPUT" ) ).toString().isEmpty() );
11085   QVERIFY( !results.value( QStringLiteral( "fill3:RASTER_OUTPUT2" ) ).toString().isEmpty() );
11086   QVERIFY( !results.value( QStringLiteral( "fill4:RASTER_OUTPUT3" ) ).toString().isEmpty() );
11087   QVERIFY( !results.contains( QStringLiteral( "buffer:BUFFER_OUTPUT" ) ) );
11088   QVERIFY( !results.contains( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ) ) );
11089   QVERIFY( !results.contains( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ) ) );
11090 }
11091 
modelBranchPruningConditional()11092 void TestQgsProcessing::modelBranchPruningConditional()
11093 {
11094   QgsProcessingContext context;
11095 
11096   context.expressionContext().appendScope( new QgsExpressionContextScope() );
11097   context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var1" ), 1 );
11098   context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var2" ), 0 );
11099 
11100   // test that model branches are trimmed for algorithms which depend on conditional branches
11101   QgsProcessingModelAlgorithm model1;
11102 
11103   // first add the filter by layer type alg
11104   QgsProcessingModelChildAlgorithm algc1;
11105   algc1.setChildId( "branch" );
11106   algc1.setAlgorithmId( "native:condition" );
11107   QVariantMap config;
11108   QVariantList conditions;
11109   QVariantMap cond1;
11110   cond1.insert( QStringLiteral( "name" ), QStringLiteral( "name1" ) );
11111   cond1.insert( QStringLiteral( "expression" ), QStringLiteral( "@var1" ) );
11112   conditions << cond1;
11113   QVariantMap cond2;
11114   cond2.insert( QStringLiteral( "name" ), QStringLiteral( "name2" ) );
11115   cond2.insert( QStringLiteral( "expression" ), QStringLiteral( "@var2" ) );
11116   conditions << cond2;
11117   config.insert( QStringLiteral( "conditions" ), conditions );
11118   algc1.setConfiguration( config );
11119   model1.addChildAlgorithm( algc1 );
11120 
11121   //then create some branches which come off this
11122   QgsProcessingModelChildAlgorithm algc2;
11123   algc2.setChildId( "exception" );
11124   algc2.setAlgorithmId( "native:raiseexception" );
11125   algc2.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( QStringLiteral( "branch" ), QStringLiteral( "name1" ) ) );
11126   model1.addChildAlgorithm( algc2 );
11127 
11128   QgsProcessingModelChildAlgorithm algc3;
11129   algc2.setChildId( "exception" );
11130   algc3.setAlgorithmId( "native:raisewarning" );
11131   algc3.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( QStringLiteral( "branch" ), QStringLiteral( "name2" ) ) );
11132   model1.addChildAlgorithm( algc3 );
11133 
11134   QgsProcessingFeedback feedback;
11135   const QVariantMap params;
11136   bool ok = false;
11137   QVariantMap results = model1.run( params, context, &feedback, &ok );
11138   QVERIFY( !ok ); // the branch with the exception should be hit
11139 
11140   // flip the condition results
11141   context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var1" ), 0 );
11142   context.expressionContext().scope( 0 )->setVariable( QStringLiteral( "var2" ), 1 );
11143 
11144   results = model1.run( params, context, &feedback, &ok );
11145   QVERIFY( ok ); // the branch with the exception should NOT be hit
11146 }
11147 
modelWithProviderWithLimitedTypes()11148 void TestQgsProcessing::modelWithProviderWithLimitedTypes()
11149 {
11150   QgsApplication::processingRegistry()->addProvider( new DummyProvider4() );
11151 
11152   QgsProcessingModelAlgorithm alg( "test", "testGroup" );
11153   QgsProcessingModelChildAlgorithm algc1;
11154   algc1.setChildId( "cx1" );
11155   algc1.setAlgorithmId( "dummy4:alg1" );
11156   QMap<QString, QgsProcessingModelOutput> algc1outputs;
11157   QgsProcessingModelOutput algc1out1( QStringLiteral( "my_vector_output" ) );
11158   algc1out1.setChildId( "cx1" );
11159   algc1out1.setChildOutputName( "vector_dest" );
11160   algc1out1.setDescription( QStringLiteral( "my output" ) );
11161   algc1outputs.insert( QStringLiteral( "my_vector_output" ), algc1out1 );
11162   QgsProcessingModelOutput algc1out2( QStringLiteral( "my_raster_output" ) );
11163   algc1out2.setChildId( "cx1" );
11164   algc1out2.setChildOutputName( "raster_dest" );
11165   algc1out2.setDescription( QStringLiteral( "my output" ) );
11166   algc1outputs.insert( QStringLiteral( "my_raster_output" ), algc1out2 );
11167   QgsProcessingModelOutput algc1out3( QStringLiteral( "my_sink_output" ) );
11168   algc1out3.setChildId( "cx1" );
11169   algc1out3.setChildOutputName( "sink" );
11170   algc1out3.setDescription( QStringLiteral( "my output" ) );
11171   algc1outputs.insert( QStringLiteral( "my_sink_output" ), algc1out3 );
11172   algc1.setModelOutputs( algc1outputs );
11173   alg.addChildAlgorithm( algc1 );
11174   // verify that model has destination parameter created
11175   QCOMPARE( alg.destinationParameterDefinitions().count(), 3 );
11176   QCOMPARE( alg.destinationParameterDefinitions().at( 2 )->name(), QStringLiteral( "cx1:my_vector_output" ) );
11177   QCOMPARE( alg.destinationParameterDefinitions().at( 2 )->description(), QStringLiteral( "my output" ) );
11178   QCOMPARE( static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 2 ) )->originalProvider()->id(), QStringLiteral( "dummy4" ) );
11179   QCOMPARE( static_cast< const QgsProcessingParameterVectorDestination * >( alg.destinationParameterDefinitions().at( 2 ) )->supportedOutputVectorLayerExtensions(), QStringList() << QStringLiteral( "mif" ) );
11180   QCOMPARE( static_cast< const QgsProcessingParameterVectorDestination * >( alg.destinationParameterDefinitions().at( 2 ) )->defaultFileExtension(), QStringLiteral( "mif" ) );
11181   QVERIFY( static_cast< const QgsProcessingParameterVectorDestination * >( alg.destinationParameterDefinitions().at( 2 ) )->generateTemporaryDestination().endsWith( QLatin1String( ".mif" ) ) );
11182   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 2 ) )->supportsNonFileBasedOutput() );
11183 
11184   QCOMPARE( alg.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_raster_output" ) );
11185   QCOMPARE( alg.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) );
11186   QCOMPARE( static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 0 ) )->originalProvider()->id(), QStringLiteral( "dummy4" ) );
11187   QCOMPARE( static_cast< const QgsProcessingParameterRasterDestination * >( alg.destinationParameterDefinitions().at( 0 ) )->supportedOutputRasterLayerExtensions(), QStringList() << QStringLiteral( "mig" ) );
11188   QCOMPARE( static_cast< const QgsProcessingParameterRasterDestination * >( alg.destinationParameterDefinitions().at( 0 ) )->defaultFileExtension(), QStringLiteral( "mig" ) );
11189   QVERIFY( static_cast< const QgsProcessingParameterRasterDestination * >( alg.destinationParameterDefinitions().at( 0 ) )->generateTemporaryDestination().endsWith( QLatin1String( ".mig" ) ) );
11190   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 0 ) )->supportsNonFileBasedOutput() );
11191 
11192   QCOMPARE( alg.destinationParameterDefinitions().at( 1 )->name(), QStringLiteral( "cx1:my_sink_output" ) );
11193   QCOMPARE( alg.destinationParameterDefinitions().at( 1 )->description(), QStringLiteral( "my output" ) );
11194   QCOMPARE( static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 1 ) )->originalProvider()->id(), QStringLiteral( "dummy4" ) );
11195   QCOMPARE( static_cast< const QgsProcessingParameterFeatureSink * >( alg.destinationParameterDefinitions().at( 1 ) )->supportedOutputVectorLayerExtensions(), QStringList() << QStringLiteral( "mif" ) );
11196   QCOMPARE( static_cast< const QgsProcessingParameterFeatureSink * >( alg.destinationParameterDefinitions().at( 1 ) )->defaultFileExtension(), QStringLiteral( "mif" ) );
11197   QVERIFY( static_cast< const QgsProcessingParameterFeatureSink * >( alg.destinationParameterDefinitions().at( 1 ) )->generateTemporaryDestination().endsWith( QLatin1String( ".mif" ) ) );
11198   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 1 ) )->supportsNonFileBasedOutput() );
11199 }
11200 
modelVectorOutputIsCompatibleType()11201 void TestQgsProcessing::modelVectorOutputIsCompatibleType()
11202 {
11203   // IMPORTANT: This method is intended to be "permissive" rather than "restrictive".
11204   // I.e. we only reject outputs which we know can NEVER be acceptable, but
11205   // if there's doubt then we default to returning true.
11206 
11207   // empty acceptable type list = all are compatible
11208   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeVector ) );
11209   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeVectorAnyGeometry ) );
11210   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeVectorPoint ) );
11211   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeVectorLine ) );
11212   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeVectorPolygon ) );
11213   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( QList<int>(), QgsProcessing::TypeMapLayer ) );
11214 
11215   // accept any vector
11216   QList< int > dataTypes;
11217   dataTypes << QgsProcessing::TypeVector;
11218   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11219   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11220   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11221   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11222   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11223   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11224 
11225   // accept any vector with geometry
11226   dataTypes.clear();
11227   dataTypes << QgsProcessing::TypeVectorAnyGeometry;
11228   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11229   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11230   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11231   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11232   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11233   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11234 
11235   // accept any point vector
11236   dataTypes.clear();
11237   dataTypes << QgsProcessing::TypeVectorPoint;
11238   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11239   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11240   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11241   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11242   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11243   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11244 
11245   // accept any line vector
11246   dataTypes.clear();
11247   dataTypes << QgsProcessing::TypeVectorLine;
11248   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11249   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11250   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11251   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11252   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11253   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11254 
11255   // accept any polygon vector
11256   dataTypes.clear();
11257   dataTypes << QgsProcessing::TypeVectorPolygon;
11258   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11259   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11260   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11261   QVERIFY( !QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11262   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11263   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11264 
11265   // accept any map layer
11266   dataTypes.clear();
11267   dataTypes << QgsProcessing::TypeMapLayer;
11268   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVector ) );
11269   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorAnyGeometry ) );
11270   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPoint ) );
11271   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorLine ) );
11272   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeVectorPolygon ) );
11273   QVERIFY( QgsProcessingModelAlgorithm::vectorOutputIsCompatibleType( dataTypes, QgsProcessing::TypeMapLayer ) );
11274 }
11275 
modelAcceptableValues()11276 void TestQgsProcessing::modelAcceptableValues()
11277 {
11278   QgsProcessingModelAlgorithm m;
11279   const QgsProcessingModelParameter stringParam1( "string" );
11280   m.addModelParameter( new QgsProcessingParameterString( "string" ), stringParam1 );
11281 
11282   const QgsProcessingModelParameter stringParam2( "string2" );
11283   m.addModelParameter( new QgsProcessingParameterString( "string2" ), stringParam2 );
11284 
11285   const QgsProcessingModelParameter numParam( "number" );
11286   m.addModelParameter( new QgsProcessingParameterNumber( "number" ), numParam );
11287 
11288   const QgsProcessingModelParameter tableFieldParam( "field" );
11289   m.addModelParameter( new QgsProcessingParameterField( "field" ), tableFieldParam );
11290 
11291   const QgsProcessingModelParameter fileParam( "file" );
11292   m.addModelParameter( new QgsProcessingParameterFile( "file" ), fileParam );
11293 
11294   // test single types
11295   QgsProcessingModelChildParameterSources sources = m.availableSourcesForChild( QString(), QStringList() << "number" );
11296   QCOMPARE( sources.count(), 1 );
11297   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "number" ) );
11298   sources = m.availableSourcesForChild( QString(), QStringList() << "field" );
11299   QCOMPARE( sources.count(), 1 );
11300   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "field" ) );
11301   sources = m.availableSourcesForChild( QString(), QStringList() << "file" );
11302   QCOMPARE( sources.count(), 1 );
11303   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "file" ) );
11304 
11305   // test multiple types
11306   sources = m.availableSourcesForChild( QString(), QStringList() << "string" << "number" << "file" );
11307   QCOMPARE( sources.count(), 4 );
11308   QSet< QString > res;
11309   res << sources.at( 0 ).parameterName();
11310   res << sources.at( 1 ).parameterName();
11311   res << sources.at( 2 ).parameterName();
11312   res << sources.at( 3 ).parameterName();
11313 
11314   QCOMPARE( res, QSet< QString >() << QStringLiteral( "string" )
11315             << QStringLiteral( "string2" )
11316             << QStringLiteral( "number" )
11317             << QStringLiteral( "file" ) );
11318 
11319   // check outputs
11320   QgsProcessingModelChildAlgorithm alg2c1;
11321   alg2c1.setChildId( "cx1" );
11322   alg2c1.setAlgorithmId( "native:centroids" );
11323   m.addChildAlgorithm( alg2c1 );
11324 
11325   sources = m.availableSourcesForChild( QString(), QStringList(), QStringList() << "string" << "outputVector" );
11326   QCOMPARE( sources.count(), 1 );
11327   res.clear();
11328   res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName();
11329   QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" );
11330 
11331   // with dependencies between child algs
11332   QgsProcessingModelChildAlgorithm alg2c2;
11333   alg2c2.setChildId( "cx2" );
11334   alg2c2.setAlgorithmId( "native:centroids" );
11335   alg2c2.setDependencies( QList< QgsProcessingModelChildDependency >() << QgsProcessingModelChildDependency( "cx1" ) );
11336   m.addChildAlgorithm( alg2c2 );
11337   sources = m.availableSourcesForChild( QString(), QStringList(), QStringList() << "string" << "outputVector" );
11338   QCOMPARE( sources.count(), 2 );
11339   res.clear();
11340   res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName();
11341   res << sources.at( 1 ).outputChildId() + ':' + sources.at( 1 ).outputName();
11342   QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" << "cx2:OUTPUT" );
11343 
11344   sources = m.availableSourcesForChild( QStringLiteral( "cx1" ), QStringList(), QStringList() << "string" << "outputVector" );
11345   QCOMPARE( sources.count(), 0 );
11346 
11347   sources = m.availableSourcesForChild( QString( "cx2" ), QStringList(), QStringList() << "string" << "outputVector" );
11348   QCOMPARE( sources.count(), 1 );
11349   res.clear();
11350   res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName();
11351   QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" );
11352 
11353   // test limiting by data types
11354   QgsProcessingModelAlgorithm m2;
11355   const QgsProcessingModelParameter vlInput( "vl" );
11356   // with no limit on data types
11357   m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl" ), vlInput );
11358   const QgsProcessingModelParameter fsInput( "fs" );
11359   m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs" ), fsInput );
11360 
11361   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" );
11362   QCOMPARE( sources.count(), 2 );
11363   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11364   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11365   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorPoint );
11366   QCOMPARE( sources.count(), 2 );
11367   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11368   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11369   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVector );
11370   QCOMPARE( sources.count(), 2 );
11371   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11372   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11373   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry );
11374   QCOMPARE( sources.count(), 2 );
11375   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11376   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11377   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeMapLayer );
11378   QCOMPARE( sources.count(), 2 );
11379   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11380   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11381 
11382   // inputs are limited to vector layers
11383   m2.removeModelParameter( vlInput.parameterName() );
11384   m2.removeModelParameter( fsInput.parameterName() );
11385   m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList<int>() << QgsProcessing::TypeVector ), vlInput );
11386   m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList<int>() << QgsProcessing::TypeVector ), fsInput );
11387   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" );
11388   QCOMPARE( sources.count(), 2 );
11389   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11390   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11391   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorPoint );
11392   QCOMPARE( sources.count(), 2 );
11393   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11394   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11395   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVector );
11396   QCOMPARE( sources.count(), 2 );
11397   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11398   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11399   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry );
11400   QCOMPARE( sources.count(), 2 );
11401   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11402   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11403   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeMapLayer );
11404   QCOMPARE( sources.count(), 2 );
11405   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11406   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11407 
11408   // inputs are limited to vector layers with geometries
11409   m2.removeModelParameter( vlInput.parameterName() );
11410   m2.removeModelParameter( fsInput.parameterName() );
11411   m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry ), vlInput );
11412   m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry ), fsInput );
11413   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" );
11414   QCOMPARE( sources.count(), 2 );
11415   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11416   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11417   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorPoint );
11418   QCOMPARE( sources.count(), 2 );
11419   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11420   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11421   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVector );
11422   QCOMPARE( sources.count(), 2 );
11423   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11424   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11425   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry );
11426   QCOMPARE( sources.count(), 2 );
11427   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11428   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11429   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeMapLayer );
11430   QCOMPARE( sources.count(), 2 );
11431   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11432   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11433 
11434   // inputs are limited to vector layers with lines
11435   m2.removeModelParameter( vlInput.parameterName() );
11436   m2.removeModelParameter( fsInput.parameterName() );
11437   m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList<int>() << QgsProcessing::TypeVectorLine ), vlInput );
11438   m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList<int>() << QgsProcessing::TypeVectorLine ), fsInput );
11439   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" );
11440   QCOMPARE( sources.count(), 2 );
11441   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11442   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11443   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorPoint );
11444   QCOMPARE( sources.count(), 0 );
11445   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorPolygon );
11446   QCOMPARE( sources.count(), 0 );
11447   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorLine );
11448   QCOMPARE( sources.count(), 2 );
11449   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11450   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11451   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVector );
11452   QCOMPARE( sources.count(), 2 );
11453   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11454   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11455   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeVectorAnyGeometry );
11456   QCOMPARE( sources.count(), 2 );
11457   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11458   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11459   sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList<int>() << QgsProcessing::TypeMapLayer );
11460   QCOMPARE( sources.count(), 2 );
11461   QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) );
11462   QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) );
11463 }
11464 
modelValidate()11465 void TestQgsProcessing::modelValidate()
11466 {
11467   QgsProcessingModelAlgorithm m;
11468   QStringList errors;
11469   QVERIFY( !m.validate( errors ) );
11470   QCOMPARE( errors.size(), 1 );
11471   QCOMPARE( errors.at( 0 ), QStringLiteral( "Model does not contain any algorithms" ) );
11472 
11473   const QgsProcessingModelParameter stringParam1( "string" );
11474   m.addModelParameter( new QgsProcessingParameterString( "string" ), stringParam1 );
11475   QgsProcessingModelChildAlgorithm alg2c1;
11476   alg2c1.setChildId( "cx1" );
11477   alg2c1.setAlgorithmId( "native:centroids" );
11478   alg2c1.setDescription( QStringLiteral( "centroids" ) );
11479   m.addChildAlgorithm( alg2c1 );
11480 
11481   QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11482   QCOMPARE( errors.size(), 2 );
11483   QCOMPARE( errors.at( 0 ), QStringLiteral( "Parameter <i>INPUT</i> is mandatory" ) );
11484   QCOMPARE( errors.at( 1 ), QStringLiteral( "Parameter <i>ALL_PARTS</i> is mandatory" ) );
11485 
11486   QVERIFY( !m.validate( errors ) );
11487   QCOMPARE( errors.size(), 2 );
11488   QCOMPARE( errors.at( 0 ), QStringLiteral( "<b>centroids</b>: Parameter <i>INPUT</i> is mandatory" ) );
11489   QCOMPARE( errors.at( 1 ), QStringLiteral( "<b>centroids</b>: Parameter <i>ALL_PARTS</i> is mandatory" ) );
11490 
11491   QgsProcessingModelChildParameterSource badSource;
11492   badSource.setSource( QgsProcessingModelChildParameterSource::StaticValue );
11493   badSource.setStaticValue( 56 );
11494   m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );
11495 
11496   QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11497   QCOMPARE( errors.size(), 2 );
11498   QCOMPARE( errors.at( 0 ), QStringLiteral( "Value for <i>INPUT</i> is not acceptable for this parameter" ) );
11499   QCOMPARE( errors.at( 1 ), QStringLiteral( "Parameter <i>ALL_PARTS</i> is mandatory" ) );
11500 
11501   QgsProcessingModelChildParameterSource goodSource;
11502   goodSource.setSource( QgsProcessingModelChildParameterSource::Expression );
11503   m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "ALL_PARTS" ), QList< QgsProcessingModelChildParameterSource >() << goodSource );
11504 
11505   QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11506   QCOMPARE( errors.size(), 1 );
11507   QCOMPARE( errors.at( 0 ), QStringLiteral( "Value for <i>INPUT</i> is not acceptable for this parameter" ) );
11508 
11509   badSource.setSource( QgsProcessingModelChildParameterSource::ChildOutput );
11510   badSource.setOutputChildId( QStringLiteral( "cc" ) );
11511   m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );
11512 
11513   QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11514   QCOMPARE( errors.size(), 1 );
11515   QCOMPARE( errors.at( 0 ), QStringLiteral( "Child algorithm <i>cc</i> used for parameter <i>INPUT</i> does not exist" ) );
11516 
11517   badSource.setSource( QgsProcessingModelChildParameterSource::ModelParameter );
11518   badSource.setParameterName( QStringLiteral( "cc" ) );
11519   m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << badSource );
11520 
11521   QVERIFY( !m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11522   QCOMPARE( errors.size(), 1 );
11523   QCOMPARE( errors.at( 0 ), QStringLiteral( "Model input <i>cc</i> used for parameter <i>INPUT</i> does not exist" ) );
11524 
11525   goodSource.setSource( QgsProcessingModelChildParameterSource::StaticValue );
11526   goodSource.setStaticValue( QString( QStringLiteral( TEST_DATA_DIR ) + "/polys.shp" ) );
11527   m.childAlgorithm( QStringLiteral( "cx1" ) ).addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << goodSource );
11528 
11529   QVERIFY( m.validateChildAlgorithm( QStringLiteral( "cx1" ), errors ) );
11530   QCOMPARE( errors.size(), 0 );
11531 
11532   QVERIFY( m.validate( errors ) );
11533   QCOMPARE( errors.size(), 0 );
11534 }
11535 
modelInputs()11536 void TestQgsProcessing::modelInputs()
11537 {
11538   QgsProcessingModelAlgorithm m;
11539 
11540   // add a bunch of inputs
11541   const QgsProcessingModelParameter stringParam1( "string" );
11542   m.addModelParameter( new QgsProcessingParameterString( "string" ), stringParam1 );
11543 
11544   const QgsProcessingModelParameter stringParam2( "a string" );
11545   m.addModelParameter( new QgsProcessingParameterString( "a string" ), stringParam2 );
11546 
11547   const QgsProcessingModelParameter stringParam3( "cc string" );
11548   m.addModelParameter( new QgsProcessingParameterString( "cc string" ), stringParam3 );
11549 
11550   // set specific input order for parameters
11551   m.setParameterOrder( QStringList() << "cc string" << "a string" );
11552 
11553   QgsProcessingModelAlgorithm m2;
11554   m2.loadVariant( m.toVariant() );
11555   QCOMPARE( m2.orderedParameters().count(), 3 );
11556   QCOMPARE( m2.orderedParameters().at( 0 ).parameterName(), QStringLiteral( "cc string" ) );
11557   QCOMPARE( m2.orderedParameters().at( 1 ).parameterName(), QStringLiteral( "a string" ) );
11558   QCOMPARE( m2.orderedParameters().at( 2 ).parameterName(), QStringLiteral( "string" ) );
11559 
11560   QCOMPARE( m2.parameterDefinitions().at( 0 )->name(), QStringLiteral( "cc string" ) );
11561   QCOMPARE( m2.parameterDefinitions().at( 1 )->name(), QStringLiteral( "a string" ) );
11562   QCOMPARE( m2.parameterDefinitions().at( 2 )->name(), QStringLiteral( "string" ) );
11563 }
11564 
modelDependencies()11565 void TestQgsProcessing::modelDependencies()
11566 {
11567   const QgsProcessingModelChildDependency dep( QStringLiteral( "childId" ), QStringLiteral( "branch" ) );
11568 
11569   QCOMPARE( dep.childId, QStringLiteral( "childId" ) );
11570   QCOMPARE( dep.conditionalBranch, QStringLiteral( "branch" ) );
11571 
11572   const QVariant v = dep.toVariant();
11573   QgsProcessingModelChildDependency dep2;
11574   QVERIFY( dep2.loadVariant( v.toMap() ) );
11575 
11576   QCOMPARE( dep2.childId, QStringLiteral( "childId" ) );
11577   QCOMPARE( dep2.conditionalBranch, QStringLiteral( "branch" ) );
11578 
11579   QVERIFY( dep == dep2 );
11580   QVERIFY( !( dep != dep2 ) );
11581   dep2.conditionalBranch = QStringLiteral( "b" );
11582 
11583   QVERIFY( !( dep == dep2 ) );
11584   QVERIFY( dep != dep2 );
11585   dep2.conditionalBranch = QStringLiteral( "branch" );
11586   dep2.childId = QStringLiteral( "c" );
11587   QVERIFY( !( dep == dep2 ) );
11588   QVERIFY( dep != dep2 );
11589   dep2.childId = QStringLiteral( "childId" );
11590   QVERIFY( dep == dep2 );
11591   QVERIFY( !( dep != dep2 ) );
11592 }
11593 
tempUtils()11594 void TestQgsProcessing::tempUtils()
11595 {
11596   QString tempFolder = QgsProcessingUtils::tempFolder();
11597   // tempFolder should remain constant for session
11598   QCOMPARE( QgsProcessingUtils::tempFolder(), tempFolder );
11599 
11600   const QString tempFile1 = QgsProcessingUtils::generateTempFilename( "test.txt" );
11601   QVERIFY( tempFile1.endsWith( "test.txt" ) );
11602   QVERIFY( tempFile1.startsWith( tempFolder ) );
11603 
11604   // expect a different file
11605   const QString tempFile2 = QgsProcessingUtils::generateTempFilename( "test.txt" );
11606   QVERIFY( tempFile1 != tempFile2 );
11607   QVERIFY( tempFile2.endsWith( "test.txt" ) );
11608   QVERIFY( tempFile2.startsWith( tempFolder ) );
11609 
11610   // invalid characters
11611   const QString tempFile3 = QgsProcessingUtils::generateTempFilename( "mybad:file.txt" );
11612   QVERIFY( tempFile3.endsWith( "mybad_file.txt" ) );
11613   QVERIFY( tempFile3.startsWith( tempFolder ) );
11614 
11615   // change temp folder in the settings
11616   std::unique_ptr< QTemporaryDir > dir = std::make_unique< QTemporaryDir >();
11617   const QString tempDirPath = dir->path();
11618   dir.reset();
11619 
11620   const QgsSettings settings;
11621   const QString alternative_tempFolder1 = tempDirPath + QStringLiteral( "/alternative_temp_test_one" );
11622   QgsProcessing::settingsTempPath.setValue( alternative_tempFolder1 );
11623   // check folder and if it's constant with alternative temp folder 1
11624   tempFolder = QgsProcessingUtils::tempFolder();
11625   QCOMPARE( tempFolder.left( alternative_tempFolder1.length() ), alternative_tempFolder1 );
11626   QCOMPARE( QgsProcessingUtils::tempFolder(), tempFolder );
11627   // create file
11628   const QString alternativeTempFile1 = QgsProcessingUtils::generateTempFilename( "alternative_temptest.txt" );
11629   QVERIFY( alternativeTempFile1.endsWith( "alternative_temptest.txt" ) );
11630   QVERIFY( alternativeTempFile1.startsWith( tempFolder ) );
11631   QVERIFY( alternativeTempFile1.startsWith( alternative_tempFolder1 ) );
11632   // change temp folder in the settings again
11633   const QString alternative_tempFolder2 =  tempDirPath + QStringLiteral( "/alternative_temp_test_two" );
11634   QgsProcessing::settingsTempPath.setValue( alternative_tempFolder2 );
11635   // check folder and if it's constant constant with alternative temp folder 2
11636   tempFolder = QgsProcessingUtils::tempFolder();
11637   QCOMPARE( tempFolder.left( alternative_tempFolder2.length() ), alternative_tempFolder2 );
11638   QCOMPARE( QgsProcessingUtils::tempFolder(), tempFolder );
11639   // create file
11640   const QString alternativeTempFile2 = QgsProcessingUtils::generateTempFilename( "alternative_temptest.txt" );
11641   QVERIFY( alternativeTempFile2.endsWith( "alternative_temptest.txt" ) );
11642   QVERIFY( alternativeTempFile2.startsWith( tempFolder ) );
11643   QVERIFY( alternativeTempFile2.startsWith( alternative_tempFolder2 ) );
11644   QgsProcessing::settingsTempPath.setValue( QString() );
11645 
11646 }
11647 
convertCompatible()11648 void TestQgsProcessing::convertCompatible()
11649 {
11650   // start with a compatible shapefile
11651   const QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
11652   const QString vector = testDataDir + "points.shp";
11653   QgsVectorLayer *layer = new QgsVectorLayer( vector, "vl" );
11654   QgsProject p;
11655   p.addMapLayer( layer );
11656 
11657   QgsProcessingContext context;
11658   context.setProject( &p );
11659   QgsProcessingFeedback feedback;
11660   QString out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11661   // layer should be returned unchanged - underlying source is compatible
11662   QCOMPARE( out, layer->source() );
11663 
11664   QString layerName;
11665   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11666   // layer should be returned unchanged - underlying source is compatible
11667   QCOMPARE( out, layer->source() );
11668   QCOMPARE( layerName, QString() );
11669 
11670   // path with layer suffix
11671   const QString vectorWithLayer = testDataDir + "points.shp|layername=points";
11672   QgsVectorLayer *layer2 = new QgsVectorLayer( vectorWithLayer, "vl" );
11673   p.addMapLayer( layer2 );
11674   out = QgsProcessingUtils::convertToCompatibleFormat( layer2, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11675   // layer should be returned unchanged - underlying source is compatible
11676   QCOMPARE( out, vector );
11677   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer2, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11678   QCOMPARE( out, vector );
11679   QCOMPARE( layerName, QStringLiteral( "points" ) );
11680 
11681   // don't include shp as compatible type
11682   out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback );
11683   QVERIFY( out != layer->source() );
11684   QVERIFY( out.endsWith( ".tab" ) );
11685   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11686 
11687   // make sure all features are copied
11688   std::unique_ptr< QgsVectorLayer > t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11689   QCOMPARE( layer->featureCount(), t->featureCount() );
11690   QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:4326" ) );
11691 
11692   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer, false, QStringLiteral( "test2" ), QStringList() << "tab", QString( "tab" ), context, &feedback, layerName );
11693   QVERIFY( out != layer->source() );
11694   QVERIFY( out.endsWith( ".tab" ) );
11695   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11696   QCOMPARE( layerName, QString() );
11697 
11698   // use a selection - this will require translation
11699   QgsFeatureIds ids;
11700   QgsFeature f;
11701   QgsFeatureIterator it = layer->getFeatures();
11702   it.nextFeature( f );
11703   ids.insert( f.id() );
11704   it.nextFeature( f );
11705   ids.insert( f.id() );
11706 
11707   layer->selectByIds( ids );
11708   out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback );
11709   QVERIFY( out != layer->source() );
11710   QVERIFY( out.endsWith( ".tab" ) );
11711   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11712   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11713   QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );
11714 
11715   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer, true, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback, layerName );
11716   QVERIFY( out != layer->source() );
11717   QVERIFY( out.endsWith( ".tab" ) );
11718   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11719   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11720   QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );
11721   QCOMPARE( layerName, QString() );
11722 
11723   // using a selection but existing format - will still require translation
11724   out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11725   QVERIFY( out != layer->source() );
11726   QVERIFY( out.endsWith( ".shp" ) );
11727   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11728   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11729   QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );
11730 
11731   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer, true, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11732   QVERIFY( out != layer->source() );
11733   QVERIFY( out.endsWith( ".shp" ) );
11734   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11735   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11736   QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );
11737   QCOMPARE( layerName, QString() );
11738 
11739   // using a feature filter -- this will require translation
11740   layer->setSubsetString( "1 or 2" );
11741   out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11742   QVERIFY( out != layer->source() );
11743   QVERIFY( out.endsWith( ".shp" ) );
11744   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11745   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11746   QCOMPARE( t->featureCount(), layer->featureCount() );
11747 
11748   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11749   QVERIFY( out != layer->source() );
11750   QVERIFY( out.endsWith( ".shp" ) );
11751   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11752   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11753   QCOMPARE( t->featureCount(), layer->featureCount() );
11754   QCOMPARE( layerName, QString() );
11755   layer->setSubsetString( QString() );
11756 
11757   // using GDAL's virtual I/O (/vsizip/, etc.)
11758   const QString vsiPath = "/vsizip/" + testDataDir + "zip/points2.zip/points.shp";
11759   QgsVectorLayer *vsiLayer = new QgsVectorLayer( vsiPath, "vl" );
11760   p.addMapLayer( vsiLayer );
11761   out = QgsProcessingUtils::convertToCompatibleFormat( vsiLayer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11762   QVERIFY( out != layer->source() );
11763   QVERIFY( out.endsWith( ".shp" ) );
11764   QVERIFY( !out.contains( "/vsizip" ) );
11765   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11766   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11767   QCOMPARE( t->featureCount(), layer->featureCount() );
11768 
11769   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( vsiLayer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11770   QVERIFY( out != layer->source() );
11771   QVERIFY( out.endsWith( ".shp" ) );
11772   QVERIFY( !out.contains( "/vsizip" ) );
11773   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11774   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11775   QCOMPARE( t->featureCount(), layer->featureCount() );
11776   QCOMPARE( layerName, QString() );
11777 
11778   // non-OGR source -- must be translated, regardless of extension. (e.g. delimited text provider handles CSV very different to OGR!)
11779   std::unique_ptr< QgsVectorLayer > memLayer = std::make_unique< QgsVectorLayer> ( "Point", "v1", "memory" );
11780   for ( int i = 1; i < 6; ++i )
11781   {
11782     QgsFeature f( i );
11783     f.setGeometry( QgsGeometry( new QgsPoint( 1, 2 ) ) );
11784     memLayer->dataProvider()->addFeatures( QgsFeatureList() << f );
11785   }
11786   out = QgsProcessingUtils::convertToCompatibleFormat( memLayer.get(), false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
11787   QVERIFY( out != memLayer->source() );
11788   QVERIFY( out.endsWith( ".shp" ) );
11789   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11790   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11791   QCOMPARE( t->featureCount(), memLayer->featureCount() );
11792 
11793   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( memLayer.get(), false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback, layerName );
11794   QVERIFY( out != memLayer->source() );
11795   QVERIFY( out.endsWith( ".shp" ) );
11796   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11797   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11798   QCOMPARE( t->featureCount(), memLayer->featureCount() );
11799   QCOMPARE( layerName, QString() );
11800 
11801   //delimited text -- must be translated, regardless of extension. (delimited text provider handles CSV very different to OGR!)
11802   const QString csvPath = "file://" + testDataDir + "delimitedtext/testpt.csv?type=csv&useHeader=No&detectTypes=yes&xyDms=yes&geomType=none&subsetIndex=no&watchFile=no";
11803   std::unique_ptr< QgsVectorLayer > csvLayer = std::make_unique< QgsVectorLayer >( csvPath, "vl", "delimitedtext" );
11804   QVERIFY( csvLayer->isValid() );
11805   out = QgsProcessingUtils::convertToCompatibleFormat( csvLayer.get(), false, QStringLiteral( "test" ), QStringList() << "csv", QString( "csv" ), context, &feedback );
11806   QVERIFY( out != csvLayer->source() );
11807   QVERIFY( out.endsWith( ".csv" ) );
11808   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11809   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11810   QCOMPARE( t->featureCount(), csvLayer->featureCount() );
11811 
11812   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( csvLayer.get(), false, QStringLiteral( "test" ), QStringList() << "csv", QString( "csv" ), context, &feedback, layerName );
11813   QVERIFY( out != csvLayer->source() );
11814   QVERIFY( out.endsWith( ".csv" ) );
11815   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11816   t = std::make_unique< QgsVectorLayer >( out, "vl2" );
11817   QCOMPARE( t->featureCount(), csvLayer->featureCount() );
11818   QCOMPARE( layerName, QString() );
11819 
11820   // geopackage with layer
11821   QString gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_gpkg";
11822   std::unique_ptr< QgsVectorLayer > gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11823   QVERIFY( gpkgLayer->isValid() );
11824   out = QgsProcessingUtils::convertToCompatibleFormat( gpkgLayer.get(), false, QStringLiteral( "test" ), QStringList() << "gpkg" << "shp", QString( "shp" ), context, &feedback );
11825   // layer must be translated -- we do not know if external tool can handle picking the correct layer automatically
11826   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11827   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_small";
11828   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11829   QVERIFY( gpkgLayer->isValid() );
11830   out = QgsProcessingUtils::convertToCompatibleFormat( gpkgLayer.get(), false, QStringLiteral( "test" ), QStringList() << "gpkg" << "shp", QString( "shp" ), context, &feedback );
11831   QVERIFY( out.endsWith( ".shp" ) );
11832   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11833 
11834   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_gpkg";
11835   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11836   QVERIFY( gpkgLayer->isValid() );
11837   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( gpkgLayer.get(), false, QStringLiteral( "test" ), QStringList() << "gpkg" << "shp", QString( "shp" ), context, &feedback, layerName );
11838   // layer SHOULD NOT be translated -- in this case we know that the external tool can handle specifying the correct layer
11839   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11840   QCOMPARE( layerName, QStringLiteral( "points_gpkg" ) );
11841   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_small";
11842   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11843   QVERIFY( gpkgLayer->isValid() );
11844   out = QgsProcessingUtils::convertToCompatibleFormatAndLayerName( gpkgLayer.get(), false, QStringLiteral( "test" ), QStringList() << "gpkg" << "shp", QString( "shp" ), context, &feedback, layerName );
11845   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11846   QCOMPARE( layerName, QStringLiteral( "points_small" ) );
11847 
11848   // also test evaluating parameter to compatible format
11849   std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterFeatureSource( QStringLiteral( "source" ) ) );
11850   QVariantMap params;
11851   params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), false ) );
11852   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11853   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11854   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11855   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11856   QCOMPARE( layerName, QString() );
11857 
11858   // incompatible format, will be converted
11859   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback );
11860   QVERIFY( out != layer->source() );
11861   QVERIFY( out.endsWith( ".tab" ) );
11862   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11863   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback, &layerName );
11864   QVERIFY( out != layer->source() );
11865   QVERIFY( out.endsWith( ".tab" ) );
11866   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11867   QCOMPARE( layerName, QString() );
11868 
11869   // layer as input
11870   params.insert( QStringLiteral( "source" ), QVariant::fromValue( layer ) );
11871   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11872   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11873   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11874   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11875   QCOMPARE( layerName, QString() );
11876 
11877   // incompatible format, will be converted
11878   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback );
11879   QVERIFY( out != layer->source() );
11880   QVERIFY( out.endsWith( ".tab" ) );
11881   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11882   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback, &layerName );
11883   QVERIFY( out != layer->source() );
11884   QVERIFY( out.endsWith( ".tab" ) );
11885   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11886   QCOMPARE( layerName, QString() );
11887 
11888   // selected only, will force export
11889   params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), true ) );
11890   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11891   QVERIFY( out != layer->source() );
11892   QVERIFY( out.endsWith( ".shp" ) );
11893   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11894   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11895   QVERIFY( out != layer->source() );
11896   QVERIFY( out.endsWith( ".shp" ) );
11897   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11898   QCOMPARE( layerName, QString() );
11899 
11900   // feature limit, will force export
11901   params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), false, 2 ) );
11902   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11903   QVERIFY( out != layer->source() );
11904   QVERIFY( out.endsWith( ".shp" ) );
11905   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11906   QgsVectorLayer *subset = new QgsVectorLayer( out );
11907   QVERIFY( subset->isValid() );
11908   QCOMPARE( subset->featureCount(), 2L );
11909   delete subset;
11910 
11911   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11912   QVERIFY( out != layer->source() );
11913   QVERIFY( out.endsWith( ".shp" ) );
11914   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11915   QCOMPARE( layerName, QString() );
11916   subset = new QgsVectorLayer( out );
11917   QVERIFY( subset->isValid() );
11918   QCOMPARE( subset->featureCount(), 2L );
11919   delete subset;
11920 
11921   // vector layer as default
11922   def.reset( new QgsProcessingParameterFeatureSource( QStringLiteral( "source" ), QString(), QList<int>(), QVariant::fromValue( layer ) ) );
11923   params.remove( QStringLiteral( "source" ) );
11924   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11925   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11926   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11927   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11928   QCOMPARE( layerName, QString() );
11929 
11930   // geopackage with layer
11931   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_gpkg";
11932   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11933   QVERIFY( gpkgLayer->isValid() );
11934   params.insert( QStringLiteral( "source" ), QVariant::fromValue( gpkgLayer.get() ) );
11935   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "gpkg" << "shp", QString( "shp" ), &feedback );
11936   // layer must be translated -- we do not know if external tool can handle picking the correct layer automatically
11937   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11938   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_small";
11939   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11940   QVERIFY( gpkgLayer->isValid() );
11941   params.insert( QStringLiteral( "source" ), QVariant::fromValue( gpkgLayer.get() ) );
11942   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "gpkg" << "shp", QString( "shp" ), &feedback );
11943   QVERIFY( out.endsWith( ".shp" ) );
11944   QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
11945 
11946   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_gpkg";
11947   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11948   QVERIFY( gpkgLayer->isValid() );
11949   params.insert( QStringLiteral( "source" ), QVariant::fromValue( gpkgLayer.get() ) );
11950   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "gpkg" << "shp", QString( "shp" ), &feedback, &layerName );
11951   // layer SHOULD NOT be translated -- in this case we know that the external tool can handle specifying the correct layer
11952   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11953   QCOMPARE( layerName, QStringLiteral( "points_gpkg" ) );
11954   gpkgPath = testDataDir + "points_gpkg.gpkg|layername=points_small";
11955   gpkgLayer = std::make_unique< QgsVectorLayer >( gpkgPath, "vl" );
11956   QVERIFY( gpkgLayer->isValid() );
11957   params.insert( QStringLiteral( "source" ), QVariant::fromValue( gpkgLayer.get() ) );
11958   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "gpkg" << "shp", QString( "shp" ), &feedback, &layerName );
11959   QCOMPARE( out, QString( testDataDir + QStringLiteral( "points_gpkg.gpkg" ) ) );
11960   QCOMPARE( layerName, QStringLiteral( "points_small" ) );
11961 
11962   // output layer as input - e.g. from a previous model child
11963   params.insert( QStringLiteral( "source" ), QgsProcessingOutputLayerDefinition( layer->id() ) );
11964   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
11965   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11966   out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPathAndLayerName( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback, &layerName );
11967   QCOMPARE( out, QString( testDataDir + "points.shp" ) );
11968   QCOMPARE( layerName, QString() );
11969 }
11970 
create()11971 void TestQgsProcessing::create()
11972 {
11973   DummyAlgorithm alg( QStringLiteral( "test" ) );
11974   DummyProvider p( QStringLiteral( "test_provider" ) );
11975   alg.setProvider( &p );
11976 
11977   std::unique_ptr< QgsProcessingAlgorithm > newInstance( alg.create() );
11978   QVERIFY( newInstance.get() );
11979   QCOMPARE( newInstance->provider(), &p );
11980 }
11981 
combineFields()11982 void TestQgsProcessing::combineFields()
11983 {
11984   QgsFields a;
11985   QgsFields b;
11986   // combine empty fields
11987   QgsFields res = QgsProcessingUtils::combineFields( a, b );
11988   QVERIFY( res.isEmpty() );
11989 
11990   // fields in a
11991   a.append( QgsField( "name" ) );
11992   res = QgsProcessingUtils::combineFields( a, b );
11993   QCOMPARE( res.count(), 1 );
11994   QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
11995   b.append( QgsField( "name" ) );
11996   res = QgsProcessingUtils::combineFields( a, b );
11997   QCOMPARE( res.count(), 2 );
11998   QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
11999   QCOMPARE( res.at( 1 ).name(), QStringLiteral( "name_2" ) );
12000 
12001   a.append( QgsField( "NEW" ) );
12002   res = QgsProcessingUtils::combineFields( a, b );
12003   QCOMPARE( res.count(), 3 );
12004   QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
12005   QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) );
12006   QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) );
12007 
12008   b.append( QgsField( "new" ) );
12009   res = QgsProcessingUtils::combineFields( a, b );
12010   QCOMPARE( res.count(), 4 );
12011   QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) );
12012   QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) );
12013   QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) );
12014   QCOMPARE( res.at( 3 ).name(), QStringLiteral( "new_2" ) );
12015 }
12016 
fieldNamesToIndices()12017 void TestQgsProcessing::fieldNamesToIndices()
12018 {
12019   QgsFields fields;
12020   fields.append( QgsField( "name" ) );
12021   fields.append( QgsField( "address" ) );
12022   fields.append( QgsField( "age" ) );
12023 
12024   const QList<int> indices1 = QgsProcessingUtils::fieldNamesToIndices( QStringList(), fields );
12025   QCOMPARE( indices1, QList<int>() << 0 << 1 << 2 );
12026 
12027   const QList<int> indices2 = QgsProcessingUtils::fieldNamesToIndices( QStringList() << "address" << "age", fields );
12028   QCOMPARE( indices2, QList<int>() << 1 << 2 );
12029 
12030   const QList<int> indices3 = QgsProcessingUtils::fieldNamesToIndices( QStringList() << "address" << "agegege", fields );
12031   QCOMPARE( indices3, QList<int>() << 1 );
12032 }
12033 
indicesToFields()12034 void TestQgsProcessing::indicesToFields()
12035 {
12036   QgsFields fields;
12037   fields.append( QgsField( "name" ) );
12038   fields.append( QgsField( "address" ) );
12039   fields.append( QgsField( "age" ) );
12040 
12041   const QList<int> indices1 = QList<int>() << 0 << 1 << 2;
12042   const QgsFields fields1 = QgsProcessingUtils::indicesToFields( indices1, fields );
12043   QCOMPARE( fields1, fields );
12044 
12045   const QList<int> indices2 = QList<int>() << 1;
12046   QgsFields fields2expected;
12047   fields2expected.append( QgsField( "address" ) );
12048   const QgsFields fields2 = QgsProcessingUtils::indicesToFields( indices2, fields );
12049   QCOMPARE( fields2, fields2expected );
12050 
12051   const QList<int> indices3;
12052   const QgsFields fields3 = QgsProcessingUtils::indicesToFields( indices3, fields );
12053   QCOMPARE( fields3, QgsFields() );
12054 }
12055 
variantToPythonLiteral()12056 void TestQgsProcessing::variantToPythonLiteral()
12057 {
12058   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant() ), QStringLiteral( "None" ) );
12059   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsProperty::fromExpression( QStringLiteral( "1+2" ) ) ) ), QStringLiteral( "QgsProperty.fromExpression('1+2')" ) );
12060   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsCoordinateReferenceSystem() ) ), QStringLiteral( "QgsCoordinateReferenceSystem()" ) );
12061   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) ) ), QStringLiteral( "QgsCoordinateReferenceSystem('EPSG:3111')" ) );
12062   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsRectangle( 1, 2, 3, 4 ) ) ), QStringLiteral( "'1, 3, 2, 4'" ) );
12063   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) ) ) ), QStringLiteral( "'1, 3, 2, 4 [EPSG:28356]'" ) );
12064   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsPointXY( 1, 2 ) ) ), QStringLiteral( "'1,2'" ) );
12065   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariant::fromValue( QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) ) ) ), QStringLiteral( "'1,2 [EPSG:28356]'" ) );
12066   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( true ), QStringLiteral( "True" ) );
12067   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( false ), QStringLiteral( "False" ) );
12068   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( 5 ), QStringLiteral( "5" ) );
12069   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( 5.5 ), QStringLiteral( "5.5" ) );
12070   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( 5LL ), QStringLiteral( "5" ) );
12071   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QVariantList() << true << QVariant() << QStringLiteral( "a" ) ), QStringLiteral( "[True,None,'a']" ) );
12072   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a" ) ), QStringLiteral( "'a'" ) );
12073   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QString() ), QStringLiteral( "''" ) );
12074   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a 'string'" ) ), QStringLiteral( "\"a 'string'\"" ) );
12075   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a \"string\" and a ' quote" ) ), QStringLiteral( "'a \"string\" and a \\' quote'" ) );
12076   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a \"string\"" ) ), QStringLiteral( "'a \"string\"'" ) );
12077   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QStringLiteral( "a \n str\tin\\g" ) ), QStringLiteral( "'a \\n str\\tin\\\\g'" ) );
12078   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( QDateTime( QDate( 2345, 1, 2 ), QTime( 6, 57, 58 ) ) ), QStringLiteral( "QDateTime(QDate(2345, 1, 2), QTime(6, 57, 58))" ) );
12079   QVariantMap map;
12080   map.insert( QStringLiteral( "list" ), QVariantList() << 1 << 2 << "a" );
12081   map.insert( QStringLiteral( "another" ), 4 );
12082   map.insert( QStringLiteral( "another2" ), QStringLiteral( "test" ) );
12083   QCOMPARE( QgsProcessingUtils::variantToPythonLiteral( map ), QStringLiteral( "{'another': 4,'another2': 'test','list': [1,2,'a']}" ) );
12084 }
12085 
stringToPythonLiteral()12086 void TestQgsProcessing::stringToPythonLiteral()
12087 {
12088   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a" ) ), QStringLiteral( "'a'" ) );
12089   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QString() ), QStringLiteral( "''" ) );
12090   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \n str\tin\\g" ) ), QStringLiteral( "'a \\n str\\tin\\\\g'" ) );
12091 
12092 
12093   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a 'string'" ) ), QStringLiteral( "\"a 'string'\"" ) );
12094   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \"string\" and a ' quote" ) ), QStringLiteral( "'a \"string\" and a \\' quote'" ) );
12095   QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \"string\"" ) ), QStringLiteral( "'a \"string\"'" ) );
12096 }
12097 
12098 
12099 
defaultExtensionsForProvider()12100 void TestQgsProcessing::defaultExtensionsForProvider()
12101 {
12102   const DummyProvider3 provider;
12103   // default implementation should return first supported format for provider
12104   QCOMPARE( provider.defaultVectorFileExtension( true ), QStringLiteral( "mif" ) );
12105   QCOMPARE( provider.defaultRasterFileExtension(), QStringLiteral( "mig" ) );
12106 
12107   // a default context should use reasonable defaults
12108   const QgsProcessingContext context;
12109   QCOMPARE( context.preferredVectorFormat(), QStringLiteral( "gpkg" ) );
12110   QCOMPARE( context.preferredRasterFormat(), QStringLiteral( "tif" ) );
12111 
12112   // unless the user has set a default format, which IS supported by that provider
12113   QgsProcessing::settingsDefaultOutputVectorLayerExt.setValue( QgsVectorFileWriter::supportedFormatExtensions().indexOf( QLatin1String( "tab" ) ) );
12114   QgsProcessing::settingsDefaultOutputRasterLayerExt.setValue( QgsRasterFileWriter::supportedFormatExtensions().indexOf( QLatin1String( "sdat" ) ) );
12115 
12116   QCOMPARE( provider.defaultVectorFileExtension( true ), QStringLiteral( "tab" ) );
12117   QCOMPARE( provider.defaultRasterFileExtension(), QStringLiteral( "sdat" ) );
12118 
12119   // context should respect these as preferred formats
12120   const QgsProcessingContext context2;
12121   QCOMPARE( context2.preferredVectorFormat(), QStringLiteral( "tab" ) );
12122   QCOMPARE( context2.preferredRasterFormat(), QStringLiteral( "sdat" ) );
12123 
12124   // but if default is not supported by provider, we use a supported format
12125   QgsProcessing::settingsDefaultOutputVectorLayerExt.setValue( QgsVectorFileWriter::supportedFormatExtensions().indexOf( QLatin1String( "gpkg" ) ) );
12126   QgsProcessing::settingsDefaultOutputRasterLayerExt.setValue( QgsRasterFileWriter::supportedFormatExtensions().indexOf( QLatin1String( "ecw" ) ) );
12127   QCOMPARE( provider.defaultVectorFileExtension( true ), QStringLiteral( "mif" ) );
12128   QCOMPARE( provider.defaultRasterFileExtension(), QStringLiteral( "mig" ) );
12129 }
12130 
supportedExtensions()12131 void TestQgsProcessing::supportedExtensions()
12132 {
12133   const DummyProvider4 provider;
12134   QCOMPARE( provider.supportedOutputVectorLayerExtensions().count(), 1 );
12135   QCOMPARE( provider.supportedOutputVectorLayerExtensions().at( 0 ), QStringLiteral( "mif" ) );
12136 
12137   // if supportedOutputTableExtensions is not implemented, supportedOutputVectorLayerExtensions should be used instead
12138   QCOMPARE( provider.supportedOutputTableExtensions().count(), 1 );
12139   QCOMPARE( provider.supportedOutputTableExtensions().at( 0 ), QStringLiteral( "mif" ) );
12140 }
12141 
supportsNonFileBasedOutput()12142 void TestQgsProcessing::supportsNonFileBasedOutput()
12143 {
12144   DummyAlgorithm alg( QStringLiteral( "test" ) );
12145   DummyProvider p( QStringLiteral( "test_provider" ) );
12146   alg.addDestParams();
12147   // provider has no support for file based outputs, so both output parameters should deny support
12148   alg.setProvider( &p );
12149   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 0 ) )->supportsNonFileBasedOutput() );
12150   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg.destinationParameterDefinitions().at( 1 ) )->supportsNonFileBasedOutput() );
12151 
12152   DummyAlgorithm alg2( QStringLiteral( "test" ) );
12153   DummyProvider p2( QStringLiteral( "test_provider" ) );
12154   p2.supportsNonFileOutputs = true;
12155   alg2.addDestParams();
12156   // provider has support for file based outputs, but only first output parameter should indicate support (since the second has support explicitly denied)
12157   alg2.setProvider( &p2 );
12158   QVERIFY( static_cast< const QgsProcessingDestinationParameter * >( alg2.destinationParameterDefinitions().at( 0 ) )->supportsNonFileBasedOutput() );
12159   QVERIFY( !static_cast< const QgsProcessingDestinationParameter * >( alg2.destinationParameterDefinitions().at( 1 ) )->supportsNonFileBasedOutput() );
12160 }
12161 
addParameterType()12162 void TestQgsProcessing::addParameterType()
12163 {
12164   QgsProcessingRegistry reg;
12165   const QSignalSpy spy( &reg, &QgsProcessingRegistry::parameterTypeAdded );
12166   DummyParameterType *dpt = new DummyParameterType();
12167   QVERIFY( reg.addParameterType( dpt ) );
12168   QCOMPARE( spy.count(), 1 );
12169   QVERIFY( !reg.addParameterType( dpt ) );
12170   QCOMPARE( spy.count(), 1 );
12171   QVERIFY( !reg.addParameterType( new DummyParameterType() ) );
12172   QCOMPARE( spy.count(), 1 );
12173 }
12174 
removeParameterType()12175 void TestQgsProcessing::removeParameterType()
12176 {
12177   QgsProcessingRegistry reg;
12178 
12179   auto paramType = new DummyParameterType();
12180 
12181   reg.addParameterType( paramType );
12182   const QSignalSpy spy( &reg, &QgsProcessingRegistry::parameterTypeRemoved );
12183   reg.removeParameterType( paramType );
12184   QCOMPARE( spy.count(), 1 );
12185 }
12186 
parameterTypes()12187 void TestQgsProcessing::parameterTypes()
12188 {
12189   QgsProcessingRegistry reg;
12190   const int coreParamCount = reg.parameterTypes().count();
12191   QVERIFY( coreParamCount > 5 );
12192 
12193   auto paramType = new DummyParameterType();
12194 
12195   reg.addParameterType( paramType );
12196   QCOMPARE( reg.parameterTypes().count(), coreParamCount + 1 );
12197   QVERIFY( reg.parameterTypes().contains( paramType ) );
12198 }
12199 
parameterType()12200 void TestQgsProcessing::parameterType()
12201 {
12202   QgsProcessingRegistry reg;
12203 
12204   QVERIFY( reg.parameterType( QStringLiteral( "string" ) ) );
12205   QVERIFY( !reg.parameterType( QStringLiteral( "borken" ) ) );  //#spellok
12206 
12207   auto paramType = new DummyParameterType();
12208 
12209   reg.addParameterType( paramType );
12210   QCOMPARE( reg.parameterType( QStringLiteral( "paramType" ) ), paramType );
12211 }
12212 
sourceTypeToString_data()12213 void TestQgsProcessing::sourceTypeToString_data()
12214 {
12215   QTest::addColumn<int>( "type" );
12216   QTest::addColumn<QString>( "expected" );
12217 
12218   // IMPORTANT -- these must match the original enum values!
12219   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeMapLayer ) << QStringLiteral( "TypeMapLayer" );
12220   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeVectorAnyGeometry ) << QStringLiteral( "TypeVectorAnyGeometry" );
12221   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeVectorPoint ) << QStringLiteral( "TypeVectorPoint" );
12222   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeVectorLine ) << QStringLiteral( "TypeVectorLine" );
12223   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeVectorPolygon ) << QStringLiteral( "TypeVectorPolygon" );
12224   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeRaster ) << QStringLiteral( "TypeRaster" );
12225   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeFile ) << QStringLiteral( "TypeFile" );
12226   QTest::newRow( "map layer" ) << static_cast< int >( QgsProcessing::TypeMesh ) << QStringLiteral( "TypeMesh" );
12227 }
12228 
sourceTypeToString()12229 void TestQgsProcessing::sourceTypeToString()
12230 {
12231   QFETCH( int, type );
12232   QFETCH( QString, expected );
12233 
12234   const QgsProcessing::SourceType sourceType = static_cast< QgsProcessing::SourceType >( type );
12235   QCOMPARE( QgsProcessing::sourceTypeToString( sourceType ), expected );
12236 }
12237 
modelSource()12238 void TestQgsProcessing::modelSource()
12239 {
12240   QgsProcessingModelChildParameterSource source;
12241   source.setExpression( QStringLiteral( "expression" ) );
12242   source.setExpressionText( QStringLiteral( "expression string" ) );
12243   source.setOutputName( QStringLiteral( "output name " ) );
12244   source.setStaticValue( QString( "value" ) );
12245   source.setOutputChildId( QStringLiteral( "output child id" ) );
12246   source.setParameterName( QStringLiteral( "parameter name" ) );
12247   source.setSource( QgsProcessingModelChildParameterSource::ChildOutput );
12248 
12249   QByteArray ba;
12250   QDataStream ds( &ba, QIODevice::ReadWrite );
12251   ds << source;
12252 
12253   ds.device()->seek( 0 );
12254 
12255   QgsProcessingModelChildParameterSource res;
12256   ds >> res;
12257 
12258   QCOMPARE( res.expression(), QStringLiteral( "expression" ) );
12259   QCOMPARE( res.expressionText(), QStringLiteral( "expression string" ) );
12260   QCOMPARE( res.outputName(), QStringLiteral( "output name " ) );
12261   QCOMPARE( res.staticValue().toString(), QString( "value" ) );
12262   QCOMPARE( res.outputChildId(), QStringLiteral( "output child id" ) );
12263   QCOMPARE( res.parameterName(), QStringLiteral( "parameter name" ) );
12264   QCOMPARE( res.source(), QgsProcessingModelChildParameterSource::ChildOutput );
12265 }
12266 
12267 QGSTEST_MAIN( TestQgsProcessing )
12268 #include "testqgsprocessing.moc"
12269