From 83a8169b7e0fc6d6128694776e16bb620a01875b Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Mon, 4 Dec 2023 13:16:24 +0100 Subject: [PATCH] Tests refactor pytest (#129) * refactors tests * add some test cases * change test infrastructure * add algorithm runs to test --- .github/workflows/test_plugin.yaml | 35 +- REQUIREMENTS_TESTING.txt | 3 + processing_r/processing/provider.py | 8 +- processing_r/processing/utils.py | 22 + processing_r/r_plugin.py | 2 - processing_r/test/__init__.py | 4 - processing_r/test/data/test_input_point.rsx | 2 - processing_r/test/data/test_rasterin.rsx | 1 - processing_r/test/qgis_interface.py | 227 -- processing_r/test/tenbytenraster.asc | 19 - processing_r/test/tenbytenraster.asc.aux.xml | 13 - processing_r/test/tenbytenraster.keywords | 1 - processing_r/test/tenbytenraster.lic | 18 - processing_r/test/tenbytenraster.prj | 1 - processing_r/test/tenbytenraster.qml | 26 - processing_r/test/test_algorithm.py | 781 ----- processing_r/test/test_guiutils.py | 48 - processing_r/test/test_init.py | 62 - processing_r/test/test_qgis_environment.py | 36 - processing_r/test/test_rutils.py | 190 -- processing_r/test/test_template.py | 69 - processing_r/test/test_translations.py | 58 - processing_r/test/utilities.py | 104 - processing_r/test_suite.py | 93 - pyproject.toml | 9 +- scripts/run_docker_locally.sh | 34 + tests/conftest.py | 38 + {processing_r/test => tests}/data/dem.tif | Bin .../test => tests}/data/dem.tif.aux.xml | 0 {processing_r/test => tests}/data/dem2.tif | Bin .../test => tests}/data/dem2.tif.aux.xml | 0 {processing_r/test => tests}/data/layers.gpx | 2584 ++++++++--------- {processing_r/test => tests}/data/lines.dbf | Bin {processing_r/test => tests}/data/lines.prj | 0 {processing_r/test => tests}/data/lines.shp | Bin {processing_r/test => tests}/data/lines.shx | Bin {processing_r/test => tests}/data/points.gml | 0 {processing_r/test => tests}/data/points.xsd | 0 .../test => tests}/data/test_gpkg.gpkg | Bin 126976 -> 126976 bytes .../data => tests/scripts}/bad_algorithm.rsx | 0 .../scripts}/test_algorithm_1.rsx | 0 .../scripts}/test_algorithm_1.rsx.help | 0 .../scripts}/test_algorithm_2.rsx | 1 + .../scripts}/test_algorithm_3.rsx | 0 .../scripts}/test_algorithm_4.rsx | 0 .../scripts}/test_algorithm_inline_help.rsx | 0 .../scripts}/test_dont_load_any_packages.rsx | 0 .../scripts}/test_enum_multiple.rsx | 2 +- .../data => tests/scripts}/test_enums.rsx | 0 .../scripts}/test_field_multiple.rsx | 0 .../scripts}/test_input_color.rsx | 0 .../scripts}/test_input_datetime.rsx | 0 .../scripts}/test_input_expression.rsx | 0 tests/scripts/test_input_point.rsx | 3 + .../scripts}/test_input_range.rsx | 0 .../scripts}/test_library_with_option.rsx | 0 .../data => tests/scripts}/test_multiout.rsx | 0 .../scripts}/test_multirasterin.rsx | 0 .../scripts}/test_multivectorin.rsx | 0 tests/scripts/test_plots.rsx | 7 + .../scripts}/test_raster_band.rsx | 0 tests/scripts/test_raster_in_out.rsx | 4 + .../scripts}/test_rasterin_names.rsx | 0 .../data => tests/scripts}/test_vectorin.rsx | 0 .../data => tests/scripts}/test_vectorout.rsx | 0 tests/test_algorithm.py | 50 + tests/test_algorithm_inputs.py | 464 +++ tests/test_algorithm_metadata.py | 99 + tests/test_algorithm_outputs.py | 83 + tests/test_algorithm_script_parsing.py | 283 ++ tests/test_gui_utils.py | 18 + tests/test_r_template.py | 36 + tests/test_r_utils.py | 175 ++ tests/utils.py | 24 + 74 files changed, 2671 insertions(+), 3066 deletions(-) delete mode 100644 processing_r/test/__init__.py delete mode 100644 processing_r/test/data/test_input_point.rsx delete mode 100644 processing_r/test/data/test_rasterin.rsx delete mode 100644 processing_r/test/qgis_interface.py delete mode 100644 processing_r/test/tenbytenraster.asc delete mode 100644 processing_r/test/tenbytenraster.asc.aux.xml delete mode 100644 processing_r/test/tenbytenraster.keywords delete mode 100644 processing_r/test/tenbytenraster.lic delete mode 100644 processing_r/test/tenbytenraster.prj delete mode 100644 processing_r/test/tenbytenraster.qml delete mode 100644 processing_r/test/test_algorithm.py delete mode 100644 processing_r/test/test_guiutils.py delete mode 100644 processing_r/test/test_init.py delete mode 100644 processing_r/test/test_qgis_environment.py delete mode 100644 processing_r/test/test_rutils.py delete mode 100644 processing_r/test/test_template.py delete mode 100644 processing_r/test/test_translations.py delete mode 100644 processing_r/test/utilities.py delete mode 100644 processing_r/test_suite.py create mode 100755 scripts/run_docker_locally.sh create mode 100644 tests/conftest.py rename {processing_r/test => tests}/data/dem.tif (100%) rename {processing_r/test => tests}/data/dem.tif.aux.xml (100%) rename {processing_r/test => tests}/data/dem2.tif (100%) rename {processing_r/test => tests}/data/dem2.tif.aux.xml (100%) rename {processing_r/test => tests}/data/layers.gpx (99%) rename {processing_r/test => tests}/data/lines.dbf (100%) rename {processing_r/test => tests}/data/lines.prj (100%) rename {processing_r/test => tests}/data/lines.shp (100%) rename {processing_r/test => tests}/data/lines.shx (100%) rename {processing_r/test => tests}/data/points.gml (100%) rename {processing_r/test => tests}/data/points.xsd (100%) rename {processing_r/test => tests}/data/test_gpkg.gpkg (99%) rename {processing_r/test/data => tests/scripts}/bad_algorithm.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_algorithm_1.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_algorithm_1.rsx.help (100%) rename {processing_r/test/data => tests/scripts}/test_algorithm_2.rsx (99%) rename {processing_r/test/data => tests/scripts}/test_algorithm_3.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_algorithm_4.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_algorithm_inline_help.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_dont_load_any_packages.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_enum_multiple.rsx (88%) rename {processing_r/test/data => tests/scripts}/test_enums.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_field_multiple.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_input_color.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_input_datetime.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_input_expression.rsx (100%) create mode 100644 tests/scripts/test_input_point.rsx rename {processing_r/test/data => tests/scripts}/test_input_range.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_library_with_option.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_multiout.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_multirasterin.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_multivectorin.rsx (100%) create mode 100644 tests/scripts/test_plots.rsx rename {processing_r/test/data => tests/scripts}/test_raster_band.rsx (100%) create mode 100644 tests/scripts/test_raster_in_out.rsx rename {processing_r/test/data => tests/scripts}/test_rasterin_names.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_vectorin.rsx (100%) rename {processing_r/test/data => tests/scripts}/test_vectorout.rsx (100%) create mode 100644 tests/test_algorithm.py create mode 100644 tests/test_algorithm_inputs.py create mode 100644 tests/test_algorithm_metadata.py create mode 100644 tests/test_algorithm_outputs.py create mode 100644 tests/test_algorithm_script_parsing.py create mode 100644 tests/test_gui_utils.py create mode 100644 tests/test_r_template.py create mode 100644 tests/test_r_utils.py create mode 100644 tests/utils.py diff --git a/.github/workflows/test_plugin.yaml b/.github/workflows/test_plugin.yaml index 027b927..d9cfbb6 100644 --- a/.github/workflows/test_plugin.yaml +++ b/.github/workflows/test_plugin.yaml @@ -15,6 +15,7 @@ env: TESTS_RUN_FUNCTION: processing_r.test_suite.test_package # Docker settings DOCKER_IMAGE: qgis/qgis + PYTHON_SETUP: "PYTHONPATH=/usr/share/qgis/python/:/usr/share/qgis/python/plugins:/usr/lib/python3/dist-packages/qgis:/usr/share/qgis/python/qgis:/tests_directory" jobs: @@ -25,28 +26,38 @@ jobs: strategy: matrix: - docker_tags: [release-3_28, release-3_30, release-3_34, latest] + docker_tags: [release-3_28, release-3_30, release-3_32, release-3_34, latest] steps: - name: Checkout uses: actions/checkout@v4 - - name: Docker pull and create qgis-testing-environment + - name: Docker pull and create qgis-test-env run: | docker pull "$DOCKER_IMAGE":${{ matrix.docker_tags }} - docker run -d --name qgis-testing-environment -v "$GITHUB_WORKSPACE":/tests_directory -e DISPLAY=:99 "$DOCKER_IMAGE":${{ matrix.docker_tags }} + docker run -d --name qgis-test-env -v "$GITHUB_WORKSPACE":/tests_directory -e DISPLAY=:99 "$DOCKER_IMAGE":${{ matrix.docker_tags }} - - name: Docker set up QGIS + - name: Docker set up run: | - docker exec qgis-testing-environment sh -c "qgis_setup.sh $PLUGIN_NAME" - docker exec qgis-testing-environment sh -c "rm -f /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME" - docker exec qgis-testing-environment sh -c "ln -s /tests_directory/$PLUGIN_NAME /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME" - docker exec qgis-testing-environment sh -c "pip3 install -r /tests_directory/REQUIREMENTS_TESTING.txt" - docker exec qgis-testing-environment sh -c "apt-get update" - docker exec qgis-testing-environment sh -c "apt-get install -y r-base" + docker exec qgis-test-env sh -c "pip3 install -r /tests_directory/REQUIREMENTS_TESTING.txt" + docker exec qgis-test-env sh -c "apt-get update" + docker exec qgis-test-env sh -c "apt-get install -y --no-install-recommends wget ca-certificates gnupg" + + - name: Add Keys and Sources for R and binary packages + run: | + docker exec qgis-test-env sh -c "wget -q -O- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc" + docker exec qgis-test-env sh -c "echo \"deb [arch=amd64] https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/\" > /etc/apt/sources.list.d/cran_r.list" + docker exec qgis-test-env sh -c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 67C2D66C4B1D4339 51716619E084DAB9" + docker exec qgis-test-env sh -c "wget -q -O- https://eddelbuettel.github.io/r2u/assets/dirk_eddelbuettel_key.asc | tee -a /etc/apt/trusted.gpg.d/cranapt_key.asc" + docker exec qgis-test-env sh -c "echo \"deb [arch=amd64] https://r2u.stat.illinois.edu/ubuntu jammy main\" > /etc/apt/sources.list.d/cranapt.list" + + - name: Install R and necessary packages + run: | + docker exec qgis-test-env sh -c "apt update -qq" + docker exec qgis-test-env sh -c "apt-get install -y r-base r-cran-sf r-cran-raster" - name: Docker run plugin tests run: | - docker exec qgis-testing-environment sh -c "qgis_testrunner.sh $TESTS_RUN_FUNCTION" - + docker exec qgis-test-env sh -c "$PYTHON_SETUP && cd tests_directory && pytest" + \ No newline at end of file diff --git a/REQUIREMENTS_TESTING.txt b/REQUIREMENTS_TESTING.txt index e0356c3..3947761 100644 --- a/REQUIREMENTS_TESTING.txt +++ b/REQUIREMENTS_TESTING.txt @@ -3,3 +3,6 @@ deepdiff mock flake8 pep257 +pytest +pytest-cov +pytest-qgis \ No newline at end of file diff --git a/processing_r/processing/provider.py b/processing_r/processing/provider.py index 70bb0c2..25e4dd3 100644 --- a/processing_r/processing/provider.py +++ b/processing_r/processing/provider.py @@ -30,7 +30,7 @@ from processing_r.processing.actions.edit_script import EditScriptAction from processing_r.processing.algorithm import RAlgorithm from processing_r.processing.exceptions import InvalidScriptException -from processing_r.processing.utils import RUtils +from processing_r.processing.utils import RUtils, plugin_version class RAlgorithmProvider(QgsProcessingProvider): @@ -38,8 +38,6 @@ class RAlgorithmProvider(QgsProcessingProvider): Processing provider for executing R scripts """ - VERSION = "3.0.0" - def __init__(self): super().__init__() self.algs = [] @@ -141,9 +139,9 @@ def versionInfo(self): Provider plugin version """ if not self.r_version: - return "QGIS R Provider version {}".format(self.VERSION) + return "QGIS R Provider version {}".format(plugin_version()) - return "QGIS R Provider version {}, {}".format(self.VERSION, self.r_version) + return "QGIS R Provider version {}, {}".format(plugin_version(), self.r_version) def id(self): """ diff --git a/processing_r/processing/utils.py b/processing_r/processing/utils.py index b589324..c7b1f4a 100644 --- a/processing_r/processing/utils.py +++ b/processing_r/processing/utils.py @@ -16,7 +16,9 @@ * * *************************************************************************** """ +import configparser import os +import pathlib import platform import re import subprocess @@ -419,3 +421,23 @@ def log(message: str) -> None: Simple logging function, most for debuging. """ QgsMessageLog.logMessage(message, "Processing R Plugin", Qgis.Info) + + +def _read_metadata() -> configparser.ConfigParser: + """ + Read metadata file. + """ + path = pathlib.Path(__file__).parent / "metadata.txt" + + config = configparser.ConfigParser() + config.read(path) + + return config + + +def plugin_version() -> str: + """ + Get plugin version. + """ + config = _read_metadata() + return config["general"]["version"] diff --git a/processing_r/r_plugin.py b/processing_r/r_plugin.py index 72dbec7..c094f69 100644 --- a/processing_r/r_plugin.py +++ b/processing_r/r_plugin.py @@ -21,8 +21,6 @@ from processing_r.processing.provider import RAlgorithmProvider -VERSION = "3.1.0" - class RProviderPlugin: """QGIS Plugin Implementation.""" diff --git a/processing_r/test/__init__.py b/processing_r/test/__init__.py deleted file mode 100644 index 65c47b5..0000000 --- a/processing_r/test/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -import qgis libs so that we set the correct sip api version -""" -import qgis # NOQA diff --git a/processing_r/test/data/test_input_point.rsx b/processing_r/test/data/test_input_point.rsx deleted file mode 100644 index e189b12..0000000 --- a/processing_r/test/data/test_input_point.rsx +++ /dev/null @@ -1,2 +0,0 @@ -##Test point input sp=name -##point=point diff --git a/processing_r/test/data/test_rasterin.rsx b/processing_r/test/data/test_rasterin.rsx deleted file mode 100644 index a1a6676..0000000 --- a/processing_r/test/data/test_rasterin.rsx +++ /dev/null @@ -1 +0,0 @@ -##Layer=raster diff --git a/processing_r/test/qgis_interface.py b/processing_r/test/qgis_interface.py deleted file mode 100644 index e780ce2..0000000 --- a/processing_r/test/qgis_interface.py +++ /dev/null @@ -1,227 +0,0 @@ -# coding=utf-8 -"""QGIS plugin implementation. - -.. note:: 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. - -.. note:: This source code was copied from the 'postgis viewer' application - with original authors: - Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk - Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org - Copyright (c) 2014 Tim Sutton, tim@linfiniti.com - -""" - -__author__ = "tim@linfiniti.com" -__revision__ = "$Format:%H$" -__date__ = "10/01/2011" -__copyright__ = ( - "Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and " - "Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org" - "Copyright (c) 2014 Tim Sutton, tim@linfiniti.com" -) - -import logging -from typing import List - -from PyQt5.QtCore import QObject, QSize, pyqtSignal, pyqtSlot -from qgis.core import QgsMapLayer, QgsProject -from qgis.gui import QgsMapCanvas, QgsMessageBar -from qgis.PyQt.QtWidgets import QDockWidget - -LOGGER = logging.getLogger("QGIS") - - -# noinspection PyMethodMayBeStatic,PyPep8Naming -class QgisInterface(QObject): - """Class to expose QGIS objects and functions to plugins. - - This class is here for enabling us to run unit tests only, - so most methods are simply stubs. - """ - - currentLayerChanged = pyqtSignal(QgsMapLayer) - - def __init__(self, canvas: QgsMapCanvas): - """Constructor - :param canvas: - """ - QObject.__init__(self) - self.canvas = canvas - # Set up slots so we can mimic the behaviour of QGIS when layers - # are added. - LOGGER.debug("Initialising canvas...") - # noinspection PyArgumentList - QgsProject.instance().layersAdded.connect(self.addLayers) - # noinspection PyArgumentList - QgsProject.instance().layerWasAdded.connect(self.addLayer) - # noinspection PyArgumentList - QgsProject.instance().removeAll.connect(self.removeAllLayers) - - # For processing module - self.destCrs = None - - self.message_bar = QgsMessageBar() - - def addLayers(self, layers: List[QgsMapLayer]): - """Handle layers being added to the registry so they show up in canvas. - - :param layers: list list of map layers that were added - - .. note:: The QgsInterface api does not include this method, - it is added here as a helper to facilitate testing. - """ - # LOGGER.debug('addLayers called on qgis_interface') - # LOGGER.debug('Number of layers being added: %s' % len(layers)) - # LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) - current_layers = self.canvas.layers() - final_layers = [] - for layer in current_layers: - final_layers.append(layer) - for layer in layers: - final_layers.append(layer) - - self.canvas.setLayers(final_layers) - # LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) - - def addLayer(self, layer: QgsMapLayer): - """Handle a layer being added to the registry so it shows up in canvas. - - :param layer: list list of map layers that were added - - .. note: The QgsInterface api does not include this method, it is added - here as a helper to facilitate testing. - - .. note: The addLayer method was deprecated in QGIS 1.8 so you should - not need this method much. - """ - pass # pylint: disable=unnecessary-pass - - @pyqtSlot() - def removeAllLayers(self): # pylint: disable=no-self-use - """Remove layers from the canvas before they get deleted.""" - self.canvas.setLayers([]) - - def newProject(self): # pylint: disable=no-self-use - """Create new project.""" - # noinspection PyArgumentList - QgsProject.instance().clear() - - # ---------------- API Mock for QgsInterface follows ------------------- - - def zoomFull(self): - """Zoom to the map full extent.""" - pass # pylint: disable=unnecessary-pass - - def zoomToPrevious(self): - """Zoom to previous view extent.""" - pass # pylint: disable=unnecessary-pass - - def zoomToNext(self): - """Zoom to next view extent.""" - pass # pylint: disable=unnecessary-pass - - def zoomToActiveLayer(self): - """Zoom to extent of active layer.""" - pass # pylint: disable=unnecessary-pass - - def addVectorLayer(self, path: str, base_name: str, provider_key: str): - """Add a vector layer. - - :param path: Path to layer. - :type path: str - - :param base_name: Base name for layer. - :type base_name: str - - :param provider_key: Provider key e.g. 'ogr' - :type provider_key: str - """ - pass # pylint: disable=unnecessary-pass - - def addRasterLayer(self, path: str, base_name: str): - """Add a raster layer given a raster layer file name - - :param path: Path to layer. - :type path: str - - :param base_name: Base name for layer. - :type base_name: str - """ - pass # pylint: disable=unnecessary-pass - - def activeLayer(self) -> QgsMapLayer: # pylint: disable=no-self-use - """Get pointer to the active layer (layer selected in the legend).""" - # noinspection PyArgumentList - layers = QgsProject.instance().mapLayers() - for item in layers: - return layers[item] - - def addToolBarIcon(self, action): - """Add an icon to the plugins toolbar. - - :param action: Action to add to the toolbar. - :type action: QAction - """ - pass # pylint: disable=unnecessary-pass - - def removeToolBarIcon(self, action): - """Remove an action (icon) from the plugin toolbar. - - :param action: Action to add to the toolbar. - :type action: QAction - """ - pass # pylint: disable=unnecessary-pass - - def addToolBar(self, name): - """Add toolbar with specified name. - - :param name: Name for the toolbar. - :type name: str - """ - pass # pylint: disable=unnecessary-pass - - def mapCanvas(self) -> QgsMapCanvas: - """Return a pointer to the map canvas.""" - return self.canvas - - def mainWindow(self): - """Return a pointer to the main window. - - In case of QGIS it returns an instance of QgisApp. - """ - pass # pylint: disable=unnecessary-pass - - def addDockWidget(self, area, dock_widget: QDockWidget): - """Add a dock widget to the main window. - - :param area: Where in the ui the dock should be placed. - :type area: - - :param dock_widget: A dock widget to add to the UI. - :type dock_widget: QDockWidget - """ - pass # pylint: disable=unnecessary-pass - - def legendInterface(self): - """Get the legend.""" - return self.canvas - - def iconSize(self, dockedToolbar) -> int: - """ - Returns the toolbar icon size. - :param dockedToolbar: If True, the icon size - for toolbars contained within docks is returned. - """ - if dockedToolbar: - return QSize(16, 16) - - return QSize(24, 24) - - def messageBar(self) -> QgsMessageBar: - """ - Return the message bar of the main app - """ - return self.message_bar diff --git a/processing_r/test/tenbytenraster.asc b/processing_r/test/tenbytenraster.asc deleted file mode 100644 index 96a0ee1..0000000 --- a/processing_r/test/tenbytenraster.asc +++ /dev/null @@ -1,19 +0,0 @@ -NCOLS 10 -NROWS 10 -XLLCENTER 1535380.000000 -YLLCENTER 5083260.000000 -DX 10 -DY 10 -NODATA_VALUE -9999 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -0 1 2 3 4 5 6 7 8 9 -CRS -NOTES diff --git a/processing_r/test/tenbytenraster.asc.aux.xml b/processing_r/test/tenbytenraster.asc.aux.xml deleted file mode 100644 index cfb1578..0000000 --- a/processing_r/test/tenbytenraster.asc.aux.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - Point - - - - 9 - 4.5 - 0 - 2.872281323269 - - - diff --git a/processing_r/test/tenbytenraster.keywords b/processing_r/test/tenbytenraster.keywords deleted file mode 100644 index 8be3f61..0000000 --- a/processing_r/test/tenbytenraster.keywords +++ /dev/null @@ -1 +0,0 @@ -title: Tenbytenraster diff --git a/processing_r/test/tenbytenraster.lic b/processing_r/test/tenbytenraster.lic deleted file mode 100644 index 8345533..0000000 --- a/processing_r/test/tenbytenraster.lic +++ /dev/null @@ -1,18 +0,0 @@ - - - - Tim Sutton, Linfiniti Consulting CC - - - - tenbytenraster.asc - 2700044251 - Yes - Tim Sutton - Tim Sutton (QGIS Source Tree) - Tim Sutton - This data is publicly available from QGIS Source Tree. The original - file was created and contributed to QGIS by Tim Sutton. - - - diff --git a/processing_r/test/tenbytenraster.prj b/processing_r/test/tenbytenraster.prj deleted file mode 100644 index a30c00a..0000000 --- a/processing_r/test/tenbytenraster.prj +++ /dev/null @@ -1 +0,0 @@ -GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/processing_r/test/tenbytenraster.qml b/processing_r/test/tenbytenraster.qml deleted file mode 100644 index 85247d4..0000000 --- a/processing_r/test/tenbytenraster.qml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - 0 - diff --git a/processing_r/test/test_algorithm.py b/processing_r/test/test_algorithm.py deleted file mode 100644 index 5db3e59..0000000 --- a/processing_r/test/test_algorithm.py +++ /dev/null @@ -1,781 +0,0 @@ -# coding=utf-8 -"""Algorithm Test. - -.. note:: 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. - -""" - -__author__ = "(C) 2018 by Nyall Dawson" -__date__ = "20/04/2018" -__copyright__ = "Copyright 2018, North Road" -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = "$Format:%H$" - -import os -import unittest - -from qgis.core import ( - Qgis, - QgsProcessing, - QgsProcessingAlgorithm, - QgsProcessingContext, - QgsProcessingFeedback, - QgsProcessingParameterFile, - QgsProcessingParameterNumber, - QgsVectorLayer, -) -from qgis.PyQt.QtCore import QDate, QDateTime, QTime -from qgis.PyQt.QtGui import QColor - -from processing_r.processing.algorithm import RAlgorithm - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - -test_data_path = os.path.join(os.path.dirname(__file__), "data") - - -class AlgorithmTest(unittest.TestCase): - """Test algorithm construction.""" - - def testScriptParsing(self): # pylint: disable=too-many-locals,too-many-statements - """ - Test script file parsing - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_1.rsx")) - alg.initAlgorithm() - self.assertFalse(alg.error) - self.assertEqual(alg.name(), "test_algorithm_1") - self.assertEqual(alg.displayName(), "test algorithm 1") - self.assertIn("test_algorithm_1.rsx", "test_algorithm_1.rsx") - self.assertTrue(alg.show_plots) - self.assertTrue(alg.pass_file_names) - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_2.rsx")) - alg.initAlgorithm() - self.assertFalse(alg.error) - self.assertEqual(alg.name(), "mytest") - self.assertEqual(alg.displayName(), "my test") - self.assertEqual(alg.group(), "my group") - self.assertEqual(alg.groupId(), "my group") - self.assertFalse(alg.show_plots) - self.assertFalse(alg.pass_file_names) - - # test that inputs were created correctly - raster_param = alg.parameterDefinition("in_raster") - self.assertEqual(raster_param.type(), "raster") - vector_param = alg.parameterDefinition("in_vector") - self.assertEqual(vector_param.type(), "source") - field_param = alg.parameterDefinition("in_field") - self.assertEqual(field_param.type(), "field") - self.assertEqual(field_param.parentLayerParameterName(), "in_vector") - extent_param = alg.parameterDefinition("in_extent") - self.assertEqual(extent_param.type(), "extent") - string_param = alg.parameterDefinition("in_string") - self.assertEqual(string_param.type(), "string") - file_param = alg.parameterDefinition("in_file") - self.assertEqual(file_param.type(), "file") - number_param = alg.parameterDefinition("in_number") - self.assertEqual(number_param.type(), "number") - self.assertEqual(number_param.dataType(), QgsProcessingParameterNumber.Double) - enum_param = alg.parameterDefinition("in_enum") - self.assertEqual(enum_param.type(), "enum") - enum_param = alg.parameterDefinition("in_enum2") - self.assertEqual(enum_param.type(), "enum") - self.assertEqual(enum_param.options(), ["normal", "log10", "ln", "sqrt", "exp"]) - bool_param = alg.parameterDefinition("in_bool") - self.assertEqual(bool_param.type(), "boolean") - - # outputs - vector_output = alg.outputDefinition("out_vector") - self.assertEqual(vector_output.type(), "outputVector") - self.assertEqual(vector_output.dataType(), QgsProcessing.TypeVectorAnyGeometry) - vector_dest_param = alg.parameterDefinition("param_vector_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorAnyGeometry) - - table_output = alg.outputDefinition("out_table") - self.assertEqual(table_output.type(), "outputVector") - self.assertEqual(table_output.dataType(), QgsProcessing.TypeVector) - table_dest_param = alg.parameterDefinition("param_table_dest") - self.assertEqual(table_dest_param.type(), "vectorDestination") - self.assertEqual(table_dest_param.dataType(), QgsProcessing.TypeVector) - - vector_dest_param = alg.parameterDefinition("param_vector_dest2") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorAnyGeometry) - - vector_dest_param = alg.parameterDefinition("param_vector_point_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorPoint) - - vector_dest_param = alg.parameterDefinition("param_vector_line_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorLine) - - vector_dest_param = alg.parameterDefinition("param_vector_polygon_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorPolygon) - - raster_output = alg.outputDefinition("out_raster") - self.assertEqual(raster_output.type(), "outputRaster") - raster_dest_param = alg.parameterDefinition("param_raster_dest") - self.assertEqual(raster_dest_param.type(), "rasterDestination") - number_output = alg.outputDefinition("out_number") - self.assertEqual(number_output.type(), "outputNumber") - string_output = alg.outputDefinition("out_string") - self.assertEqual(string_output.type(), "outputString") - layer_output = alg.outputDefinition("out_layer") - self.assertEqual(layer_output.type(), "outputLayer") - folder_output = alg.outputDefinition("out_folder") - self.assertEqual(folder_output.type(), "outputFolder") - folder_dest_param = alg.parameterDefinition("param_folder_dest") - self.assertEqual(folder_dest_param.type(), "folderDestination") - html_output = alg.outputDefinition("out_html") - self.assertEqual(html_output.type(), "outputHtml") - html_dest_param = alg.parameterDefinition("param_html_dest") - self.assertEqual(html_dest_param.type(), "fileDestination") - file_output = alg.outputDefinition("out_file") - self.assertEqual(file_output.type(), "outputFile") - file_dest_param = alg.parameterDefinition("param_file_dest") - self.assertEqual(file_dest_param.type(), "fileDestination") - csv_output = alg.outputDefinition("out_csv") - self.assertEqual(csv_output.type(), "outputFile") - csv_dest_param = alg.parameterDefinition("param_csv_dest") - self.assertEqual(csv_dest_param.type(), "fileDestination") - self.assertEqual(csv_dest_param.defaultFileExtension(), "csv") - - # test display_name - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_3.rsx")) - alg.initAlgorithm() - self.assertFalse(alg.error) - self.assertEqual(alg.name(), "thealgid") - self.assertEqual(alg.displayName(), "the algo title") - self.assertEqual(alg.group(), "my group") - self.assertEqual(alg.groupId(), "my group") - - # test that inputs are defined as parameter description - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_4.rsx")) - alg.initAlgorithm() - self.assertFalse(alg.error) - self.assertEqual(alg.name(), "mytest") - self.assertEqual(alg.displayName(), "my test") - self.assertEqual(alg.group(), "my group") - self.assertEqual(alg.groupId(), "my group") - - raster_param = alg.parameterDefinition("in_raster") - self.assertEqual(raster_param.type(), "raster") - vector_param = alg.parameterDefinition("in_vector") - self.assertEqual(vector_param.type(), "source") - field_param = alg.parameterDefinition("in_field") - self.assertEqual(field_param.type(), "field") - self.assertEqual(field_param.parentLayerParameterName(), "in_vector") - extent_param = alg.parameterDefinition("in_extent") - self.assertEqual(extent_param.type(), "extent") - crs_param = alg.parameterDefinition("in_crs") - self.assertEqual(crs_param.type(), "crs") - string_param = alg.parameterDefinition("in_string") - self.assertEqual(string_param.type(), "string") - number_param = alg.parameterDefinition("in_number") - self.assertEqual(number_param.type(), "number") - self.assertEqual(number_param.dataType(), QgsProcessingParameterNumber.Integer) - enum_param = alg.parameterDefinition("in_enum") - self.assertEqual(enum_param.type(), "enum") - bool_param = alg.parameterDefinition("in_bool") - self.assertEqual(bool_param.type(), "boolean") - - file_param = alg.parameterDefinition("in_file") - self.assertEqual(file_param.type(), "file") - self.assertEqual(file_param.behavior(), QgsProcessingParameterFile.File) - folder_param = alg.parameterDefinition("in_folder") - self.assertEqual(folder_param.type(), "file") - self.assertEqual(folder_param.behavior(), QgsProcessingParameterFile.Folder) - gpkg_param = alg.parameterDefinition("in_gpkg") - self.assertEqual(gpkg_param.type(), "file") - self.assertEqual(gpkg_param.behavior(), QgsProcessingParameterFile.File) - self.assertEqual(gpkg_param.extension(), "gpkg") - img_param = alg.parameterDefinition("in_img") - self.assertEqual(img_param.type(), "file") - self.assertEqual(img_param.behavior(), QgsProcessingParameterFile.File) - self.assertEqual(img_param.extension(), "") - self.assertEqual(img_param.fileFilter(), "PNG Files (*.png);; JPG Files (*.jpg *.jpeg)") - - vector_dest_param = alg.parameterDefinition("param_vector_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorAnyGeometry) - vector_dest_param = alg.parameterDefinition("param_vector_point_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorPoint) - vector_dest_param = alg.parameterDefinition("param_vector_line_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorLine) - vector_dest_param = alg.parameterDefinition("param_vector_polygon_dest") - self.assertEqual(vector_dest_param.type(), "vectorDestination") - self.assertEqual(vector_dest_param.dataType(), QgsProcessing.TypeVectorPolygon) - table_dest_param = alg.parameterDefinition("param_table_dest") - self.assertEqual(table_dest_param.type(), "vectorDestination") - self.assertEqual(table_dest_param.dataType(), QgsProcessing.TypeVector) - raster_dest_param = alg.parameterDefinition("param_raster_dest") - self.assertEqual(raster_dest_param.type(), "rasterDestination") - - folder_dest_param = alg.parameterDefinition("param_folder_dest") - self.assertEqual(folder_dest_param.type(), "folderDestination") - file_dest_param = alg.parameterDefinition("param_file_dest") - self.assertEqual(file_dest_param.type(), "fileDestination") - html_dest_param = alg.parameterDefinition("param_html_dest") - self.assertEqual(html_dest_param.type(), "fileDestination") - self.assertEqual(html_dest_param.fileFilter(), "HTML Files (*.html)") - self.assertEqual(html_dest_param.defaultFileExtension(), "html") - csv_dest_param = alg.parameterDefinition("param_csv_dest") - self.assertEqual(csv_dest_param.type(), "fileDestination") - self.assertEqual(csv_dest_param.fileFilter(), "CSV Files (*.csv)") - self.assertEqual(csv_dest_param.defaultFileExtension(), "csv") - img_dest_param = alg.parameterDefinition("param_img_dest") - self.assertEqual(img_dest_param.type(), "fileDestination") - self.assertEqual(img_dest_param.fileFilter(), "PNG Files (*.png);; JPG Files (*.jpg *.jpeg)") - self.assertEqual(img_dest_param.defaultFileExtension(), "png") - - def testBadAlgorithm(self): - """ - Test a bad script - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "bad_algorithm.rsx")) - alg.initAlgorithm() - self.assertEqual(alg.name(), "bad_algorithm") - self.assertEqual(alg.displayName(), "bad algorithm") - self.assertEqual(alg.error, "This script has a syntax error.\nProblem with line: polyg=xvector") - - def testInputs(self): - """ - Test creation of script with algorithm inputs - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_2.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - # enum evaluation - script = alg.build_import_commands({"in_enum": 0}, context, feedback) - self.assertIn("in_enum <- 0", script) - - # boolean evaluation - script = alg.build_import_commands({"in_bool": True}, context, feedback) - self.assertIn("in_bool <- TRUE", script) - script = alg.build_import_commands({"in_bool": False}, context, feedback) - self.assertIn("in_bool <- FALSE", script) - - # number evaluation - script = alg.build_import_commands({"in_number": None}, context, feedback) - self.assertIn("in_number <- NULL", script) - script = alg.build_import_commands({"in_number": 5}, context, feedback) - self.assertIn("in_number <- 5.0", script) - script = alg.build_import_commands({"in_number": 5.5}, context, feedback) - self.assertIn("in_number <- 5.5", script) - - # folder destination - script = alg.build_import_commands( - {"param_folder_dest": "/tmp/processing/test_algorithm_2_r/"}, context, feedback - ) - - # file destination - script = alg.build_import_commands( - {"param_html_dest": "/tmp/processing/test_algorithm_2_r/dest.html"}, context, feedback - ) - self.assertIn('param_html_dest <- "/tmp/processing/test_algorithm_2_r/dest.html"', script) - script = alg.build_import_commands( - {"param_file_dest": "/tmp/processing/test_algorithm_2_r/dest.file"}, context, feedback - ) - self.assertIn('param_file_dest <- "/tmp/processing/test_algorithm_2_r/dest.file"', script) - script = alg.build_import_commands( - {"param_csv_dest": "/tmp/processing/test_algorithm_2_r/dest.csv"}, context, feedback - ) - self.assertIn('param_csv_dest <- "/tmp/processing/test_algorithm_2_r/dest.csv"', script) - - def testReadSf(self): - """ - Test reading vector inputs - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_vectorin.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands({"Layer": os.path.join(test_data_path, "lines.shp")}, context, feedback) - - USE_NEW_API = Qgis.QGIS_VERSION_INT >= 30900 and hasattr( - QgsProcessingAlgorithm, "parameterAsCompatibleSourceLayerPathAndLayerName" - ) - if USE_NEW_API: - self.assertEqual( - script[0], - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - ) - else: - self.assertEqual( - script[0], - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - ) - script = alg.build_import_commands( - {"Layer": os.path.join(test_data_path, "lines.shp").replace("/", "\\")}, context, feedback - ) - if USE_NEW_API: - self.assertEqual( - script[0], - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - ) - else: - self.assertEqual( - script[0], - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - ) - vl = QgsVectorLayer(os.path.join(test_data_path, "test_gpkg.gpkg") + "|layername=points") - self.assertTrue(vl.isValid()) - vl2 = QgsVectorLayer(os.path.join(test_data_path, "test_gpkg.gpkg") + "|layername=lines") - self.assertTrue(vl2.isValid()) - script = alg.build_import_commands({"Layer": vl, "Layer2": vl2}, context, feedback) - - if USE_NEW_API: - # use the newer api and avoid unnecessary layer translation - self.assertEqual( - script, - [ - 'Layer <- st_read("{}", layer = "points", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "test_gpkg.gpkg") - ), - 'Layer2 <- st_read("{}", layer = "lines", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "test_gpkg.gpkg") - ), - ], - ) - else: - # older version, forced to use inefficient api - self.assertIn('Layer <- st_read("/tmp', script[0]) - self.assertIn('Layer2 <- st_read("/tmp', script[1]) - - def testRasterIn(self): - """ - Test reading raster inputs - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_rasterin.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands({"Layer": os.path.join(test_data_path, "dem.tif")}, context, feedback) - self.assertEqual(script, ['Layer <- brick("{}")'.format(os.path.join(test_data_path, "dem.tif"))]) - script = alg.build_import_commands( - {"Layer": os.path.join(test_data_path, "dem.tif").replace("/", "\\")}, context, feedback - ) - self.assertEqual(script, ['Layer <- brick("{}")'.format(os.path.join(test_data_path, "dem.tif"))]) - script = alg.build_import_commands({"Layer": None}, context, feedback) - self.assertEqual(script, ["Layer <- NULL"]) - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_rasterin_names.rsx")) - alg.initAlgorithm() - script = alg.build_import_commands({"Layer": os.path.join(test_data_path, "dem.tif")}, context, feedback) - self.assertEqual(script, ['Layer <- "{}"'.format(os.path.join(test_data_path, "dem.tif"))]) - script = alg.build_import_commands({"Layer": None}, context, feedback) - self.assertEqual(script, ["Layer <- NULL"]) - - def testMultiRasterIn(self): - """ - Test raster multilayer input parameter - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_multirasterin.rsx")) - alg.initAlgorithm() - raster_param = alg.parameterDefinition("Layer") - self.assertEqual(raster_param.type(), "multilayer") - self.assertEqual(raster_param.layerType(), QgsProcessing.TypeRaster) - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands( - {"Layer": [os.path.join(test_data_path, "dem.tif"), os.path.join(test_data_path, "dem2.tif")]}, - context, - feedback, - ) - self.assertEqual( - script, - [ - 'tempvar0 <- brick("{}")'.format(os.path.join(test_data_path, "dem.tif")), - 'tempvar1 <- brick("{}")'.format(os.path.join(test_data_path, "dem2.tif")), - "Layer = list(tempvar0,tempvar1)", - ], - ) - script = alg.build_import_commands({"Layer": []}, context, feedback) - self.assertEqual(script, ["Layer = list()"]) - - def testMultiVectorIn(self): - """ - Test vector multilayer input parameter - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_multivectorin.rsx")) - alg.initAlgorithm() - param = alg.parameterDefinition("Layer") - self.assertEqual(param.type(), "multilayer") - self.assertEqual(param.layerType(), QgsProcessing.TypeVectorAnyGeometry) - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands( - {"Layer": [os.path.join(test_data_path, "lines.shp"), os.path.join(test_data_path, "points.gml")]}, - context, - feedback, - ) - self.assertEqual( - script, - [ - 'tempvar0 <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - 'tempvar1 <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "points.gml") - ), - "Layer = list(tempvar0,tempvar1)", - ], - ) - script = alg.build_import_commands({"Layer": []}, context, feedback) - self.assertEqual(script, ["Layer = list()"]) - - def testMultiFieldIn(self): - """ - Test multiple field input parameter - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_field_multiple.rsx")) - alg.initAlgorithm() - param = alg.parameterDefinition("MultiField") - self.assertEqual(param.type(), "field") - self.assertTrue(param.allowMultiple()) - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands({"Layer": os.path.join(test_data_path, "lines.shp")}, context, feedback) - self.assertEqual( - script, - [ - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - "MultiField <- NULL", - ], - ) - script = alg.build_import_commands( - {"Layer": os.path.join(test_data_path, "lines.shp"), "MultiField": ["a"]}, context, feedback - ) - self.assertEqual( - script, - [ - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - 'MultiField <- c("a")', - ], - ) - script = alg.build_import_commands( - {"Layer": os.path.join(test_data_path, "lines.shp"), "MultiField": ["a", 'b"c']}, context, feedback - ) - self.assertEqual( - script, - [ - 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( - os.path.join(test_data_path, "lines.shp") - ), - 'MultiField <- c("a","b\\"c")', - ], - ) - - def testVectorOutputs(self): - """ - Test writing vector outputs - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_vectorout.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_export_commands( - {"Output": "/home/test/lines.shp", "OutputCSV": "/home/test/tab.csv"}, context, feedback - ) - self.assertEqual( - script, - [ - 'st_write(Output, "/home/test/lines.shp", layer = "lines", quiet = TRUE)', - 'write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)', - ], - ) - script = alg.build_export_commands( - {"Output": "/home/test/lines.gpkg", "OutputCSV": "/home/test/tab.csv"}, context, feedback - ) - self.assertEqual( - script, - [ - 'st_write(Output, "/home/test/lines.gpkg", layer = "lines", quiet = TRUE)', - 'write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)', - ], - ) - - def testMultiOutputs(self): - """ - Test writing vector outputs - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_multiout.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_export_commands( - {"Output": "/home/test/lines.shp", "OutputCSV": "/home/test/tab.csv", "OutputFile": "/home/test/file.csv"}, - context, - feedback, - ) - - self.assertIn('st_write(Output, "/home/test/lines.shp", layer = "lines", quiet = TRUE)', script) - self.assertIn('write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)', script) - self.assertTrue(script[2].startswith('cat("##OutputFile", file='), script[2]) - self.assertTrue(script[3].startswith("cat(OutputFile, file="), script[3]) - self.assertTrue(script[4].startswith('cat("##OutputNum", file='), script[4]) - self.assertTrue(script[5].startswith("cat(OutputNum, file="), script[5]) - self.assertTrue(script[6].startswith('cat("##OutputStr", file='), script[6]) - self.assertTrue(script[7].startswith("cat(OutputStr, file="), script[7]) - - def testEnums(self): - """ - Test for both enum types - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_enums.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - script = alg.build_import_commands({"enum_normal": 0}, context, feedback) - self.assertIn("enum_normal <- 0", script) - script = alg.build_import_commands({"enum_string": 0}, context, feedback) - self.assertIn('enum_string <- "enum_a"', script) - - def testEnumsMultiple(self): - """ - Test for both enum multiple types - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_enum_multiple.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - params = {"enum_normal": [0, 1], "enum_string": [0, 1], "R_CONSOLE_OUTPUT": "TEMPORARY_OUTPUT"} - - script = alg.build_import_commands(params, context, feedback) - - self.assertIn("enum_normal <- c(0, 1)", script) - self.assertIn('enum_string <- c("enum_a","enum_b")', script) - self.assertIn("enum_string_optional <- NULL", script) - self.assertIn("enum_normal_optional <- NULL", script) - - params = { - "enum_normal": [0, 1, 2], - "enum_string": [0, 2], - "enum_string_optional": [0], - "enum_normal_optional": [1], - "R_CONSOLE_OUTPUT": "TEMPORARY_OUTPUT", - } - - script = alg.build_import_commands(params, context, feedback) - - self.assertIn("enum_normal <- c(0, 1, 2)", script) - self.assertIn('enum_string <- c("enum_a","enum_c")', script) - self.assertIn('enum_string_optional <- c("enum_a")', script) - self.assertIn("enum_normal_optional <- c(1)", script) - - def testAlgHelp(self): # pylint: disable=too-many-locals,too-many-statements - """ - Test algorithm help - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_1.rsx")) - alg.initAlgorithm() - self.assertIn("A polygon layer", alg.shortHelpString()) - self.assertIn("Me2", alg.shortHelpString()) - self.assertIn("Test help.", alg.shortHelpString()) - - # param help - if Qgis.QGIS_VERSION_INT >= 31604: - polyg_param = alg.parameterDefinition("polyg") - self.assertEqual(polyg_param.help(), "A polygon layer") - - # no help file - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_2.rsx")) - alg.initAlgorithm() - self.assertEqual(alg.shortHelpString(), "") - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_algorithm_inline_help.rsx")) - alg.initAlgorithm() - self.assertIn("A polygon layer", alg.shortHelpString()) - self.assertIn("Me2", alg.shortHelpString()) - self.assertIn("Test help.", alg.shortHelpString()) - - # param help - if Qgis.QGIS_VERSION_INT >= 31604: - polyg_param = alg.parameterDefinition("polyg") - self.assertEqual(polyg_param.help(), "A polygon layer description from multi-lines") - - def testAlgDontLoadAnyPackages(self): - """ - Test dont_load_any_packages keyword - """ - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_dont_load_any_packages.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - script = alg.build_r_script({}, context, feedback) - self.assertNotIn('library("sf")', script) - self.assertNotIn('library("raster")', script) - self.assertNotIn('library("rgdal")', script) - self.assertNotIn('library("sp")', script) - - def testAlgPointInput(self): - """ - Test Point parameter - """ - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_input_point.rsx")) - alg.initAlgorithm() - - script = alg.build_r_script({"point": "20.219926,49.138354 [EPSG:4326]"}, context, feedback) - - self.assertIn('library("sf")', script) - self.assertIn("point <- st_sfc(st_point(c(20.219926,49.138354)), crs = point_crs)", script) - - def testAlgRangeInput(self): - """ - Test range parameter - """ - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_input_range.rsx")) - alg.initAlgorithm() - - script = alg.build_r_script({"range": [0, 1]}, context, feedback) - self.assertIn("range <- c(min = 0.0, max = 1.0)", script) - script = alg.build_r_script({"range": [5, 10]}, context, feedback) - self.assertIn("range <- c(min = 5.0, max = 10.0)", script) - script = alg.build_r_script({"range": [0.5, 1.5]}, context, feedback) - self.assertIn("range <- c(min = 0.5, max = 1.5)", script) - - def testAlgColorInput(self): - """ - Test color parameter - """ - - if Qgis.QGIS_VERSION_INT < 31000: - self.skipTest("QGIS version does not support this.") - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_input_color.rsx")) - alg.initAlgorithm() - - script = alg.build_r_script({"color": QColor(0, 255, 0)}, context, feedback) - self.assertIn("color <- rgb(0, 255, 0, 255, maxColorValue = 255)", script) - script = alg.build_r_script({"color": QColor(255, 0, 0)}, context, feedback) - self.assertIn("color <- rgb(255, 0, 0, 255, maxColorValue = 255)", script) - - def testAlgDateTimeInput(self): - """ - Test datetime parameter - """ - - if Qgis.QGIS_VERSION_INT < 31400: - self.skipTest("QGIS version does not support this.") - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_input_datetime.rsx")) - alg.initAlgorithm() - - script = alg.build_r_script({"datetime": QDateTime(QDate(2021, 10, 1), QTime(16, 57, 0))}, context, feedback) - self.assertIn('datetime <- as.POSIXct("2021-10-01T16:57:00", format = "%Y-%m-%dT%H:%M:%S")', script) - script = alg.build_r_script({"datetime": QDateTime(QDate(2021, 10, 14), QTime(14, 48, 52))}, context, feedback) - self.assertIn('datetime <- as.POSIXct("2021-10-14T14:48:52", format = "%Y-%m-%dT%H:%M:%S")', script) - - def testAlgExpressions(self): - """ - Test Expression parameter - """ - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_input_expression.rsx")) - alg.initAlgorithm() - - script = alg.build_r_script({""}, context, feedback) - - self.assertIn("number <- 6", script) - self.assertTrue( - any(['geometry <- sf::st_as_sfc("Polygon ' in line for line in script]) - ) # pylint: disable=use-a-generator - self.assertIn('date_a <- as.POSIXct("2020-05-04", format = "%Y-%m-%d")', script) - self.assertIn('time_a <- lubridate::hms("13:45:30")', script) - - def testAlgRasterBand(self): - """ - Test datetime parameter - """ - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_raster_band.rsx")) - alg.initAlgorithm() - - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - script = alg.build_import_commands( - {"Band": 1, "Layer": os.path.join(test_data_path, "dem.tif")}, context, feedback - ) - self.assertIn("Band <- 1", script) - - def testAlgLibraryWithOptions(self): - """ - Test library with options - """ - - alg = RAlgorithm(description_file=os.path.join(test_data_path, "test_library_with_option.rsx")) - alg.initAlgorithm() - - script = alg.r_templates.build_script_header_commands(alg.script) - - self.assertIn( - 'tryCatch(find.package("MASS"), error = function(e) install.packages("MASS", dependencies=TRUE))', script - ) - self.assertIn( - 'tryCatch(find.package("Matrix"), error = function(e) install.packages("Matrix", dependencies=TRUE))', - script, - ) - self.assertIn('library("MASS", quietly=True)', script) - self.assertIn('library("Matrix")', script) - - -if __name__ == "__main__": - suite = unittest.makeSuite(AlgorithmTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/processing_r/test/test_guiutils.py b/processing_r/test/test_guiutils.py deleted file mode 100644 index 02a60d6..0000000 --- a/processing_r/test/test_guiutils.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding=utf-8 -"""GUI Utils Test. - -.. note:: 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. - -""" - -__author__ = "(C) 2018 by Nyall Dawson" -__date__ = "20/04/2018" -__copyright__ = "Copyright 2018, North Road" -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = "$Format:%H$" - -import unittest - -from processing_r.gui.gui_utils import GuiUtils - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - - -class GuiUtilsTest(unittest.TestCase): - """Test GuiUtils work.""" - - def testGetIcon(self): - """ - Tests get_icon - """ - self.assertFalse(GuiUtils.get_icon("providerR.svg").isNull()) - self.assertTrue(GuiUtils.get_icon("not_an_icon.svg").isNull()) - - def testGetIconSvg(self): - """ - Tests get_icon svg path - """ - self.assertTrue(GuiUtils.get_icon_svg("providerR.svg")) - self.assertIn("providerR.svg", GuiUtils.get_icon_svg("providerR.svg")) - self.assertFalse(GuiUtils.get_icon_svg("not_an_icon.svg")) - - -if __name__ == "__main__": - suite = unittest.makeSuite(GuiUtilsTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/processing_r/test/test_init.py b/processing_r/test/test_init.py deleted file mode 100644 index 662ae68..0000000 --- a/processing_r/test/test_init.py +++ /dev/null @@ -1,62 +0,0 @@ -# coding=utf-8 -"""Tests QGIS plugin init. - -.. note:: 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. -""" - -__author__ = "Nyall Dawson " -__revision__ = "$Format:%H$" -__date__ = "20/04/2018" -__license__ = "GPL" -__copyright__ = "Copyright 2018, LINZ" - - -import configparser -import logging -import os -import unittest - -LOGGER = logging.getLogger("QGIS") - - -class TestInit(unittest.TestCase): - """Test that the plugin init is usable for QGIS. - - Based heavily on the validator class by Alessandro - Passoti available here: - - http://github.com/qgis/qgis-django/blob/master/qgis-app/ - plugins/validator.py - - """ - - def test_read_init(self): - """Test that the plugin __init__ will validate on plugins.qgis.org.""" - - # You should update this list according to the latest in - # https://github.com/qgis/qgis-django/blob/master/qgis-app/ - # plugins/validator.py - - required_metadata = ["name", "description", "version", "qgisMinimumVersion", "email", "author"] - - file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "metadata.txt")) - LOGGER.info(file_path) - metadata = [] - parser = configparser.ConfigParser() - parser.optionxform = str - parser.read(file_path) - message = 'Cannot find a section named "general" in %s' % file_path - assert parser.has_section("general"), message - metadata.extend(parser.items("general")) - - for expectation in required_metadata: - message = 'Cannot find metadata "%s" in metadata source (%s).' % (expectation, file_path) - - self.assertIn(expectation, dict(metadata), message) - - -if __name__ == "__main__": - unittest.main() diff --git a/processing_r/test/test_qgis_environment.py b/processing_r/test/test_qgis_environment.py deleted file mode 100644 index 653cb7a..0000000 --- a/processing_r/test/test_qgis_environment.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -"""Tests for QGIS functionality. - -.. note:: 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. - -""" -__author__ = "tim@linfiniti.com" -__date__ = "20/01/2011" -__copyright__ = "Copyright 2012, Australia Indonesia Facility for " "Disaster Reduction" - -import unittest - -from qgis.core import QgsProviderRegistry - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - - -class QGISTest(unittest.TestCase): - """Test the QGIS Environment""" - - def test_qgis_environment(self): - """QGIS environment has the expected providers""" - - r = QgsProviderRegistry.instance() - self.assertIn("gdal", r.providerList()) - self.assertIn("ogr", r.providerList()) - self.assertIn("postgres", r.providerList()) - - -if __name__ == "__main__": - unittest.main() diff --git a/processing_r/test/test_rutils.py b/processing_r/test/test_rutils.py deleted file mode 100644 index 52d79eb..0000000 --- a/processing_r/test/test_rutils.py +++ /dev/null @@ -1,190 +0,0 @@ -# coding=utf-8 -"""R Utils Test. - -.. note:: 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. - -""" - -__author__ = "(C) 2018 by Nyall Dawson" -__date__ = "20/04/2018" -__copyright__ = "Copyright 2018, North Road" -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = "$Format:%H$" - -import os -import unittest - -from processing.core.ProcessingConfig import ProcessingConfig -from qgis.PyQt.QtCore import QCoreApplication, QSettings - -from processing_r.processing.provider import RAlgorithmProvider -from processing_r.processing.utils import RUtils - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - - -class RUtilsTest(unittest.TestCase): - """Test RUtils work.""" - - def __init__(self, methodName): - """Run before all tests and set up environment""" - super().__init__(methodName) - - # Don't mess with actual user settings - QCoreApplication.setOrganizationName("North Road") - QCoreApplication.setOrganizationDomain("qgis.org") - QCoreApplication.setApplicationName("QGIS-R") - QSettings().clear() - - # make Provider settings available - self.provider = RAlgorithmProvider() - self.provider.load() - - def testBuiltInPath(self): - """ - Tests built in scripts path - """ - self.assertTrue(RUtils.builtin_scripts_folder()) - self.assertIn("builtin_scripts", RUtils.builtin_scripts_folder()) - self.assertTrue(os.path.exists(RUtils.builtin_scripts_folder())) - - def testDefaultScriptsFolder(self): - """ - Tests default user scripts folder - """ - self.assertTrue(RUtils.default_scripts_folder()) - self.assertIn("rscripts", RUtils.default_scripts_folder()) - self.assertTrue(os.path.exists(RUtils.default_scripts_folder())) - - def testScriptsFolders(self): - """ - Test script folders - """ - self.assertTrue(RUtils.script_folders()) - self.assertIn(RUtils.default_scripts_folder(), RUtils.script_folders()) - self.assertIn(RUtils.builtin_scripts_folder(), RUtils.script_folders()) - - def testDescriptiveName(self): - """ - Tests creating descriptive name - """ - self.assertEqual(RUtils.create_descriptive_name("a B_4324_asd"), "a B 4324 asd") - - def testStripSpecialCharacters(self): - """ - Tests stripping special characters from a name - """ - self.assertEqual(RUtils.strip_special_characters("aB 43 24a:sd"), "aB4324asd") - - def test_is_windows(self): - """ - Test is_windows - """ - self.assertFalse(RUtils.is_windows()) # suck it, Windows users! - - def test_is_macos(self): - """ - Test is_macos - """ - self.assertFalse(RUtils.is_macos()) # suck it even more, MacOS users! - - def test_guess_r_binary_folder(self): - """ - Test guessing the R binary folder -- not much to do here, all the logic is Windows specific - """ - self.assertFalse(RUtils.guess_r_binary_folder()) - - def test_r_binary_folder(self): - """ - Test retrieving R binary folder - """ - self.assertFalse(RUtils.r_binary_folder()) - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/usr/local/bin") - self.assertEqual(RUtils.r_binary_folder(), "/usr/local/bin") - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) - self.assertFalse(RUtils.r_binary_folder()) - - def test_r_executable(self): - """ - Test retrieving R executable - """ - self.assertEqual(RUtils.path_to_r_executable(), "R") - self.assertEqual(RUtils.path_to_r_executable(script_executable=True), "Rscript") - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/usr/local/bin") - self.assertEqual(RUtils.path_to_r_executable(), "/usr/local/bin/R") - self.assertEqual(RUtils.path_to_r_executable(script_executable=True), "/usr/local/bin/Rscript") - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) - self.assertEqual(RUtils.path_to_r_executable(), "R") - self.assertEqual(RUtils.path_to_r_executable(script_executable=True), "Rscript") - - def test_package_repo(self): - """ - Test retrieving/setting the package repo - """ - self.assertEqual(RUtils.package_repo(), "http://cran.at.r-project.org/") - ProcessingConfig.setSettingValue(RUtils.R_REPO, "http://mirror.at.r-project.org/") - self.assertEqual(RUtils.package_repo(), "http://mirror.at.r-project.org/") - ProcessingConfig.setSettingValue(RUtils.R_REPO, "http://cran.at.r-project.org/") - self.assertEqual(RUtils.package_repo(), "http://cran.at.r-project.org/") - - def test_use_user_library(self): - """ - Test retrieving/setting the user library setting - """ - self.assertTrue(RUtils.use_user_library()) - ProcessingConfig.setSettingValue(RUtils.R_USE_USER_LIB, False) - self.assertFalse(RUtils.use_user_library()) - ProcessingConfig.setSettingValue(RUtils.R_USE_USER_LIB, True) - self.assertTrue(RUtils.use_user_library()) - - def test_library_folder(self): - """ - Test retrieving/setting the library folder - """ - self.assertIn("/profiles/default/processing/rlibs", RUtils.r_library_folder()) - ProcessingConfig.setSettingValue(RUtils.R_LIBS_USER, "/usr/local") - self.assertEqual(RUtils.r_library_folder(), "/usr/local") - ProcessingConfig.setSettingValue(RUtils.R_LIBS_USER, None) - self.assertIn("/profiles/default/processing/rlibs", RUtils.r_library_folder()) - - def test_is_error_line(self): - """ - Test is_error_line - """ - self.assertFalse(RUtils.is_error_line("xxx yyy")) - self.assertTrue(RUtils.is_error_line("Error something went wrong")) - self.assertTrue(RUtils.is_error_line("Execution halted")) - - def test_r_is_installed(self): - """ - Test checking that R is installed - """ - self.assertIsNone(RUtils.check_r_is_installed()) - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/home") - self.assertTrue(RUtils.check_r_is_installed()) - self.assertIn("R is not installed", RUtils.check_r_is_installed()) - ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) - self.assertIsNone(RUtils.check_r_is_installed()) - - def test_is_valid_r_variable(self): - """ - Test for strings to check if they are valid R variables. - """ - self.assertFalse(RUtils.is_valid_r_variable("var_name%")) - self.assertFalse(RUtils.is_valid_r_variable("2var_name")) - self.assertFalse(RUtils.is_valid_r_variable(".2var_name")) - self.assertFalse(RUtils.is_valid_r_variable("_var_name")) - self.assertTrue(RUtils.is_valid_r_variable("var_name2.")) - self.assertTrue(RUtils.is_valid_r_variable(".var_name")) - self.assertTrue(RUtils.is_valid_r_variable("var.name")) - - -if __name__ == "__main__": - suite = unittest.makeSuite(RUtilsTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/processing_r/test/test_template.py b/processing_r/test/test_template.py deleted file mode 100644 index aa2910b..0000000 --- a/processing_r/test/test_template.py +++ /dev/null @@ -1,69 +0,0 @@ -# coding=utf-8 -"""R templates Test. - -.. note:: 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. - -""" - -__author__ = "(C) 2018 by Nyall Dawson" -__date__ = "20/04/2018" -__copyright__ = "Copyright 2018, North Road" -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = "$Format:%H$" - -import os -import unittest - -from processing_r.processing.r_templates import RTemplates - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - -test_data_path = os.path.join(os.path.dirname(__file__), "data") - - -class TemplateTest(unittest.TestCase): - """Test template generation.""" - - def testGithubInstall(self): - """ - Test github install code generation. - """ - templates = RTemplates() - templates.install_github = True - templates.github_dependencies = "user_1/repo_1, user_2/repo_2" - self.assertEqual( - templates.install_package_github(templates.github_dependencies[0]), - 'remotes::install_github("user_1/repo_1")', - ) - self.assertEqual( - templates.install_package_github(templates.github_dependencies[1]), - 'remotes::install_github("user_2/repo_2")', - ) - - def testString(self): # pylint: disable=too-many-locals,too-many-statements - """ - Test string variable - """ - templates = RTemplates() - self.assertEqual(templates.set_variable_string("var", "val"), 'var <- "val"') - self.assertEqual(templates.set_variable_string("var", 'va"l'), 'var <- "va\\"l"') - - def testStringListVariable(self): # pylint: disable=too-many-locals,too-many-statements - """ - Test string list variable - """ - templates = RTemplates() - self.assertEqual(templates.set_variable_string_list("var", []), "var <- c()") - self.assertEqual(templates.set_variable_string_list("var", ["aaaa"]), 'var <- c("aaaa")') - self.assertEqual(templates.set_variable_string_list("var", ["aaaa", 'va"l']), 'var <- c("aaaa","va\\"l")') - - -if __name__ == "__main__": - suite = unittest.makeSuite(TemplateTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/processing_r/test/test_translations.py b/processing_r/test/test_translations.py deleted file mode 100644 index 9bb68fb..0000000 --- a/processing_r/test/test_translations.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding=utf-8 -"""Safe Translations Test. - -.. note:: 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. - -""" - -__author__ = "ismailsunni@yahoo.co.id" -__date__ = "12/10/2011" -__copyright__ = "Copyright 2012, Australia Indonesia Facility for " "Disaster Reduction" - -import os -import unittest - -from qgis.PyQt.QtCore import QCoreApplication, QTranslator - -from .utilities import get_qgis_app - -QGIS_APP = get_qgis_app() - - -class SafeTranslationsTest(unittest.TestCase): - """Test translations work.""" - - def setUp(self): - """Runs before each test.""" - if os.getenv("LANG"): - del os.environ["LANG"] - - def tearDown(self): - """Runs after each test.""" - if os.getenv("LANG"): - del os.environ["LANG"] - - def test_qgis_translations(self): - """Test that translations work.""" - parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) - dir_path = os.path.abspath(parent_path) - file_path = os.path.join(dir_path, "i18n", "af.qm") - self.assertTrue( - os.path.isfile(file_path), "%s is not a valid translation file or it does not exist" % file_path - ) - translator = QTranslator() - translator.load(file_path) - QCoreApplication.installTranslator(translator) - - expected_message = "Goeie more" - real_message = QCoreApplication.translate("@default", "Good morning") - self.assertEqual(real_message, expected_message) - - -if __name__ == "__main__": - suite = unittest.makeSuite(SafeTranslationsTest) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) diff --git a/processing_r/test/utilities.py b/processing_r/test/utilities.py deleted file mode 100644 index 85348b4..0000000 --- a/processing_r/test/utilities.py +++ /dev/null @@ -1,104 +0,0 @@ -# coding=utf-8 -"""Common functionality used by regression tests.""" - -import atexit -import logging -import os -import sys - -from qgis.core import QgsApplication -from qgis.gui import QgsMapCanvas -from qgis.PyQt.QtCore import QSize -from qgis.PyQt.QtWidgets import QWidget -from qgis.utils import iface - -from .qgis_interface import QgisInterface - -LOGGER = logging.getLogger("QGIS") -QGIS_APP = None # Static variable used to hold hand to running QGIS app -CANVAS = None -PARENT = None -IFACE = None - - -def get_qgis_app(cleanup=True): - """Start one QGIS application to test against. - - :returns: Handle to QGIS app, canvas, iface and parent. If there are any - errors the tuple members will be returned as None. - :rtype: (QgsApplication, CANVAS, IFACE, PARENT) - - If QGIS is already running the handle to that app will be returned. - """ - - global QGIS_APP, PARENT, IFACE, CANVAS # pylint: disable=W0603 - - if iface: - QGIS_APP = QgsApplication - CANVAS = iface.mapCanvas() - PARENT = iface.mainWindow() - IFACE = iface - return QGIS_APP, CANVAS, IFACE, PARENT - - global QGISAPP # pylint: disable=global-variable-undefined - - try: - QGISAPP # pylint: disable=E0601 - except NameError: - myGuiFlag = True # All test will run qgis in gui mode - - # In python3 we need to convert to a bytes object (or should - # QgsApplication accept a QString instead of const char* ?) - try: - argvb = list(map(os.fsencode, sys.argv)) - except AttributeError: - argvb = sys.argv - - # Note: QGIS_PREFIX_PATH is evaluated in QgsApplication - - # no need to mess with it here. - QGISAPP = QgsApplication(argvb, myGuiFlag) - - QGISAPP.initQgis() - s = QGISAPP.showSettings() - LOGGER.debug(s) - - def debug_log_message(message, tag, level): - """ - Prints a debug message to a log - :param message: message to print - :param tag: log tag - :param level: log message level (severity) - :return: - """ - print("{}({}): {}".format(tag, level, message)) - - QgsApplication.instance().messageLog().messageReceived.connect(debug_log_message) - - if cleanup: - - @atexit.register - def exitQgis(): # pylint: disable=unused-variable - """ - Gracefully closes the QgsApplication instance - """ - try: - QGISAPP.exitQgis() # pylint: disable=used-before-assignment - QGISAPP = None # pylint: disable=redefined-outer-name - except NameError: - pass - - if PARENT is None: - # noinspection PyPep8Naming - PARENT = QWidget() - - if CANVAS is None: - # noinspection PyPep8Naming - CANVAS = QgsMapCanvas(PARENT) - CANVAS.resize(QSize(400, 400)) - - if IFACE is None: - # QgisInterface is a stub implementation of the QGIS plugin interface - # noinspection PyPep8Naming - IFACE = QgisInterface(CANVAS) - - return QGISAPP, CANVAS, IFACE, PARENT diff --git a/processing_r/test_suite.py b/processing_r/test_suite.py deleted file mode 100644 index b1b79af..0000000 --- a/processing_r/test_suite.py +++ /dev/null @@ -1,93 +0,0 @@ -# coding=utf-8 -""" -Test Suite. - -.. note:: 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. - -""" - -import os -import sys -import tempfile -import unittest - -import qgis # pylint: disable=unused-import -from osgeo import gdal -from qgis.core import Qgis - -try: - from pip import main as pipmain -except ImportError: - from pip._internal import main as pipmain - -try: - import coverage -except ImportError: - pipmain(["install", "coverage"]) - import coverage - -__author__ = "Alessandro Pasotti" -__revision__ = "$Format:%H$" -__date__ = "30/04/2018" -__copyright__ = "Copyright 2018, North Road" - - -def _run_tests(test_suite, package_name, with_coverage=False): - """Core function to test a test suite.""" - count = test_suite.countTestCases() - print("########") - print("%s tests has been discovered in %s" % (count, package_name)) - print("Python GDAL : %s" % gdal.VersionInfo("VERSION_NUM")) - print("QGIS version : {}".format(Qgis.version())) - print("########") - if with_coverage: - cov = coverage.Coverage( - source=["/processing_r"], - omit=["*/test/*"], - ) - cov.start() - - unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite) - - if with_coverage: - cov.stop() - cov.save() - - with tempfile.NamedTemporaryFile(delete=False) as report: - cov.report(file=report) - # Produce HTML reports in the `htmlcov` folder and open index.html - # cov.html_report() - report.close() - - with open(report.name, "r", encoding="utf8") as fin: - print(fin.read()) - - -def test_package(package="processing_r"): - """Test package. - This function is called by travis without arguments. - - :param package: The package to test. - :type package: str - """ - test_loader = unittest.defaultTestLoader - try: - test_suite = test_loader.discover(package) - except ImportError: - test_suite = unittest.TestSuite() - _run_tests(test_suite, package) - - -def test_environment(): - """Test package with an environment variable.""" - package = os.environ.get("TESTING_PACKAGE", "processing_r") - test_loader = unittest.defaultTestLoader - test_suite = test_loader.discover(package) - _run_tests(test_suite, package) - - -if __name__ == "__main__": - test_package() diff --git a/pyproject.toml b/pyproject.toml index b8b2bab..8e8eaaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,14 @@ max-line-length = 120 [tool.pytest.ini_options] testpaths = [ - "processing_r/tests" + "tests" +] +addopts = [ + "--cov=processing_r", + "--cov-report=term-missing:skip-covered", + "-rP", + "-vv", + "-s" ] [tool.black] diff --git a/scripts/run_docker_locally.sh b/scripts/run_docker_locally.sh new file mode 100755 index 0000000..44f973d --- /dev/null +++ b/scripts/run_docker_locally.sh @@ -0,0 +1,34 @@ +DOCKER_IMAGE=qgis/qgis +DOCKER_IMAGE_TAG=latest +CONTAINER_NAME=qgis-testing-environment +PYTHON_SETUP="PYTHONPATH=/usr/share/qgis/python/:/usr/share/qgis/python/plugins:/usr/lib/python3/dist-packages/qgis:/usr/share/qgis/python/qgis:/tests_directory" + +if [ ! "$(docker ps -a | grep $CONTAINER_NAME)" ]; then + echo "CONTAINER NOT FOUND CREATING!" + + docker pull "$DOCKER_IMAGE":$DOCKER_IMAGE_TAG + docker run -d --name $CONTAINER_NAME -v .:/tests_directory -e DISPLAY=:99 "$DOCKER_IMAGE":$DOCKER_IMAGE_TAG + + docker exec $CONTAINER_NAME sh -c "pip3 install -r /tests_directory/REQUIREMENTS_TESTING.txt" + docker exec $CONTAINER_NAME sh -c "apt-get update -qq" + docker exec $CONTAINER_NAME sh -c "apt-get install --yes --no-install-recommends wget ca-certificates gnupg" + + docker exec $CONTAINER_NAME sh -c "wget -q -O- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc" + docker exec $CONTAINER_NAME sh -c "echo \"deb [arch=amd64] https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/\" > /etc/apt/sources.list.d/cran_r.list" + docker exec $CONTAINER_NAME sh -c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 67C2D66C4B1D4339 51716619E084DAB9" + + docker exec $CONTAINER_NAME sh -c "wget -q -O- https://eddelbuettel.github.io/r2u/assets/dirk_eddelbuettel_key.asc | tee -a /etc/apt/trusted.gpg.d/cranapt_key.asc" + docker exec $CONTAINER_NAME sh -c "echo \"deb [arch=amd64] https://r2u.stat.illinois.edu/ubuntu jammy main\" > /etc/apt/sources.list.d/cranapt.list" + + docker exec $CONTAINER_NAME sh -c "apt update -qq" + + docker exec $CONTAINER_NAME sh -c "apt-get install -y r-base r-cran-sf r-cran-raster" +else + echo "CONTAINER FOUND, STARTING" + docker start $CONTAINER_NAME +fi + +docker exec $CONTAINER_NAME sh -c "$PYTHON_SETUP && cd tests_directory && pytest tests --cov=processing_r --cov-report=term-missing:skip-covered -rP -vv -s" + +docker stop $CONTAINER_NAME +echo "CONTAINER STOPPED" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..83ccb04 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,38 @@ +from pathlib import Path + +import pytest +from processing.core.ProcessingConfig import ProcessingConfig, Setting +from qgis.core import QgsApplication +from qgis.PyQt.QtCore import QCoreApplication, QSettings + +from processing_r.processing.provider import RAlgorithmProvider +from processing_r.processing.utils import RUtils + + +@pytest.fixture +def data_folder() -> Path: + return Path(__file__).parent / "data" + + +@pytest.fixture(autouse=True, scope="session") +def setup_plugin(): + QCoreApplication.setOrganizationName("North Road") + QCoreApplication.setOrganizationDomain("qgis.org") + QCoreApplication.setApplicationName("QGIS-R") + QSettings().clear() + + +@pytest.fixture(autouse=True, scope="session") +def plugin_provider(setup_plugin, qgis_processing) -> RAlgorithmProvider: + provider = RAlgorithmProvider() + + QgsApplication.processingRegistry().addProvider(provider) + + test_scripts_path = Path(__file__).parent / "scripts" + scripts_paths = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER) + ";" + test_scripts_path.as_posix() + + ProcessingConfig.setSettingValue(RUtils.RSCRIPTS_FOLDER, scripts_paths) + + provider.loadAlgorithms() + + return provider diff --git a/processing_r/test/data/dem.tif b/tests/data/dem.tif similarity index 100% rename from processing_r/test/data/dem.tif rename to tests/data/dem.tif diff --git a/processing_r/test/data/dem.tif.aux.xml b/tests/data/dem.tif.aux.xml similarity index 100% rename from processing_r/test/data/dem.tif.aux.xml rename to tests/data/dem.tif.aux.xml diff --git a/processing_r/test/data/dem2.tif b/tests/data/dem2.tif similarity index 100% rename from processing_r/test/data/dem2.tif rename to tests/data/dem2.tif diff --git a/processing_r/test/data/dem2.tif.aux.xml b/tests/data/dem2.tif.aux.xml similarity index 100% rename from processing_r/test/data/dem2.tif.aux.xml rename to tests/data/dem2.tif.aux.xml diff --git a/processing_r/test/data/layers.gpx b/tests/data/layers.gpx similarity index 99% rename from processing_r/test/data/layers.gpx rename to tests/data/layers.gpx index cb69ff7..b00a476 100644 --- a/processing_r/test/data/layers.gpx +++ b/tests/data/layers.gpx @@ -1,1292 +1,1292 @@ - - - -802.325007Flag -887.8824008Flag -881.1768009Flag -777.3314010Flag -780.7147011Flag - -30NOV02 - -786.9631 -783.6103 -788.9138 -785.5305 -785.5305 -782.6654 -782.6654 -779.2821 -780.7147 -781.2024 -781.69 -778.3372 -782.6654 -779.7698 -781.2024 -779.7698 -779.7698 -780.2575 -778.3372 -779.7698 -779.2821 -779.2821 -779.7698 -780.2575 -780.2575 -780.2575 -782.1777 -783.1226 -783.6103 -784.5856 -786.5059 -787.9384 -788.9138 -789.8587 -790.3464 -791.2912 -790.834 -792.7543 -792.7543 -792.7543 -793.6992 -794.6745 -795.1622 -794.6745 -796.5948 -798.515 -798.515 -798.515 -798.515 -799.4599 -799.9476 -797.5396 -798.515 -800.4352 -801.4106 -801.4106 -800.4352 -801.4106 -801.8678 -802.3555 -801.8678 -801.4106 -800.9229 -800.9229 -803.3308 -802.3555 -802.8432 -802.8432 -803.788 -805.2511 -805.7083 -806.196 -807.1713 -806.6836 -807.659 -809.5792 -809.0916 -809.0916 -809.0916 -809.0916 -809.5792 -810.0364 -810.0364 -811.0118 -812.932 -812.932 -811.9567 -811.0118 -812.4444 -813.9074 -815.34 -815.8276 -815.34 -813.4197 -815.34 -816.7725 -817.2602 -817.7479 -817.7479 -818.6928 -819.6681 -819.6681 -820.613 -821.5884 -823.0209 -823.9963 -824.4535 -824.9412 -825.4288 -825.4288 -825.9165 -825.9165 -826.8614 -828.3244 -827.8368 -829.757 -830.2447 -829.757 -830.2447 -830.2447 -830.7019 -831.1896 -831.6772 -831.6772 -833.5975 -834.0852 -835.03 -835.5177 -836.9503 -834.5728 -833.5975 -834.5728 -834.5728 -835.5177 -836.0054 -836.4931 -837.438 -837.9256 -838.4133 -839.3582 -839.8459 -840.3336 -839.8459 -839.3582 -839.3582 -839.3582 -840.3336 -839.3582 -838.901 -838.901 -847.5268 -847.5268 -848.0145 -845.6066 -846.582 -849.9348 -850.9101 -850.4224 -851.855 -852.8304 -851.3978 -851.855 -851.3978 -854.2629 -854.2629 -854.7506 -854.7506 -854.2629 -854.7506 -854.2629 -855.2383 -862.9192 -862.9192 -872.5204 -872.5204 -872.5204 -871.5756 -872.0632 -871.5756 -875.416 -875.416 -875.416 -874.9284 -875.416 -875.9037 -875.9037 -880.2319 -880.2319 -880.7196 -880.2319 -881.6644 -881.6644 -880.7196 -881.6644 -881.6644 -881.1768 -882.1521 -883.5847 -884.56 -884.56 -884.56 -885.0172 -884.56 -884.56 -884.56 -884.0724 -884.56 -882.6398 -883.097 -883.097 -882.6398 -882.1521 -881.6644 -881.6644 -881.1768 -882.1521 -880.7196 -879.7442 -879.2565 -879.7442 -879.2565 -880.2319 -881.1768 -880.2319 -879.2565 -880.7196 -880.7196 -880.7196 -881.1768 -881.6644 -881.6644 -882.1521 -885.9926 -885.9926 -885.9926 -885.0172 -885.0172 -884.56 -884.0724 -884.56 -878.3116 -878.3116 -878.3116 -877.824 -878.3116 -877.3363 -877.824 -876.8486 -876.8486 -876.8486 -876.3914 -875.416 -874.4712 -876.3914 -873.0081 -874.4712 -874.4712 -873.4958 -873.4958 -873.4958 -872.5204 -872.0632 -871.0879 -871.0879 -869.6553 -868.2228 -864.8395 -865.8148 -866.7597 -866.272 -866.272 -867.2474 -867.2474 -866.7597 -866.272 -865.8148 -865.8148 -866.272 -864.8395 -865.3272 -864.3518 -864.3518 -863.8946 -863.4069 -862.9192 -863.4069 -863.8946 -862.4316 -863.4069 -863.4069 -863.4069 -862.9192 -862.4316 -861.9744 -862.4316 -860.999 -863.4069 -865.8148 -865.8148 -865.8148 -864.8395 -864.3518 -862.9192 -863.8946 -862.4316 -861.4867 -858.5911 -857.1585 -856.1832 -856.1832 -855.2383 -854.7506 -852.3427 -851.855 -850.4224 -849.9348 -848.9899 -846.582 -846.582 -845.6066 -845.6066 -845.1494 -844.6617 -843.6864 -840.8212 -839.3582 -838.4133 -838.901 -838.4133 -837.438 -836.4931 -836.0054 -835.03 -835.03 -833.1098 -833.5975 -833.1098 -832.6526 -832.6526 -832.6526 -831.6772 -831.6772 -831.1896 -830.7019 -829.757 -830.2447 -829.757 -829.757 -829.757 -828.3244 -827.8368 -827.3491 -826.4042 -826.8614 -826.4042 -825.9165 -825.4288 -824.4535 -823.9963 -824.4535 -825.4288 -825.9165 -826.4042 -827.3491 -828.3244 -827.3491 -827.3491 -827.3491 -826.8614 -826.4042 -826.4042 -827.3491 -826.4042 -826.4042 -826.4042 -825.9165 -825.9165 -825.4288 -824.9412 -824.9412 -824.4535 -824.4535 -824.4535 -823.9963 -822.5332 -822.076 -822.076 -822.076 -822.076 -822.5332 -822.076 -821.5884 -821.5884 -821.1007 -821.5884 -821.1007 -821.1007 -820.613 -821.1007 -820.1558 -819.6681 -819.1804 -819.1804 -818.6928 -818.2051 -818.2051 -818.2051 -817.2602 -817.2602 -817.2602 -816.2848 -815.8276 -815.34 -814.8523 -815.34 -815.34 -814.8523 -814.3646 -814.3646 -814.3646 -814.8523 -814.8523 -813.4197 -813.4197 -812.932 -812.932 -813.4197 -812.4444 -811.9567 -811.4995 -811.4995 -811.4995 -811.4995 -811.4995 -811.9567 -811.0118 -811.0118 -812.4444 -812.932 -813.4197 -814.3646 -814.3646 -814.8523 -814.3646 -814.8523 -813.9074 -814.8523 -813.4197 -812.932 -811.9567 -812.4444 -810.5241 -811.4995 -811.4995 -811.9567 -811.4995 -811.0118 -811.4995 -811.0118 -810.5241 -811.0118 -811.0118 -811.4995 -811.0118 -811.0118 -811.0118 -810.5241 -810.0364 -810.0364 -810.0364 -809.5792 -810.5241 -809.5792 -809.5792 -809.5792 -809.0916 -809.5792 -809.5792 -809.0916 -809.0916 -808.6039 -809.5792 -808.6039 -808.1162 -807.1713 -807.659 -807.1713 -807.1713 -806.6836 -807.1713 -806.6836 -806.6836 -806.6836 -806.196 -805.2511 -806.6836 -806.196 -806.6836 -806.6836 -806.6836 -806.6836 -806.6836 -807.1713 -806.6836 -807.659 -807.1713 -808.1162 -808.1162 -808.1162 -808.1162 -808.1162 -808.1162 -807.1713 -807.1713 -807.1713 -806.6836 -806.6836 -806.196 -806.196 -805.7083 -803.3308 -801.4106 -801.8678 -800.4352 -799.9476 -798.515 -797.5396 -798.0273 -797.0824 -797.0824 -796.5948 -795.6194 -797.5396 -797.0824 -797.0824 -797.5396 -796.1071 -796.5948 -796.5948 -796.5948 -797.0824 -797.0824 -796.5948 -798.0273 -798.515 -799.4599 -799.0027 -799.4599 -798.515 -799.4599 -799.4599 -799.4599 -799.0027 -799.4599 -799.4599 -801.4106 -801.8678 -801.4106 -802.3555 -803.788 -803.788 -803.3308 -802.8432 -802.8432 -801.4106 -802.8432 -803.3308 -802.3555 -803.3308 -801.8678 -802.8432 -802.3555 -802.8432 -802.3555 -805.7083 -805.7083 -807.1713 -806.196 -805.7083 -805.2511 -805.2511 -803.788 -803.3308 -802.3555 -800.4352 -799.9476 -800.9229 -800.4352 -800.9229 -800.9229 -799.9476 -799.4599 -799.4599 -799.0027 -799.9476 -799.4599 -798.0273 -798.515 -799.0027 -799.9476 -801.8678 -803.3308 -803.788 -804.2757 -804.2757 -804.2757 -804.2757 -803.788 -807.1713 -807.659 -809.0916 -808.6039 -808.1162 -807.659 -807.659 -807.1713 -803.788 -804.2757 -803.788 -802.8432 -802.3555 -801.4106 -799.4599 -799.9476 -799.9476 -799.4599 -799.0027 -798.0273 -797.5396 -797.5396 -794.1868 -796.1071 -795.6194 -796.5948 -796.1071 -795.6194 -795.6194 -796.1071 -797.5396 -798.0273 -799.9476 -801.8678 -803.3308 -803.788 -804.2757 -804.2757 -804.2757 -803.788 -804.7634 -804.7634 -804.7634 -805.7083 -805.7083 -805.2511 -806.6836 -806.196 -806.196 -806.6836 -806.6836 -807.1713 -807.1713 -807.1713 -806.196 -806.6836 -809.0916 -809.5792 -809.5792 -809.0916 -807.659 -805.7083 -805.2511 -805.2511 -805.2511 -805.2511 -805.2511 -805.7083 -804.2757 -804.2757 -802.8432 -803.788 -804.7634 -804.2757 -805.2511 -804.7634 -805.2511 -804.7634 -805.7083 -806.196 -806.6836 -807.1713 -806.6836 -807.1713 -807.659 -809.0916 -810.0364 -811.0118 -812.4444 -811.9567 -811.4995 -811.0118 -811.0118 -811.4995 -811.0118 -810.0364 -810.5241 -810.0364 -810.0364 -810.0364 -810.0364 -811.0118 -811.0118 -810.0364 -810.0364 -811.4995 -811.9567 -811.4995 -812.932 -813.4197 -813.4197 -813.4197 -813.9074 -813.9074 -813.9074 -813.4197 -812.932 -812.4444 -812.4444 -811.9567 -811.9567 -811.4995 -811.9567 -812.4444 -812.4444 -812.4444 -812.932 -812.932 -812.932 -813.4197 -813.9074 -813.9074 -814.8523 -814.8523 -814.8523 -816.7725 -816.2848 -815.8276 -815.34 -815.8276 -814.8523 -814.3646 -816.7725 -816.2848 -820.1558 -820.1558 -820.613 -821.5884 -821.5884 -821.5884 -821.5884 -821.5884 -822.076 -821.5884 -822.5332 -823.9963 -824.4535 -824.4535 -824.9412 -824.9412 -825.4288 -826.4042 -826.4042 -826.8614 -827.3491 -826.8614 -826.4042 -826.4042 -826.4042 -827.3491 -827.3491 -826.4042 -826.8614 -825.4288 -825.9165 -825.4288 -824.9412 -824.4535 -824.4535 -823.9963 -823.9963 -823.9963 -824.9412 -825.4288 -825.9165 -825.9165 -825.9165 -826.4042 -826.4042 -826.8614 -827.3491 -828.3244 -828.7816 -829.2693 -829.2693 -830.2447 -830.7019 -830.7019 -832.1649 -833.1098 -833.1098 -833.1098 -833.1098 -833.5975 -833.5975 -833.5975 -834.5728 -835.5177 -835.5177 -836.4931 -836.9503 -836.9503 -837.9256 -837.9256 -838.4133 -838.4133 -838.901 -838.901 -839.8459 -840.3336 -840.3336 -840.8212 -840.3336 -841.7661 -841.7661 -840.8212 -844.174 -841.7661 -841.7661 -842.7415 -844.174 -845.1494 -846.0943 -847.0696 -846.582 -847.0696 -848.9899 -849.4471 -851.3978 -852.3427 -853.7752 -853.318 -854.2629 -854.7506 -854.7506 -855.2383 -854.7506 -855.6955 -855.6955 -858.1034 -857.6462 -858.5911 -859.0788 -860.5113 -860.5113 -862.9192 -863.4069 -863.4069 -863.8946 -863.8946 -864.3518 -864.3518 -865.3272 -866.272 -865.8148 -865.8148 -866.7597 -868.2228 -868.2228 -868.2228 -868.2228 -868.2228 -868.2228 -868.68 -869.6553 -869.1676 -869.1676 -869.6553 -869.1676 -869.6553 -871.0879 -871.0879 -871.0879 -871.0879 -872.0632 -872.0632 -872.5204 -872.0632 -872.0632 -872.0632 -871.5756 -871.0879 -872.0632 -872.0632 -871.5756 -872.5204 -872.0632 -872.5204 -872.5204 -873.0081 -873.4958 -873.9835 -874.4712 -874.4712 -875.416 -875.416 -875.416 -875.416 -875.9037 -876.3914 -876.8486 -877.3363 -877.3363 -877.3363 -878.3116 -877.824 -878.7688 -879.2565 -879.2565 -881.1768 -880.7196 -881.6644 -882.1521 -883.097 -883.5847 -884.0724 -884.0724 -883.5847 -884.0724 -885.5049 -885.9926 -886.4803 -886.4803 -886.968 -887.9128 -886.968 -887.9128 -887.9128 -886.4803 -886.968 -886.4803 -886.4803 -886.4803 -885.9926 -883.5847 -883.5847 -883.5847 -883.5847 -885.5049 -885.0172 -885.0172 -885.9926 -885.9926 -885.9926 -886.968 -888.8882 -888.4005 -888.4005 -887.4252 -887.9128 -887.4252 -886.968 -886.4803 -886.968 -885.9926 -885.9926 -885.9926 -885.0172 -885.0172 -884.56 -884.0724 -884.56 -883.5847 -883.5847 -883.097 -883.5847 -883.5847 -883.097 -883.097 -882.6398 -884.0724 -883.097 -882.1521 -882.1521 -881.6644 -883.097 -881.6644 -881.1768 -881.1768 -880.2319 -880.2319 -879.7442 -879.2565 -879.2565 -879.2565 -878.7688 -878.3116 -878.3116 -877.824 -877.824 -877.3363 -877.824 -876.8486 -875.416 -873.0081 -873.4958 -872.5204 -871.5756 -871.5756 -871.5756 -871.0879 -870.6002 -869.1676 -869.1676 -869.1676 -868.68 -868.2228 -868.68 -868.2228 -867.7351 -867.7351 -868.2228 -868.2228 -867.2474 -867.2474 -866.272 -865.8148 -866.272 -865.3272 -865.3272 -864.8395 -865.3272 -866.272 -865.8148 -865.3272 -863.4069 -863.4069 -863.4069 -861.4867 -861.4867 -861.4867 -860.0236 -860.0236 -860.0236 -859.0788 -859.0788 -859.0788 -856.1832 -856.1832 -855.2383 -854.7506 -853.318 -851.3978 -851.855 -852.8304 -851.3978 -850.4224 -850.9101 -850.9101 -850.9101 -850.9101 -849.4471 -848.5022 -848.0145 -848.0145 -848.0145 -847.5268 -848.0145 -845.6066 -845.6066 -844.6617 -843.6864 -844.174 -842.7415 -842.2538 -840.8212 -841.7661 -842.2538 -842.2538 -841.2784 -840.8212 -840.8212 -841.7661 -841.2784 -842.2538 -842.2538 -840.8212 -837.9256 -836.4931 -836.9503 -835.03 -835.03 -835.03 -835.5177 -834.5728 -832.6526 -831.6772 -831.6772 -828.3244 -825.4288 -822.5332 -821.1007 -821.1007 -820.613 -818.2051 -817.7479 -817.2602 -817.2602 -816.2848 -813.4197 -813.4197 -812.4444 -811.9567 -812.4444 -812.4444 -812.4444 -813.4197 -812.932 -811.4995 -810.5241 -810.5241 -809.0916 -808.1162 -808.1162 -805.7083 -805.2511 -803.788 -803.788 -803.788 -804.7634 -802.8432 -801.8678 -801.8678 -801.8678 -801.4106 -802.3555 -801.8678 -800.9229 -799.0027 -798.515 -799.4599 -796.5948 -797.0824 -795.6194 -795.6194 -794.6745 -793.2115 -791.7789 -789.371 -788.9138 -786.9631 -786.5059 -785.5305 -784.098 -784.098 -782.6654 -782.1777 -781.69 -782.1777 -781.2024 -780.7147 -779.2821 -779.2821 -778.7944 -779.7698 -778.7944 -778.7944 -779.2821 -777.3619 -777.8496 -777.8496 -777.8496 -777.8496 -777.8496 -777.8496 -777.3619 -778.3372 -778.7944 -778.7944 -779.7698 -779.7698 -779.7698 -780.7147 -781.2024 -779.7698 -780.7147 -782.1777 -782.1777 -782.6654 -781.69 -781.69 -783.6103 -782.6654 -784.098 -784.5856 -786.0182 -786.5059 -786.0182 -786.5059 -784.098 -784.098 -784.5856 -784.098 -782.6654 -783.1226 -783.1226 -782.1777 -783.1226 -780.7147 -780.7147 -781.69 -780.7147 -782.6654 -782.1777 -782.6654 -782.6654 -783.1226 -783.1226 -783.1226 -783.1226 -782.6654 -783.6103 -783.6103 -783.6103 -784.098 -784.098 -786.0182 -788.9138 -788.9138 -789.371 -790.3464 -791.2912 -792.2666 -793.2115 -793.6992 -793.6992 -794.1868 -793.6992 -794.1868 -793.6992 -795.1622 -795.6194 -796.5948 -797.5396 -798.0273 -797.5396 -798.0273 -798.515 -799.4599 -799.9476 -799.4599 -799.4599 -798.0273 -799.0027 -799.0027 -797.5396 -797.5396 -797.0824 -797.0824 -796.5948 -797.5396 -796.5948 -790.3464 -790.834 -789.8587 -790.3464 - - - - + + + +802.325007Flag +887.8824008Flag +881.1768009Flag +777.3314010Flag +780.7147011Flag + +30NOV02 + +786.9631 +783.6103 +788.9138 +785.5305 +785.5305 +782.6654 +782.6654 +779.2821 +780.7147 +781.2024 +781.69 +778.3372 +782.6654 +779.7698 +781.2024 +779.7698 +779.7698 +780.2575 +778.3372 +779.7698 +779.2821 +779.2821 +779.7698 +780.2575 +780.2575 +780.2575 +782.1777 +783.1226 +783.6103 +784.5856 +786.5059 +787.9384 +788.9138 +789.8587 +790.3464 +791.2912 +790.834 +792.7543 +792.7543 +792.7543 +793.6992 +794.6745 +795.1622 +794.6745 +796.5948 +798.515 +798.515 +798.515 +798.515 +799.4599 +799.9476 +797.5396 +798.515 +800.4352 +801.4106 +801.4106 +800.4352 +801.4106 +801.8678 +802.3555 +801.8678 +801.4106 +800.9229 +800.9229 +803.3308 +802.3555 +802.8432 +802.8432 +803.788 +805.2511 +805.7083 +806.196 +807.1713 +806.6836 +807.659 +809.5792 +809.0916 +809.0916 +809.0916 +809.0916 +809.5792 +810.0364 +810.0364 +811.0118 +812.932 +812.932 +811.9567 +811.0118 +812.4444 +813.9074 +815.34 +815.8276 +815.34 +813.4197 +815.34 +816.7725 +817.2602 +817.7479 +817.7479 +818.6928 +819.6681 +819.6681 +820.613 +821.5884 +823.0209 +823.9963 +824.4535 +824.9412 +825.4288 +825.4288 +825.9165 +825.9165 +826.8614 +828.3244 +827.8368 +829.757 +830.2447 +829.757 +830.2447 +830.2447 +830.7019 +831.1896 +831.6772 +831.6772 +833.5975 +834.0852 +835.03 +835.5177 +836.9503 +834.5728 +833.5975 +834.5728 +834.5728 +835.5177 +836.0054 +836.4931 +837.438 +837.9256 +838.4133 +839.3582 +839.8459 +840.3336 +839.8459 +839.3582 +839.3582 +839.3582 +840.3336 +839.3582 +838.901 +838.901 +847.5268 +847.5268 +848.0145 +845.6066 +846.582 +849.9348 +850.9101 +850.4224 +851.855 +852.8304 +851.3978 +851.855 +851.3978 +854.2629 +854.2629 +854.7506 +854.7506 +854.2629 +854.7506 +854.2629 +855.2383 +862.9192 +862.9192 +872.5204 +872.5204 +872.5204 +871.5756 +872.0632 +871.5756 +875.416 +875.416 +875.416 +874.9284 +875.416 +875.9037 +875.9037 +880.2319 +880.2319 +880.7196 +880.2319 +881.6644 +881.6644 +880.7196 +881.6644 +881.6644 +881.1768 +882.1521 +883.5847 +884.56 +884.56 +884.56 +885.0172 +884.56 +884.56 +884.56 +884.0724 +884.56 +882.6398 +883.097 +883.097 +882.6398 +882.1521 +881.6644 +881.6644 +881.1768 +882.1521 +880.7196 +879.7442 +879.2565 +879.7442 +879.2565 +880.2319 +881.1768 +880.2319 +879.2565 +880.7196 +880.7196 +880.7196 +881.1768 +881.6644 +881.6644 +882.1521 +885.9926 +885.9926 +885.9926 +885.0172 +885.0172 +884.56 +884.0724 +884.56 +878.3116 +878.3116 +878.3116 +877.824 +878.3116 +877.3363 +877.824 +876.8486 +876.8486 +876.8486 +876.3914 +875.416 +874.4712 +876.3914 +873.0081 +874.4712 +874.4712 +873.4958 +873.4958 +873.4958 +872.5204 +872.0632 +871.0879 +871.0879 +869.6553 +868.2228 +864.8395 +865.8148 +866.7597 +866.272 +866.272 +867.2474 +867.2474 +866.7597 +866.272 +865.8148 +865.8148 +866.272 +864.8395 +865.3272 +864.3518 +864.3518 +863.8946 +863.4069 +862.9192 +863.4069 +863.8946 +862.4316 +863.4069 +863.4069 +863.4069 +862.9192 +862.4316 +861.9744 +862.4316 +860.999 +863.4069 +865.8148 +865.8148 +865.8148 +864.8395 +864.3518 +862.9192 +863.8946 +862.4316 +861.4867 +858.5911 +857.1585 +856.1832 +856.1832 +855.2383 +854.7506 +852.3427 +851.855 +850.4224 +849.9348 +848.9899 +846.582 +846.582 +845.6066 +845.6066 +845.1494 +844.6617 +843.6864 +840.8212 +839.3582 +838.4133 +838.901 +838.4133 +837.438 +836.4931 +836.0054 +835.03 +835.03 +833.1098 +833.5975 +833.1098 +832.6526 +832.6526 +832.6526 +831.6772 +831.6772 +831.1896 +830.7019 +829.757 +830.2447 +829.757 +829.757 +829.757 +828.3244 +827.8368 +827.3491 +826.4042 +826.8614 +826.4042 +825.9165 +825.4288 +824.4535 +823.9963 +824.4535 +825.4288 +825.9165 +826.4042 +827.3491 +828.3244 +827.3491 +827.3491 +827.3491 +826.8614 +826.4042 +826.4042 +827.3491 +826.4042 +826.4042 +826.4042 +825.9165 +825.9165 +825.4288 +824.9412 +824.9412 +824.4535 +824.4535 +824.4535 +823.9963 +822.5332 +822.076 +822.076 +822.076 +822.076 +822.5332 +822.076 +821.5884 +821.5884 +821.1007 +821.5884 +821.1007 +821.1007 +820.613 +821.1007 +820.1558 +819.6681 +819.1804 +819.1804 +818.6928 +818.2051 +818.2051 +818.2051 +817.2602 +817.2602 +817.2602 +816.2848 +815.8276 +815.34 +814.8523 +815.34 +815.34 +814.8523 +814.3646 +814.3646 +814.3646 +814.8523 +814.8523 +813.4197 +813.4197 +812.932 +812.932 +813.4197 +812.4444 +811.9567 +811.4995 +811.4995 +811.4995 +811.4995 +811.4995 +811.9567 +811.0118 +811.0118 +812.4444 +812.932 +813.4197 +814.3646 +814.3646 +814.8523 +814.3646 +814.8523 +813.9074 +814.8523 +813.4197 +812.932 +811.9567 +812.4444 +810.5241 +811.4995 +811.4995 +811.9567 +811.4995 +811.0118 +811.4995 +811.0118 +810.5241 +811.0118 +811.0118 +811.4995 +811.0118 +811.0118 +811.0118 +810.5241 +810.0364 +810.0364 +810.0364 +809.5792 +810.5241 +809.5792 +809.5792 +809.5792 +809.0916 +809.5792 +809.5792 +809.0916 +809.0916 +808.6039 +809.5792 +808.6039 +808.1162 +807.1713 +807.659 +807.1713 +807.1713 +806.6836 +807.1713 +806.6836 +806.6836 +806.6836 +806.196 +805.2511 +806.6836 +806.196 +806.6836 +806.6836 +806.6836 +806.6836 +806.6836 +807.1713 +806.6836 +807.659 +807.1713 +808.1162 +808.1162 +808.1162 +808.1162 +808.1162 +808.1162 +807.1713 +807.1713 +807.1713 +806.6836 +806.6836 +806.196 +806.196 +805.7083 +803.3308 +801.4106 +801.8678 +800.4352 +799.9476 +798.515 +797.5396 +798.0273 +797.0824 +797.0824 +796.5948 +795.6194 +797.5396 +797.0824 +797.0824 +797.5396 +796.1071 +796.5948 +796.5948 +796.5948 +797.0824 +797.0824 +796.5948 +798.0273 +798.515 +799.4599 +799.0027 +799.4599 +798.515 +799.4599 +799.4599 +799.4599 +799.0027 +799.4599 +799.4599 +801.4106 +801.8678 +801.4106 +802.3555 +803.788 +803.788 +803.3308 +802.8432 +802.8432 +801.4106 +802.8432 +803.3308 +802.3555 +803.3308 +801.8678 +802.8432 +802.3555 +802.8432 +802.3555 +805.7083 +805.7083 +807.1713 +806.196 +805.7083 +805.2511 +805.2511 +803.788 +803.3308 +802.3555 +800.4352 +799.9476 +800.9229 +800.4352 +800.9229 +800.9229 +799.9476 +799.4599 +799.4599 +799.0027 +799.9476 +799.4599 +798.0273 +798.515 +799.0027 +799.9476 +801.8678 +803.3308 +803.788 +804.2757 +804.2757 +804.2757 +804.2757 +803.788 +807.1713 +807.659 +809.0916 +808.6039 +808.1162 +807.659 +807.659 +807.1713 +803.788 +804.2757 +803.788 +802.8432 +802.3555 +801.4106 +799.4599 +799.9476 +799.9476 +799.4599 +799.0027 +798.0273 +797.5396 +797.5396 +794.1868 +796.1071 +795.6194 +796.5948 +796.1071 +795.6194 +795.6194 +796.1071 +797.5396 +798.0273 +799.9476 +801.8678 +803.3308 +803.788 +804.2757 +804.2757 +804.2757 +803.788 +804.7634 +804.7634 +804.7634 +805.7083 +805.7083 +805.2511 +806.6836 +806.196 +806.196 +806.6836 +806.6836 +807.1713 +807.1713 +807.1713 +806.196 +806.6836 +809.0916 +809.5792 +809.5792 +809.0916 +807.659 +805.7083 +805.2511 +805.2511 +805.2511 +805.2511 +805.2511 +805.7083 +804.2757 +804.2757 +802.8432 +803.788 +804.7634 +804.2757 +805.2511 +804.7634 +805.2511 +804.7634 +805.7083 +806.196 +806.6836 +807.1713 +806.6836 +807.1713 +807.659 +809.0916 +810.0364 +811.0118 +812.4444 +811.9567 +811.4995 +811.0118 +811.0118 +811.4995 +811.0118 +810.0364 +810.5241 +810.0364 +810.0364 +810.0364 +810.0364 +811.0118 +811.0118 +810.0364 +810.0364 +811.4995 +811.9567 +811.4995 +812.932 +813.4197 +813.4197 +813.4197 +813.9074 +813.9074 +813.9074 +813.4197 +812.932 +812.4444 +812.4444 +811.9567 +811.9567 +811.4995 +811.9567 +812.4444 +812.4444 +812.4444 +812.932 +812.932 +812.932 +813.4197 +813.9074 +813.9074 +814.8523 +814.8523 +814.8523 +816.7725 +816.2848 +815.8276 +815.34 +815.8276 +814.8523 +814.3646 +816.7725 +816.2848 +820.1558 +820.1558 +820.613 +821.5884 +821.5884 +821.5884 +821.5884 +821.5884 +822.076 +821.5884 +822.5332 +823.9963 +824.4535 +824.4535 +824.9412 +824.9412 +825.4288 +826.4042 +826.4042 +826.8614 +827.3491 +826.8614 +826.4042 +826.4042 +826.4042 +827.3491 +827.3491 +826.4042 +826.8614 +825.4288 +825.9165 +825.4288 +824.9412 +824.4535 +824.4535 +823.9963 +823.9963 +823.9963 +824.9412 +825.4288 +825.9165 +825.9165 +825.9165 +826.4042 +826.4042 +826.8614 +827.3491 +828.3244 +828.7816 +829.2693 +829.2693 +830.2447 +830.7019 +830.7019 +832.1649 +833.1098 +833.1098 +833.1098 +833.1098 +833.5975 +833.5975 +833.5975 +834.5728 +835.5177 +835.5177 +836.4931 +836.9503 +836.9503 +837.9256 +837.9256 +838.4133 +838.4133 +838.901 +838.901 +839.8459 +840.3336 +840.3336 +840.8212 +840.3336 +841.7661 +841.7661 +840.8212 +844.174 +841.7661 +841.7661 +842.7415 +844.174 +845.1494 +846.0943 +847.0696 +846.582 +847.0696 +848.9899 +849.4471 +851.3978 +852.3427 +853.7752 +853.318 +854.2629 +854.7506 +854.7506 +855.2383 +854.7506 +855.6955 +855.6955 +858.1034 +857.6462 +858.5911 +859.0788 +860.5113 +860.5113 +862.9192 +863.4069 +863.4069 +863.8946 +863.8946 +864.3518 +864.3518 +865.3272 +866.272 +865.8148 +865.8148 +866.7597 +868.2228 +868.2228 +868.2228 +868.2228 +868.2228 +868.2228 +868.68 +869.6553 +869.1676 +869.1676 +869.6553 +869.1676 +869.6553 +871.0879 +871.0879 +871.0879 +871.0879 +872.0632 +872.0632 +872.5204 +872.0632 +872.0632 +872.0632 +871.5756 +871.0879 +872.0632 +872.0632 +871.5756 +872.5204 +872.0632 +872.5204 +872.5204 +873.0081 +873.4958 +873.9835 +874.4712 +874.4712 +875.416 +875.416 +875.416 +875.416 +875.9037 +876.3914 +876.8486 +877.3363 +877.3363 +877.3363 +878.3116 +877.824 +878.7688 +879.2565 +879.2565 +881.1768 +880.7196 +881.6644 +882.1521 +883.097 +883.5847 +884.0724 +884.0724 +883.5847 +884.0724 +885.5049 +885.9926 +886.4803 +886.4803 +886.968 +887.9128 +886.968 +887.9128 +887.9128 +886.4803 +886.968 +886.4803 +886.4803 +886.4803 +885.9926 +883.5847 +883.5847 +883.5847 +883.5847 +885.5049 +885.0172 +885.0172 +885.9926 +885.9926 +885.9926 +886.968 +888.8882 +888.4005 +888.4005 +887.4252 +887.9128 +887.4252 +886.968 +886.4803 +886.968 +885.9926 +885.9926 +885.9926 +885.0172 +885.0172 +884.56 +884.0724 +884.56 +883.5847 +883.5847 +883.097 +883.5847 +883.5847 +883.097 +883.097 +882.6398 +884.0724 +883.097 +882.1521 +882.1521 +881.6644 +883.097 +881.6644 +881.1768 +881.1768 +880.2319 +880.2319 +879.7442 +879.2565 +879.2565 +879.2565 +878.7688 +878.3116 +878.3116 +877.824 +877.824 +877.3363 +877.824 +876.8486 +875.416 +873.0081 +873.4958 +872.5204 +871.5756 +871.5756 +871.5756 +871.0879 +870.6002 +869.1676 +869.1676 +869.1676 +868.68 +868.2228 +868.68 +868.2228 +867.7351 +867.7351 +868.2228 +868.2228 +867.2474 +867.2474 +866.272 +865.8148 +866.272 +865.3272 +865.3272 +864.8395 +865.3272 +866.272 +865.8148 +865.3272 +863.4069 +863.4069 +863.4069 +861.4867 +861.4867 +861.4867 +860.0236 +860.0236 +860.0236 +859.0788 +859.0788 +859.0788 +856.1832 +856.1832 +855.2383 +854.7506 +853.318 +851.3978 +851.855 +852.8304 +851.3978 +850.4224 +850.9101 +850.9101 +850.9101 +850.9101 +849.4471 +848.5022 +848.0145 +848.0145 +848.0145 +847.5268 +848.0145 +845.6066 +845.6066 +844.6617 +843.6864 +844.174 +842.7415 +842.2538 +840.8212 +841.7661 +842.2538 +842.2538 +841.2784 +840.8212 +840.8212 +841.7661 +841.2784 +842.2538 +842.2538 +840.8212 +837.9256 +836.4931 +836.9503 +835.03 +835.03 +835.03 +835.5177 +834.5728 +832.6526 +831.6772 +831.6772 +828.3244 +825.4288 +822.5332 +821.1007 +821.1007 +820.613 +818.2051 +817.7479 +817.2602 +817.2602 +816.2848 +813.4197 +813.4197 +812.4444 +811.9567 +812.4444 +812.4444 +812.4444 +813.4197 +812.932 +811.4995 +810.5241 +810.5241 +809.0916 +808.1162 +808.1162 +805.7083 +805.2511 +803.788 +803.788 +803.788 +804.7634 +802.8432 +801.8678 +801.8678 +801.8678 +801.4106 +802.3555 +801.8678 +800.9229 +799.0027 +798.515 +799.4599 +796.5948 +797.0824 +795.6194 +795.6194 +794.6745 +793.2115 +791.7789 +789.371 +788.9138 +786.9631 +786.5059 +785.5305 +784.098 +784.098 +782.6654 +782.1777 +781.69 +782.1777 +781.2024 +780.7147 +779.2821 +779.2821 +778.7944 +779.7698 +778.7944 +778.7944 +779.2821 +777.3619 +777.8496 +777.8496 +777.8496 +777.8496 +777.8496 +777.8496 +777.3619 +778.3372 +778.7944 +778.7944 +779.7698 +779.7698 +779.7698 +780.7147 +781.2024 +779.7698 +780.7147 +782.1777 +782.1777 +782.6654 +781.69 +781.69 +783.6103 +782.6654 +784.098 +784.5856 +786.0182 +786.5059 +786.0182 +786.5059 +784.098 +784.098 +784.5856 +784.098 +782.6654 +783.1226 +783.1226 +782.1777 +783.1226 +780.7147 +780.7147 +781.69 +780.7147 +782.6654 +782.1777 +782.6654 +782.6654 +783.1226 +783.1226 +783.1226 +783.1226 +782.6654 +783.6103 +783.6103 +783.6103 +784.098 +784.098 +786.0182 +788.9138 +788.9138 +789.371 +790.3464 +791.2912 +792.2666 +793.2115 +793.6992 +793.6992 +794.1868 +793.6992 +794.1868 +793.6992 +795.1622 +795.6194 +796.5948 +797.5396 +798.0273 +797.5396 +798.0273 +798.515 +799.4599 +799.9476 +799.4599 +799.4599 +798.0273 +799.0027 +799.0027 +797.5396 +797.5396 +797.0824 +797.0824 +796.5948 +797.5396 +796.5948 +790.3464 +790.834 +789.8587 +790.3464 + + + + diff --git a/processing_r/test/data/lines.dbf b/tests/data/lines.dbf similarity index 100% rename from processing_r/test/data/lines.dbf rename to tests/data/lines.dbf diff --git a/processing_r/test/data/lines.prj b/tests/data/lines.prj similarity index 100% rename from processing_r/test/data/lines.prj rename to tests/data/lines.prj diff --git a/processing_r/test/data/lines.shp b/tests/data/lines.shp similarity index 100% rename from processing_r/test/data/lines.shp rename to tests/data/lines.shp diff --git a/processing_r/test/data/lines.shx b/tests/data/lines.shx similarity index 100% rename from processing_r/test/data/lines.shx rename to tests/data/lines.shx diff --git a/processing_r/test/data/points.gml b/tests/data/points.gml similarity index 100% rename from processing_r/test/data/points.gml rename to tests/data/points.gml diff --git a/processing_r/test/data/points.xsd b/tests/data/points.xsd similarity index 100% rename from processing_r/test/data/points.xsd rename to tests/data/points.xsd diff --git a/processing_r/test/data/test_gpkg.gpkg b/tests/data/test_gpkg.gpkg similarity index 99% rename from processing_r/test/data/test_gpkg.gpkg rename to tests/data/test_gpkg.gpkg index 1a8614af5f862b113a6b0cc293f27c0f73810923..1fb4332f1d93a726c70cc7f2117255c61be85044 100644 GIT binary patch delta 22 ecmZp8z~1nHeS$RO*NHODj9(iQwk9wxcn<(>9tqq4 delta 22 ecmZp8z~1nHeS$RO%ZW11j4vA#wk9wxcn<(=A_>F* diff --git a/processing_r/test/data/bad_algorithm.rsx b/tests/scripts/bad_algorithm.rsx similarity index 100% rename from processing_r/test/data/bad_algorithm.rsx rename to tests/scripts/bad_algorithm.rsx diff --git a/processing_r/test/data/test_algorithm_1.rsx b/tests/scripts/test_algorithm_1.rsx similarity index 100% rename from processing_r/test/data/test_algorithm_1.rsx rename to tests/scripts/test_algorithm_1.rsx diff --git a/processing_r/test/data/test_algorithm_1.rsx.help b/tests/scripts/test_algorithm_1.rsx.help similarity index 100% rename from processing_r/test/data/test_algorithm_1.rsx.help rename to tests/scripts/test_algorithm_1.rsx.help diff --git a/processing_r/test/data/test_algorithm_2.rsx b/tests/scripts/test_algorithm_2.rsx similarity index 99% rename from processing_r/test/data/test_algorithm_2.rsx rename to tests/scripts/test_algorithm_2.rsx index 3c09b1a..81ebeab 100644 --- a/processing_r/test/data/test_algorithm_2.rsx +++ b/tests/scripts/test_algorithm_2.rsx @@ -21,6 +21,7 @@ ##param_vector_polygon_dest=output vector polygon ##param_table_dest=output table ##param_raster_dest=output raster + ##out_vector=output vector noprompt ##out_table=output table noprompt ##out_raster=output raster noprompt diff --git a/processing_r/test/data/test_algorithm_3.rsx b/tests/scripts/test_algorithm_3.rsx similarity index 100% rename from processing_r/test/data/test_algorithm_3.rsx rename to tests/scripts/test_algorithm_3.rsx diff --git a/processing_r/test/data/test_algorithm_4.rsx b/tests/scripts/test_algorithm_4.rsx similarity index 100% rename from processing_r/test/data/test_algorithm_4.rsx rename to tests/scripts/test_algorithm_4.rsx diff --git a/processing_r/test/data/test_algorithm_inline_help.rsx b/tests/scripts/test_algorithm_inline_help.rsx similarity index 100% rename from processing_r/test/data/test_algorithm_inline_help.rsx rename to tests/scripts/test_algorithm_inline_help.rsx diff --git a/processing_r/test/data/test_dont_load_any_packages.rsx b/tests/scripts/test_dont_load_any_packages.rsx similarity index 100% rename from processing_r/test/data/test_dont_load_any_packages.rsx rename to tests/scripts/test_dont_load_any_packages.rsx diff --git a/processing_r/test/data/test_enum_multiple.rsx b/tests/scripts/test_enum_multiple.rsx similarity index 88% rename from processing_r/test/data/test_enum_multiple.rsx rename to tests/scripts/test_enum_multiple.rsx index 6ea1fd2..f7bf923 100644 --- a/processing_r/test/data/test_enum_multiple.rsx +++ b/tests/scripts/test_enum_multiple.rsx @@ -1,4 +1,4 @@ -##Test enums type=name +##Test enums type multiple=name ##enum_string=enum literal multiple enum_a;enum_b;enum_c ##enum_string_optional=optional enum literal multiple enum_a;enum_b;enum_c ##enum_normal=enum multiple a;b;c diff --git a/processing_r/test/data/test_enums.rsx b/tests/scripts/test_enums.rsx similarity index 100% rename from processing_r/test/data/test_enums.rsx rename to tests/scripts/test_enums.rsx diff --git a/processing_r/test/data/test_field_multiple.rsx b/tests/scripts/test_field_multiple.rsx similarity index 100% rename from processing_r/test/data/test_field_multiple.rsx rename to tests/scripts/test_field_multiple.rsx diff --git a/processing_r/test/data/test_input_color.rsx b/tests/scripts/test_input_color.rsx similarity index 100% rename from processing_r/test/data/test_input_color.rsx rename to tests/scripts/test_input_color.rsx diff --git a/processing_r/test/data/test_input_datetime.rsx b/tests/scripts/test_input_datetime.rsx similarity index 100% rename from processing_r/test/data/test_input_datetime.rsx rename to tests/scripts/test_input_datetime.rsx diff --git a/processing_r/test/data/test_input_expression.rsx b/tests/scripts/test_input_expression.rsx similarity index 100% rename from processing_r/test/data/test_input_expression.rsx rename to tests/scripts/test_input_expression.rsx diff --git a/tests/scripts/test_input_point.rsx b/tests/scripts/test_input_point.rsx new file mode 100644 index 0000000..edb473d --- /dev/null +++ b/tests/scripts/test_input_point.rsx @@ -0,0 +1,3 @@ +##testpointinput=name +##Test point input=display_name +##point=point diff --git a/processing_r/test/data/test_input_range.rsx b/tests/scripts/test_input_range.rsx similarity index 100% rename from processing_r/test/data/test_input_range.rsx rename to tests/scripts/test_input_range.rsx diff --git a/processing_r/test/data/test_library_with_option.rsx b/tests/scripts/test_library_with_option.rsx similarity index 100% rename from processing_r/test/data/test_library_with_option.rsx rename to tests/scripts/test_library_with_option.rsx diff --git a/processing_r/test/data/test_multiout.rsx b/tests/scripts/test_multiout.rsx similarity index 100% rename from processing_r/test/data/test_multiout.rsx rename to tests/scripts/test_multiout.rsx diff --git a/processing_r/test/data/test_multirasterin.rsx b/tests/scripts/test_multirasterin.rsx similarity index 100% rename from processing_r/test/data/test_multirasterin.rsx rename to tests/scripts/test_multirasterin.rsx diff --git a/processing_r/test/data/test_multivectorin.rsx b/tests/scripts/test_multivectorin.rsx similarity index 100% rename from processing_r/test/data/test_multivectorin.rsx rename to tests/scripts/test_multivectorin.rsx diff --git a/tests/scripts/test_plots.rsx b/tests/scripts/test_plots.rsx new file mode 100644 index 0000000..3483cd8 --- /dev/null +++ b/tests/scripts/test_plots.rsx @@ -0,0 +1,7 @@ +##Basic statistics=group +##Graphs=name +##output_plots_to_html +##Layer=vector +##Field=Field Layer +qqnorm(Layer[[Field]]) +qqline(Layer[[Field]]) \ No newline at end of file diff --git a/processing_r/test/data/test_raster_band.rsx b/tests/scripts/test_raster_band.rsx similarity index 100% rename from processing_r/test/data/test_raster_band.rsx rename to tests/scripts/test_raster_band.rsx diff --git a/tests/scripts/test_raster_in_out.rsx b/tests/scripts/test_raster_in_out.rsx new file mode 100644 index 0000000..265abd3 --- /dev/null +++ b/tests/scripts/test_raster_in_out.rsx @@ -0,0 +1,4 @@ +##rasterinout=name +##Layer=raster +##out_raster=output raster +out_raster = Layer \ No newline at end of file diff --git a/processing_r/test/data/test_rasterin_names.rsx b/tests/scripts/test_rasterin_names.rsx similarity index 100% rename from processing_r/test/data/test_rasterin_names.rsx rename to tests/scripts/test_rasterin_names.rsx diff --git a/processing_r/test/data/test_vectorin.rsx b/tests/scripts/test_vectorin.rsx similarity index 100% rename from processing_r/test/data/test_vectorin.rsx rename to tests/scripts/test_vectorin.rsx diff --git a/processing_r/test/data/test_vectorout.rsx b/tests/scripts/test_vectorout.rsx similarity index 100% rename from processing_r/test/data/test_vectorout.rsx rename to tests/scripts/test_vectorout.rsx diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py new file mode 100644 index 0000000..6ed2ba2 --- /dev/null +++ b/tests/test_algorithm.py @@ -0,0 +1,50 @@ +from pathlib import Path + +import processing +from utils import data_path, script_path + +from processing_r.processing.algorithm import RAlgorithm + + +def test_can_run(): + alg = RAlgorithm(description_file=script_path("test_algorithm_1.rsx")) + alg.initAlgorithm() + + assert alg.canExecute() + + +def test_run_point(): + result = processing.run("r:testpointinput", {"point": "20.219926,49.138354 [EPSG:4326]"}) + + assert result == {} + + +def test_run_raster_creation(): + result = processing.run("r:rasterinout", {"Layer": data_path("dem.tif"), "out_raster": "TEMPORARY_OUTPUT"}) + + assert "out_raster" in result.keys() + assert Path(result["out_raster"]).exists() + + +def test_run_enums(): + result = processing.run( + "r:testenumstypemultiple", {"enum_normal": 0, "enum_string": 1, "R_CONSOLE_OUTPUT": "TEMPORARY_OUTPUT"} + ) + + print(result.keys()) + assert "R_CONSOLE_OUTPUT" in result.keys() + assert Path(result["R_CONSOLE_OUTPUT"]).exists() + + +def test_run_graphs(): + result = processing.run( + "r:graphs", + { + "RPLOTS": "TEMPORARY_OUTPUT", + "Layer": data_path("points.gml"), + "Field": "id", + }, + ) + + assert "RPLOTS" in result.keys() + assert Path(result["RPLOTS"]).exists() diff --git a/tests/test_algorithm_inputs.py b/tests/test_algorithm_inputs.py new file mode 100644 index 0000000..94646e4 --- /dev/null +++ b/tests/test_algorithm_inputs.py @@ -0,0 +1,464 @@ +import pytest +from qgis.core import ( + QgsCoordinateReferenceSystem, + QgsProcessing, + QgsProcessingContext, + QgsProcessingFeedback, + QgsVectorLayer, +) +from qgis.PyQt.QtCore import QDate, QDateTime, QTime +from qgis.PyQt.QtGui import QColor + +from processing_r.processing.algorithm import RAlgorithm +from tests.utils import IS_API_BELOW_31000, IS_API_BELOW_31400, USE_API_30900, data_path, script_path + + +def test_simple_inputs(): + """ + Test creation of script with algorithm inputs + """ + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + # enum evaluation + script = alg.build_import_commands({"in_enum": 0}, context, feedback) + assert "in_enum <- 0" in script + + # boolean evaluation + script = alg.build_import_commands({"in_bool": True}, context, feedback) + assert "in_bool <- TRUE" in script + + script = alg.build_import_commands({"in_bool": False}, context, feedback) + assert "in_bool <- FALSE" in script + + # number evaluation + script = alg.build_import_commands({"in_number": None}, context, feedback) + assert "in_number <- NULL" in script + + script = alg.build_import_commands({"in_number": 5}, context, feedback) + assert "in_number <- 5.0" in script + + script = alg.build_import_commands({"in_number": 5.5}, context, feedback) + assert "in_number <- 5.5" in script + + script = alg.build_import_commands({"in_string": "some string"}, context, feedback) + assert 'in_string <- "some string"' in script + + +def test_folder_inputs(): + """ + Test creation of script with algorithm inputs + """ + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + # folder destination + script = alg.build_import_commands({"param_folder_dest": "/tmp/processing/test_algorithm_2_r/"}, context, feedback) + + # file destination + script = alg.build_import_commands( + {"param_html_dest": "/tmp/processing/test_algorithm_2_r/dest.html"}, context, feedback + ) + assert 'param_html_dest <- "/tmp/processing/test_algorithm_2_r/dest.html"' in script + + script = alg.build_import_commands( + {"param_file_dest": "/tmp/processing/test_algorithm_2_r/dest.file"}, context, feedback + ) + assert 'param_file_dest <- "/tmp/processing/test_algorithm_2_r/dest.file"' in script + + script = alg.build_import_commands( + {"param_csv_dest": "/tmp/processing/test_algorithm_2_r/dest.csv"}, context, feedback + ) + assert 'param_csv_dest <- "/tmp/processing/test_algorithm_2_r/dest.csv"' in script + + +def test_read_sf(): + """ + Test reading vector inputs + """ + alg = RAlgorithm(description_file=script_path("test_vectorin.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + script = alg.build_import_commands({"Layer": data_path("lines.shp")}, context, feedback) + + if USE_API_30900: + assert script[0] == 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("lines.shp") + ) + else: + assert script[0] == 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("lines.shp") + ) + + script = alg.build_import_commands({"Layer": data_path("lines.shp").replace("/", "\\")}, context, feedback) + if USE_API_30900: + assert script[0] == 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("lines.shp") + ) + else: + assert script[0] == 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("lines.shp") + ) + + vl = QgsVectorLayer(data_path("test_gpkg.gpkg") + "|layername=points") + assert vl.isValid() + + vl2 = QgsVectorLayer(data_path("test_gpkg.gpkg") + "|layername=lines") + assert vl2.isValid() + + script = alg.build_import_commands({"Layer": vl, "Layer2": vl2}, context, feedback) + + if USE_API_30900: + # use the newer api and avoid unnecessary layer translation + assert script == [ + 'Layer <- st_read("{}", layer = "points", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("test_gpkg.gpkg") + ), + 'Layer2 <- st_read("{}", layer = "lines", quiet = TRUE, stringsAsFactors = FALSE)'.format( + data_path("test_gpkg.gpkg") + ), + ] + else: + # older version, forced to use inefficient api + assert 'Layer <- st_read("/tmp' == script[0] + assert 'Layer2 <- st_read("/tmp' == script[1] + + +def test_read_raster(): + """ + Test reading raster inputs + """ + alg = RAlgorithm(description_file=script_path("test_raster_in_out.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"Layer": data_path("dem.tif")}, context, feedback) + assert 'Layer <- brick("{}")'.format(data_path("dem.tif")) in script + + script = alg.build_import_commands({"Layer": data_path("dem.tif").replace("/", "\\")}, context, feedback) + assert 'Layer <- brick("{}")'.format(data_path("dem.tif")) in script + + script = alg.build_import_commands({"Layer": None}, context, feedback) + assert "Layer <- NULL" in script + + alg = RAlgorithm(description_file=script_path("test_rasterin_names.rsx")) + alg.initAlgorithm() + + script = alg.build_import_commands({"Layer": data_path("dem.tif")}, context, feedback) + assert 'Layer <- "{}"'.format(data_path("dem.tif")) in script + + script = alg.build_import_commands({"Layer": None}, context, feedback) + assert "Layer <- NULL" in script + + +def test_read_multi_raster(): + """ + Test raster multilayer input parameter + """ + alg = RAlgorithm(description_file=script_path("test_multirasterin.rsx")) + alg.initAlgorithm() + + raster_param = alg.parameterDefinition("Layer") + assert raster_param.type() == "multilayer" + assert raster_param.layerType() == QgsProcessing.TypeRaster + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + script = alg.build_import_commands( + {"Layer": [data_path("dem.tif"), data_path("dem2.tif")]}, + context, + feedback, + ) + + assert script == [ + 'tempvar0 <- brick("{}")'.format(data_path("dem.tif")), + 'tempvar1 <- brick("{}")'.format(data_path("dem2.tif")), + "Layer = list(tempvar0,tempvar1)", + ] + + script = alg.build_import_commands({"Layer": []}, context, feedback) + assert script == ["Layer = list()"] + + +def test_read_multi_vector(): + """ + Test vector multilayer input parameter + """ + alg = RAlgorithm(description_file=script_path("test_multivectorin.rsx")) + alg.initAlgorithm() + + param = alg.parameterDefinition("Layer") + assert param.type() == "multilayer" + assert param.layerType() == QgsProcessing.TypeVectorAnyGeometry + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands( + {"Layer": [data_path("lines.shp"), data_path("points.gml")]}, + context, + feedback, + ) + + assert script == [ + 'tempvar0 <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format(data_path("lines.shp")), + 'tempvar1 <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format(data_path("points.gml")), + "Layer = list(tempvar0,tempvar1)", + ] + + script = alg.build_import_commands({"Layer": []}, context, feedback) + assert script == ["Layer = list()"] + + +def test_field(): + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + + param = alg.parameterDefinition("in_field") + assert param.type() == "field" + assert param.allowMultiple() is False + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"in_vector": data_path("lines.shp"), "in_field": "a"}, context, feedback) + assert 'in_field <- "a"' in script + + +def test_multi_field(): + """ + Test multiple field input parameter + """ + alg = RAlgorithm(description_file=script_path("test_field_multiple.rsx")) + alg.initAlgorithm() + + param = alg.parameterDefinition("MultiField") + assert param.type() == "field" + assert param.allowMultiple() is True + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"Layer": data_path("lines.shp")}, context, feedback) + + assert script == [ + 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format(data_path("lines.shp")), + "MultiField <- NULL", + ] + + script = alg.build_import_commands({"Layer": data_path("lines.shp"), "MultiField": ["a"]}, context, feedback) + assert script == [ + 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format(data_path("lines.shp")), + 'MultiField <- c("a")', + ] + + script = alg.build_import_commands({"Layer": data_path("lines.shp"), "MultiField": ["a", 'b"c']}, context, feedback) + assert script == [ + 'Layer <- st_read("{}", quiet = TRUE, stringsAsFactors = FALSE)'.format(data_path("lines.shp")), + 'MultiField <- c("a","b\\"c")', + ] + + +def test_enums(): + """ + Test for both enum types + """ + alg = RAlgorithm(description_file=script_path("test_enums.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"enum_normal": 0}, context, feedback) + assert "enum_normal <- 0" == script[1] + + script = alg.build_import_commands({"enum_string": 0}, context, feedback) + assert 'enum_string <- "enum_a"' == script[0] + + +def test_enums_multiple(): + """ + Test for both enum multiple types + """ + alg = RAlgorithm(description_file=script_path("test_enum_multiple.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + params = {"enum_normal": [0, 1], "enum_string": [0, 1], "R_CONSOLE_OUTPUT": "TEMPORARY_OUTPUT"} + script = alg.build_import_commands(params, context, feedback) + + assert "enum_normal <- c(0, 1)" in script + assert 'enum_string <- c("enum_a","enum_b")' in script + assert "enum_string_optional <- NULL" in script + assert "enum_normal_optional <- NULL" in script + + params = { + "enum_normal": [0, 1, 2], + "enum_string": [0, 2], + "enum_string_optional": [0], + "enum_normal_optional": [1], + "R_CONSOLE_OUTPUT": "TEMPORARY_OUTPUT", + } + script = alg.build_import_commands(params, context, feedback) + + assert "enum_normal <- c(0, 1, 2)" in script + assert 'enum_string <- c("enum_a","enum_c")' in script + assert 'enum_string_optional <- c("enum_a")' in script + assert "enum_normal_optional <- c(1)" in script + + +def test_point(): + """ + Test Point parameter + """ + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + alg = RAlgorithm(description_file=script_path("test_input_point.rsx")) + alg.initAlgorithm() + + script = alg.build_r_script({"point": "20.219926,49.138354 [EPSG:4326]"}, context, feedback) + + assert 'library("sf")' in script + assert "point <- st_sfc(st_point(c(20.219926,49.138354)), crs = point_crs)" in script + + +def test_range(): + """ + Test range parameter + """ + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + alg = RAlgorithm(description_file=script_path("test_input_range.rsx")) + alg.initAlgorithm() + + script = alg.build_r_script({"range": [0, 1]}, context, feedback) + assert "range <- c(min = 0.0, max = 1.0)" in script + + script = alg.build_r_script({"range": [5, 10]}, context, feedback) + assert "range <- c(min = 5.0, max = 10.0)" in script + + script = alg.build_r_script({"range": [0.5, 1.5]}, context, feedback) + assert "range <- c(min = 0.5, max = 1.5)" in script + + +def test_color(): + """ + Test color parameter + """ + + if IS_API_BELOW_31000: + pytest.skip("QGIS version does not support this.") + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + alg = RAlgorithm(description_file=script_path("test_input_color.rsx")) + alg.initAlgorithm() + + script = alg.build_r_script({"color": QColor(0, 255, 0)}, context, feedback) + assert "color <- rgb(0, 255, 0, 255, maxColorValue = 255)" in script + + script = alg.build_r_script({"color": QColor(255, 0, 0)}, context, feedback) + assert "color <- rgb(255, 0, 0, 255, maxColorValue = 255)" in script + + +def test_date_time(): + """ + Test datetime parameter + """ + + if IS_API_BELOW_31400: + pytest.skip("QGIS version does not support this.") + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + alg = RAlgorithm(description_file=script_path("test_input_datetime.rsx")) + alg.initAlgorithm() + + script = alg.build_r_script({"datetime": QDateTime(QDate(2021, 10, 1), QTime(16, 57, 0))}, context, feedback) + assert 'datetime <- as.POSIXct("2021-10-01T16:57:00", format = "%Y-%m-%dT%H:%M:%S")' in script + + script = alg.build_r_script({"datetime": QDateTime(QDate(2021, 10, 14), QTime(14, 48, 52))}, context, feedback) + assert 'datetime <- as.POSIXct("2021-10-14T14:48:52", format = "%Y-%m-%dT%H:%M:%S")' in script + + +def test_expressions(): + """ + Test Expression parameter + """ + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + alg = RAlgorithm(description_file=script_path("test_input_expression.rsx")) + alg.initAlgorithm() + + script = alg.build_r_script({""}, context, feedback) + + assert "number <- 6" in script + assert any(['geometry <- sf::st_as_sfc("Polygon ' in line for line in script]) # pylint: disable=use-a-generator + + assert 'date_a <- as.POSIXct("2020-05-04", format = "%Y-%m-%d")' in script + assert 'time_a <- lubridate::hms("13:45:30")' in script + + +def test_raster_band(): + """ + Test datetime parameter + """ + + alg = RAlgorithm(description_file=script_path("test_raster_band.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"Band": 1, "Layer": data_path("dem.tif")}, context, feedback) + assert "Band <- 1" in script + + +def test_plot_outputs(): + """ + Test plot outputs + """ + + alg = RAlgorithm(description_file=script_path("test_algorithm_1.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"RPLOTS": "/tmp/plots"}, context, feedback) + assert any(["png(" in x for x in script]) + + +def test_crs(): + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_import_commands({"in_crs": QgsCoordinateReferenceSystem("EPSG:4326")}, context, feedback) + assert 'in_crs <- "EPSG:4326"' in script + + # invalid CRS + script = alg.build_import_commands({"in_crs": QgsCoordinateReferenceSystem("user:4326")}, context, feedback) + assert "in_crs <- NULL" in script diff --git a/tests/test_algorithm_metadata.py b/tests/test_algorithm_metadata.py new file mode 100644 index 0000000..60df26d --- /dev/null +++ b/tests/test_algorithm_metadata.py @@ -0,0 +1,99 @@ +from qgis.core import QgsProcessingContext, QgsProcessingFeedback + +from processing_r.processing.algorithm import RAlgorithm +from tests.utils import IS_API_ABOVE_31604, script_path + + +def test_dont_load_any_packages(): + """ + Test dont_load_any_packages keyword + """ + alg = RAlgorithm(description_file=script_path("test_dont_load_any_packages.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_r_script({}, context, feedback) + + assert not any([x == 'library("sf")' for x in script]) + assert not any([x == 'library("raster")' for x in script]) + assert not any(["library(" in x for x in script]) + + +def test_library_with_options(): + """ + Test library with options + """ + + alg = RAlgorithm(description_file=script_path("test_library_with_option.rsx")) + alg.initAlgorithm() + + script = alg.r_templates.build_script_header_commands(alg.script) + + assert 'tryCatch(find.package("MASS"), error = function(e) install.packages("MASS", dependencies=TRUE))' in script + + assert ( + 'tryCatch(find.package("Matrix"), error = function(e) install.packages("Matrix", dependencies=TRUE))' in script + ) + + assert 'library("MASS", quietly=True)' in script + assert 'library("Matrix")' in script + + +def test_help(): # pylint: disable=too-many-locals,too-many-statements + """ + Test algorithm help + """ + alg = RAlgorithm(description_file=script_path("test_algorithm_1.rsx")) + alg.initAlgorithm() + + assert "A polygon layer" in alg.shortHelpString() + assert "Me2" in alg.shortHelpString() + assert "Test help." in alg.shortHelpString() + + # param help + if IS_API_ABOVE_31604: + polyg_param = alg.parameterDefinition("polyg") + assert polyg_param.help() == "A polygon layer" + + # no help file + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + + assert alg.shortHelpString() == "" + + alg = RAlgorithm(description_file=script_path("test_algorithm_inline_help.rsx")) + alg.initAlgorithm() + + assert "A polygon layer" in alg.shortHelpString() + assert "Me2" in alg.shortHelpString() + assert "Test help." in alg.shortHelpString() + + # param help + if IS_API_ABOVE_31604: + polyg_param = alg.parameterDefinition("polyg") + assert polyg_param.help() == "A polygon layer description from multi-lines" + + +def test_unsupported_lines(): + alg = RAlgorithm(None, script="##load_raster_using_rgdal") + alg.initAlgorithm() + assert "This command is no longer supported" in alg.error + + alg = RAlgorithm(None, script="##load_vector_using_rgdal") + alg.initAlgorithm() + assert "This command is no longer supported" in alg.error + + alg = RAlgorithm(None, script="##dontuserasterpackage") + alg.initAlgorithm() + assert "This command is no longer supported" in alg.error + + +def test_github_install(): + alg = RAlgorithm(None, script="##user/repository=github_install") + alg.initAlgorithm() + + script = alg.r_templates.build_script_header_commands(alg.script) + assert 'library("remotes")' in script + assert 'remotes::install_github("user/repository")' in script diff --git a/tests/test_algorithm_outputs.py b/tests/test_algorithm_outputs.py new file mode 100644 index 0000000..889cf18 --- /dev/null +++ b/tests/test_algorithm_outputs.py @@ -0,0 +1,83 @@ +from qgis.core import ( + QgsProcessing, + QgsProcessingContext, + QgsProcessingFeedback, + QgsRasterLayer, + QgsVectorLayer, +) + +from processing_r.processing.algorithm import RAlgorithm +from tests.utils import USE_API_30900, data_path, script_path + + +def test_vector_outputs(): + """ + Test writing vector outputs + """ + alg = RAlgorithm(description_file=script_path("test_vectorout.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_export_commands( + {"Output": "/home/test/lines.shp", "OutputCSV": "/home/test/tab.csv"}, context, feedback + ) + assert script == [ + 'st_write(Output, "/home/test/lines.shp", layer = "lines", quiet = TRUE)', + 'write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)', + ] + + script = alg.build_export_commands( + {"Output": "/home/test/lines.gpkg", "OutputCSV": "/home/test/tab.csv"}, context, feedback + ) + assert script == [ + 'st_write(Output, "/home/test/lines.gpkg", layer = "lines", quiet = TRUE)', + 'write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)', + ] + + +def test_multi_outputs(): + """ + Test writing vector outputs + """ + alg = RAlgorithm(description_file=script_path("test_multiout.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_export_commands( + {"Output": "/home/test/lines.shp", "OutputCSV": "/home/test/tab.csv", "OutputFile": "/home/test/file.csv"}, + context, + feedback, + ) + + assert 'st_write(Output, "/home/test/lines.shp", layer = "lines", quiet = TRUE)' in script + assert 'write.csv(OutputCSV, "/home/test/tab.csv", row.names = FALSE)' in script + + assert script[2].startswith('cat("##OutputFile", file=') + assert script[3].startswith("cat(OutputFile, file=") + assert script[4].startswith('cat("##OutputNum", file=') + assert script[5].startswith("cat(OutputNum, file=") + assert script[6].startswith('cat("##OutputStr", file=') + assert script[7].startswith("cat(OutputStr, file=") + + +def test_raster_output(): + """ + Test writing raster outputs + """ + alg = RAlgorithm(description_file=script_path("test_raster_in_out.rsx")) + alg.initAlgorithm() + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + script = alg.build_export_commands( + {"Layer": data_path("dem.tif"), "out_raster": "/tmp/raster.tif"}, + context, + feedback, + ) + + assert 'writeRaster(out_raster, "/tmp/raster.tif", overwrite = TRUE)' in script diff --git a/tests/test_algorithm_script_parsing.py b/tests/test_algorithm_script_parsing.py new file mode 100644 index 0000000..76d23f6 --- /dev/null +++ b/tests/test_algorithm_script_parsing.py @@ -0,0 +1,283 @@ +from qgis.core import QgsProcessing, QgsProcessingParameterFile, QgsProcessingParameterNumber + +from processing_r.processing.algorithm import RAlgorithm +from tests.utils import script_path + + +def test_script_parsing_1(): + """ + Test script file parsing + """ + alg = RAlgorithm(description_file=script_path("test_algorithm_1.rsx")) + alg.initAlgorithm() + + assert alg.error is None + assert alg.name() == "test_algorithm_1" + assert alg.displayName() == "test algorithm 1" + assert "test_algorithm_1.rsx" in alg.description_file + assert alg.show_plots is True + assert alg.pass_file_names is True + assert alg.show_console_output is False + + +def test_script_parsing_2(): + """ + Test script file parsing + """ + alg = RAlgorithm(description_file=script_path("test_algorithm_2.rsx")) + alg.initAlgorithm() + assert alg.error is None + assert alg.name() == "mytest" + assert alg.displayName() == "my test" + assert alg.group() == "my group" + assert alg.groupId() == "my group" + assert alg.show_plots is False + assert alg.pass_file_names is False + assert alg.show_console_output is False + + # test that inputs were created correctly + raster_param = alg.parameterDefinition("in_raster") + assert raster_param.type() == "raster" + + vector_param = alg.parameterDefinition("in_vector") + assert vector_param.type() == "source" + + field_param = alg.parameterDefinition("in_field") + assert field_param.type() == "field" + assert field_param.parentLayerParameterName() == "in_vector" + + extent_param = alg.parameterDefinition("in_extent") + assert extent_param.type() == "extent" + + string_param = alg.parameterDefinition("in_string") + assert string_param.type() == "string" + + file_param = alg.parameterDefinition("in_file") + assert file_param.type() == "file" + + number_param = alg.parameterDefinition("in_number") + assert number_param.type() == "number" + assert number_param.dataType() == QgsProcessingParameterNumber.Double + + enum_param = alg.parameterDefinition("in_enum") + assert enum_param.type() == "enum" + + enum_param = alg.parameterDefinition("in_enum2") + assert enum_param.type() == "enum" + assert enum_param.options() == ["normal", "log10", "ln", "sqrt", "exp"] + + bool_param = alg.parameterDefinition("in_bool") + assert bool_param.type() == "boolean" + + # outputs + vector_output = alg.outputDefinition("out_vector") + assert vector_output.type() == "outputVector" + assert vector_output.dataType() == QgsProcessing.TypeVectorAnyGeometry + + vector_dest_param = alg.parameterDefinition("param_vector_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorAnyGeometry + + table_output = alg.outputDefinition("out_table") + assert table_output.type() == "outputVector" + assert table_output.dataType() == QgsProcessing.TypeVector + + table_dest_param = alg.parameterDefinition("param_table_dest") + assert table_dest_param.type() == "vectorDestination" + assert table_dest_param.dataType() == QgsProcessing.TypeVector + + vector_dest_param = alg.parameterDefinition("param_vector_dest2") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorAnyGeometry + + vector_dest_param = alg.parameterDefinition("param_vector_point_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorPoint + + vector_dest_param = alg.parameterDefinition("param_vector_line_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorLine + + vector_dest_param = alg.parameterDefinition("param_vector_polygon_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorPolygon + + raster_output = alg.outputDefinition("out_raster") + assert raster_output.type() == "outputRaster" + + raster_dest_param = alg.parameterDefinition("param_raster_dest") + assert raster_dest_param.type() == "rasterDestination" + + number_output = alg.outputDefinition("out_number") + assert number_output.type() == "outputNumber" + + string_output = alg.outputDefinition("out_string") + assert string_output.type() == "outputString" + + layer_output = alg.outputDefinition("out_layer") + assert layer_output.type() == "outputLayer" + + folder_output = alg.outputDefinition("out_folder") + assert folder_output.type() == "outputFolder" + + folder_dest_param = alg.parameterDefinition("param_folder_dest") + assert folder_dest_param.type() == "folderDestination" + + html_output = alg.outputDefinition("out_html") + assert html_output.type() == "outputHtml" + + html_dest_param = alg.parameterDefinition("param_html_dest") + assert html_dest_param.type() == "fileDestination" + + file_output = alg.outputDefinition("out_file") + assert file_output.type() == "outputFile" + + file_dest_param = alg.parameterDefinition("param_file_dest") + assert file_dest_param.type() == "fileDestination" + + csv_output = alg.outputDefinition("out_csv") + assert csv_output.type() == "outputFile" + + csv_dest_param = alg.parameterDefinition("param_csv_dest") + assert csv_dest_param.type() == "fileDestination" + assert csv_dest_param.defaultFileExtension() == "csv" + + +def test_script_parsing_3(): + """ + Test script file parsing + """ + # test display_name + alg = RAlgorithm(description_file=script_path("test_algorithm_3.rsx")) + alg.initAlgorithm() + + assert alg.error is None + assert alg.name() == "thealgid" + assert alg.displayName() == "the algo title" + assert alg.group() == "my group" + assert alg.groupId() == "my group" + + +def test_script_parsing_4(): + """ + Test script file parsing + """ + # test that inputs are defined as parameter description + alg = RAlgorithm(description_file=script_path("test_algorithm_4.rsx")) + alg.initAlgorithm() + + assert alg.error is None + assert alg.name() == "mytest" + assert alg.displayName() == "my test" + assert alg.group() == "my group" + assert alg.groupId() == "my group" + + raster_param = alg.parameterDefinition("in_raster") + assert raster_param.type() == "raster" + + vector_param = alg.parameterDefinition("in_vector") + assert vector_param.type() == "source" + + field_param = alg.parameterDefinition("in_field") + assert field_param.type() == "field" + assert field_param.parentLayerParameterName() == "in_vector" + + extent_param = alg.parameterDefinition("in_extent") + assert extent_param.type() == "extent" + + crs_param = alg.parameterDefinition("in_crs") + assert crs_param.type() == "crs" + + string_param = alg.parameterDefinition("in_string") + assert string_param.type() == "string" + + number_param = alg.parameterDefinition("in_number") + assert number_param.type() == "number" + assert number_param.dataType() == QgsProcessingParameterNumber.Integer + + enum_param = alg.parameterDefinition("in_enum") + assert enum_param.type() == "enum" + + bool_param = alg.parameterDefinition("in_bool") + assert bool_param.type() == "boolean" + + file_param = alg.parameterDefinition("in_file") + assert file_param.type() == "file" + assert file_param.behavior() == QgsProcessingParameterFile.File + + folder_param = alg.parameterDefinition("in_folder") + assert folder_param.type() == "file" + assert folder_param.behavior() == QgsProcessingParameterFile.Folder + + gpkg_param = alg.parameterDefinition("in_gpkg") + assert gpkg_param.type() == "file" + assert gpkg_param.behavior() == QgsProcessingParameterFile.File + assert gpkg_param.extension() == "gpkg" + + img_param = alg.parameterDefinition("in_img") + assert img_param.type() == "file" + assert img_param.behavior() == QgsProcessingParameterFile.File + assert img_param.extension() == "" + assert img_param.fileFilter() == "PNG Files (*.png);; JPG Files (*.jpg *.jpeg)" + + vector_dest_param = alg.parameterDefinition("param_vector_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorAnyGeometry + + vector_dest_param = alg.parameterDefinition("param_vector_point_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorPoint + + vector_dest_param = alg.parameterDefinition("param_vector_line_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorLine + + vector_dest_param = alg.parameterDefinition("param_vector_polygon_dest") + assert vector_dest_param.type() == "vectorDestination" + assert vector_dest_param.dataType() == QgsProcessing.TypeVectorPolygon + + table_dest_param = alg.parameterDefinition("param_table_dest") + assert table_dest_param.type() == "vectorDestination" + assert table_dest_param.dataType() == QgsProcessing.TypeVector + + raster_dest_param = alg.parameterDefinition("param_raster_dest") + assert raster_dest_param.type() == "rasterDestination" + + folder_dest_param = alg.parameterDefinition("param_folder_dest") + assert folder_dest_param.type() == "folderDestination" + + file_dest_param = alg.parameterDefinition("param_file_dest") + assert file_dest_param.type() == "fileDestination" + + html_dest_param = alg.parameterDefinition("param_html_dest") + assert html_dest_param.type() == "fileDestination" + assert html_dest_param.fileFilter() == "HTML Files (*.html)" + assert html_dest_param.defaultFileExtension() == "html" + + csv_dest_param = alg.parameterDefinition("param_csv_dest") + assert csv_dest_param.type() == "fileDestination" + assert csv_dest_param.fileFilter() == "CSV Files (*.csv)" + assert csv_dest_param.defaultFileExtension() == "csv" + + img_dest_param = alg.parameterDefinition("param_img_dest") + assert img_dest_param.type() == "fileDestination" + assert img_dest_param.fileFilter() == "PNG Files (*.png);; JPG Files (*.jpg *.jpeg)" + assert img_dest_param.defaultFileExtension() == "png" + + +def test_bad_syntax(): + """ + Test a bad script + """ + alg = RAlgorithm(description_file=script_path("bad_algorithm.rsx")) + alg.initAlgorithm() + assert alg.name() == "bad_algorithm" + assert alg.displayName() == "bad algorithm" + assert alg.error == "This script has a syntax error.\nProblem with line: polyg=xvector" + + +def test_console_output(): + alg = RAlgorithm(description_file=script_path("test_enum_multiple.rsx")) + alg.initAlgorithm() + + assert alg.show_console_output is True diff --git a/tests/test_gui_utils.py b/tests/test_gui_utils.py new file mode 100644 index 0000000..e107240 --- /dev/null +++ b/tests/test_gui_utils.py @@ -0,0 +1,18 @@ +from processing_r.gui.gui_utils import GuiUtils + + +def test_get_icon(): + """ + Tests get_icon + """ + assert GuiUtils.get_icon("providerR.svg").isNull() is False + assert GuiUtils.get_icon("not_an_icon.svg").isNull() is True + + +def test_get_icon_svg(): + """ + Tests get_icon svg path + """ + assert GuiUtils.get_icon_svg("providerR.svg") + assert "providerR.svg" in GuiUtils.get_icon_svg("providerR.svg") + assert GuiUtils.get_icon_svg("not_an_icon.svg") == "" diff --git a/tests/test_r_template.py b/tests/test_r_template.py new file mode 100644 index 0000000..6329406 --- /dev/null +++ b/tests/test_r_template.py @@ -0,0 +1,36 @@ +from processing_r.processing.r_templates import RTemplates + + +def test_github_install(): + """ + Test github install code generation. + """ + templates = RTemplates() + templates.install_github = True + templates.github_dependencies = "user_1/repo_1, user_2/repo_2" + assert ( + templates.install_package_github(templates.github_dependencies[0]) == 'remotes::install_github("user_1/repo_1")' + ) + + assert ( + templates.install_package_github(templates.github_dependencies[1]) == 'remotes::install_github("user_2/repo_2")' + ) + + +def test_string(): # pylint: disable=too-many-locals,too-many-statements + """ + Test string variable + """ + templates = RTemplates() + assert templates.set_variable_string("var", "val") == 'var <- "val"' + assert templates.set_variable_string("var", 'va"l') == 'var <- "va\\"l"' + + +def test_string_list_variable(): # pylint: disable=too-many-locals,too-many-statements + """ + Test string list variable + """ + templates = RTemplates() + assert templates.set_variable_string_list("var", []) == "var <- c()" + assert templates.set_variable_string_list("var", ["aaaa"]) == 'var <- c("aaaa")' + assert templates.set_variable_string_list("var", ["aaaa", 'va"l']) == 'var <- c("aaaa","va\\"l")' diff --git a/tests/test_r_utils.py b/tests/test_r_utils.py new file mode 100644 index 0000000..853785c --- /dev/null +++ b/tests/test_r_utils.py @@ -0,0 +1,175 @@ +from pathlib import Path + +from processing.core.ProcessingConfig import ProcessingConfig +from qgis.PyQt.QtCore import QCoreApplication, QSettings + +from processing_r.processing.provider import RAlgorithmProvider +from processing_r.processing.utils import RUtils + + +def test_r_is_installed(): + """ + Test checking that R is installed + """ + assert RUtils.check_r_is_installed() is None + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/home") + + assert isinstance(RUtils.check_r_is_installed(), str) + assert "R is not installed" in RUtils.check_r_is_installed() + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) + assert RUtils.check_r_is_installed() is None + + +def test_guess_r_binary_folder(): + """ + Test guessing the R binary folder -- not much to do here, all the logic is Windows specific + """ + assert RUtils.guess_r_binary_folder() == "" + + +def test_r_binary_folder(): + """ + Test retrieving R binary folder + """ + assert RUtils.r_binary_folder() == "" + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/usr/local/bin") + assert RUtils.r_binary_folder() == "/usr/local/bin" + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) + assert RUtils.r_binary_folder() == "" + + +def test_r_executable(): + """ + Test retrieving R executable + """ + assert RUtils.path_to_r_executable() == "R" + assert RUtils.path_to_r_executable(script_executable=True) == "Rscript" + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, "/usr/local/bin") + assert RUtils.path_to_r_executable() == "/usr/local/bin/R" + assert RUtils.path_to_r_executable(script_executable=True) == "/usr/local/bin/Rscript" + + ProcessingConfig.setSettingValue(RUtils.R_FOLDER, None) + assert RUtils.path_to_r_executable() == "R" + assert RUtils.path_to_r_executable(script_executable=True) == "Rscript" + + +def test_package_repo(): + """ + Test retrieving/setting the package repo + """ + assert RUtils.package_repo() == "http://cran.at.r-project.org/" + + ProcessingConfig.setSettingValue(RUtils.R_REPO, "http://mirror.at.r-project.org/") + assert RUtils.package_repo() == "http://mirror.at.r-project.org/" + + ProcessingConfig.setSettingValue(RUtils.R_REPO, "http://cran.at.r-project.org/") + assert RUtils.package_repo() == "http://cran.at.r-project.org/" + + +def test_use_user_library(): + """ + Test retrieving/setting the user library setting + """ + assert RUtils.use_user_library() is True + + ProcessingConfig.setSettingValue(RUtils.R_USE_USER_LIB, False) + assert RUtils.use_user_library() is False + + ProcessingConfig.setSettingValue(RUtils.R_USE_USER_LIB, True) + assert RUtils.use_user_library() is True + + +def test_library_folder(): + """ + Test retrieving/setting the library folder + """ + assert "/profiles/default/processing/rlibs" in RUtils.r_library_folder() + + ProcessingConfig.setSettingValue(RUtils.R_LIBS_USER, "/usr/local") + assert RUtils.r_library_folder() == "/usr/local" + + ProcessingConfig.setSettingValue(RUtils.R_LIBS_USER, None) + assert "/profiles/default/processing/rlibs" in RUtils.r_library_folder() + + +def test_is_error_line(): + """ + Test is_error_line + """ + assert RUtils.is_error_line("xxx yyy") is False + assert RUtils.is_error_line("Error something went wrong") is True + assert RUtils.is_error_line("Execution halted") is True + + +def test_is_valid_r_variable(): + """ + Test for strings to check if they are valid R variables. + """ + assert RUtils.is_valid_r_variable("var_name%") is False + assert RUtils.is_valid_r_variable("2var_name") is False + assert RUtils.is_valid_r_variable(".2var_name") is False + assert RUtils.is_valid_r_variable("_var_name") is False + + assert RUtils.is_valid_r_variable("var_name2.") is True + assert RUtils.is_valid_r_variable(".var_name") is True + assert RUtils.is_valid_r_variable("var.name") is True + + +def test_scripts_folders(): + """ + Test script folders + """ + assert RUtils.script_folders() + assert RUtils.default_scripts_folder() in RUtils.script_folders() + assert RUtils.builtin_scripts_folder() in RUtils.script_folders() + + +def test_descriptive_name(): + """ + Tests creating descriptive name + """ + assert RUtils.create_descriptive_name("a B_4324_asd") == "a B 4324 asd" + + +def test_strip_special_characters(): + """ + Tests stripping special characters from a name + """ + assert RUtils.strip_special_characters("aB 43 24a:sd") == "aB4324asd" + + +def test_is_windows(): + """ + Test is_windows + """ + assert RUtils.is_windows() is False # suck it, Windows users! + + +def test_is_macos(): + """ + Test is_macos + """ + assert RUtils.is_macos() is False # suck it even more, MacOS users! + + +def test_built_in_path(): + """ + Tests built in scripts path + """ + assert RUtils.builtin_scripts_folder() + assert "builtin_scripts" in RUtils.builtin_scripts_folder() + assert Path(RUtils.builtin_scripts_folder()).exists() + + +def test_default_scripts_folder(): + """ + Tests default user scripts folder + """ + assert RUtils.default_scripts_folder() + assert "rscripts" in RUtils.default_scripts_folder() + assert Path(RUtils.default_scripts_folder()).exists() diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..2f5b9b6 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,24 @@ +from pathlib import Path + +from qgis.core import Qgis, QgsProcessingAlgorithm + + +def script_path(filename: str) -> str: + path = Path(__file__).parent / "scripts" / filename + return path.as_posix() + + +def data_path(filename: str) -> str: + path = Path(__file__).parent / "data" / filename + return path.as_posix() + + +USE_API_30900 = Qgis.QGIS_VERSION_INT >= 30900 and hasattr( + QgsProcessingAlgorithm, "parameterAsCompatibleSourceLayerPathAndLayerName" +) + +IS_API_BELOW_31000 = Qgis.QGIS_VERSION_INT < 31000 + +IS_API_BELOW_31400 = Qgis.QGIS_VERSION_INT < 31400 + +IS_API_ABOVE_31604 = Qgis.QGIS_VERSION_INT >= 31604