/*************************************************************************** testqgsprocessing.cpp --------------------- begin : January 2017 copyright : (C) 2017 by Nyall Dawson email : nyall dot dawson at gmail dot com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsprocessingregistry.h" #include "qgsprocessingprovider.h" #include "qgsprocessingutils.h" #include "qgsprocessingalgorithm.h" #include "qgsprocessingcontext.h" #include "qgsprocessingmodelalgorithm.h" #include "qgsnativealgorithms.h" #include #include #include "qgis.h" #include "qgstest.h" #include "qgsrasterlayer.h" #include "qgsproject.h" #include "qgspoint.h" #include "qgsgeometry.h" #include "qgsvectorfilewriter.h" #include "qgsexpressioncontext.h" #include "qgsxmlutils.h" #include "qgsreferencedgeometry.h" class DummyAlgorithm : public QgsProcessingAlgorithm { public: DummyAlgorithm( const QString &name ) : mName( name ) { mFlags = QgsProcessingAlgorithm::flags(); } void initAlgorithm( const QVariantMap & = QVariantMap() ) override {} QString name() const override { return mName; } QString displayName() const override { return mName; } QVariantMap processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); } virtual Flags flags() const override { return mFlags; } DummyAlgorithm *createInstance() const override { return new DummyAlgorithm( name() ); } QString mName; Flags mFlags; void checkParameterVals() { addParameter( new QgsProcessingParameterString( "p1" ) ); QVariantMap params; QgsProcessingContext context; QVERIFY( !checkParameterValues( params, context ) ); params.insert( "p1", "a" ); QVERIFY( checkParameterValues( params, context ) ); // optional param addParameter( new QgsProcessingParameterString( "p2", QString(), QVariant(), false, true ) ); QVERIFY( checkParameterValues( params, context ) ); params.insert( "p2", "a" ); QVERIFY( checkParameterValues( params, context ) ); } void runParameterChecks() { QVERIFY( parameterDefinitions().isEmpty() ); QVERIFY( addParameter( new QgsProcessingParameterBoolean( "p1" ) ) ); QCOMPARE( parameterDefinitions().count(), 1 ); QCOMPARE( parameterDefinitions().at( 0 )->name(), QString( "p1" ) ); QVERIFY( !addParameter( nullptr ) ); QCOMPARE( parameterDefinitions().count(), 1 ); // duplicate name! QgsProcessingParameterBoolean *p2 = new QgsProcessingParameterBoolean( "p1" ); QVERIFY( !addParameter( p2 ) ); delete p2; QCOMPARE( parameterDefinitions().count(), 1 ); QCOMPARE( parameterDefinition( "p1" ), parameterDefinitions().at( 0 ) ); // parameterDefinition should be case insensitive QCOMPARE( parameterDefinition( "P1" ), parameterDefinitions().at( 0 ) ); QVERIFY( !parameterDefinition( "invalid" ) ); QCOMPARE( countVisibleParameters(), 1 ); QgsProcessingParameterBoolean *p3 = new QgsProcessingParameterBoolean( "p3" ); QVERIFY( addParameter( p3 ) ); QCOMPARE( countVisibleParameters(), 2 ); QgsProcessingParameterBoolean *p4 = new QgsProcessingParameterBoolean( "p4" ); p4->setFlags( QgsProcessingParameterDefinition::FlagHidden ); QVERIFY( addParameter( p4 ) ); QCOMPARE( countVisibleParameters(), 2 ); //destination styleparameters QVERIFY( destinationParameterDefinitions().isEmpty() ); QgsProcessingParameterFeatureSink *p5 = new QgsProcessingParameterFeatureSink( "p5" ); QVERIFY( addParameter( p5, false ) ); QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 ); QgsProcessingParameterFeatureSink *p6 = new QgsProcessingParameterFeatureSink( "p6" ); QVERIFY( addParameter( p6, false ) ); QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 << p6 ); // remove parameter removeParameter( "non existent" ); removeParameter( "p6" ); QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p5 ); removeParameter( "p5" ); QVERIFY( destinationParameterDefinitions().isEmpty() ); // try with auto output creation QgsProcessingParameterVectorDestination *p7 = new QgsProcessingParameterVectorDestination( "p7", "my output" ); QVERIFY( addParameter( p7 ) ); QCOMPARE( destinationParameterDefinitions(), QgsProcessingParameterDefinitions() << p7 ); QVERIFY( outputDefinition( "p7" ) ); QCOMPARE( outputDefinition( "p7" )->name(), QStringLiteral( "p7" ) ); QCOMPARE( outputDefinition( "p7" )->type(), QStringLiteral( "outputVector" ) ); QCOMPARE( outputDefinition( "p7" )->description(), QStringLiteral( "my output" ) ); // duplicate output name QVERIFY( addOutput( new QgsProcessingOutputVectorLayer( "p8" ) ) ); QgsProcessingParameterVectorDestination *p8 = new QgsProcessingParameterVectorDestination( "p8" ); // this should fail - it would result in a duplicate output name QVERIFY( !addParameter( p8 ) ); } void runOutputChecks() { QVERIFY( outputDefinitions().isEmpty() ); QVERIFY( addOutput( new QgsProcessingOutputVectorLayer( "p1" ) ) ); QCOMPARE( outputDefinitions().count(), 1 ); QCOMPARE( outputDefinitions().at( 0 )->name(), QString( "p1" ) ); QVERIFY( !addOutput( nullptr ) ); QCOMPARE( outputDefinitions().count(), 1 ); // duplicate name! QgsProcessingOutputVectorLayer *p2 = new QgsProcessingOutputVectorLayer( "p1" ); QVERIFY( !addOutput( p2 ) ); delete p2; QCOMPARE( outputDefinitions().count(), 1 ); QCOMPARE( outputDefinition( "p1" ), outputDefinitions().at( 0 ) ); // parameterDefinition should be case insensitive QCOMPARE( outputDefinition( "P1" ), outputDefinitions().at( 0 ) ); QVERIFY( !outputDefinition( "invalid" ) ); QVERIFY( !hasHtmlOutputs() ); QgsProcessingOutputHtml *p3 = new QgsProcessingOutputHtml( "p3" ); QVERIFY( addOutput( p3 ) ); QVERIFY( hasHtmlOutputs() ); } void runValidateInputCrsChecks() { addParameter( new QgsProcessingParameterMapLayer( "p1" ) ); addParameter( new QgsProcessingParameterMapLayer( "p2" ) ); QVariantMap parameters; QgsVectorLayer *layer3111 = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" ); QgsProject p; p.addMapLayer( layer3111 ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QVERIFY( r1->isValid() ); p.addMapLayer( r1 ); QgsVectorLayer *layer4326 = new QgsVectorLayer( "Point?crs=epsg:4326", "v1", "memory" ); p.addMapLayer( layer4326 ); QgsProcessingContext context; context.setProject( &p ); // flag not set mFlags = 0; parameters.insert( "p1", QVariant::fromValue( layer3111 ) ); QVERIFY( validateInputCrs( parameters, context ) ); mFlags = FlagRequiresMatchingCrs; QVERIFY( validateInputCrs( parameters, context ) ); // two layers, different crs parameters.insert( "p2", QVariant::fromValue( layer4326 ) ); // flag not set mFlags = 0; QVERIFY( validateInputCrs( parameters, context ) ); mFlags = FlagRequiresMatchingCrs; QVERIFY( !validateInputCrs( parameters, context ) ); // raster layer parameters.remove( "p2" ); addParameter( new QgsProcessingParameterRasterLayer( "p3" ) ); parameters.insert( "p3", QVariant::fromValue( r1 ) ); QVERIFY( !validateInputCrs( parameters, context ) ); // feature source parameters.remove( "p3" ); addParameter( new QgsProcessingParameterFeatureSource( "p4" ) ); parameters.insert( "p4", layer4326->id() ); QVERIFY( !validateInputCrs( parameters, context ) ); parameters.remove( "p4" ); addParameter( new QgsProcessingParameterMultipleLayers( "p5" ) ); parameters.insert( "p5", QVariantList() << layer4326->id() << r1->id() ); QVERIFY( !validateInputCrs( parameters, context ) ); } void runAsPythonCommandChecks() { addParameter( new QgsProcessingParameterString( "p1" ) ); addParameter( new QgsProcessingParameterString( "p2" ) ); QgsProcessingParameterString *hidden = new QgsProcessingParameterString( "p3" ); hidden->setFlags( QgsProcessingParameterDefinition::FlagHidden ); addParameter( hidden ); QVariantMap params; QgsProcessingContext context; QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {})" ) ); params.insert( "p1", "a" ); QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a'})" ) ); params.insert( "p2", QVariant() ); // not set, should be no change QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a'})" ) ); params.insert( "p2", "b" ); QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a','p2':'b'})" ) ); // hidden, shouldn't be shown params.insert( "p3", "b" ); QCOMPARE( asPythonCommand( params, context ), QStringLiteral( "processing.run(\"test\", {'p1':'a','p2':'b'})" ) ); } }; //dummy provider for testing class DummyProvider : public QgsProcessingProvider { public: DummyProvider( const QString &id ) : mId( id ) {} virtual QString id() const override { return mId; } virtual QString name() const override { return "dummy"; } void unload() override { if ( unloaded ) { *unloaded = true; } } bool *unloaded = nullptr; protected: virtual void loadAlgorithms() override { QVERIFY( addAlgorithm( new DummyAlgorithm( "alg1" ) ) ); QVERIFY( addAlgorithm( new DummyAlgorithm( "alg2" ) ) ); //dupe name QgsProcessingAlgorithm *a = new DummyAlgorithm( "alg1" ); QVERIFY( !addAlgorithm( a ) ); delete a; QVERIFY( !addAlgorithm( nullptr ) ); } QString mId; }; class DummyProviderNoLoad : public DummyProvider { public: DummyProviderNoLoad( const QString &id ) : DummyProvider( id ) {} bool load() override { return false; } }; class TestQgsProcessing: public QObject { Q_OBJECT private slots: void initTestCase();// will be called before the first testfunction is executed. void cleanupTestCase(); // will be called after the last testfunction was executed. void init() {} // will be called before each testfunction is executed. void cleanup() {} // will be called after every testfunction. void instance(); void addProvider(); void providerById(); void removeProvider(); void compatibleLayers(); void normalizeLayerSource(); void context(); void mapLayers(); void mapLayerFromStore(); void mapLayerFromString(); void algorithm(); void features(); void uniqueValues(); void createIndex(); void createFeatureSink(); void parameters(); void algorithmParameters(); void algorithmOutputs(); void parameterGeneral(); void parameterBoolean(); void parameterCrs(); void parameterLayer(); void parameterExtent(); void parameterPoint(); void parameterFile(); void parameterMatrix(); void parameterLayerList(); void parameterNumber(); void parameterRange(); void parameterRasterLayer(); void parameterEnum(); void parameterString(); void parameterExpression(); void parameterField(); void parameterVectorLayer(); void parameterFeatureSource(); void parameterFeatureSink(); void parameterVectorOut(); void parameterRasterOut(); void parameterFileOut(); void parameterFolderOut(); void parameterBand(); void checkParamValues(); void combineLayerExtent(); void processingFeatureSource(); void processingFeatureSink(); void algorithmScope(); void validateInputCrs(); void generateIteratingDestination(); void asPythonCommand(); void modelerAlgorithm(); void modelExecution(); void modelAcceptableValues(); void tempUtils(); void convertCompatible(); void create(); void combineFields(); void stringToPythonLiteral(); private: }; void TestQgsProcessing::initTestCase() { QgsApplication::init(); QgsApplication::initQgis(); // Set up the QgsSettings environment QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) ); } void TestQgsProcessing::cleanupTestCase() { QFile::remove( QDir::tempPath() + "/create_feature_sink.tab" ); QgsVectorFileWriter::deleteShapeFile( QDir::tempPath() + "/create_feature_sink2.gpkg" ); QgsApplication::exitQgis(); } void TestQgsProcessing::instance() { // test that application has a registry instance QVERIFY( QgsApplication::processingRegistry() ); } void TestQgsProcessing::addProvider() { QgsProcessingRegistry r; QSignalSpy spyProviderAdded( &r, &QgsProcessingRegistry::providerAdded ); QVERIFY( r.providers().isEmpty() ); QVERIFY( !r.addProvider( nullptr ) ); // add a provider DummyProvider *p = new DummyProvider( "p1" ); QVERIFY( r.addProvider( p ) ); QCOMPARE( r.providers(), QList< QgsProcessingProvider * >() << p ); QCOMPARE( spyProviderAdded.count(), 1 ); QCOMPARE( spyProviderAdded.last().at( 0 ).toString(), QString( "p1" ) ); //try adding another provider DummyProvider *p2 = new DummyProvider( "p2" ); QVERIFY( r.addProvider( p2 ) ); QCOMPARE( r.providers().toSet(), QSet< QgsProcessingProvider * >() << p << p2 ); QCOMPARE( spyProviderAdded.count(), 2 ); QCOMPARE( spyProviderAdded.last().at( 0 ).toString(), QString( "p2" ) ); //try adding a provider with duplicate id DummyProvider *p3 = new DummyProvider( "p2" ); QVERIFY( !r.addProvider( p3 ) ); QCOMPARE( r.providers().toSet(), QSet< QgsProcessingProvider * >() << p << p2 ); QCOMPARE( spyProviderAdded.count(), 2 ); delete p3; // test that adding a provider which does not load means it is not added to registry DummyProviderNoLoad *p4 = new DummyProviderNoLoad( "p4" ); QVERIFY( !r.addProvider( p4 ) ); QCOMPARE( r.providers().toSet(), QSet< QgsProcessingProvider * >() << p << p2 ); QCOMPARE( spyProviderAdded.count(), 2 ); delete p4; } void TestQgsProcessing::providerById() { QgsProcessingRegistry r; // no providers QVERIFY( !r.providerById( "p1" ) ); // add a provider DummyProvider *p = new DummyProvider( "p1" ); QVERIFY( r.addProvider( p ) ); QCOMPARE( r.providerById( "p1" ), p ); QVERIFY( !r.providerById( "p2" ) ); //try adding another provider DummyProvider *p2 = new DummyProvider( "p2" ); QVERIFY( r.addProvider( p2 ) ); QCOMPARE( r.providerById( "p1" ), p ); QCOMPARE( r.providerById( "p2" ), p2 ); QVERIFY( !r.providerById( "p3" ) ); } void TestQgsProcessing::removeProvider() { QgsProcessingRegistry r; QSignalSpy spyProviderRemoved( &r, &QgsProcessingRegistry::providerRemoved ); QVERIFY( !r.removeProvider( nullptr ) ); QVERIFY( !r.removeProvider( "p1" ) ); // provider not in registry DummyProvider *p = new DummyProvider( "p1" ); QVERIFY( !r.removeProvider( p ) ); QCOMPARE( spyProviderRemoved.count(), 0 ); // add some providers QVERIFY( r.addProvider( p ) ); DummyProvider *p2 = new DummyProvider( "p2" ); QVERIFY( r.addProvider( p2 ) ); // remove one by pointer bool unloaded = false; p->unloaded = &unloaded; QVERIFY( r.removeProvider( p ) ); QCOMPARE( spyProviderRemoved.count(), 1 ); QCOMPARE( spyProviderRemoved.last().at( 0 ).toString(), QString( "p1" ) ); QCOMPARE( r.providers(), QList< QgsProcessingProvider * >() << p2 ); //test that provider was unloaded QVERIFY( unloaded ); // should fail, already removed QVERIFY( !r.removeProvider( "p1" ) ); // remove one by id QVERIFY( r.removeProvider( "p2" ) ); QCOMPARE( spyProviderRemoved.count(), 2 ); QCOMPARE( spyProviderRemoved.last().at( 0 ).toString(), QString( "p2" ) ); QVERIFY( r.providers().isEmpty() ); } void TestQgsProcessing::compatibleLayers() { QgsProject p; // add a bunch of layers to a project QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QString raster3 = testDataDir + "/raster/band1_float32_noct_epsg4326.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QVERIFY( r1->isValid() ); QFileInfo fi2( raster2 ); QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" ); QVERIFY( r2->isValid() ); QFileInfo fi3( raster3 ); QgsRasterLayer *r3 = new QgsRasterLayer( fi3.filePath(), "zz" ); QVERIFY( r3->isValid() ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" ); QgsVectorLayer *v3 = new QgsVectorLayer( "LineString", "v3", "memory" ); QgsVectorLayer *v4 = new QgsVectorLayer( "none", "vvvv4", "memory" ); p.addMapLayers( QList() << r1 << r2 << r3 << v1 << v2 << v3 << v4 ); // compatibleRasterLayers QVERIFY( QgsProcessingUtils::compatibleRasterLayers( nullptr ).isEmpty() ); // sorted QStringList lIds; Q_FOREACH ( QgsRasterLayer *rl, QgsProcessingUtils::compatibleRasterLayers( &p ) ) lIds << rl->name(); QCOMPARE( lIds, QStringList() << "ar2" << "R1" << "zz" ); // unsorted lIds.clear(); Q_FOREACH ( QgsRasterLayer *rl, QgsProcessingUtils::compatibleRasterLayers( &p, false ) ) lIds << rl->name(); QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" ); // compatibleVectorLayers QVERIFY( QgsProcessingUtils::compatibleVectorLayers( nullptr ).isEmpty() ); // sorted lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "v1" << "v3" << "V4" << "vvvv4" ); // unsorted lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p, QList(), false ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "V4" << "v1" << "v3" << "vvvv4" ); // point only lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p, QList() << QgsWkbTypes::PointGeometry ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "v1" ); // polygon only lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p, QList() << QgsWkbTypes::PolygonGeometry ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "V4" ); // line only lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p, QList() << QgsWkbTypes::LineGeometry ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "v3" ); // point and line only lIds.clear(); Q_FOREACH ( QgsVectorLayer *vl, QgsProcessingUtils::compatibleVectorLayers( &p, QList() << QgsWkbTypes::PointGeometry << QgsWkbTypes::LineGeometry ) ) lIds << vl->name(); QCOMPARE( lIds, QStringList() << "v1" << "v3" ); // all layers QVERIFY( QgsProcessingUtils::compatibleLayers( nullptr ).isEmpty() ); // sorted lIds.clear(); Q_FOREACH ( QgsMapLayer *l, QgsProcessingUtils::compatibleLayers( &p ) ) lIds << l->name(); QCOMPARE( lIds, QStringList() << "ar2" << "R1" << "v1" << "v3" << "V4" << "vvvv4" << "zz" ); // unsorted lIds.clear(); Q_FOREACH ( QgsMapLayer *l, QgsProcessingUtils::compatibleLayers( &p, false ) ) lIds << l->name(); QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" << "V4" << "v1" << "v3" << "vvvv4" ); } void TestQgsProcessing::normalizeLayerSource() { QCOMPARE( QgsProcessingUtils::normalizeLayerSource( "data\\layers\\test.shp" ), QString( "data/layers/test.shp" ) ); QCOMPARE( QgsProcessingUtils::normalizeLayerSource( "data\\layers \"new\"\\test.shp" ), QString( "data/layers 'new'/test.shp" ) ); } void TestQgsProcessing::context() { QgsProcessingContext context; // simple tests for getters/setters context.setDefaultEncoding( "my_enc" ); QCOMPARE( context.defaultEncoding(), QStringLiteral( "my_enc" ) ); context.setFlags( QgsProcessingContext::Flags( 0 ) ); QCOMPARE( context.flags(), QgsProcessingContext::Flags( 0 ) ); QgsProject p; context.setProject( &p ); QCOMPARE( context.project(), &p ); context.setInvalidGeometryCheck( QgsFeatureRequest::GeometrySkipInvalid ); QCOMPARE( context.invalidGeometryCheck(), QgsFeatureRequest::GeometrySkipInvalid ); QgsVectorLayer *vector = new QgsVectorLayer( "Polygon", "vector", "memory" ); context.temporaryLayerStore()->addMapLayer( vector ); QCOMPARE( context.temporaryLayerStore()->mapLayer( vector->id() ), vector ); QgsProcessingContext context2; context2.copyThreadSafeSettings( context ); QCOMPARE( context2.defaultEncoding(), context.defaultEncoding() ); QCOMPARE( context2.invalidGeometryCheck(), context.invalidGeometryCheck() ); QCOMPARE( context2.flags(), context.flags() ); QCOMPARE( context2.project(), context.project() ); // layers from temporaryLayerStore must not be copied by copyThreadSafeSettings QVERIFY( context2.temporaryLayerStore()->mapLayers().isEmpty() ); // layers to load on completion QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V1", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Polygon", "V2", "memory" ); QVERIFY( context.layersToLoadOnCompletion().isEmpty() ); QMap< QString, QgsProcessingContext::LayerDetails > layers; layers.insert( v1->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v1" ), &p ) ); context.setLayersToLoadOnCompletion( layers ); QCOMPARE( context.layersToLoadOnCompletion().count(), 1 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v1" ) ); context.addLayerToLoadOnCompletion( v2->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v2" ), &p ) ); QCOMPARE( context.layersToLoadOnCompletion().count(), 2 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v1" ) ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 1 ).name, QStringLiteral( "v2" ) ); // ensure that copyThreadSafeSettings doesn't copy layersToLoadOnCompletion() context2.copyThreadSafeSettings( context ); QVERIFY( context2.layersToLoadOnCompletion().isEmpty() ); layers.clear(); layers.insert( v2->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v2" ), &p ) ); context.setLayersToLoadOnCompletion( layers ); QCOMPARE( context.layersToLoadOnCompletion().count(), 1 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v2->id() ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v2" ) ); context.addLayerToLoadOnCompletion( v1->id(), QgsProcessingContext::LayerDetails( QString(), &p ) ); QCOMPARE( context.layersToLoadOnCompletion().count(), 2 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); context.temporaryLayerStore()->addMapLayer( v1 ); context.temporaryLayerStore()->addMapLayer( v2 ); // test takeResultsFrom context2.takeResultsFrom( context ); QVERIFY( context.temporaryLayerStore()->mapLayers().isEmpty() ); QVERIFY( context.layersToLoadOnCompletion().isEmpty() ); // should now be in context2 QCOMPARE( context2.temporaryLayerStore()->mapLayer( v1->id() ), v1 ); QCOMPARE( context2.temporaryLayerStore()->mapLayer( v2->id() ), v2 ); QCOMPARE( context2.layersToLoadOnCompletion().count(), 2 ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); // take result layer QgsMapLayer *result = context2.takeResultLayer( v1->id() ); QCOMPARE( result, v1 ); QString id = v1->id(); delete v1; QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) ); QVERIFY( !context2.takeResultLayer( id ) ); result = context2.takeResultLayer( v2->id() ); QCOMPARE( result, v2 ); id = v2->id(); delete v2; QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) ); } void TestQgsProcessing::mapLayers() { QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster = testDataDir + "landsat.tif"; QString vector = testDataDir + "points.shp"; // test loadMapLayerFromString with raster QgsMapLayer *l = QgsProcessingUtils::loadMapLayerFromString( raster ); QVERIFY( l->isValid() ); QCOMPARE( l->type(), QgsMapLayer::RasterLayer ); QCOMPARE( l->name(), QStringLiteral( "landsat" ) ); delete l; //test with vector l = QgsProcessingUtils::loadMapLayerFromString( vector ); QVERIFY( l->isValid() ); QCOMPARE( l->type(), QgsMapLayer::VectorLayer ); QCOMPARE( l->name(), QStringLiteral( "points" ) ); delete l; l = QgsProcessingUtils::loadMapLayerFromString( QString() ); QVERIFY( !l ); l = QgsProcessingUtils::loadMapLayerFromString( QStringLiteral( "so much room for activities!" ) ); QVERIFY( !l ); l = QgsProcessingUtils::loadMapLayerFromString( testDataDir + "multipoint.shp" ); QVERIFY( l->isValid() ); QCOMPARE( l->type(), QgsMapLayer::VectorLayer ); QCOMPARE( l->name(), QStringLiteral( "multipoint" ) ); delete l; // Test layers from a string with parameters QString osmFilePath = testDataDir + "openstreetmap/testdata.xml"; QgsVectorLayer *osm = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath ) ); QVERIFY( osm->isValid() ); QCOMPARE( osm->geometryType(), QgsWkbTypes::PointGeometry ); osm = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath + "|layerid=3" ) ); QVERIFY( osm->isValid() ); QCOMPARE( osm->geometryType(), QgsWkbTypes::PolygonGeometry ); osm = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::loadMapLayerFromString( osmFilePath + "|layerid=3|subset=\"building\" is not null" ) ); QVERIFY( osm->isValid() ); QCOMPARE( osm->geometryType(), QgsWkbTypes::PolygonGeometry ); QCOMPARE( osm->subsetString(), QStringLiteral( "\"building\" is not null" ) ); } void TestQgsProcessing::mapLayerFromStore() { // test mapLayerFromStore QgsMapLayerStore store; // add a bunch of layers to a project QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QVERIFY( r1->isValid() ); QFileInfo fi2( raster2 ); QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" ); QVERIFY( r2->isValid() ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" ); store.addMapLayers( QList() << r1 << r2 << v1 << v2 ); QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QString(), nullptr ) ); QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QStringLiteral( "v1" ), nullptr ) ); QVERIFY( ! QgsProcessingUtils::mapLayerFromStore( QString(), &store ) ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( raster1, &store ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( raster2, &store ), r2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "R1", &store ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "ar2", &store ), r2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "V4", &store ), v1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( "v1", &store ), v2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( r1->id(), &store ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromStore( v1->id(), &store ), v1 ); } void TestQgsProcessing::mapLayerFromString() { // test mapLayerFromString QgsProcessingContext c; QgsProject p; // add a bunch of layers to a project QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QVERIFY( r1->isValid() ); QFileInfo fi2( raster2 ); QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "ar2" ); QVERIFY( r2->isValid() ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V4", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Point", "v1", "memory" ); p.addMapLayers( QList() << r1 << r2 << v1 << v2 ); // no project set yet QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QString(), c ) ); QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QStringLiteral( "v1" ), c ) ); c.setProject( &p ); // layers from current project QVERIFY( ! QgsProcessingUtils::mapLayerFromString( QString(), c ) ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( raster1, c ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( raster2, c ), r2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "R1", c ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "ar2", c ), r2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V4", c ), v1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "v1", c ), v2 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( r1->id(), c ), r1 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( v1->id(), c ), v1 ); // check that layers in context temporary store are used QgsVectorLayer *v5 = new QgsVectorLayer( "Polygon", "V5", "memory" ); QgsVectorLayer *v6 = new QgsVectorLayer( "Point", "v6", "memory" ); c.temporaryLayerStore()->addMapLayers( QList() << v5 << v6 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "V5", c ), v5 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( "v6", c ), v6 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( v5->id(), c ), v5 ); QCOMPARE( QgsProcessingUtils::mapLayerFromString( v6->id(), c ), v6 ); QVERIFY( ! QgsProcessingUtils::mapLayerFromString( "aaaaa", c ) ); // if specified, check that layers can be loaded QVERIFY( ! QgsProcessingUtils::mapLayerFromString( "aaaaa", c ) ); QString newRaster = testDataDir + "requires_warped_vrt.tif"; // don't allow loading QVERIFY( ! QgsProcessingUtils::mapLayerFromString( newRaster, c, false ) ); // allow loading QgsMapLayer *loadedLayer = QgsProcessingUtils::mapLayerFromString( newRaster, c, true ); QVERIFY( loadedLayer->isValid() ); QCOMPARE( loadedLayer->type(), QgsMapLayer::RasterLayer ); // should now be in temporary store QCOMPARE( c.temporaryLayerStore()->mapLayer( loadedLayer->id() ), loadedLayer ); // since it's now in temporary store, should be accessible even if we deny loading new layers QCOMPARE( QgsProcessingUtils::mapLayerFromString( newRaster, c, false ), loadedLayer ); } void TestQgsProcessing::algorithm() { DummyAlgorithm alg( "test" ); DummyProvider *p = new DummyProvider( "p1" ); QCOMPARE( alg.id(), QString( "test" ) ); alg.setProvider( p ); QCOMPARE( alg.provider(), p ); QCOMPARE( alg.id(), QString( "p1:test" ) ); QVERIFY( p->algorithms().isEmpty() ); QSignalSpy providerRefreshed( p, &DummyProvider::algorithmsLoaded ); p->refreshAlgorithms(); QCOMPARE( providerRefreshed.count(), 1 ); for ( int i = 0; i < 2; ++i ) { QCOMPARE( p->algorithms().size(), 2 ); QCOMPARE( p->algorithm( "alg1" )->name(), QStringLiteral( "alg1" ) ); QCOMPARE( p->algorithm( "alg1" )->provider(), p ); QCOMPARE( p->algorithm( "alg2" )->provider(), p ); QCOMPARE( p->algorithm( "alg2" )->name(), QStringLiteral( "alg2" ) ); QVERIFY( !p->algorithm( "aaaa" ) ); QVERIFY( p->algorithms().contains( p->algorithm( "alg1" ) ) ); QVERIFY( p->algorithms().contains( p->algorithm( "alg2" ) ) ); // reload, then retest on next loop // must be safe for providers to reload their algorithms p->refreshAlgorithms(); QCOMPARE( providerRefreshed.count(), 2 + i ); } QgsProcessingRegistry r; r.addProvider( p ); QCOMPARE( r.algorithms().size(), 2 ); QVERIFY( r.algorithms().contains( p->algorithm( "alg1" ) ) ); QVERIFY( r.algorithms().contains( p->algorithm( "alg2" ) ) ); // algorithmById QCOMPARE( r.algorithmById( "p1:alg1" ), p->algorithm( "alg1" ) ); QCOMPARE( r.algorithmById( "p1:alg2" ), p->algorithm( "alg2" ) ); QVERIFY( !r.algorithmById( "p1:alg3" ) ); QVERIFY( !r.algorithmById( "px:alg1" ) ); // test that algorithmById can transparently map 'qgis' algorithms across to matching 'native' algorithms // this allows us the freedom to convert qgis python algs to c++ without breaking api or existing models QCOMPARE( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:dissolve" ) )->id(), QStringLiteral( "native:dissolve" ) ); QCOMPARE( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:clip" ) )->id(), QStringLiteral( "native:clip" ) ); QVERIFY( !QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "qgis:notanalg" ) ) ); // createAlgorithmById QVERIFY( !r.createAlgorithmById( "p1:alg3" ) ); std::unique_ptr< QgsProcessingAlgorithm > creation( r.createAlgorithmById( "p1:alg1" ) ); QVERIFY( creation.get() ); QCOMPARE( creation->provider()->id(), QStringLiteral( "p1" ) ); QCOMPARE( creation->id(), QStringLiteral( "p1:alg1" ) ); creation.reset( r.createAlgorithmById( "p1:alg2" ) ); QVERIFY( creation.get() ); QCOMPARE( creation->provider()->id(), QStringLiteral( "p1" ) ); QCOMPARE( creation->id(), QStringLiteral( "p1:alg2" ) ); //test that loading a provider triggers an algorithm refresh DummyProvider *p2 = new DummyProvider( "p2" ); QVERIFY( p2->algorithms().isEmpty() ); p2->load(); QCOMPARE( p2->algorithms().size(), 2 ); // test that adding a provider to the registry automatically refreshes algorithms (via load) DummyProvider *p3 = new DummyProvider( "p3" ); QVERIFY( p3->algorithms().isEmpty() ); r.addProvider( p3 ); QCOMPARE( p3->algorithms().size(), 2 ); } void TestQgsProcessing::features() { QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" ); for ( int i = 1; i < 6; ++i ) { QgsFeature f( i ); f.setGeometry( QgsGeometry( new QgsPoint( 1, 2 ) ) ); layer->dataProvider()->addFeatures( QgsFeatureList() << f ); } QgsProject p; p.addMapLayer( layer ); QgsProcessingContext context; context.setProject( &p ); // disable check for geometry validity context.setFlags( QgsProcessingContext::Flags( 0 ) ); std::function< QgsFeatureIds( QgsFeatureIterator it ) > getIds = []( QgsFeatureIterator it ) { QgsFeature f; QgsFeatureIds ids; while ( it.nextFeature( f ) ) { ids << f.id(); } return ids; }; std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) ); QVariantMap params; params.insert( QStringLiteral( "layer" ), layer->id() ); std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); // test with all features QgsFeatureIds ids = getIds( source->getFeatures() ); QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 ); QCOMPARE( source->featureCount(), 5L ); // test with selected features params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) ); layer->selectByIds( QgsFeatureIds() << 2 << 4 ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures() ); QCOMPARE( ids, QgsFeatureIds() << 2 << 4 ); QCOMPARE( source->featureCount(), 2L ); // selection, but not using selected features params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) ); layer->selectByIds( QgsFeatureIds() << 2 << 4 ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures() ); QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 ); QCOMPARE( source->featureCount(), 5L ); // using selected features, but no selection params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) ); layer->removeSelection(); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures() ); QVERIFY( ids.isEmpty() ); QCOMPARE( source->featureCount(), 0L ); // test that feature request is honored params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << 1 << 3 << 5 ) ) ); QCOMPARE( ids, QgsFeatureIds() << 1 << 3 << 5 ); // count is only rough - but we expect (for now) to see full layer count QCOMPARE( source->featureCount(), 5L ); //test that feature request is honored when using selections params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) ); layer->selectByIds( QgsFeatureIds() << 2 << 4 ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) ) ); QCOMPARE( ids, QgsFeatureIds() << 2 << 4 ); // test callback is hit when filtering invalid geoms bool encountered = false; std::function< void( const QgsFeature & ) > callback = [ &encountered ]( const QgsFeature & ) { encountered = true; }; context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid ); context.setInvalidGeometryCallback( callback ); QgsVectorLayer *polyLayer = new QgsVectorLayer( "Polygon", "v2", "memory" ); QgsFeature f; f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 1 0, 0 1, 1 1, 0 0))" ) ) ); polyLayer->dataProvider()->addFeatures( QgsFeatureList() << f ); p.addMapLayer( polyLayer ); params.insert( QStringLiteral( "layer" ), polyLayer->id() ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures() ); QVERIFY( encountered ); encountered = false; context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryNoCheck ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); ids = getIds( source->getFeatures() ); QVERIFY( !encountered ); // equality operator QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) == QgsProcessingFeatureSourceDefinition( layer->id(), true ) ); QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( "b", true ) ); QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( layer->id(), false ) ); } void TestQgsProcessing::uniqueValues() { QgsVectorLayer *layer = new QgsVectorLayer( "Point?field=a:integer&field=b:string", "v1", "memory" ); for ( int i = 0; i < 6; ++i ) { QgsFeature f( i ); f.setAttributes( QgsAttributes() << i % 3 + 1 << QString( QChar( ( i % 3 ) + 65 ) ) ); layer->dataProvider()->addFeatures( QgsFeatureList() << f ); } QgsProcessingContext context; context.setFlags( QgsProcessingContext::Flags( 0 ) ); QgsProject p; p.addMapLayer( layer ); context.setProject( &p ); std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) ); QVariantMap params; params.insert( QStringLiteral( "layer" ), layer->id() ); std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); // some bad checks QVERIFY( source->uniqueValues( -1 ).isEmpty() ); QVERIFY( source->uniqueValues( 10001 ).isEmpty() ); // good checks QSet< QVariant > vals = source->uniqueValues( 0 ); QCOMPARE( vals.count(), 3 ); QVERIFY( vals.contains( 1 ) ); QVERIFY( vals.contains( 2 ) ); QVERIFY( vals.contains( 3 ) ); vals = source->uniqueValues( 1 ); QCOMPARE( vals.count(), 3 ); QVERIFY( vals.contains( QString( "A" ) ) ); QVERIFY( vals.contains( QString( "B" ) ) ); QVERIFY( vals.contains( QString( "C" ) ) ); //using only selected features layer->selectByIds( QgsFeatureIds() << 1 << 2 << 4 ); // but not using selection yet... source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); vals = source->uniqueValues( 0 ); QCOMPARE( vals.count(), 3 ); QVERIFY( vals.contains( 1 ) ); QVERIFY( vals.contains( 2 ) ); QVERIFY( vals.contains( 3 ) ); vals = source->uniqueValues( 1 ); QCOMPARE( vals.count(), 3 ); QVERIFY( vals.contains( QString( "A" ) ) ); QVERIFY( vals.contains( QString( "B" ) ) ); QVERIFY( vals.contains( QString( "C" ) ) ); // selection and using selection params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); QVERIFY( source->uniqueValues( -1 ).isEmpty() ); QVERIFY( source->uniqueValues( 10001 ).isEmpty() ); vals = source->uniqueValues( 0 ); QCOMPARE( vals.count(), 2 ); QVERIFY( vals.contains( 1 ) ); QVERIFY( vals.contains( 2 ) ); vals = source->uniqueValues( 1 ); QCOMPARE( vals.count(), 2 ); QVERIFY( vals.contains( QString( "A" ) ) ); QVERIFY( vals.contains( QString( "B" ) ) ); } void TestQgsProcessing::createIndex() { QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" ); for ( int i = 1; i < 6; ++i ) { QgsFeature f( i ); f.setGeometry( QgsGeometry( new QgsPoint( i, 2 ) ) ); layer->dataProvider()->addFeatures( QgsFeatureList() << f ); } QgsProcessingContext context; QgsProject p; p.addMapLayer( layer ); context.setProject( &p ); std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) ); QVariantMap params; params.insert( QStringLiteral( "layer" ), layer->id() ); // disable selected features check std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); QVERIFY( source.get() ); QgsSpatialIndex index( *source.get() ); QList ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 ); QCOMPARE( ids, QList() << 2 ); // selected features check, but none selected params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); bool caught = false; try { index = QgsSpatialIndex( *source.get() ); ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 ); } catch ( ... ) { caught = true; } QVERIFY( caught ); // create selection layer->selectByIds( QgsFeatureIds() << 4 << 5 ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); index = QgsSpatialIndex( *source.get() ); ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 ); QCOMPARE( ids, QList() << 4 ); // selection but not using selection mode params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); index = QgsSpatialIndex( *source.get() ); ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 ); QCOMPARE( ids, QList() << 2 ); } void TestQgsProcessing::createFeatureSink() { QgsProcessingContext context; // empty destination QString destination; destination = QString(); QgsVectorLayer *layer = nullptr; // should create a memory layer std::unique_ptr< QgsFeatureSink > sink( QgsProcessingUtils::createFeatureSink( destination, context, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem() ) ); QVERIFY( sink.get() ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) ); QVERIFY( layer ); QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() ); QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) ); QCOMPARE( destination, layer->id() ); QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store QgsFeature f; QCOMPARE( layer->featureCount(), 0L ); QVERIFY( sink->addFeature( f ) ); QCOMPARE( layer->featureCount(), 1L ); context.temporaryLayerStore()->removeAllMapLayers(); layer = nullptr; // specific memory layer output destination = QStringLiteral( "memory:mylayer" ); sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem() ) ); QVERIFY( sink.get() ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) ); QVERIFY( layer ); QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() ); QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) ); QCOMPARE( layer->name(), QStringLiteral( "memory:mylayer" ) ); QCOMPARE( destination, layer->id() ); QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store QCOMPARE( layer->featureCount(), 0L ); QVERIFY( sink->addFeature( f ) ); QCOMPARE( layer->featureCount(), 1L ); context.temporaryLayerStore()->removeAllMapLayers(); layer = nullptr; // memory layer parameters destination = QStringLiteral( "memory:mylayer" ); QgsFields fields; fields.append( QgsField( QStringLiteral( "my_field" ), QVariant::String, QString(), 100 ) ); sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::PointZM, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) ); QVERIFY( sink.get() ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, false ) ); QVERIFY( layer ); QCOMPARE( static_cast< QgsProxyFeatureSink *>( sink.get() )->destinationSink(), layer->dataProvider() ); QCOMPARE( layer->dataProvider()->name(), QStringLiteral( "memory" ) ); QCOMPARE( layer->name(), QStringLiteral( "memory:mylayer" ) ); QCOMPARE( layer->wkbType(), QgsWkbTypes::PointZM ); QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) ); QCOMPARE( layer->fields().size(), 1 ); QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "my_field" ) ); QCOMPARE( layer->fields().at( 0 ).type(), QVariant::String ); QCOMPARE( destination, layer->id() ); QCOMPARE( context.temporaryLayerStore()->mapLayer( layer->id() ), layer ); // layer should be in store QCOMPARE( layer->featureCount(), 0L ); QVERIFY( sink->addFeature( f ) ); QCOMPARE( layer->featureCount(), 1L ); context.temporaryLayerStore()->removeAllMapLayers(); layer = nullptr; // non memory layer output destination = QDir::tempPath() + "/create_feature_sink.tab"; QString prevDest = destination; sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) ); QVERIFY( sink.get() ); f = QgsFeature( fields ); f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 0 1, 1 1, 1 0, 0 0 ))" ) ) ); f.setAttributes( QgsAttributes() << "val" ); QVERIFY( sink->addFeature( f ) ); QCOMPARE( destination, prevDest ); sink.reset( nullptr ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) ); QVERIFY( layer->isValid() ); QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) ); QCOMPARE( layer->fields().size(), 1 ); QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "my_field" ) ); QCOMPARE( layer->fields().at( 0 ).type(), QVariant::String ); QCOMPARE( layer->featureCount(), 1L ); delete layer; layer = nullptr; // no extension, should default to shp destination = QDir::tempPath() + "/create_feature_sink2"; prevDest = QDir::tempPath() + "/create_feature_sink2.gpkg"; sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Point25D, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) ); QVERIFY( sink.get() ); f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PointZ(1 2 3)" ) ) ); QVERIFY( sink->addFeature( f ) ); QVERIFY( !layer ); QCOMPARE( destination, prevDest ); sink.reset( nullptr ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) ); QCOMPARE( layer->wkbType(), QgsWkbTypes::Point25D ); QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) ); QCOMPARE( layer->fields().size(), 2 ); QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "fid" ) ); QCOMPARE( layer->fields().at( 1 ).name(), QStringLiteral( "my_field" ) ); QCOMPARE( layer->fields().at( 1 ).type(), QVariant::String ); QCOMPARE( layer->featureCount(), 1L ); delete layer; layer = nullptr; //windows style path destination = "d:\\temp\\create_feature_sink.tab"; sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Polygon, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) ); QVERIFY( sink.get() ); } void TestQgsProcessing::parameters() { // test parameter utilities std::unique_ptr< QgsProcessingParameterDefinition > def; QVariantMap params; params.insert( QStringLiteral( "prop" ), QgsProperty::fromField( "a_field" ) ); params.insert( QStringLiteral( "string" ), QStringLiteral( "a string" ) ); params.insert( QStringLiteral( "double" ), 5.2 ); params.insert( QStringLiteral( "int" ), 15 ); params.insert( QStringLiteral( "bool" ), true ); QgsProcessingContext context; // isDynamic QVERIFY( QgsProcessingParameters::isDynamic( params, QStringLiteral( "prop" ) ) ); QVERIFY( !QgsProcessingParameters::isDynamic( params, QStringLiteral( "string" ) ) ); QVERIFY( !QgsProcessingParameters::isDynamic( params, QStringLiteral( "bad" ) ) ); // parameterAsString def.reset( new QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "desc" ) ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "a string" ) ); def->setName( QStringLiteral( "double" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ).left( 3 ), QStringLiteral( "5.2" ) ); def->setName( QStringLiteral( "int" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "15" ) ); def->setName( QStringLiteral( "bool" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "true" ) ); def->setName( QStringLiteral( "bad" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() ); // string with dynamic property (feature not set) def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() ); // correctly setup feature QgsFields fields; fields.append( QgsField( "a_field", QVariant::String, QString(), 30 ) ); QgsFeature f( fields ); f.setAttribute( 0, QStringLiteral( "field value" ) ); context.expressionContext().setFeature( f ); context.expressionContext().setFields( fields ); def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QStringLiteral( "field value" ) ); // as double def->setName( QStringLiteral( "double" ) ); QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 5.2 ); def->setName( QStringLiteral( "int" ) ); QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 15.0 ); f.setAttribute( 0, QStringLiteral( "6.2" ) ); context.expressionContext().setFeature( f ); def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsDouble( def.get(), params, context ), 6.2 ); // as int def->setName( QStringLiteral( "double" ) ); QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 5 ); def->setName( QStringLiteral( "int" ) ); QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 15 ); def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsInt( def.get(), params, context ), 6 ); // as bool def->setName( QStringLiteral( "double" ) ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); def->setName( QStringLiteral( "int" ) ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); def->setName( QStringLiteral( "bool" ) ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); f.setAttribute( 0, false ); context.expressionContext().setFeature( f ); def->setName( QStringLiteral( "prop" ) ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); // as layer def->setName( QStringLiteral( "double" ) ); QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); def->setName( QStringLiteral( "int" ) ); QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); def->setName( QStringLiteral( "bool" ) ); QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); def->setName( QStringLiteral( "prop" ) ); QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); QVERIFY( context.temporaryLayerStore()->mapLayers().isEmpty() ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt f.setAttribute( 0, testDataDir + "/raster/band1_float32_noct_epsg4326.tif" ); context.expressionContext().setFeature( f ); def->setName( QStringLiteral( "prop" ) ); QVERIFY( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); // make sure layer was loaded QVERIFY( !context.temporaryLayerStore()->mapLayers().isEmpty() ); // parameters as sinks QgsWkbTypes::Type wkbType = QgsWkbTypes::PolygonM; QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:3111" ) ); QString destId; def->setName( QStringLiteral( "string" ) ); params.insert( QStringLiteral( "string" ), QStringLiteral( "memory:mem" ) ); std::unique_ptr< QgsFeatureSink > sink; sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) ); QVERIFY( sink.get() ); QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) ); QVERIFY( layer ); QVERIFY( layer->isValid() ); QCOMPARE( layer->fields().count(), 1 ); QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "a_field" ) ); QCOMPARE( layer->wkbType(), wkbType ); QCOMPARE( layer->crs(), crs ); // property defined sink destination params.insert( QStringLiteral( "prop" ), QgsProperty::fromExpression( "'memory:mem2'" ) ); def->setName( QStringLiteral( "prop" ) ); crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:3113" ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) ); QVERIFY( sink.get() ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) ); QVERIFY( layer ); QVERIFY( layer->isValid() ); QCOMPARE( layer->fields().count(), 1 ); QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "a_field" ) ); QCOMPARE( layer->wkbType(), wkbType ); QCOMPARE( layer->crs(), crs ); // QgsProcessingFeatureSinkDefinition as parameter QgsProject p; QgsProcessingOutputLayerDefinition fs( QStringLiteral( "test.shp" ) ); fs.destinationProject = &p; QVERIFY( context.layersToLoadOnCompletion().isEmpty() ); params.insert( QStringLiteral( "fs" ), QVariant::fromValue( fs ) ); def->setName( QStringLiteral( "fs" ) ); crs = QgsCoordinateReferenceSystem( QStringLiteral( "epsg:28356" ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context, destId ) ); QVERIFY( sink.get() ); QgsVectorFileWriter *writer = dynamic_cast< QgsVectorFileWriter *>( sink.get() ); QVERIFY( writer ); layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destId, context ) ); QVERIFY( layer ); QVERIFY( layer->isValid() ); QCOMPARE( layer->wkbType(), wkbType ); QCOMPARE( layer->crs(), crs ); // make sure layer was automatically added to list to load on completion QCOMPARE( context.layersToLoadOnCompletion().size(), 1 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), destId ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "desc" ) ); // with name overloading QgsProcessingContext context2; fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.shp" ) ); fs.destinationProject = &p; fs.destinationName = QStringLiteral( "my_dest" ); params.insert( QStringLiteral( "fs" ), QVariant::fromValue( fs ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, fields, wkbType, crs, context2, destId ) ); QVERIFY( sink.get() ); QCOMPARE( context2.layersToLoadOnCompletion().size(), 1 ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), destId ); QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "my_dest" ) ); QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).outputName, QStringLiteral( "fs" ) ); } void TestQgsProcessing::algorithmParameters() { DummyAlgorithm alg( "test" ); alg.runParameterChecks(); } void TestQgsProcessing::algorithmOutputs() { DummyAlgorithm alg( "test" ); alg.runOutputChecks(); } void TestQgsProcessing::parameterGeneral() { // test constructor QgsProcessingParameterBoolean param( "p1", "desc", true, true ); QCOMPARE( param.name(), QString( "p1" ) ); QCOMPARE( param.description(), QString( "desc" ) ); QCOMPARE( param.defaultValue(), QVariant( true ) ); QVERIFY( param.flags() & QgsProcessingParameterDefinition::FlagOptional ); QVERIFY( param.dependsOnOtherParameters().isEmpty() ); // test getters and setters param.setDescription( "p2" ); QCOMPARE( param.description(), QString( "p2" ) ); param.setDefaultValue( false ); QCOMPARE( param.defaultValue(), QVariant( false ) ); param.setFlags( QgsProcessingParameterDefinition::FlagHidden ); QCOMPARE( param.flags(), QgsProcessingParameterDefinition::FlagHidden ); param.setDefaultValue( true ); QCOMPARE( param.defaultValue(), QVariant( true ) ); param.setDefaultValue( QVariant() ); QCOMPARE( param.defaultValue(), QVariant() ); QVariantMap metadata; metadata.insert( "p1", 5 ); metadata.insert( "p2", 7 ); param.setMetadata( metadata ); QCOMPARE( param.metadata(), metadata ); param.metadata().insert( "p3", 9 ); QCOMPARE( param.metadata().value( "p3" ).toInt(), 9 ); QVariantMap map = param.toVariantMap(); QgsProcessingParameterBoolean fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), param.name() ); QCOMPARE( fromMap.description(), param.description() ); QCOMPARE( fromMap.flags(), param.flags() ); QCOMPARE( fromMap.defaultValue(), param.defaultValue() ); QCOMPARE( fromMap.metadata(), param.metadata() ); } void TestQgsProcessing::parameterBoolean() { QgsProcessingContext context; // test no def QVariantMap params; params.insert( "no_def", false ); QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false ); params.insert( "no_def", "false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false ); params.insert( "no_def", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false ); params.remove( "no_def" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( nullptr, params, context ), false ); // with defs std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterBoolean( "non_optional_default_false" ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( "false" ) ); QVERIFY( def->checkValueIsAcceptable( "true" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); params.insert( "non_optional_default_false", false ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.insert( "non_optional_default_false", true ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "non_optional_default_false", "true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "non_optional_default_false", "false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); //non-optional - behavior is undefined, but internally default to false params.insert( "non_optional_default_false", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.remove( "non_optional_default_false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); QCOMPARE( def->valueAsPythonString( false, context ), QStringLiteral( "False" ) ); QCOMPARE( def->valueAsPythonString( true, context ), QStringLiteral( "True" ) ); QCOMPARE( def->valueAsPythonString( "false", context ), QStringLiteral( "False" ) ); QCOMPARE( def->valueAsPythonString( "true", context ), QStringLiteral( "True" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional_default_false=boolean false" ) ); std::unique_ptr< QgsProcessingParameterBoolean > fromCode( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional default false" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toBool(), false ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterBoolean fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( QgsProcessingParameters::parameterFromVariantMap( map ) ); QVERIFY( dynamic_cast< QgsProcessingParameterBoolean *>( def.get() ) ); def.reset( new QgsProcessingParameterBoolean( "optional_default_true", QString(), true, true ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( "false" ) ); QVERIFY( def->checkValueIsAcceptable( "true" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional_default_true", false ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.insert( "optional_default_true", true ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "optional_default_true", "true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "optional_default_true", "false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); //optional - should be default params.insert( "optional_default_true", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.remove( "optional_default_true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional_default_true=optional boolean true" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional default true" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toBool(), true ); def.reset( new QgsProcessingParameterBoolean( "optional_default_false", QString(), false, true ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( "false" ) ); QVERIFY( def->checkValueIsAcceptable( "true" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( "false" ) ); QVERIFY( def->checkValueIsAcceptable( "true" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional_default_false", false ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.insert( "optional_default_false", true ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "optional_default_false", "true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "optional_default_false", "false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); //optional - should be default params.insert( "optional_default_false", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.remove( "optional_default_false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional default false" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toBool(), false ); def.reset( new QgsProcessingParameterBoolean( "non_optional_default_true", QString(), true, false ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( "false" ) ); QVERIFY( def->checkValueIsAcceptable( "true" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); params.insert( "non_optional_default_true", false ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); params.insert( "non_optional_default_true", true ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "non_optional_default_true", "true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.insert( "non_optional_default_true", "false" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), false ); //non-optional - behavior is undefined, but internally fallback to default params.insert( "non_optional_default_true", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); params.remove( "non_optional_default_true" ); QCOMPARE( QgsProcessingParameters::parameterAsBool( def.get(), params, context ), true ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterBoolean * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional default true" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toBool(), true ); } void TestQgsProcessing::parameterCrs() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" ); p.addMapLayers( QList() << v1 << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterCrs > def( new QgsProcessingParameterCrs( "non_optional", QString(), QString( "EPSG:3113" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "EPSG:12003" ) ); QVERIFY( def->checkValueIsAcceptable( "EPSG:3111" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // using map layer QVariantMap params; params.insert( "non_optional", v1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3111" ) ); QVERIFY( def->checkValueIsAcceptable( v1->id() ) ); // special ProjectCrs string params.insert( "non_optional", QStringLiteral( "ProjectCrs" ) ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:28353" ) ); QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "ProjectCrs" ) ) ); // string representing a project layer source params.insert( "non_optional", raster1 ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:4326" ) ); QVERIFY( def->checkValueIsAcceptable( raster1 ) ); // string representing a non-project layer source params.insert( "non_optional", raster2 ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:32633" ) ); QVERIFY( def->checkValueIsAcceptable( raster2 ) ); // string representation of a crs params.insert( "non_optional", QString( "EPSG:28355" ) ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:28355" ) ); QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "EPSG:28355" ) ) ); // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); QVERIFY( !QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).isValid() ); QCOMPARE( def->valueAsPythonString( "EPSG:12003", context ), QStringLiteral( "'EPSG:12003'" ) ); QCOMPARE( def->valueAsPythonString( "ProjectCrs", context ), QStringLiteral( "'ProjectCrs'" ) ); QCOMPARE( def->valueAsPythonString( raster1, context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterCrs fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterCrs *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterCrs *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=crs EPSG:3113" ) ); std::unique_ptr< QgsProcessingParameterCrs > fromCode( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // optional def.reset( new QgsProcessingParameterCrs( "optional", QString(), QString( "EPSG:3113" ), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsCrs( def.get(), params, context ).authid(), QString( "EPSG:3113" ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "EPSG:12003" ) ); QVERIFY( def->checkValueIsAcceptable( "EPSG:3111" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional crs EPSG:3113" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); code = QStringLiteral( "##optional=optional crs None" ); fromCode.reset( dynamic_cast< QgsProcessingParameterCrs * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); } void TestQgsProcessing::parameterLayer() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" ); p.addMapLayers( QList() << v1 << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterMapLayer > def( new QgsProcessingParameterMapLayer( "non_optional", QString(), QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); // using existing map layer ID QVariantMap params; params.insert( "non_optional", v1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), v1->id() ); QVERIFY( def->checkValueIsAcceptable( v1->id() ) ); QVERIFY( def->checkValueIsAcceptable( v1->id(), &context ) ); // string representing a project layer source params.insert( "non_optional", raster1 ); QVERIFY( def->checkValueIsAcceptable( raster1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), r1->id() ); // string representing a non-project layer source params.insert( "non_optional", raster2 ); QVERIFY( def->checkValueIsAcceptable( raster2 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->publicSource(), raster2 ); // nonsense string params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); QVERIFY( !QgsProcessingParameters::parameterAsLayer( def.get(), params, context ) ); // layer params.insert( "non_optional", QVariant::fromValue( r1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ), r1 ); params.insert( "non_optional", QVariant::fromValue( v1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context ), v1 ); QCOMPARE( def->valueAsPythonString( raster1, context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=layer" ) ); std::unique_ptr< QgsProcessingParameterMapLayer > fromCode( dynamic_cast< QgsProcessingParameterMapLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterMapLayer fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterMapLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterMapLayer *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterMapLayer( "optional", QString(), v1->id(), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsLayer( def.get(), params, context )->id(), v1->id() ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional layer " ) + v1->id() ); fromCode.reset( dynamic_cast< QgsProcessingParameterMapLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterExtent() { // setup a context QgsProject p; QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); p.addMapLayers( QList() << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterExtent > def( new QgsProcessingParameterExtent( "non_optional", QString(), QString( "1,2,3,4" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) ); QVERIFY( def->checkValueIsAcceptable( " 1, 2 ,3 , 4 " ) ); QVERIFY( def->checkValueIsAcceptable( " 1, 2 ,3 , 4 ", &context ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4" ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4", &context ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4" ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4", &context ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]", &context ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]", &context ) ); QVERIFY( def->checkValueIsAcceptable( " -1.1, -2, -3, -4.4 [EPSG:4326] " ) ); QVERIFY( def->checkValueIsAcceptable( " -1.1, -2, -3, -4.4 [EPSG:4326] ", &context ) ); QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]" ) ); QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]", &context ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsRectangle( 1, 2, 3, 4 ) ) ); QVERIFY( !def->checkValueIsAcceptable( QgsRectangle() ) ); QVERIFY( def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) ); QVERIFY( !def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle(), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) ); // these checks require a context - otherwise we could potentially be referring to a layer source QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3,a" ) ); QVERIFY( !def->checkValueIsAcceptable( "1,2,3", &context ) ); QVERIFY( !def->checkValueIsAcceptable( "1,2,3,a", &context ) ); // using map layer QVariantMap params; params.insert( "non_optional", r1->id() ); QVERIFY( def->checkValueIsAcceptable( r1->id() ) ); QgsRectangle ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QCOMPARE( ext, r1->extent() ); // string representing a project layer source params.insert( "non_optional", raster1 ); QVERIFY( def->checkValueIsAcceptable( raster1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ), r1->extent() ); QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); QGSCOMPARENEAR( ext.xMinimum(), 1535375, 100 ); QGSCOMPARENEAR( ext.xMaximum(), 1535475, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 5083255, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 5083355, 100 ); // string representing a non-project layer source params.insert( "non_optional", raster2 ); QVERIFY( def->checkValueIsAcceptable( raster2 ) ); QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:32633" ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 781662.375000, 10 ); QGSCOMPARENEAR( ext.xMaximum(), 793062.375000, 10 ); QGSCOMPARENEAR( ext.yMinimum(), 3339523.125000, 10 ); QGSCOMPARENEAR( ext.yMaximum(), 3350923.125000, 10 ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 ); QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 ); QGSCOMPARENEAR( ext.yMinimum(), 30.151856, 0.01 ); QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 ); QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); QCOMPARE( gExt.constGet()->vertexCount(), 85 ); ext = gExt.boundingBox(); QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 ); QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 ); QGSCOMPARENEAR( ext.yMinimum(), 30.151856, 0.01 ); QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 ); // string representation of an extent params.insert( "non_optional", QString( "1.1,2.2,3.3,4.4" ) ); QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "1.1,2.2,3.3,4.4" ) ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 2.2, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 3.3, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); // with target CRS - should make no difference, because source CRS is unknown ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 2.2, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 3.3, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); // with crs in string params.insert( "non_optional", QString( "1.1,3.3,2.2,4.4 [EPSG:4326]" ) ); QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 2.2, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( gExt.constGet()->vertexCount(), 85 ); ext = gExt.boundingBox(); QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); QVERIFY( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ).isNull() ); // QgsRectangle params.insert( "non_optional", QgsRectangle( 11.1, 12.2, 13.3, 14.4 ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 12.2, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 ); // with target CRS - should make no difference, because source CRS is unknown ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 12.2, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 ); gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( gExt.exportToWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) ); p.setCrs( QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:3785" ) ); // QgsReferencedRectangle params.insert( "non_optional", QgsReferencedRectangle( QgsRectangle( 1.1, 2.2, 3.3, 4.4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 ); QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 ); QGSCOMPARENEAR( ext.yMinimum(), 2.2, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); // with target CRS ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); // as reprojected geometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( gExt.constGet()->vertexCount(), 85 ); ext = gExt.boundingBox(); QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); QCOMPARE( def->valueAsPythonString( "1,2,3,4", context ), QStringLiteral( "'1,2,3,4'" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( raster2, context ), QString( "'" ) + testDataDir + QStringLiteral( "landsat.tif'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "'11, 13, 12, 14'" ) ); QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11, 13, 12, 14 [EPSG:4326]'" ) ); QCOMPARE( def->valueAsPythonString( "1,2,3,4 [EPSG:4326]", context ), QStringLiteral( "'1,2,3,4 [EPSG:4326]'" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=extent 1,2,3,4" ) ); std::unique_ptr< QgsProcessingParameterExtent > fromCode( dynamic_cast< QgsProcessingParameterExtent * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterExtent fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterExtent *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterExtent *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterExtent( "optional", QString(), QString( "5,6,7,8" ), true ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // Extent is unique in that it will let you set invalid, whereas other // optional parameters become "default" when assigning invalid. params.insert( "optional", QVariant() ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QVERIFY( ext.isNull() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional extent 5,6,7,8" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterExtent * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterPoint() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterPoint > def( new QgsProcessingParameterPoint( "non_optional", QString(), QString( "1,2" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) ); QVERIFY( def->checkValueIsAcceptable( " 1.1, 2 " ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,2" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,-2" ) ); QVERIFY( def->checkValueIsAcceptable( "-1.1,-2" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2[EPSG:4326]" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2 [EPSG:4326]" ) ); QVERIFY( def->checkValueIsAcceptable( " -1.1, -2 [EPSG:4326] " ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1,a" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsPointXY( 1, 2 ) ) ); QVERIFY( def->checkValueIsAcceptable( QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) ); // string representing a point QVariantMap params; params.insert( "non_optional", QString( "1.1,2.2" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2.2" ) ); QgsPointXY point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); // with target CRS - should make no difference, because source CRS is unknown point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); // with CRS as string params.insert( "non_optional", QString( "1.1,2.2[EPSG:4326]" ) ); QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( point.x(), 122451, 100 ); QGSCOMPARENEAR( point.y(), 244963, 100 ); params.insert( "non_optional", QString( "1.1,2.2 [EPSG:4326]" ) ); QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( point.x(), 122451, 100 ); QGSCOMPARENEAR( point.y(), 244963, 100 ); // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QCOMPARE( point.x(), 0.0 ); QCOMPARE( point.y(), 0.0 ); // QgsPointXY params.insert( "non_optional", QgsPointXY( 11.1, 12.2 ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QGSCOMPARENEAR( point.x(), 11.1, 0.001 ); QGSCOMPARENEAR( point.y(), 12.2, 0.001 ); // with target CRS - should make no difference, because source CRS is unknown point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( point.x(), 11.1, 0.001 ); QGSCOMPARENEAR( point.y(), 12.2, 0.001 ); // QgsReferencedPointXY params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); // with target CRS point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QGSCOMPARENEAR( point.x(), 122451, 100 ); QGSCOMPARENEAR( point.y(), 244963, 100 ); QCOMPARE( def->valueAsPythonString( "1,2", context ), QStringLiteral( "'1,2'" ) ); QCOMPARE( def->valueAsPythonString( "1,2 [EPSG:4326]", context ), QStringLiteral( "'1,2 [EPSG:4326]'" ) ); QCOMPARE( def->valueAsPythonString( QgsPointXY( 11, 12 ), context ), QStringLiteral( "'11,12'" ) ); QCOMPARE( def->valueAsPythonString( QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11,12 [EPSG:4326]'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=point 1,2" ) ); std::unique_ptr< QgsProcessingParameterPoint > fromCode( dynamic_cast< QgsProcessingParameterPoint * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterPoint fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterPoint *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterPoint *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterPoint( "optional", QString(), QString( "5.1,6.2" ), true ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1,a" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QGSCOMPARENEAR( point.x(), 5.1, 0.001 ); QGSCOMPARENEAR( point.y(), 6.2, 0.001 ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional point 5.1,6.2" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterPoint * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterFile() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterFile > def( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QString(), QString( "abc.bmp" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( " " ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string representing a file QVariantMap params; params.insert( "non_optional", QString( "def.bmp" ) ); QCOMPARE( QgsProcessingParameters::parameterAsFile( def.get(), params, context ), QString( "def.bmp" ) ); // with extension def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QStringLiteral( ".bmp" ), QString( "abc.bmp" ), false ) ); QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) ); QVERIFY( def->checkValueIsAcceptable( "bricks.BMP" ) ); QVERIFY( !def->checkValueIsAcceptable( "bricks.pcx" ) ); QCOMPARE( def->valueAsPythonString( "bricks.bmp", context ), QStringLiteral( "'bricks.bmp'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=file abc.bmp" ) ); std::unique_ptr< QgsProcessingParameterFile > fromCode( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->behavior(), def->behavior() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFile fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.extension(), def->extension() ); QCOMPARE( fromMap.behavior(), def->behavior() ); def.reset( dynamic_cast< QgsProcessingParameterFile *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterFile *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::File, QString(), QString( "gef.bmp" ), true ) ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "bricks.bmp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( " " ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsFile( def.get(), params, context ), QString( "gef.bmp" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional file gef.bmp" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->behavior(), def->behavior() ); // folder def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::Folder, QString(), QString( "/home/me" ), true ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional folder /home/me" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->behavior(), def->behavior() ); } void TestQgsProcessing::parameterMatrix() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterMatrix > def( new QgsProcessingParameterMatrix( "non_optional", QString(), 3, false, QStringList(), QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 << 2 << 3 ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << ( QVariantList() << 1 << 2 << 3 ) << ( QVariantList() << 1 << 2 << 3 ) ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // list QVariantMap params; params.insert( "non_optional", QVariantList() << 1 << 2 << 3 ); QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 1 << 2 << 3 ); //string params.insert( "non_optional", QString( "4,5,6" ) ); QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 4 << 5 << 6 ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "[5]" ) ); QCOMPARE( def->valueAsPythonString( QVariantList() << 1 << 2 << 3, context ), QStringLiteral( "[1,2,3]" ) ); QCOMPARE( def->valueAsPythonString( QVariantList() << ( QVariantList() << 1 << 2 << 3 ) << ( QVariantList() << 1 << 2 << 3 ), context ), QStringLiteral( "[1,2,3,1,2,3]" ) ); QCOMPARE( def->valueAsPythonString( "1,2,3", context ), QStringLiteral( "[1,2,3]" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=matrix" ) ); std::unique_ptr< QgsProcessingParameterMatrix > fromCode( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterMatrix fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.headers(), def->headers() ); QCOMPARE( fromMap.numberRows(), def->numberRows() ); QCOMPARE( fromMap.hasFixedNumberRows(), def->hasFixedNumberRows() ); def.reset( dynamic_cast< QgsProcessingParameterMatrix *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterMatrix *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterMatrix( "optional", QString(), 3, false, QStringList(), QVariantList() << 4 << 5 << 6, true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 << 2 << 3 ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional matrix" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 4 << 5 << 6 ); def.reset( new QgsProcessingParameterMatrix( "optional", QString(), 3, false, QStringList(), QString( "1,2,3" ), true ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional matrix 1,2,3" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterMatrix * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsMatrix( def.get(), params, context ), QVariantList() << 1 << 2 << 3 ); } void TestQgsProcessing::parameterLayerList() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V5", "memory" ); p.addMapLayers( QList() << v1 << v2 << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterMultipleLayers > def( new QgsProcessingParameterMultipleLayers( "non_optional", QString(), QgsProcessing::TypeMapLayer, QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); // using existing map layer ID QVariantMap params; params.insert( "non_optional", v1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 ); // using existing map layer params.insert( "non_optional", QVariant::fromValue( v1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 ); // using two existing map layer ID params.insert( "non_optional", QVariantList() << v1->id() << r1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 ); // using two existing map layers params.insert( "non_optional", QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( r1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 ); // mix of list and single layers (happens from models) params.insert( "non_optional", QVariantList() << QVariant( QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( v2 ) ) << QVariant::fromValue( r1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 << r1 ); // mix of two lists (happens from models) params.insert( "non_optional", QVariantList() << QVariant( QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( v2 ) ) << QVariant( QVariantList() << QVariant::fromValue( r1 ) ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << v2 << r1 ); // mix of existing layers and non project layer string params.insert( "non_optional", QVariantList() << v1->id() << raster2 ); QList< QgsMapLayer *> layers = QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ); QCOMPARE( layers.at( 0 ), v1 ); QCOMPARE( layers.at( 1 )->publicSource(), raster2 ); // empty string params.insert( "non_optional", QString( "" ) ); QVERIFY( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ).isEmpty() ); // nonsense string params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); QVERIFY( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ).isEmpty() ); // with 2 min inputs def->setMinimumNumberInputs( 2 ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); def->setMinimumNumberInputs( 3 ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "layer12312312" << "layerB" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "layer12312312" << "layerB" ) ); QCOMPARE( def->valueAsPythonString( "layer12312312", context ), QStringLiteral( "'layer12312312'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc']" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc']" ) ); QCOMPARE( def->valueAsPythonString( QStringList() << r1->id() << raster2, context ), QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc','" ) + testDataDir + QStringLiteral( "landsat.tif']" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=multiple vector" ) ); std::unique_ptr< QgsProcessingParameterMultipleLayers > fromCode( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterMultipleLayers fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.layerType(), def->layerType() ); QCOMPARE( fromMap.minimumNumberInputs(), def->minimumNumberInputs() ); def.reset( dynamic_cast< QgsProcessingParameterMultipleLayers *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterMultipleLayers *>( def.get() ) ); // optional with one default layer def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, v1->id(), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 ); params.insert( "optional", QVariantList() << QVariant::fromValue( r1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << r1 ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional multiple vector " ) + v1->id() ); fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry ); // optional with two default layers def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariantList() << v1->id() << r1->publicSource(), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional multiple vector " ) + v1->id() + "," + r1->publicSource() ); fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toString(), v1->id() + "," + r1->publicSource() ); QCOMPARE( fromCode->layerType(), QgsProcessing::TypeVectorAnyGeometry ); // optional with one default direct layer def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariant::fromValue( v1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 ); // optional with two default direct layers def.reset( new QgsProcessingParameterMultipleLayers( "optional", QString(), QgsProcessing::TypeMapLayer, QVariantList() << QVariant::fromValue( v1 ) << QVariant::fromValue( r1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsLayerList( def.get(), params, context ), QList< QgsMapLayer *>() << v1 << r1 ); def.reset( new QgsProcessingParameterMultipleLayers( "type", QString(), QgsProcessing::TypeRaster ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##type=multiple raster" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "type" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); QCOMPARE( fromCode->layerType(), QgsProcessing::TypeRaster ); def.reset( new QgsProcessingParameterMultipleLayers( "type", QString(), QgsProcessing::TypeFile ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##type=multiple file" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterMultipleLayers * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "type" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); QCOMPARE( fromCode->layerType(), def->layerType() ); } void TestQgsProcessing::parameterNumber() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterNumber > def( new QgsProcessingParameterNumber( "non_optional", QString(), QgsProcessingParameterNumber::Double, 5, false ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1.1" ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1,2" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string representing a number QVariantMap params; params.insert( "non_optional", QString( "1.1" ) ); double number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QGSCOMPARENEAR( number, 1.1, 0.001 ); int iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 1 ); // double params.insert( "non_optional", 1.1 ); number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QGSCOMPARENEAR( number, 1.1, 0.001 ); iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 1 ); // int params.insert( "non_optional", 1 ); number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QGSCOMPARENEAR( number, 1, 0.001 ); iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 1 ); // nonsense string params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) ); number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QCOMPARE( number, 5.0 ); iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 5 ); // with min value def->setMinimum( 11 ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1" ) ); QVERIFY( def->checkValueIsAcceptable( 25 ) ); QVERIFY( def->checkValueIsAcceptable( "21.1" ) ); // with max value def->setMaximum( 21 ); QVERIFY( !def->checkValueIsAcceptable( 35 ) ); QVERIFY( !def->checkValueIsAcceptable( "31.1" ) ); QVERIFY( def->checkValueIsAcceptable( 15 ) ); QVERIFY( def->checkValueIsAcceptable( "11.1" ) ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1.1" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=number 5" ) ); std::unique_ptr< QgsProcessingParameterNumber > fromCode( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterNumber fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.minimum(), def->minimum() ); QCOMPARE( fromMap.maximum(), def->maximum() ); QCOMPARE( fromMap.dataType(), def->dataType() ); def.reset( dynamic_cast< QgsProcessingParameterNumber *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterNumber *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterNumber( "optional", QString(), QgsProcessingParameterNumber::Double, 5.4, true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1.1" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QGSCOMPARENEAR( number, 5.4, 0.001 ); iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 5 ); // unconvertible string params.insert( "optional", QVariant( "aaaa" ) ); number = QgsProcessingParameters::parameterAsDouble( def.get(), params, context ); QGSCOMPARENEAR( number, 5.4, 0.001 ); iNumber = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( iNumber, 5 ); code = def->asScriptCode(); QCOMPARE( code.left( 30 ), QStringLiteral( "##optional=optional number 5.4" ) ); // truncate code to 30, to avoid Qt 5.6 rounding issues fromCode.reset( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); fromCode.reset( dynamic_cast< QgsProcessingParameterNumber * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##optional=optional number None" ) ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); } void TestQgsProcessing::parameterRange() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterRange > def( new QgsProcessingParameterRange( "non_optional", QString(), QgsProcessingParameterNumber::Double, QString( "5,6" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1" ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1,2,3" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "1,a" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 << 3 ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string representing a range of numbers QVariantMap params; params.insert( "non_optional", QString( "1.1,1.2" ) ); QList< double > range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 ); QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 ); // list params.insert( "non_optional", QVariantList() << 1.1 << 1.2 ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 ); QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 ); // too many elements: params.insert( "non_optional", QString( "1.1,1.2,1.3" ) ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 ); QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 ); params.insert( "non_optional", QVariantList() << 1.1 << 1.2 << 1.3 ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QGSCOMPARENEAR( range.at( 0 ), 1.1, 0.001 ); QGSCOMPARENEAR( range.at( 1 ), 1.2, 0.001 ); // not enough elements - don't care about the result, just don't crash! params.insert( "non_optional", QString( "1.1" ) ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); params.insert( "non_optional", QVariantList() << 1.1 ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QCOMPARE( def->valueAsPythonString( "1.1,2", context ), QStringLiteral( "[1.1,2]" ) ); QCOMPARE( def->valueAsPythonString( QVariantList() << 1.1 << 2, context ), QStringLiteral( "[1.1,2]" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=range 5,6" ) ); std::unique_ptr< QgsProcessingParameterRange > fromCode( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterRange fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.dataType(), def->dataType() ); def.reset( dynamic_cast< QgsProcessingParameterRange *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterRange *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterRange( "optional", QString(), QgsProcessingParameterNumber::Double, QString( "5.4,7.4" ), true ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1.1 << 2 ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); range = QgsProcessingParameters::parameterAsRange( def.get(), params, context ); QGSCOMPARENEAR( range.at( 0 ), 5.4, 0.001 ); QGSCOMPARENEAR( range.at( 1 ), 7.4, 0.001 ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional range 5.4,7.4" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterRange * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterRasterLayer() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" ); p.addMapLayers( QList() << v1 << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterRasterLayer > def( new QgsProcessingParameterRasterLayer( "non_optional", QString(), QVariant(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif", &context ) ); // using existing map layer ID QVariantMap params; params.insert( "non_optional", r1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); // using existing map layer params.insert( "non_optional", QVariant::fromValue( r1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); // not raster layer params.insert( "non_optional", v1->id() ); QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) ); // using existing vector layer params.insert( "non_optional", QVariant::fromValue( v1 ) ); QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) ); // string representing a project layer source params.insert( "non_optional", raster1 ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); // string representing a non-project layer source params.insert( "non_optional", raster2 ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->publicSource(), raster2 ); // nonsense string params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); QVERIFY( !QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context ) ); QCOMPARE( def->valueAsPythonString( raster1, context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=raster" ) ); std::unique_ptr< QgsProcessingParameterRasterLayer > fromCode( dynamic_cast< QgsProcessingParameterRasterLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // optional def.reset( new QgsProcessingParameterRasterLayer( "optional", QString(), r1->id(), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional raster " ) + r1->id() ); fromCode.reset( dynamic_cast< QgsProcessingParameterRasterLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterRasterLayer fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterRasterLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterRasterLayer *>( def.get() ) ); // optional with direct layer def.reset( new QgsProcessingParameterRasterLayer( "optional", QString(), QVariant::fromValue( r1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsRasterLayer( def.get(), params, context )->id(), r1->id() ); } void TestQgsProcessing::parameterEnum() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterEnum > def( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", false, 2, false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( !def->checkValueIsAcceptable( "1,2" ) ); QVERIFY( def->checkValueIsAcceptable( 0 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) ); QVERIFY( !def->checkValueIsAcceptable( 15 ) ); QVERIFY( !def->checkValueIsAcceptable( -1 ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string representing a number QVariantMap params; params.insert( "non_optional", QString( "1" ) ); int iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 1 ); // double params.insert( "non_optional", 2.2 ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 2 ); // int params.insert( "non_optional", 1 ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 1 ); // nonsense string params.insert( "non_optional", QString( "i'm not a number, and nothing you can do will make me one" ) ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 2 ); // out of range params.insert( "non_optional", 4 ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 2 ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "1.1" ), context ), QStringLiteral( "1" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=enum A;B;C 2" ) ); std::unique_ptr< QgsProcessingParameterEnum > fromCode( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->options(), def->options() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterEnum fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.options(), def->options() ); QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() ); def.reset( dynamic_cast< QgsProcessingParameterEnum *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterEnum *>( def.get() ) ); // multiple def.reset( new QgsProcessingParameterEnum( "non_optional", QString(), QStringList() << "A" << "B" << "C", true, 5, false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( def->checkValueIsAcceptable( "1,2" ) ); QVERIFY( def->checkValueIsAcceptable( 0 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); // since non-optional, empty list not allowed QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) ); QVERIFY( !def->checkValueIsAcceptable( 15 ) ); QVERIFY( !def->checkValueIsAcceptable( -1 ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); params.insert( "non_optional", QString( "1,2" ) ); QList< int > iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() << 1 << 2 ); params.insert( "non_optional", QVariantList() << 0 << 2 ); iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() << 0 << 2 ); // empty list params.insert( "non_optional", QVariantList() ); iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() ); QCOMPARE( def->valueAsPythonString( QVariantList() << 1 << 2, context ), QStringLiteral( "[1,2]" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "1,2" ), context ), QStringLiteral( "[1,2]" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=enum multiple A;B;C 5" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->options(), def->options() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); // optional def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "a" << "b", false, 5, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( !def->checkValueIsAcceptable( "1,2" ) ); QVERIFY( def->checkValueIsAcceptable( 0 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) ); QVERIFY( !def->checkValueIsAcceptable( 15 ) ); QVERIFY( !def->checkValueIsAcceptable( -1 ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional enum a;b 5" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->options(), def->options() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); params.insert( "optional", QVariant() ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 5 ); // unconvertible string params.insert( "optional", QVariant( "aaaa" ) ); iNumber = QgsProcessingParameters::parameterAsEnum( def.get(), params, context ); QCOMPARE( iNumber, 5 ); //optional with multiples def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "A" << "B" << "C", true, QVariantList() << 1 << 2, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( def->checkValueIsAcceptable( "1,2" ) ); QVERIFY( def->checkValueIsAcceptable( 0 ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) ); QVERIFY( !def->checkValueIsAcceptable( 15 ) ); QVERIFY( !def->checkValueIsAcceptable( -1 ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() << 1 << 2 ); def.reset( new QgsProcessingParameterEnum( "optional", QString(), QStringList() << "A" << "B" << "C", true, "1,2", true ) ); params.insert( "optional", QVariant() ); iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() << 1 << 2 ); // empty list params.insert( "optional", QVariantList() ); iNumbers = QgsProcessingParameters::parameterAsEnums( def.get(), params, context ); QCOMPARE( iNumbers, QList() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional enum multiple A;B;C 1,2" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterEnum * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->options(), def->options() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); } void TestQgsProcessing::parameterString() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterString > def( new QgsProcessingParameterString( "non_optional", QString(), QString(), false, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string QVariantMap params; params.insert( "non_optional", QString( "abcdef" ) ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=string" ) ); std::unique_ptr< QgsProcessingParameterString > fromCode( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterString fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.multiLine(), def->multiLine() ); def.reset( dynamic_cast< QgsProcessingParameterString *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterString *>( def.get() ) ); def->setMultiLine( true ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=string long" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); def->setMultiLine( false ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string None" ) ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QVERIFY( !fromCode->defaultValue().isValid() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string it's mario" ) ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); def->setDefaultValue( QStringLiteral( "it's mario" ) ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string 'my val'" ) ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=string \"my val\"" ) ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); // optional def.reset( new QgsProcessingParameterString( "optional", QString(), QString( "default" ), false, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) ); params.insert( "optional", QString() ); //empty string should not result in default value QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional string default" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); def->setMultiLine( true ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional string long default" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterString * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->multiLine(), def->multiLine() ); } void TestQgsProcessing::parameterExpression() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterExpression > def( new QgsProcessingParameterExpression( "non_optional", QString(), QString(), QString(), false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string QVariantMap params; params.insert( "non_optional", QString( "abcdef" ) ); QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "abcdef" ) ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=expression" ) ); std::unique_ptr< QgsProcessingParameterExpression > fromCode( dynamic_cast< QgsProcessingParameterExpression * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterExpression fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() ); def.reset( dynamic_cast< QgsProcessingParameterExpression *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterExpression *>( def.get() ) ); QVERIFY( def->dependsOnOtherParameters().isEmpty() ); def->setParentLayerParameterName( QStringLiteral( "test_layer" ) ); QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "test_layer" ) ); // optional def.reset( new QgsProcessingParameterExpression( "optional", QString(), QString( "default" ), QString(), true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "default" ) ); // valid expression, should not fallback params.insert( "optional", QVariant( "1+2" ) ); QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "1+2" ) ); // invalid expression, should fallback params.insert( "optional", QVariant( "1+" ) ); QCOMPARE( QgsProcessingParameters::parameterAsExpression( def.get(), params, context ), QString( "default" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional expression default" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterExpression * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterField() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterField > def( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, false, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string QVariantMap params; params.insert( "non_optional", QString( "a" ) ); QStringList fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "a" ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "probably\'invalid\"field", context ), QStringLiteral( "'probably\\'invalid\\\"field'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=field" ) ); std::unique_ptr< QgsProcessingParameterField > fromCode( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); QVERIFY( def->dependsOnOtherParameters().isEmpty() ); def->setParentLayerParameterName( "my_parent" ); QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); def->setDataType( QgsProcessingParameterField::Numeric ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); def->setDataType( QgsProcessingParameterField::String ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); def->setDataType( QgsProcessingParameterField::DateTime ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); // multiple def.reset( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, true, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); params.insert( "non_optional", QString( "a;b" ) ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "a" << "b" ); params.insert( "non_optional", QVariantList() << "a" << "b" ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "a" << "b" ); QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "b", context ), QStringLiteral( "['a','b']" ) ); QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "b", context ), QStringLiteral( "['a','b']" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterField fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromMap.dataType(), def->dataType() ); QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() ); def.reset( dynamic_cast< QgsProcessingParameterField *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterField *>( def.get() ) ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); // optional def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "def" ), QString(), QgsProcessingParameterField::Any, false, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "def" ); // optional, no default def.reset( new QgsProcessingParameterField( "optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, false, true ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QVERIFY( fields.isEmpty() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional field" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); //optional with multiples def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "abc;def" ), QString(), QgsProcessingParameterField::Any, true, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); QVERIFY( def->checkValueIsAcceptable( QVariantList() << "a" << "b" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "abc" << "def" ); def.reset( new QgsProcessingParameterField( "optional", QString(), QVariantList() << "abc" << "def", QString(), QgsProcessingParameterField::Any, true, true ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "abc" << "def" ); } void TestQgsProcessing::parameterVectorLayer() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString vector1 = testDataDir + "multipoint.shp"; QString raster = testDataDir + "landsat.tif"; QFileInfo fi1( raster ); QFileInfo fi2( vector1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" ); p.addMapLayers( QList() << v1 << r1 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterVectorLayer > def( new QgsProcessingParameterVectorLayer( "non_optional", QString(), QList< int >(), QString( "somelayer" ), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); // using existing map layer ID QVariantMap params; params.insert( "non_optional", v1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); // using existing layer params.insert( "non_optional", QVariant::fromValue( v1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); // not vector layer params.insert( "non_optional", r1->id() ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); // using existing non-vector layer params.insert( "non_optional", QVariant::fromValue( r1 ) ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); // string representing a layer source params.insert( "non_optional", vector1 ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->publicSource(), vector1 ); // nonsense string params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); QCOMPARE( def->valueAsPythonString( vector1, context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); QCOMPARE( def->valueAsPythonString( v1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=vector somelayer" ) ); std::unique_ptr< QgsProcessingParameterVectorLayer > fromCode( dynamic_cast< QgsProcessingParameterVectorLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterVectorLayer fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); def.reset( dynamic_cast< QgsProcessingParameterVectorLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterVectorLayer *>( def.get() ) ); // optional def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), v1->id(), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional vector " ) + v1->id() ); fromCode.reset( dynamic_cast< QgsProcessingParameterVectorLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); //optional with direct layer default def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QList< int >(), QVariant::fromValue( v1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); } void TestQgsProcessing::parameterFeatureSource() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString vector1 = testDataDir + "multipoint.shp"; QString vector2 = testDataDir + "lines.shp"; QString raster = testDataDir + "landsat.tif"; QFileInfo fi1( raster ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon?crs=EPSG:3111", "V4", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( vector2, "V5", "ogr" ); p.addMapLayers( QList() << v1 << r1 << v2 ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterFeatureSource > def( new QgsProcessingParameterFeatureSource( "non_optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); // should be OK QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); // ... unless we use context, when the check that the layer actually exists is performed QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); // using existing map layer ID QVariantMap params; params.insert( "non_optional", v1->id() ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); // using existing layer params.insert( "non_optional", QVariant::fromValue( v1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); // not vector layer params.insert( "non_optional", r1->id() ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); // using existing non-vector layer params.insert( "non_optional", QVariant::fromValue( r1 ) ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); // string representing a layer source params.insert( "non_optional", vector1 ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->publicSource(), vector1 ); // nonsense string params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( v2->id() ) ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), true ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', True)" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v2 ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFeatureSource fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.dataTypes(), def->dataTypes() ); def.reset( dynamic_cast< QgsProcessingParameterFeatureSource *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterFeatureSource *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source" ) ); std::unique_ptr< QgsProcessingParameterFeatureSource > fromCode( dynamic_cast< QgsProcessingParameterFeatureSource * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source point" ) ); def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorLine ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source line" ) ); def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPolygon ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source polygon" ) ); def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source point line" ) ); def->setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorPolygon ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=source point polygon" ) ); // optional def.reset( new QgsProcessingParameterFeatureSource( "optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, v1->id(), true ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); QVERIFY( def->checkValueIsAcceptable( false ) ); QVERIFY( def->checkValueIsAcceptable( true ) ); QVERIFY( def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional source " ) + v1->id() ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSource * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); //optional with direct layer default def.reset( new QgsProcessingParameterFeatureSource( "optional", QString(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry, QVariant::fromValue( v1 ), true ) ); QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); } void TestQgsProcessing::parameterFeatureSink() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterFeatureSink > def( new QgsProcessingParameterFeatureSink( "non_optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); // should be OK with or without context - it's an output layer! QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "shp" ) ); QCOMPARE( def->generateTemporaryDestination(), QStringLiteral( "memory:" ) ); def->setSupportsNonFileBasedOutputs( false ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".shp" ) ) ); QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFeatureSink fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.dataType(), def->dataType() ); QCOMPARE( fromMap.supportsNonFileBasedOutputs(), def->supportsNonFileBasedOutputs() ); def.reset( dynamic_cast< QgsProcessingParameterFeatureSink *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterFeatureSink *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=sink" ) ); std::unique_ptr< QgsProcessingParameterFeatureSink > fromCode( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorPoint ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=sink point" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorLine ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=sink line" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorPolygon ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=sink polygon" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVector ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=sink table" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); // optional def.reset( new QgsProcessingParameterFeatureSink( "optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); def->setCreateByDefault( false ); QVERIFY( !def->createByDefault() ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional sink" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFeatureSink * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->dataType(), def->dataType() ); QVERIFY( !def->createByDefault() ); // test hasGeometry QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeMapLayer ).hasGeometry() ); QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorAnyGeometry ).hasGeometry() ); QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorPoint ).hasGeometry() ); QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorLine ).hasGeometry() ); QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVectorPolygon ).hasGeometry() ); QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeRaster ).hasGeometry() ); QVERIFY( !QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeFile ).hasGeometry() ); QVERIFY( QgsProcessingParameterFeatureSink( "test", QString(), QgsProcessing::TypeVector ).hasGeometry() ); } void TestQgsProcessing::parameterVectorOut() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterVectorDestination > def( new QgsProcessingParameterVectorDestination( "non_optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); // should be OK with or without context - it's an output layer! QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "shp" ) ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".shp" ) ) ); QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterVectorDestination fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.dataType(), def->dataType() ); def.reset( dynamic_cast< QgsProcessingParameterVectorDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterVectorDestination *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination" ) ); std::unique_ptr< QgsProcessingParameterVectorDestination > fromCode( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorPoint ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination point" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorLine ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination line" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); def->setDataType( QgsProcessing::TypeVectorPolygon ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=vectorDestination polygon" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->dataType(), def->dataType() ); // optional def.reset( new QgsProcessingParameterVectorDestination( "optional", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional vectorDestination" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterVectorDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->dataType(), def->dataType() ); // test hasGeometry QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeMapLayer ).hasGeometry() ); QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorAnyGeometry ).hasGeometry() ); QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorPoint ).hasGeometry() ); QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorLine ).hasGeometry() ); QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVectorPolygon ).hasGeometry() ); QVERIFY( !QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeRaster ).hasGeometry() ); QVERIFY( !QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeFile ).hasGeometry() ); QVERIFY( QgsProcessingParameterVectorDestination( "test", QString(), QgsProcessing::TypeVector ).hasGeometry() ); } void TestQgsProcessing::parameterRasterOut() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterRasterDestination > def( new QgsProcessingParameterRasterDestination( "non_optional", QString(), QVariant(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); // should be OK with or without context - it's an output layer! QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif", &context ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "tif" ) ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".tif" ) ) ); QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) ); QVariantMap params; params.insert( "non_optional", "test.tif" ); QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) ); params.insert( "non_optional", QgsProcessingOutputLayerDefinition( "test.tif" ) ); QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterRasterDestination fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.supportsNonFileBasedOutputs(), def->supportsNonFileBasedOutputs() ); def.reset( dynamic_cast< QgsProcessingParameterRasterDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterRasterDestination *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=rasterDestination" ) ); std::unique_ptr< QgsProcessingParameterRasterDestination > fromCode( dynamic_cast< QgsProcessingParameterRasterDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // optional def.reset( new QgsProcessingParameterRasterDestination( "optional", QString(), QString( "default.tif" ), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.tif" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "default.tif" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional rasterDestination default.tif" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterRasterDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // test layers to load on completion def.reset( new QgsProcessingParameterRasterDestination( "x", QStringLiteral( "desc" ), QStringLiteral( "default.tif" ), true ) ); QgsProcessingOutputLayerDefinition fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.tif" ) ); fs.destinationProject = &p; params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) ); QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context ), QStringLiteral( "test.tif" ) ); // make sure layer was automatically added to list to load on completion QCOMPARE( context.layersToLoadOnCompletion().size(), 1 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.tif" ) ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "desc" ) ); // with name overloading QgsProcessingContext context2; fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.tif" ) ); fs.destinationProject = &p; fs.destinationName = QStringLiteral( "my_dest" ); params.insert( QStringLiteral( "x" ), QVariant::fromValue( fs ) ); QCOMPARE( QgsProcessingParameters::parameterAsOutputLayer( def.get(), params, context2 ), QStringLiteral( "test.tif" ) ); QCOMPARE( context2.layersToLoadOnCompletion().size(), 1 ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), QStringLiteral( "test.tif" ) ); QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "my_dest" ) ); QCOMPARE( context2.layersToLoadOnCompletion().values().at( 0 ).outputName, QStringLiteral( "x" ) ); } void TestQgsProcessing::parameterFileOut() { // setup a context QgsProject p; p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterFileDestination > def( new QgsProcessingParameterFileDestination( "non_optional", QString(), QStringLiteral( "BMP files (*.bmp)" ), QVariant(), false ) ); QCOMPARE( def->fileFilter(), QStringLiteral( "BMP files (*.bmp)" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "bmp" ) ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".bmp" ) ) ); QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) ); def->setFileFilter( QStringLiteral( "PCX files (*.pcx)" ) ); QCOMPARE( def->fileFilter(), QStringLiteral( "PCX files (*.pcx)" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) ); def->setFileFilter( QStringLiteral( "PCX files (*.pcx *.picx)" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) ); def->setFileFilter( QStringLiteral( "PCX files (*.pcx *.picx);;BMP files (*.bmp)" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "pcx" ) ); def->setFileFilter( QString() ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "file" ) ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".file" ) ) ); QVERIFY( def->generateTemporaryDestination().startsWith( QgsProcessingUtils::tempFolder() ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); // should be OK with or without context - it's an output file! QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt", &context ) ); QVariantMap params; params.insert( "non_optional", "test.txt" ); QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "test.txt" ) ); params.insert( "non_optional", QgsProcessingOutputLayerDefinition( "test.txt" ) ); QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "test.txt" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFileDestination fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.fileFilter(), def->fileFilter() ); QCOMPARE( fromMap.supportsNonFileBasedOutputs(), def->supportsNonFileBasedOutputs() ); def.reset( dynamic_cast< QgsProcessingParameterFileDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterFileDestination *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=fileDestination" ) ); std::unique_ptr< QgsProcessingParameterFileDestination > fromCode( dynamic_cast< QgsProcessingParameterFileDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // optional def.reset( new QgsProcessingParameterFileDestination( "optional", QString(), QString(), QString( "default.txt" ), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.txt" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "default.txt" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional fileDestination default.txt" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFileDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterFolderOut() { // setup a context QgsProject p; QgsProcessingContext context; context.setProject( &p ); // not optional! std::unique_ptr< QgsProcessingParameterFolderDestination > def( new QgsProcessingParameterFolderDestination( "non_optional", QString(), QVariant(), false ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "asdasd" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // should be OK with or without context - it's an output folder! QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/", &context ) ); QVariantMap params; params.insert( "non_optional", "c:/mine" ); QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "c:/mine" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFolderDestination fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); QCOMPARE( fromMap.flags(), def->flags() ); QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); QCOMPARE( fromMap.supportsNonFileBasedOutputs(), def->supportsNonFileBasedOutputs() ); def.reset( dynamic_cast< QgsProcessingParameterFolderDestination *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); QVERIFY( dynamic_cast< QgsProcessingParameterFolderDestination *>( def.get() ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=folderDestination" ) ); std::unique_ptr< QgsProcessingParameterFolderDestination > fromCode( dynamic_cast< QgsProcessingParameterFolderDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); // optional def.reset( new QgsProcessingParameterFolderDestination( "optional", QString(), QString( "c:/junk" ), true ) ); QVERIFY( !def->checkValueIsAcceptable( false ) ); QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); QCOMPARE( QgsProcessingParameters::parameterAsFileOutput( def.get(), params, context ), QStringLiteral( "c:/junk" ) ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional folderDestination c:/junk" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterFolderDestination * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } void TestQgsProcessing::parameterBand() { QgsProcessingContext context; // not optional! std::unique_ptr< QgsProcessingParameterBand > def( new QgsProcessingParameterBand( "non_optional", QString(), QVariant(), QString(), false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); // string representing a band QVariantMap params; params.insert( "non_optional", "1" ); int band = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( band, 1 ); QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "5" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=band" ) ); std::unique_ptr< QgsProcessingParameterBand > fromCode( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); QVERIFY( def->dependsOnOtherParameters().isEmpty() ); def->setParentLayerParameterName( "my_parent" ); QCOMPARE( def->dependsOnOtherParameters(), QStringList() << QStringLiteral( "my_parent" ) ); code = def->asScriptCode(); fromCode.reset( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); // optional def.reset( new QgsProcessingParameterBand( "optional", QString(), 1, QString(), true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "1" ) ); QVERIFY( def->checkValueIsAcceptable( "" ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); params.insert( "optional", QVariant() ); band = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( band, 1 ); // optional, no default def.reset( new QgsProcessingParameterBand( "optional", QString(), QVariant(), QString(), true ) ); params.insert( "optional", QVariant() ); band = QgsProcessingParameters::parameterAsInt( def.get(), params, context ); QCOMPARE( band, 0 ); code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##optional=optional band" ) ); fromCode.reset( dynamic_cast< QgsProcessingParameterBand * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); QVERIFY( fromCode.get() ); QCOMPARE( fromCode->name(), def->name() ); QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); QCOMPARE( fromCode->flags(), def->flags() ); QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); } void TestQgsProcessing::checkParamValues() { DummyAlgorithm a( "asd" ); a.checkParameterVals(); } void TestQgsProcessing::combineLayerExtent() { QgsRectangle ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() ); QVERIFY( ext.isNull() ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString raster1 = testDataDir + "tenbytenraster.asc"; QString raster2 = testDataDir + "landsat.tif"; QFileInfo fi1( raster1 ); QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); QFileInfo fi2( raster2 ); QgsRasterLayer *r2 = new QgsRasterLayer( fi2.filePath(), "R2" ); ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1 ); QGSCOMPARENEAR( ext.xMinimum(), 1535375.000000, 10 ); QGSCOMPARENEAR( ext.xMaximum(), 1535475, 10 ); QGSCOMPARENEAR( ext.yMinimum(), 5083255, 10 ); QGSCOMPARENEAR( ext.yMaximum(), 5083355, 10 ); ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1 << r2 ); QGSCOMPARENEAR( ext.xMinimum(), 781662, 10 ); QGSCOMPARENEAR( ext.xMaximum(), 1535475, 10 ); QGSCOMPARENEAR( ext.yMinimum(), 3339523, 10 ); QGSCOMPARENEAR( ext.yMaximum(), 5083355, 10 ); // with reprojection ext = QgsProcessingUtils::combineLayerExtents( QList< QgsMapLayer *>() << r1 << r2, QgsCoordinateReferenceSystem::fromEpsgId( 3785 ) ); QGSCOMPARENEAR( ext.xMinimum(), 1995320, 10 ); QGSCOMPARENEAR( ext.xMaximum(), 2008833, 10 ); QGSCOMPARENEAR( ext.yMinimum(), 3523084, 10 ); QGSCOMPARENEAR( ext.yMaximum(), 3536664, 10 ); } void TestQgsProcessing::processingFeatureSource() { QString sourceString = QStringLiteral( "test.shp" ); QgsProcessingFeatureSourceDefinition fs( sourceString, true ); QCOMPARE( fs.source.staticValue().toString(), sourceString ); QVERIFY( fs.selectedFeaturesOnly ); // test storing QgsProcessingFeatureSource in variant and retrieving QVariant fsInVariant = QVariant::fromValue( fs ); QVERIFY( fsInVariant.isValid() ); QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast( fsInVariant ); QCOMPARE( fromVar.source.staticValue().toString(), sourceString ); QVERIFY( fromVar.selectedFeaturesOnly ); // test evaluating parameter as source QgsVectorLayer *layer = new QgsVectorLayer( "Point", "v1", "memory" ); QgsFeature f( 10001 ); f.setGeometry( QgsGeometry( new QgsPoint( 1, 2 ) ) ); layer->dataProvider()->addFeatures( QgsFeatureList() << f ); QgsProject p; p.addMapLayer( layer ); QgsProcessingContext context; context.setProject( &p ); // first using static string definition std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) ); QVariantMap params; params.insert( QStringLiteral( "layer" ), QgsProcessingFeatureSourceDefinition( layer->id(), false ) ); std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); // can't directly match it to layer, so instead just get the feature and test that it matches what we expect QgsFeature f2; QVERIFY( source.get() ); QVERIFY( source->getFeatures().nextFeature( f2 ) ); QCOMPARE( f2.geometry(), f.geometry() ); // direct map layer params.insert( QStringLiteral( "layer" ), QVariant::fromValue( layer ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); // can't directly match it to layer, so instead just get the feature and test that it matches what we expect QVERIFY( source.get() ); QVERIFY( source->getFeatures().nextFeature( f2 ) ); QCOMPARE( f2.geometry(), f.geometry() ); // next using property based definition params.insert( QStringLiteral( "layer" ), QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('%1' + ' ')" ).arg( layer->id() ) ), false ) ); source.reset( QgsProcessingParameters::parameterAsSource( def.get(), params, context ) ); // can't directly match it to layer, so instead just get the feature and test that it matches what we expect QVERIFY( source.get() ); QVERIFY( source->getFeatures().nextFeature( f2 ) ); QCOMPARE( f2.geometry(), f.geometry() ); } void TestQgsProcessing::processingFeatureSink() { QString sinkString( QStringLiteral( "test.shp" ) ); QgsProject p; QgsProcessingOutputLayerDefinition fs( sinkString, &p ); QCOMPARE( fs.sink.staticValue().toString(), sinkString ); QCOMPARE( fs.destinationProject, &p ); // test storing QgsProcessingFeatureSink in variant and retrieving QVariant fsInVariant = QVariant::fromValue( fs ); QVERIFY( fsInVariant.isValid() ); QgsProcessingOutputLayerDefinition fromVar = qvariant_cast( fsInVariant ); QCOMPARE( fromVar.sink.staticValue().toString(), sinkString ); QCOMPARE( fromVar.destinationProject, &p ); // test evaluating parameter as sink QgsProcessingContext context; context.setProject( &p ); // first using static string definition std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) ); QVariantMap params; params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( "memory:test", nullptr ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) ); QVERIFY( sink.get() ); QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) ); QVERIFY( layer ); QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) ); // next using property based definition params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('memory' + ':test2')" ) ), nullptr ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) ); QVERIFY( sink.get() ); QgsVectorLayer *layer2 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) ); QVERIFY( layer2 ); QCOMPARE( layer2->crs().authid(), QStringLiteral( "EPSG:3113" ) ); // non optional sink def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QVariant(), false ) ); QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) ); QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) ); QVERIFY( !def->checkValueIsAcceptable( QString() ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) ); QVERIFY( sink.get() ); // optional sink def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QVariant(), true ) ); QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) ); QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) ); QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) ); QVERIFY( def->checkValueIsAcceptable( QString() ) ); QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) ); QVERIFY( sink.get() ); // optional sink, not set - should be no sink params.insert( QStringLiteral( "layer" ), QVariant() ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) ); QVERIFY( !sink.get() ); //.... unless there's a default set def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessing::TypeMapLayer, QStringLiteral( "memory:defaultlayer" ), true ) ); params.insert( QStringLiteral( "layer" ), QVariant() ); sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) ); QVERIFY( sink.get() ); } void TestQgsProcessing::algorithmScope() { QgsProcessingContext pc; // no alg std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::processingAlgorithmScope( nullptr, QVariantMap(), pc ) ); QVERIFY( scope.get() ); // with alg std::unique_ptr< QgsProcessingAlgorithm > alg( new DummyAlgorithm( "alg1" ) ); QVariantMap params; params.insert( QStringLiteral( "a_param" ), 5 ); scope.reset( QgsExpressionContextUtils::processingAlgorithmScope( alg.get(), params, pc ) ); QVERIFY( scope.get() ); QCOMPARE( scope->variable( QStringLiteral( "algorithm_id" ) ).toString(), alg->id() ); QgsExpressionContext context; context.appendScope( scope.release() ); QgsExpression exp( "parameter('bad')" ); QVERIFY( !exp.evaluate( &context ).isValid() ); QgsExpression exp2( "parameter('a_param')" ); QCOMPARE( exp2.evaluate( &context ).toInt(), 5 ); } void TestQgsProcessing::validateInputCrs() { DummyAlgorithm alg( "test" ); alg.runValidateInputCrsChecks(); } void TestQgsProcessing::generateIteratingDestination() { QgsProcessingContext context; QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "memory:x", 1, context ).toString(), QStringLiteral( "memory:x_1" ) ); QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "memory:x", 2, context ).toString(), QStringLiteral( "memory:x_2" ) ); QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "ape.shp", 1, context ).toString(), QStringLiteral( "ape_1.shp" ) ); QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "ape.shp", 2, context ).toString(), QStringLiteral( "ape_2.shp" ) ); QCOMPARE( QgsProcessingUtils::generateIteratingDestination( "/home/bif.o/ape.shp", 2, context ).toString(), QStringLiteral( "/home/bif.o/ape_2.shp" ) ); QgsProject p; QgsProcessingOutputLayerDefinition def; def.sink = QgsProperty::fromValue( "ape.shp" ); def.destinationProject = &p; QVariant res = QgsProcessingUtils::generateIteratingDestination( def, 2, context ); QVERIFY( res.canConvert() ); QgsProcessingOutputLayerDefinition fromVar = qvariant_cast( res ); QCOMPARE( fromVar.sink.staticValue().toString(), QStringLiteral( "ape_2.shp" ) ); QCOMPARE( fromVar.destinationProject, &p ); def.sink = QgsProperty::fromExpression( "'ape' || '.shp'" ); res = QgsProcessingUtils::generateIteratingDestination( def, 2, context ); QVERIFY( res.canConvert() ); fromVar = qvariant_cast( res ); QCOMPARE( fromVar.sink.staticValue().toString(), QStringLiteral( "ape_2.shp" ) ); QCOMPARE( fromVar.destinationProject, &p ); } void TestQgsProcessing::asPythonCommand() { DummyAlgorithm alg( "test" ); alg.runAsPythonCommandChecks(); } void TestQgsProcessing::modelerAlgorithm() { //static value source QgsProcessingModelChildParameterSource svSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 ); QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); QCOMPARE( svSource.staticValue().toInt(), 5 ); svSource.setStaticValue( 7 ); QCOMPARE( svSource.staticValue().toInt(), 7 ); svSource = QgsProcessingModelChildParameterSource::fromModelParameter( "a" ); // check that calling setStaticValue flips source to StaticValue QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::ModelParameter ); svSource.setStaticValue( 7 ); QCOMPARE( svSource.staticValue().toInt(), 7 ); QCOMPARE( svSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); // model parameter source QgsProcessingModelChildParameterSource mpSource = QgsProcessingModelChildParameterSource::fromModelParameter( "a" ); QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( mpSource.parameterName(), QStringLiteral( "a" ) ); mpSource.setParameterName( "b" ); QCOMPARE( mpSource.parameterName(), QStringLiteral( "b" ) ); mpSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 ); // check that calling setParameterName flips source to ModelParameter QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); mpSource.setParameterName( "c" ); QCOMPARE( mpSource.parameterName(), QStringLiteral( "c" ) ); QCOMPARE( mpSource.source(), QgsProcessingModelChildParameterSource::ModelParameter ); // child alg output source QgsProcessingModelChildParameterSource oSource = QgsProcessingModelChildParameterSource::fromChildOutput( "a", "b" ); QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( oSource.outputChildId(), QStringLiteral( "a" ) ); QCOMPARE( oSource.outputName(), QStringLiteral( "b" ) ); oSource.setOutputChildId( "c" ); QCOMPARE( oSource.outputChildId(), QStringLiteral( "c" ) ); oSource.setOutputName( "d" ); QCOMPARE( oSource.outputName(), QStringLiteral( "d" ) ); oSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 ); // check that calling setOutputChildId flips source to ChildOutput QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); oSource.setOutputChildId( "c" ); QCOMPARE( oSource.outputChildId(), QStringLiteral( "c" ) ); QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput ); oSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 ); // check that calling setOutputName flips source to ChildOutput QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); oSource.setOutputName( "d" ); QCOMPARE( oSource.outputName(), QStringLiteral( "d" ) ); QCOMPARE( oSource.source(), QgsProcessingModelChildParameterSource::ChildOutput ); // expression source QgsProcessingModelChildParameterSource expSource = QgsProcessingModelChildParameterSource::fromExpression( "1+2" ); QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::Expression ); QCOMPARE( expSource.expression(), QStringLiteral( "1+2" ) ); expSource.setExpression( "1+3" ); QCOMPARE( expSource.expression(), QStringLiteral( "1+3" ) ); expSource = QgsProcessingModelChildParameterSource::fromStaticValue( 5 ); // check that calling setExpression flips source to Expression QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::StaticValue ); expSource.setExpression( "1+4" ); QCOMPARE( expSource.expression(), QStringLiteral( "1+4" ) ); QCOMPARE( expSource.source(), QgsProcessingModelChildParameterSource::Expression ); // source equality operator QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) == QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) != QgsProcessingModelChildParameterSource::fromStaticValue( 7 ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) != QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) == QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) != QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "b" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "a" ) ) != QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) == QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) != QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg2" ), QStringLiteral( "out" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) != QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out2" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) == QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) != QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "b" ) ) ); QVERIFY( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "a" ) ) != QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "b" ) ) ); QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) ); QCOMPARE( child.algorithmId(), QStringLiteral( "some_id" ) ); QVERIFY( !child.algorithm() ); child.setAlgorithmId( QStringLiteral( "native:centroids" ) ); QVERIFY( child.algorithm() ); QCOMPARE( child.algorithm()->id(), QStringLiteral( "native:centroids" ) ); QVariantMap myConfig; myConfig.insert( QStringLiteral( "some_key" ), 11 ); child.setConfiguration( myConfig ); QCOMPARE( child.configuration(), myConfig ); child.setDescription( QStringLiteral( "desc" ) ); QCOMPARE( child.description(), QStringLiteral( "desc" ) ); QVERIFY( child.isActive() ); child.setActive( false ); QVERIFY( !child.isActive() ); child.setPosition( QPointF( 1, 2 ) ); QCOMPARE( child.position(), QPointF( 1, 2 ) ); QVERIFY( child.parametersCollapsed() ); child.setParametersCollapsed( false ); QVERIFY( !child.parametersCollapsed() ); QVERIFY( child.outputsCollapsed() ); child.setOutputsCollapsed( false ); QVERIFY( !child.outputsCollapsed() ); child.setChildId( QStringLiteral( "my_id" ) ); QCOMPARE( child.childId(), QStringLiteral( "my_id" ) ); child.setDependencies( QStringList() << "a" << "b" ); QCOMPARE( child.dependencies(), QStringList() << "a" << "b" ); QMap< QString, QgsProcessingModelChildParameterSources > sources; sources.insert( QStringLiteral( "a" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) ); child.setParameterSources( sources ); QCOMPARE( child.parameterSources().value( QStringLiteral( "a" ) ).at( 0 ).staticValue().toInt(), 5 ); child.addParameterSources( QStringLiteral( "b" ), QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 7 ) << QgsProcessingModelChildParameterSource::fromStaticValue( 9 ) ); QCOMPARE( child.parameterSources().value( QStringLiteral( "a" ) ).at( 0 ).staticValue().toInt(), 5 ); QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).count(), 2 ); QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 0 ).staticValue().toInt(), 7 ); QCOMPARE( child.parameterSources().value( QStringLiteral( "b" ) ).at( 1 ).staticValue().toInt(), 9 ); QgsProcessingModelOutput testModelOut; testModelOut.setChildId( QStringLiteral( "my_id" ) ); QCOMPARE( testModelOut.childId(), QStringLiteral( "my_id" ) ); testModelOut.setChildOutputName( QStringLiteral( "my_output" ) ); QCOMPARE( testModelOut.childOutputName(), QStringLiteral( "my_output" ) ); QMap outputs; QgsProcessingModelOutput out1; out1.setDescription( QStringLiteral( "my output" ) ); outputs.insert( QStringLiteral( "a" ), out1 ); child.setModelOutputs( outputs ); QCOMPARE( child.modelOutputs().count(), 1 ); QCOMPARE( child.modelOutputs().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "my output" ) ); QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output" ) ); child.modelOutput( "a" ).setDescription( QStringLiteral( "my output 2" ) ); QCOMPARE( child.modelOutput( "a" ).description(), QStringLiteral( "my output 2" ) ); // no existent child.modelOutput( "b" ).setDescription( QStringLiteral( "my output 3" ) ); QCOMPARE( child.modelOutput( "b" ).description(), QStringLiteral( "my output 3" ) ); QCOMPARE( child.modelOutputs().count(), 2 ); // model algorithm tests QgsProcessingModelAlgorithm alg( "test", "testGroup" ); QCOMPARE( alg.name(), QStringLiteral( "test" ) ); QCOMPARE( alg.displayName(), QStringLiteral( "test" ) ); QCOMPARE( alg.group(), QStringLiteral( "testGroup" ) ); alg.setName( QStringLiteral( "test2" ) ); QCOMPARE( alg.name(), QStringLiteral( "test2" ) ); QCOMPARE( alg.displayName(), QStringLiteral( "test2" ) ); alg.setGroup( QStringLiteral( "group2" ) ); QCOMPARE( alg.group(), QStringLiteral( "group2" ) ); // child algorithms QMap algs; QgsProcessingModelChildAlgorithm a1; a1.setDescription( QStringLiteral( "alg1" ) ); QgsProcessingModelChildAlgorithm a2; a2.setDescription( QStringLiteral( "alg2" ) ); algs.insert( QStringLiteral( "a" ), a1 ); algs.insert( QStringLiteral( "b" ), a2 ); alg.setChildAlgorithms( algs ); QCOMPARE( alg.childAlgorithms().count(), 2 ); QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "alg1" ) ); QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "b" ) ).description(), QStringLiteral( "alg2" ) ); QgsProcessingModelChildAlgorithm a3; a3.setChildId( QStringLiteral( "c" ) ); a3.setDescription( QStringLiteral( "alg3" ) ); QCOMPARE( alg.addChildAlgorithm( a3 ), QStringLiteral( "c" ) ); QCOMPARE( alg.childAlgorithms().count(), 3 ); QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "alg1" ) ); QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "b" ) ).description(), QStringLiteral( "alg2" ) ); QCOMPARE( alg.childAlgorithms().value( QStringLiteral( "c" ) ).description(), QStringLiteral( "alg3" ) ); QCOMPARE( alg.childAlgorithm( "a" ).description(), QStringLiteral( "alg1" ) ); QCOMPARE( alg.childAlgorithm( "b" ).description(), QStringLiteral( "alg2" ) ); QCOMPARE( alg.childAlgorithm( "c" ).description(), QStringLiteral( "alg3" ) ); // initially non-existent QVERIFY( alg.childAlgorithm( "d" ).description().isEmpty() ); alg.childAlgorithm( "d" ).setDescription( QStringLiteral( "alg4" ) ); QCOMPARE( alg.childAlgorithm( "d" ).description(), QStringLiteral( "alg4" ) ); // overwrite existing QgsProcessingModelChildAlgorithm a4a; a4a.setChildId( "d" ); a4a.setDescription( "new" ); alg.setChildAlgorithm( a4a ); QCOMPARE( alg.childAlgorithm( "d" ).description(), QStringLiteral( "new" ) ); // generating child ids QgsProcessingModelChildAlgorithm c1; c1.setAlgorithmId( QStringLiteral( "buffer" ) ); c1.generateChildId( alg ); QCOMPARE( c1.childId(), QStringLiteral( "buffer_1" ) ); QCOMPARE( alg.addChildAlgorithm( c1 ), QStringLiteral( "buffer_1" ) ); QgsProcessingModelChildAlgorithm c2; c2.setAlgorithmId( QStringLiteral( "buffer" ) ); c2.generateChildId( alg ); QCOMPARE( c2.childId(), QStringLiteral( "buffer_2" ) ); QCOMPARE( alg.addChildAlgorithm( c2 ), QStringLiteral( "buffer_2" ) ); QgsProcessingModelChildAlgorithm c3; c3.setAlgorithmId( QStringLiteral( "centroid" ) ); c3.generateChildId( alg ); QCOMPARE( c3.childId(), QStringLiteral( "centroid_1" ) ); QCOMPARE( alg.addChildAlgorithm( c3 ), QStringLiteral( "centroid_1" ) ); QgsProcessingModelChildAlgorithm c4; c4.setAlgorithmId( QStringLiteral( "centroid" ) ); c4.setChildId( QStringLiteral( "centroid_1" ) );// dupe id QCOMPARE( alg.addChildAlgorithm( c4 ), QStringLiteral( "centroid_2" ) ); QCOMPARE( alg.childAlgorithm( QStringLiteral( "centroid_2" ) ).childId(), QStringLiteral( "centroid_2" ) ); // parameter components QMap pComponents; QgsProcessingModelParameter pc1; pc1.setParameterName( QStringLiteral( "my_param" ) ); QCOMPARE( pc1.parameterName(), QStringLiteral( "my_param" ) ); pComponents.insert( QStringLiteral( "my_param" ), pc1 ); alg.setParameterComponents( pComponents ); QCOMPARE( alg.parameterComponents().count(), 1 ); QCOMPARE( alg.parameterComponents().value( QStringLiteral( "my_param" ) ).parameterName(), QStringLiteral( "my_param" ) ); QCOMPARE( alg.parameterComponent( "my_param" ).parameterName(), QStringLiteral( "my_param" ) ); alg.parameterComponent( "my_param" ).setDescription( QStringLiteral( "my param 2" ) ); QCOMPARE( alg.parameterComponent( "my_param" ).description(), QStringLiteral( "my param 2" ) ); // no existent alg.parameterComponent( "b" ).setDescription( QStringLiteral( "my param 3" ) ); QCOMPARE( alg.parameterComponent( "b" ).description(), QStringLiteral( "my param 3" ) ); QCOMPARE( alg.parameterComponent( "b" ).parameterName(), QStringLiteral( "b" ) ); QCOMPARE( alg.parameterComponents().count(), 2 ); // parameter definitions QgsProcessingModelAlgorithm alg1a( "test", "testGroup" ); QgsProcessingModelParameter bool1; bool1.setPosition( QPointF( 1, 2 ) ); alg1a.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool1 ); QCOMPARE( alg1a.parameterDefinitions().count(), 1 ); QCOMPARE( alg1a.parameterDefinition( "p1" )->type(), QStringLiteral( "boolean" ) ); QCOMPARE( alg1a.parameterComponent( "p1" ).position().x(), 1.0 ); QCOMPARE( alg1a.parameterComponent( "p1" ).position().y(), 2.0 ); alg1a.updateModelParameter( new QgsProcessingParameterBoolean( "p1", "descx" ) ); QCOMPARE( alg1a.parameterDefinition( "p1" )->description(), QStringLiteral( "descx" ) ); alg1a.removeModelParameter( "bad" ); QCOMPARE( alg1a.parameterDefinitions().count(), 1 ); alg1a.removeModelParameter( "p1" ); QVERIFY( alg1a.parameterDefinitions().isEmpty() ); QVERIFY( alg1a.parameterComponents().isEmpty() ); // test canExecute QgsProcessingModelAlgorithm alg2( "test", "testGroup" ); QVERIFY( alg2.canExecute() ); QgsProcessingModelChildAlgorithm c5; c5.setAlgorithmId( "native:centroids" ); alg2.addChildAlgorithm( c5 ); QVERIFY( alg2.canExecute() ); // non-existing alg QgsProcessingModelChildAlgorithm c6; c6.setAlgorithmId( "i'm not an alg" ); alg2.addChildAlgorithm( c6 ); QVERIFY( !alg2.canExecute() ); // dependencies QgsProcessingModelAlgorithm alg3( "test", "testGroup" ); QVERIFY( alg3.dependentChildAlgorithms( "notvalid" ).isEmpty() ); QVERIFY( alg3.dependsOnChildAlgorithms( "notvalid" ).isEmpty() ); // add a child QgsProcessingModelChildAlgorithm c7; c7.setChildId( "c7" ); alg3.addChildAlgorithm( c7 ); QVERIFY( alg3.dependentChildAlgorithms( "c7" ).isEmpty() ); QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() ); // direct dependency QgsProcessingModelChildAlgorithm c8; c8.setChildId( "c8" ); c8.setDependencies( QStringList() << "c7" ); alg3.addChildAlgorithm( c8 ); QVERIFY( alg3.dependentChildAlgorithms( "c8" ).isEmpty() ); QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() ); QCOMPARE( alg3.dependentChildAlgorithms( "c7" ).count(), 1 ); QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c8" ) ); QCOMPARE( alg3.dependsOnChildAlgorithms( "c8" ).count(), 1 ); QVERIFY( alg3.dependsOnChildAlgorithms( "c8" ).contains( "c7" ) ); // dependency via parameter source QgsProcessingModelChildAlgorithm c9; c9.setChildId( "c9" ); c9.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "c8", "x" ) ); alg3.addChildAlgorithm( c9 ); QVERIFY( alg3.dependentChildAlgorithms( "c9" ).isEmpty() ); QCOMPARE( alg3.dependentChildAlgorithms( "c8" ).count(), 1 ); QVERIFY( alg3.dependentChildAlgorithms( "c8" ).contains( "c9" ) ); QCOMPARE( alg3.dependentChildAlgorithms( "c7" ).count(), 2 ); QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c8" ) ); QVERIFY( alg3.dependentChildAlgorithms( "c7" ).contains( "c9" ) ); QVERIFY( alg3.dependsOnChildAlgorithms( "c7" ).isEmpty() ); QCOMPARE( alg3.dependsOnChildAlgorithms( "c8" ).count(), 1 ); QVERIFY( alg3.dependsOnChildAlgorithms( "c8" ).contains( "c7" ) ); QCOMPARE( alg3.dependsOnChildAlgorithms( "c9" ).count(), 2 ); QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c7" ) ); QVERIFY( alg3.dependsOnChildAlgorithms( "c9" ).contains( "c8" ) ); // (de)activate child algorithm alg3.deactivateChildAlgorithm( "c9" ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( alg3.childAlgorithm( "c9" ).isActive() ); alg3.deactivateChildAlgorithm( "c8" ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( !alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( alg3.activateChildAlgorithm( "c8" ) ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c8" ).isActive() ); alg3.deactivateChildAlgorithm( "c7" ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c7" ).isActive() ); QVERIFY( !alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( !alg3.activateChildAlgorithm( "c8" ) ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c7" ).isActive() ); QVERIFY( !alg3.activateChildAlgorithm( "c8" ) ); QVERIFY( alg3.activateChildAlgorithm( "c7" ) ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( !alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c7" ).isActive() ); QVERIFY( !alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( alg3.activateChildAlgorithm( "c8" ) ); QVERIFY( !alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c7" ).isActive() ); QVERIFY( alg3.activateChildAlgorithm( "c9" ) ); QVERIFY( alg3.childAlgorithm( "c9" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c8" ).isActive() ); QVERIFY( alg3.childAlgorithm( "c7" ).isActive() ); //remove child algorithm QVERIFY( !alg3.removeChildAlgorithm( "c7" ) ); QVERIFY( !alg3.removeChildAlgorithm( "c8" ) ); QVERIFY( alg3.removeChildAlgorithm( "c9" ) ); QCOMPARE( alg3.childAlgorithms().count(), 2 ); QVERIFY( alg3.childAlgorithms().contains( "c7" ) ); QVERIFY( alg3.childAlgorithms().contains( "c8" ) ); QVERIFY( !alg3.removeChildAlgorithm( "c7" ) ); QVERIFY( alg3.removeChildAlgorithm( "c8" ) ); QCOMPARE( alg3.childAlgorithms().count(), 1 ); QVERIFY( alg3.childAlgorithms().contains( "c7" ) ); QVERIFY( alg3.removeChildAlgorithm( "c7" ) ); QVERIFY( alg3.childAlgorithms().isEmpty() ); // parameter dependencies QgsProcessingModelAlgorithm alg4( "test", "testGroup" ); QVERIFY( !alg4.childAlgorithmsDependOnParameter( "not a param" ) ); QgsProcessingModelChildAlgorithm c10; c10.setChildId( "c10" ); alg4.addChildAlgorithm( c10 ); QVERIFY( !alg4.childAlgorithmsDependOnParameter( "not a param" ) ); QgsProcessingModelParameter bool2; alg4.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool2 ); QVERIFY( !alg4.childAlgorithmsDependOnParameter( "p1" ) ); c10.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p2" ) ); alg4.setChildAlgorithm( c10 ); QVERIFY( !alg4.childAlgorithmsDependOnParameter( "p1" ) ); c10.addParameterSources( "y", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p1" ) ); alg4.setChildAlgorithm( c10 ); QVERIFY( alg4.childAlgorithmsDependOnParameter( "p1" ) ); QgsProcessingModelParameter vlP; alg4.addModelParameter( new QgsProcessingParameterVectorLayer( "layer" ), vlP ); QgsProcessingModelParameter field; alg4.addModelParameter( new QgsProcessingParameterField( "field", QString(), QVariant(), QStringLiteral( "layer" ) ), field ); QVERIFY( !alg4.otherParametersDependOnParameter( "p1" ) ); QVERIFY( !alg4.otherParametersDependOnParameter( "field" ) ); QVERIFY( alg4.otherParametersDependOnParameter( "layer" ) ); // to/from XML QgsProcessingModelAlgorithm alg5( "test", "testGroup" ); alg5.helpContent().insert( "author", "me" ); alg5.helpContent().insert( "usage", "run" ); QgsProcessingModelChildAlgorithm alg5c1; alg5c1.setChildId( "cx1" ); alg5c1.setAlgorithmId( "buffer" ); alg5c1.setConfiguration( myConfig ); alg5c1.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "p1" ) ); alg5c1.addParameterSources( "y", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx2", "out3" ) ); alg5c1.addParameterSources( "z", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 5 ) ); alg5c1.addParameterSources( "a", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromExpression( "2*2" ) ); alg5c1.addParameterSources( "zm", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 6 ) << QgsProcessingModelChildParameterSource::fromModelParameter( "p2" ) << QgsProcessingModelChildParameterSource::fromChildOutput( "cx2", "out4" ) << QgsProcessingModelChildParameterSource::fromExpression( "1+2" ) ); alg5c1.setActive( true ); alg5c1.setOutputsCollapsed( true ); alg5c1.setParametersCollapsed( true ); alg5c1.setDescription( "child 1" ); alg5c1.setPosition( QPointF( 1, 2 ) ); QMap alg5c1outputs; QgsProcessingModelOutput alg5c1out1; alg5c1out1.setDescription( QStringLiteral( "my output" ) ); alg5c1out1.setPosition( QPointF( 3, 4 ) ); alg5c1outputs.insert( QStringLiteral( "a" ), alg5c1out1 ); alg5c1.setModelOutputs( alg5c1outputs ); alg5.addChildAlgorithm( alg5c1 ); QgsProcessingModelChildAlgorithm alg5c2; alg5c2.setChildId( "cx2" ); alg5c2.setActive( false ); alg5c2.setOutputsCollapsed( false ); alg5c2.setParametersCollapsed( false ); alg5c2.setDependencies( QStringList() << "a" << "b" ); alg5.addChildAlgorithm( alg5c2 ); QgsProcessingModelParameter alg5pc1; alg5pc1.setParameterName( QStringLiteral( "my_param" ) ); alg5pc1.setPosition( QPointF( 11, 12 ) ); alg5.addModelParameter( new QgsProcessingParameterBoolean( QStringLiteral( "my_param" ) ), alg5pc1 ); QDomDocument doc = QDomDocument( "model" ); QDomElement elem = QgsXmlUtils::writeVariant( alg5.toVariant(), doc ); doc.appendChild( elem ); QgsProcessingModelAlgorithm alg6; QVERIFY( alg6.loadVariant( QgsXmlUtils::readVariant( doc.firstChildElement() ) ) ); QCOMPARE( alg6.name(), QStringLiteral( "test" ) ); QCOMPARE( alg6.group(), QStringLiteral( "testGroup" ) ); QCOMPARE( alg6.helpContent(), alg5.helpContent() ); QgsProcessingModelChildAlgorithm alg6c1 = alg6.childAlgorithm( "cx1" ); QCOMPARE( alg6c1.childId(), QStringLiteral( "cx1" ) ); QCOMPARE( alg6c1.algorithmId(), QStringLiteral( "buffer" ) ); QCOMPARE( alg6c1.configuration(), myConfig ); QVERIFY( alg6c1.isActive() ); QVERIFY( alg6c1.outputsCollapsed() ); QVERIFY( alg6c1.parametersCollapsed() ); QCOMPARE( alg6c1.description(), QStringLiteral( "child 1" ) ); QCOMPARE( alg6c1.position().x(), 1.0 ); QCOMPARE( alg6c1.position().y(), 2.0 ); QCOMPARE( alg6c1.parameterSources().count(), 5 ); QCOMPARE( alg6c1.parameterSources().value( "x" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( alg6c1.parameterSources().value( "x" ).at( 0 ).parameterName(), QStringLiteral( "p1" ) ); QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).outputChildId(), QStringLiteral( "cx2" ) ); QCOMPARE( alg6c1.parameterSources().value( "y" ).at( 0 ).outputName(), QStringLiteral( "out3" ) ); QCOMPARE( alg6c1.parameterSources().value( "z" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::StaticValue ); QCOMPARE( alg6c1.parameterSources().value( "z" ).at( 0 ).staticValue().toInt(), 5 ); QCOMPARE( alg6c1.parameterSources().value( "a" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::Expression ); QCOMPARE( alg6c1.parameterSources().value( "a" ).at( 0 ).expression(), QStringLiteral( "2*2" ) ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).count(), 4 ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 0 ).source(), QgsProcessingModelChildParameterSource::StaticValue ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 0 ).staticValue().toInt(), 6 ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 1 ).source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 1 ).parameterName(), QStringLiteral( "p2" ) ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).outputChildId(), QStringLiteral( "cx2" ) ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 2 ).outputName(), QStringLiteral( "out4" ) ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 3 ).source(), QgsProcessingModelChildParameterSource::Expression ); QCOMPARE( alg6c1.parameterSources().value( "zm" ).at( 3 ).expression(), QStringLiteral( "1+2" ) ); QCOMPARE( alg6c1.modelOutputs().count(), 1 ); QCOMPARE( alg6c1.modelOutputs().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "my output" ) ); QCOMPARE( alg6c1.modelOutput( "a" ).description(), QStringLiteral( "my output" ) ); QCOMPARE( alg6c1.modelOutput( "a" ).position().x(), 3.0 ); QCOMPARE( alg6c1.modelOutput( "a" ).position().y(), 4.0 ); QgsProcessingModelChildAlgorithm alg6c2 = alg6.childAlgorithm( "cx2" ); QCOMPARE( alg6c2.childId(), QStringLiteral( "cx2" ) ); QVERIFY( !alg6c2.isActive() ); QVERIFY( !alg6c2.outputsCollapsed() ); QVERIFY( !alg6c2.parametersCollapsed() ); QCOMPARE( alg6c2.dependencies(), QStringList() << "a" << "b" ); QCOMPARE( alg6.parameterComponents().count(), 1 ); QCOMPARE( alg6.parameterComponents().value( QStringLiteral( "my_param" ) ).parameterName(), QStringLiteral( "my_param" ) ); QCOMPARE( alg6.parameterComponent( "my_param" ).parameterName(), QStringLiteral( "my_param" ) ); QCOMPARE( alg6.parameterComponent( "my_param" ).position().x(), 11.0 ); QCOMPARE( alg6.parameterComponent( "my_param" ).position().y(), 12.0 ); QCOMPARE( alg6.parameterDefinitions().count(), 1 ); QCOMPARE( alg6.parameterDefinitions().at( 0 )->type(), QStringLiteral( "boolean" ) ); // destination parameters QgsProcessingModelAlgorithm alg7( "test", "testGroup" ); QgsProcessingModelChildAlgorithm alg7c1; alg7c1.setChildId( "cx1" ); alg7c1.setAlgorithmId( "native:centroids" ); QMap alg7c1outputs; QgsProcessingModelOutput alg7c1out1( QStringLiteral( "my_output" ) ); alg7c1out1.setChildId( "cx1" ); alg7c1out1.setChildOutputName( "OUTPUT" ); alg7c1out1.setDescription( QStringLiteral( "my output" ) ); alg7c1outputs.insert( QStringLiteral( "my_output" ), alg7c1out1 ); alg7c1.setModelOutputs( alg7c1outputs ); alg7.addChildAlgorithm( alg7c1 ); // verify that model has destination parameter created QCOMPARE( alg7.destinationParameterDefinitions().count(), 1 ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) ); QCOMPARE( alg7.outputDefinitions().count(), 1 ); QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) ); QgsProcessingModelChildAlgorithm alg7c2; alg7c2.setChildId( "cx2" ); alg7c2.setAlgorithmId( "native:centroids" ); QMap alg7c2outputs; QgsProcessingModelOutput alg7c2out1( QStringLiteral( "my_output2" ) ); alg7c2out1.setChildId( "cx2" ); alg7c2out1.setChildOutputName( "OUTPUT" ); alg7c2out1.setDescription( QStringLiteral( "my output2" ) ); alg7c2outputs.insert( QStringLiteral( "my_output2" ), alg7c2out1 ); alg7c2.setModelOutputs( alg7c2outputs ); alg7.addChildAlgorithm( alg7c2 ); QCOMPARE( alg7.destinationParameterDefinitions().count(), 2 ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) ); QCOMPARE( alg7.destinationParameterDefinitions().at( 1 )->name(), QStringLiteral( "cx2:my_output2" ) ); QCOMPARE( alg7.destinationParameterDefinitions().at( 1 )->description(), QStringLiteral( "my output2" ) ); QCOMPARE( alg7.outputDefinitions().count(), 2 ); QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx1:my_output" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output" ) ); QCOMPARE( alg7.outputDefinitions().at( 1 )->name(), QStringLiteral( "cx2:my_output2" ) ); QCOMPARE( alg7.outputDefinitions().at( 1 )->type(), QStringLiteral( "outputVector" ) ); QCOMPARE( alg7.outputDefinitions().at( 1 )->description(), QStringLiteral( "my output2" ) ); alg7.removeChildAlgorithm( "cx1" ); QCOMPARE( alg7.destinationParameterDefinitions().count(), 1 ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->name(), QStringLiteral( "cx2:my_output2" ) ); QCOMPARE( alg7.destinationParameterDefinitions().at( 0 )->description(), QStringLiteral( "my output2" ) ); QCOMPARE( alg7.outputDefinitions().count(), 1 ); QCOMPARE( alg7.outputDefinitions().at( 0 )->name(), QStringLiteral( "cx2:my_output2" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->type(), QStringLiteral( "outputVector" ) ); QCOMPARE( alg7.outputDefinitions().at( 0 )->description(), QStringLiteral( "my output2" ) ); } void TestQgsProcessing::modelExecution() { // test childOutputIsRequired QgsProcessingModelAlgorithm model1; QgsProcessingModelChildAlgorithm algc1; algc1.setChildId( "cx1" ); algc1.setAlgorithmId( "native:centroids" ); model1.addChildAlgorithm( algc1 ); QgsProcessingModelChildAlgorithm algc2; algc2.setChildId( "cx2" ); algc2.setAlgorithmId( "native:centroids" ); algc2.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "p1" ) ); model1.addChildAlgorithm( algc2 ); QgsProcessingModelChildAlgorithm algc3; algc3.setChildId( "cx3" ); algc3.setAlgorithmId( "native:centroids" ); algc3.addParameterSources( "x", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "p2" ) ); algc3.setActive( false ); model1.addChildAlgorithm( algc3 ); QVERIFY( model1.childOutputIsRequired( "cx1", "p1" ) ); // cx2 depends on p1 QVERIFY( !model1.childOutputIsRequired( "cx1", "p2" ) ); // cx3 depends on p2, but cx3 is not active QVERIFY( !model1.childOutputIsRequired( "cx1", "p3" ) ); // nothing requires p3 QVERIFY( !model1.childOutputIsRequired( "cx2", "p1" ) ); QVERIFY( !model1.childOutputIsRequired( "cx3", "p1" ) ); // test parametersForChildAlgorithm QgsProcessingModelAlgorithm model2; model2.addModelParameter( new QgsProcessingParameterFeatureSource( "SOURCE_LAYER" ), QgsProcessingModelParameter( "SOURCE_LAYER" ) ); model2.addModelParameter( new QgsProcessingParameterNumber( "DIST", QString(), QgsProcessingParameterNumber::Double ), QgsProcessingModelParameter( "DIST" ) ); QgsProcessingModelChildAlgorithm alg2c1; QgsExpressionContext expContext; QgsExpressionContextScope *scope = new QgsExpressionContextScope(); scope->setVariable( "myvar", 8 ); expContext.appendScope( scope ); alg2c1.setChildId( "cx1" ); alg2c1.setAlgorithmId( "native:buffer" ); alg2c1.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "SOURCE_LAYER" ) ); alg2c1.addParameterSources( "DISTANCE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "DIST" ) ); alg2c1.addParameterSources( "SEGMENTS", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "@myvar*2" ) ) ); alg2c1.addParameterSources( "END_CAP_STYLE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 1 ) ); alg2c1.addParameterSources( "JOIN_STYLE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( 2 ) ); alg2c1.addParameterSources( "DISSOLVE", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( false ) ); QMap outputs1; QgsProcessingModelOutput out1( "MODEL_OUT_LAYER" ); out1.setChildOutputName( "OUTPUT" ); outputs1.insert( QStringLiteral( "MODEL_OUT_LAYER" ), out1 ); alg2c1.setModelOutputs( outputs1 ); model2.addChildAlgorithm( alg2c1 ); QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString vector = testDataDir + "points.shp"; QVariantMap modelInputs; modelInputs.insert( "SOURCE_LAYER", vector ); modelInputs.insert( "DIST", 271 ); modelInputs.insert( "cx1:MODEL_OUT_LAYER", "dest.shp" ); QgsProcessingOutputLayerDefinition layerDef( "memory:" ); layerDef.destinationName = "my_dest"; modelInputs.insert( "cx3:MY_OUT", QVariant::fromValue( layerDef ) ); QVariantMap childResults; QVariantMap params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx1" ), modelInputs, childResults, expContext ); QCOMPARE( params.value( "DISSOLVE" ).toBool(), false ); QCOMPARE( params.value( "DISTANCE" ).toInt(), 271 ); QCOMPARE( params.value( "SEGMENTS" ).toInt(), 16 ); QCOMPARE( params.value( "END_CAP_STYLE" ).toInt(), 1 ); QCOMPARE( params.value( "JOIN_STYLE" ).toInt(), 2 ); QCOMPARE( params.value( "INPUT" ).toString(), vector ); QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "dest.shp" ) ); QCOMPARE( params.count(), 7 ); QgsProcessingContext context; // Check variables for child algorithm // without values QMap variables = model2.variablesForChildAlgorithm( "cx1", context ); QCOMPARE( variables.count(), 5 ); QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); // with values variables = model2.variablesForChildAlgorithm( "cx1", context, modelInputs, childResults ); QCOMPARE( variables.count(), 5 ); QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 ); std::unique_ptr< QgsExpressionContextScope > childScope( model2.createExpressionContextScopeForChildAlgorithm( "cx1", context, modelInputs, childResults ) ); QCOMPARE( childScope->variableCount(), 5 ); QCOMPARE( childScope->variable( "DIST" ).toInt(), 271 ); QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_minx" ).toDouble(), -118.8888, 0.001 ); QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_miny" ).toDouble(), 22.8002, 0.001 ); QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_maxx" ).toDouble(), -83.3333, 0.001 ); QGSCOMPARENEAR( childScope->variable( "SOURCE_LAYER_maxy" ).toDouble(), 46.8719, 0.001 ); QVariantMap results; results.insert( "OUTPUT", QStringLiteral( "dest.shp" ) ); childResults.insert( "cx1", results ); // a child who uses an output from another alg as a parameter value QgsProcessingModelChildAlgorithm alg2c2; alg2c2.setChildId( "cx2" ); alg2c2.setAlgorithmId( "native:centroids" ); alg2c2.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) ); model2.addChildAlgorithm( alg2c2 ); params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx2" ), modelInputs, childResults, expContext ); QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) ); QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "memory:" ) ); QCOMPARE( params.count(), 2 ); variables = model2.variablesForChildAlgorithm( "cx2", context ); QCOMPARE( variables.count(), 9 ); QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx1" ) ); // with values variables = model2.variablesForChildAlgorithm( "cx2", context, modelInputs, childResults ); QCOMPARE( variables.count(), 9 ); QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 ); // a child with an optional output QgsProcessingModelChildAlgorithm alg2c3; alg2c3.setChildId( "cx3" ); alg2c3.setAlgorithmId( "native:extractbyexpression" ); alg2c3.addParameterSources( "INPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) ); alg2c3.addParameterSources( "EXPRESSION", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromStaticValue( "true" ) ); alg2c3.addParameterSources( "OUTPUT", QgsProcessingModelChildParameterSources() << QgsProcessingModelChildParameterSource::fromModelParameter( "MY_OUT" ) ); alg2c3.setDependencies( QStringList() << "cx2" ); QMap outputs3; QgsProcessingModelOutput out2( "MY_OUT" ); out2.setChildOutputName( "OUTPUT" ); outputs3.insert( QStringLiteral( "MY_OUT" ), out2 ); alg2c3.setModelOutputs( outputs3 ); model2.addChildAlgorithm( alg2c3 ); params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx3" ), modelInputs, childResults, expContext ); QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) ); QCOMPARE( params.value( "EXPRESSION" ).toString(), QStringLiteral( "true" ) ); QVERIFY( params.value( "OUTPUT" ).canConvert() ); QgsProcessingOutputLayerDefinition outDef = qvariant_cast( params.value( "OUTPUT" ) ); QCOMPARE( outDef.destinationName, QStringLiteral( "MY_OUT" ) ); QCOMPARE( outDef.sink.staticValue().toString(), QStringLiteral( "memory:" ) ); QCOMPARE( params.count(), 3 ); // don't want FAIL_OUTPUT set! variables = model2.variablesForChildAlgorithm( "cx3", context ); QCOMPARE( variables.count(), 13 ); QCOMPARE( variables.value( "DIST" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_minx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_miny" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "SOURCE_LAYER_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx1_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx1" ) ); QCOMPARE( variables.value( "cx2_OUTPUT_minx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx2_OUTPUT_minx" ).source.outputChildId(), QStringLiteral( "cx2" ) ); QCOMPARE( variables.value( "cx2_OUTPUT_miny" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx2_OUTPUT_miny" ).source.outputChildId(), QStringLiteral( "cx2" ) ); QCOMPARE( variables.value( "cx2_OUTPUT_maxx" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx2_OUTPUT_maxx" ).source.outputChildId(), QStringLiteral( "cx2" ) ); QCOMPARE( variables.value( "cx2_OUTPUT_maxy" ).source.source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( variables.value( "cx2_OUTPUT_maxy" ).source.outputChildId(), QStringLiteral( "cx2" ) ); // with values variables = model2.variablesForChildAlgorithm( "cx3", context, modelInputs, childResults ); QCOMPARE( variables.count(), 13 ); QCOMPARE( variables.value( "DIST" ).value.toInt(), 271 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_minx" ).value.toDouble(), -118.8888, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_miny" ).value.toDouble(), 22.8002, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxx" ).value.toDouble(), -83.3333, 0.001 ); QGSCOMPARENEAR( variables.value( "SOURCE_LAYER_maxy" ).value.toDouble(), 46.8719, 0.001 ); QStringList actualParts = model2.asPythonCode().split( '\n' ); QStringList expectedParts = QStringLiteral( "##model=name\n" "##DIST=number\n" "##SOURCE_LAYER=source\n" "##model_out_layer=output outputVector\n" "##my_out=output outputVector\n" "results={}\n" "outputs['cx1']=processing.run('native:buffer', {'DISSOLVE':false,'DISTANCE':parameters['DIST'],'END_CAP_STYLE':1,'INPUT':parameters['SOURCE_LAYER'],'JOIN_STYLE':2,'SEGMENTS':QgsExpression('@myvar*2').evaluate()}, context=context, feedback=feedback)\n" "results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n" "outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n" "outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n" "results['MY_OUT']=outputs['cx3']['OUTPUT']\n" "return results" ).split( '\n' ); QCOMPARE( actualParts, expectedParts ); } void TestQgsProcessing::modelAcceptableValues() { QgsProcessingModelAlgorithm m; QgsProcessingModelParameter stringParam1( "string" ); m.addModelParameter( new QgsProcessingParameterString( "string" ), stringParam1 ); QgsProcessingModelParameter stringParam2( "string2" ); m.addModelParameter( new QgsProcessingParameterString( "string2" ), stringParam2 ); QgsProcessingModelParameter numParam( "number" ); m.addModelParameter( new QgsProcessingParameterNumber( "number" ), numParam ); QgsProcessingModelParameter tableFieldParam( "field" ); m.addModelParameter( new QgsProcessingParameterField( "field" ), tableFieldParam ); QgsProcessingModelParameter fileParam( "file" ); m.addModelParameter( new QgsProcessingParameterFile( "file" ), fileParam ); // test single types QgsProcessingModelChildParameterSources sources = m.availableSourcesForChild( QString(), QStringList() << "number" ); QCOMPARE( sources.count(), 1 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "number" ) ); sources = m.availableSourcesForChild( QString(), QStringList() << "field" ); QCOMPARE( sources.count(), 1 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "field" ) ); sources = m.availableSourcesForChild( QString(), QStringList() << "file" ); QCOMPARE( sources.count(), 1 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "file" ) ); // test multiple types sources = m.availableSourcesForChild( QString(), QStringList() << "string" << "number" << "file" ); QCOMPARE( sources.count(), 4 ); QSet< QString > res; res << sources.at( 0 ).parameterName(); res << sources.at( 1 ).parameterName(); res << sources.at( 2 ).parameterName(); res << sources.at( 3 ).parameterName(); QCOMPARE( res, QSet< QString >() << QStringLiteral( "string" ) << QStringLiteral( "string2" ) << QStringLiteral( "number" ) << QStringLiteral( "file" ) ); // check outputs QgsProcessingModelChildAlgorithm alg2c1; alg2c1.setChildId( "cx1" ); alg2c1.setAlgorithmId( "native:centroids" ); m.addChildAlgorithm( alg2c1 ); sources = m.availableSourcesForChild( QString(), QStringList(), QStringList() << "string" << "outputVector" ); QCOMPARE( sources.count(), 1 ); res.clear(); res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName(); QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" ); // with dependencies between child algs QgsProcessingModelChildAlgorithm alg2c2; alg2c2.setChildId( "cx2" ); alg2c2.setAlgorithmId( "native:centroids" ); alg2c2.setDependencies( QStringList() << "cx1" ); m.addChildAlgorithm( alg2c2 ); sources = m.availableSourcesForChild( QString(), QStringList(), QStringList() << "string" << "outputVector" ); QCOMPARE( sources.count(), 2 ); res.clear(); res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName(); res << sources.at( 1 ).outputChildId() + ':' + sources.at( 1 ).outputName(); QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" << "cx2:OUTPUT" ); sources = m.availableSourcesForChild( QStringLiteral( "cx1" ), QStringList(), QStringList() << "string" << "outputVector" ); QCOMPARE( sources.count(), 0 ); sources = m.availableSourcesForChild( QString( "cx2" ), QStringList(), QStringList() << "string" << "outputVector" ); QCOMPARE( sources.count(), 1 ); res.clear(); res << sources.at( 0 ).outputChildId() + ':' + sources.at( 0 ).outputName(); QCOMPARE( res, QSet< QString >() << "cx1:OUTPUT" ); // test limiting by data types QgsProcessingModelAlgorithm m2; QgsProcessingModelParameter vlInput( "vl" ); // with no limit on data types m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl" ), vlInput ); QgsProcessingModelParameter fsInput( "fs" ); m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs" ), fsInput ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorPoint ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVector ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorAnyGeometry ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeMapLayer ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); // inputs are limited to vector layers m2.removeModelParameter( vlInput.parameterName() ); m2.removeModelParameter( fsInput.parameterName() ); m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList() << QgsProcessing::TypeVector ), vlInput ); m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList() << QgsProcessing::TypeVector ), fsInput ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorPoint ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVector ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorAnyGeometry ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeMapLayer ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); // inputs are limited to vector layers with geometries m2.removeModelParameter( vlInput.parameterName() ); m2.removeModelParameter( fsInput.parameterName() ); m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList() << QgsProcessing::TypeVectorAnyGeometry ), vlInput ); m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList() << QgsProcessing::TypeVectorAnyGeometry ), fsInput ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorPoint ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVector ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorAnyGeometry ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeMapLayer ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); // inputs are limited to vector layers with lines m2.removeModelParameter( vlInput.parameterName() ); m2.removeModelParameter( fsInput.parameterName() ); m2.addModelParameter( new QgsProcessingParameterVectorLayer( "vl", QString(), QList() << QgsProcessing::TypeVectorLine ), vlInput ); m2.addModelParameter( new QgsProcessingParameterFeatureSource( "fs", QString(), QList() << QgsProcessing::TypeVectorLine ), fsInput ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source" ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorPoint ); QCOMPARE( sources.count(), 0 ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorPolygon ); QCOMPARE( sources.count(), 0 ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorLine ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVector ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeVectorAnyGeometry ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); sources = m2.availableSourcesForChild( QString(), QStringList() << "vector" << "source", QStringList(), QList() << QgsProcessing::TypeMapLayer ); QCOMPARE( sources.count(), 2 ); QCOMPARE( sources.at( 0 ).parameterName(), QStringLiteral( "fs" ) ); QCOMPARE( sources.at( 1 ).parameterName(), QStringLiteral( "vl" ) ); } void TestQgsProcessing::tempUtils() { QString tempFolder = QgsProcessingUtils::tempFolder(); // tempFolder should remain constant for session QCOMPARE( QgsProcessingUtils::tempFolder(), tempFolder ); QString tempFile1 = QgsProcessingUtils::generateTempFilename( "test.txt" ); QVERIFY( tempFile1.endsWith( "test.txt" ) ); QVERIFY( tempFile1.startsWith( tempFolder ) ); // expect a different file QString tempFile2 = QgsProcessingUtils::generateTempFilename( "test.txt" ); QVERIFY( tempFile1 != tempFile2 ); QVERIFY( tempFile2.endsWith( "test.txt" ) ); QVERIFY( tempFile2.startsWith( tempFolder ) ); } void TestQgsProcessing::convertCompatible() { // start with a compatible shapefile QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt QString vector = testDataDir + "points.shp"; QgsVectorLayer *layer = new QgsVectorLayer( vector, "vl" ); QgsProject p; p.addMapLayer( layer ); QgsProcessingContext context; context.setProject( &p ); QgsProcessingFeedback feedback; QString out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback ); // layer should be returned unchanged - underlying source is compatible QCOMPARE( out, layer->source() ); // don't include shp as compatible type out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback ); QVERIFY( out != layer->source() ); QVERIFY( out.endsWith( ".tab" ) ); QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) ); // make sure all features are copied QgsVectorLayer *t = new QgsVectorLayer( out, "vl2" ); QCOMPARE( layer->featureCount(), t->featureCount() ); QCOMPARE( layer->crs(), t->crs() ); // use a selection - this will require translation QgsFeatureIds ids; QgsFeature f; QgsFeatureIterator it = layer->getFeatures(); it.nextFeature( f ); ids.insert( f.id() ); it.nextFeature( f ); ids.insert( f.id() ); layer->selectByIds( ids ); out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback ); QVERIFY( out != layer->source() ); QVERIFY( out.endsWith( ".tab" ) ); QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) ); delete t; t = new QgsVectorLayer( out, "vl2" ); QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) ); // using a selection but existing format - will still require translation out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback ); QVERIFY( out != layer->source() ); QVERIFY( out.endsWith( ".shp" ) ); QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) ); delete t; t = new QgsVectorLayer( out, "vl2" ); QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) ); // also test evaluating parameter to compatible format std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterFeatureSource( QStringLiteral( "source" ) ) ); QVariantMap params; params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), false ) ); out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback ); QCOMPARE( out, layer->source() ); out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback ); QVERIFY( out != layer->source() ); QVERIFY( out.endsWith( ".tab" ) ); QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) ); // selected only, will force export params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), true ) ); out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback ); QVERIFY( out != layer->source() ); QVERIFY( out.endsWith( ".shp" ) ); QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) ); } void TestQgsProcessing::create() { DummyAlgorithm alg( QStringLiteral( "test" ) ); DummyProvider p( QStringLiteral( "test_provider" ) ); alg.setProvider( &p ); std::unique_ptr< QgsProcessingAlgorithm > newInstance( alg.create() ); QVERIFY( newInstance.get() ); QCOMPARE( newInstance->provider(), &p ); } void TestQgsProcessing::combineFields() { QgsFields a; QgsFields b; // combine empty fields QgsFields res = QgsProcessingUtils::combineFields( a, b ); QVERIFY( res.isEmpty() ); // fields in a a.append( QgsField( "name" ) ); res = QgsProcessingUtils::combineFields( a, b ); QCOMPARE( res.count(), 1 ); QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) ); b.append( QgsField( "name" ) ); res = QgsProcessingUtils::combineFields( a, b ); QCOMPARE( res.count(), 2 ); QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) ); QCOMPARE( res.at( 1 ).name(), QStringLiteral( "name_2" ) ); a.append( QgsField( "NEW" ) ); res = QgsProcessingUtils::combineFields( a, b ); QCOMPARE( res.count(), 3 ); QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) ); QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) ); QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) ); b.append( QgsField( "new" ) ); res = QgsProcessingUtils::combineFields( a, b ); QCOMPARE( res.count(), 4 ); QCOMPARE( res.at( 0 ).name(), QStringLiteral( "name" ) ); QCOMPARE( res.at( 1 ).name(), QStringLiteral( "NEW" ) ); QCOMPARE( res.at( 2 ).name(), QStringLiteral( "name_2" ) ); QCOMPARE( res.at( 3 ).name(), QStringLiteral( "new_2" ) ); } void TestQgsProcessing::stringToPythonLiteral() { QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a" ) ), QStringLiteral( "'a'" ) ); QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QString() ), QStringLiteral( "''" ) ); QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a 'string'" ) ), QStringLiteral( "'a \\'string\\''" ) ); QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \"string\"" ) ), QStringLiteral( "'a \\\"string\\\"'" ) ); } QGSTEST_MAIN( TestQgsProcessing ) #include "testqgsprocessing.moc"