From 889fbfc1d09aec78fa6c2c3557cc56575401a0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Mon, 19 Aug 2024 10:58:47 -0500 Subject: [PATCH] [tests] Add test for unloading Expression Functions from projects (on project close) and reloading user ones, specifically to restore overwritten user functions when opening a project with expressions inside --- .gitignore | 2 + src/app/qgisapp.h | 1 + tests/src/app/CMakeLists.txt | 1 + tests/src/app/testqgsprojectexpressions.cpp | 109 ++++++++++++++++++ .../projects/test_project_functions.qgz | Bin 0 -> 4524 bytes .../default/python/expressions/__init__.py | 0 .../default/python/expressions/test.py | 7 ++ .../default/python/expressions/test2.py | 7 ++ 8 files changed, 127 insertions(+) create mode 100644 tests/src/app/testqgsprojectexpressions.cpp create mode 100644 tests/testdata/projects/test_project_functions.qgz create mode 100644 tests/testdata/test_qgis_config_path/profiles/default/python/expressions/__init__.py create mode 100644 tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test.py create mode 100644 tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test2.py diff --git a/.gitignore b/.gitignore index 4b61866b3377..31c8f9267358 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,8 @@ tests/testdata/raster/band3_float32_noct_epsg4326.tif.aux.xml tests/testdata/raster/band3_int16_noct_epsg4326.tif.aux.xml tests/testdata/tenbytenraster.asc.aux.xml tests/testdata/test_plugin_path/plugin_started.txt +tests/testdata/test_qgis_config_path/profiles/default/* +!tests/testdata/test_qgis_config_path/profiles/default/python tests/testdata/widget_config.qlr tests/testdata/zip/testtar.tgz.properties venv diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index bf8511ddad11..1db55d1f87d4 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -2761,6 +2761,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow friend class TestQgisAppPython; friend class TestQgisApp; + friend class TestQgsProjectExpressions; friend class QgisAppInterface; friend class QgsAppScreenShots; }; diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 1cedc22e87e2..8c20fdddc9ed 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -63,6 +63,7 @@ endif() if (WITH_BINDINGS) set(TESTS ${TESTS} testqgisapppython.cpp) + set(TESTS ${TESTS} testqgsprojectexpressions.cpp) endif() foreach(TESTSRC ${TESTS}) diff --git a/tests/src/app/testqgsprojectexpressions.cpp b/tests/src/app/testqgsprojectexpressions.cpp new file mode 100644 index 000000000000..24fd093392f8 --- /dev/null +++ b/tests/src/app/testqgsprojectexpressions.cpp @@ -0,0 +1,109 @@ +/*************************************************************************** + testqgsprojectexpressions.cpp + -------------------- + Date : Aug 2024 + Copyright : (C) 2024 by Germán Carrillo + Email : german at opengis dot ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include "qgstest.h" + +#include +#include + +/** + * \ingroup UnitTests + * This is a unit test for the Save Python Expression in project support. + */ +class TestQgsProjectExpressions : public QObject +{ + Q_OBJECT + + public: + TestQgsProjectExpressions(); + + 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 projectExpressions(); + + private: + QgisApp *mQgisApp = nullptr; +}; + +TestQgsProjectExpressions::TestQgsProjectExpressions() = default; + +//runs before all tests +void TestQgsProjectExpressions::initTestCase() +{ + const QByteArray configPath = QByteArray( TEST_DATA_DIR ) + "/test_qgis_config_path"; + qputenv( "QGIS_CUSTOM_CONFIG_PATH", configPath ); + + // Set up the QgsSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + qDebug() << "TestQgsProjectExpressions::initTestCase()"; + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + mQgisApp = new QgisApp(); + mQgisApp->loadPythonSupport(); +} + +//runs after all tests +void TestQgsProjectExpressions::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsProjectExpressions::projectExpressions() +{ + const int count_before_project = QgsExpression::functionCount(); + QVERIFY( QgsExpression::functionIndex( QStringLiteral( "mychoice" ) ) != -1 ); // User expression loaded + + const QString myExpression = QStringLiteral( "mychoice(1, 2)" ); + QgsExpression exp( QStringLiteral( "mychoice(1, 2)" ) ); + QCOMPARE( exp.evaluate().toInt(), 1 ); + + // Load expressions from project + // Project registers 2 functions: mychoice (overwriting it) and myprojectfunction + const QByteArray projectPath = QByteArray( TEST_DATA_DIR ) + "/projects/test_project_functions.qgz"; + QgsProject::instance()->read( projectPath ); + QCOMPARE( QgsExpression::functionIndex( QStringLiteral( "myprojectfunction" ) ), -1 ); + QgsExpression::loadFunctionsFromProject(); + QVERIFY( QgsExpression::functionIndex( QStringLiteral( "myprojectfunction" ) ) != -1 ); + QVERIFY( QgsExpression::functionIndex( QStringLiteral( "mychoice" ) ) != -1 ); // Overwritten function + const int count_project_loaded = QgsExpression::functionCount(); + + QCOMPARE( count_project_loaded - count_before_project, 1 ); // myprojectfunction + + exp = myExpression; // Re-parse it + QCOMPARE( exp.evaluate().toInt(), 2 ); // Different result because now it's from project + + // Unload expressions from project, reload user ones + QgsExpression::cleanFunctionsFromProject(); + const int count_project_unloaded = QgsExpression::functionCount(); + QCOMPARE( count_before_project, count_project_unloaded ); // myprojectfunction is gone + + QCOMPARE( QgsExpression::functionIndex( QStringLiteral( "myprojectfunction" ) ), -1 ); + exp = myExpression; // Re-parse it + QCOMPARE( exp.evaluate().toInt(), 1 ); // Original result, coming from user function +} + + +QGSTEST_MAIN( TestQgsProjectExpressions ) +#include "testqgsprojectexpressions.moc" diff --git a/tests/testdata/projects/test_project_functions.qgz b/tests/testdata/projects/test_project_functions.qgz new file mode 100644 index 0000000000000000000000000000000000000000..533594fd56da96d4fadf1f4113e18a93d3a09adc GIT binary patch literal 4524 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 literal 0 HcmV?d00001 diff --git a/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/__init__.py b/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test.py b/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test.py new file mode 100644 index 000000000000..72941c5adfdc --- /dev/null +++ b/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test.py @@ -0,0 +1,7 @@ +from qgis.core import qgsfunction + + +@qgsfunction(group='Custom', referenced_columns=[]) +def mychoice(value1, value2): + return value1 + diff --git a/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test2.py b/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test2.py new file mode 100644 index 000000000000..8bcde0850037 --- /dev/null +++ b/tests/testdata/test_qgis_config_path/profiles/default/python/expressions/test2.py @@ -0,0 +1,7 @@ +from qgis.core import qgsfunction + + +@qgsfunction(group='Custom', referenced_columns=[]) +def mychoice2(value1, value2): + return value1 + value2 +