From 5a4efccd23a2fee9b30cfc7558d2234d43f6386f Mon Sep 17 00:00:00 2001 From: ptresson <paul.tresson@ird.fr> Date: Tue, 29 Oct 2024 10:51:36 +0100 Subject: [PATCH] github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix github-actions for test matrix test output with resut geotiff properties rather than hashes that seem to restrictive github-actions for test matrix fix import error for windows tests fix import error for windows tests fix import error for windows tests fix import error for windows tests change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install change qgis install check path change qgis install change qgis install update rtree requirement to fix OSError change qgis install back to original conda install for now bump rtree requirement update pthon paths for windows update pthon paths for windows - change python requirement update pthon paths for windows - change python requirement update pthon paths for windows - change python requirement update pthon paths for windows update pthon paths for windows update pthon paths for windows update pthon paths for windows isntall qgis with mamba ? https://stackoverflow.com/questions/77059426/module-not-found-error-after-qgis-installation test with mamba test with mamba test with mamba test with mamba test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init test with mamba with shell init --- .github/workflows/github-actions-demo.yml | 18 ---- .github/workflows/jobs.yml | 118 ++++++++++++++++++++++ requirements-ga.txt | 14 +++ requirements.txt | 2 +- tests/__init__.py | 5 + tests/test_common.py | 19 +++- tests/test_encoder.py | 39 +------ utils/geo.py | 37 +++++++ 8 files changed, 193 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/github-actions-demo.yml create mode 100644 .github/workflows/jobs.yml create mode 100644 requirements-ga.txt diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml deleted file mode 100644 index 15a61d6..0000000 --- a/.github/workflows/github-actions-demo.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: GitHub Actions Demo -run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 -on: [push] -jobs: - Explore-GitHub-Actions: - runs-on: ubuntu-latest - steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🧠This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - name: Check out repository code - uses: actions/checkout@v4 - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "ðŸ–¥ï¸ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ github.workspace }} - - run: echo "ðŸ This job's status is ${{ job.status }}." diff --git a/.github/workflows/jobs.yml b/.github/workflows/jobs.yml new file mode 100644 index 0000000..39faa7f --- /dev/null +++ b/.github/workflows/jobs.yml @@ -0,0 +1,118 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - github-actions + pull_request: + branches: + - github-actions + +jobs: + build: + name: ${{ matrix.os }}, Python 3.${{ matrix.python-minor-version }}, QGIS 3.${{ matrix.qgis-minor-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 6 + matrix: + # os: [ubuntu-latest , macos-latest , windows-latest] + # python-minor-version: [11, 12] + # qgis-minor-version: [34, 36, 38] + os: [windows-latest] + python-minor-version: [11] + qgis-minor-version: [38] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Miniconda + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: 3.${{ matrix.python-minor-version }} + channels: conda-forge + auto-update-conda: true + + - name: Set Python Encoding for Windows + if: matrix.os == 'windows-latest' + run: | + set PYTHONIOENCODING=utf-8 + set PYTHONLEGACYWINDOWSSTDIO=utf-8 + + + - name: Install Mamba in Base Environment + run: conda install -n test -c conda-forge mamba --yes + + - name: Set up Environment and Install Dependencies + run: | + mamba create -n pytest python=3.${{ matrix.python-minor-version }} qgis=3.${{ matrix.qgis-minor-version }} --yes + mamba install -n pytest --file requirements.txt --yes + mamba install -n pytest pytest --yes + shell: bash -el {0} + + - name: Run Tests + run: | + conda run -n pytest pytest + shell: bash -el {0} + + # steps: + # - name: Checkout + # uses: actions/checkout@v3 + + # - name: Set up Miniconda + # uses: conda-incubator/setup-miniconda@v3 + # with: + # python-version: 3.${{matrix.python-minor-version}} + # channels: conda-forge + + # - name: Install Mamba + # run: | + # conda init + # conda install -n test -c conda-forge mamba + # - name: Init Mamba shell + # if: matrix.os == 'windows-latest' + # run: | + # eval "$(mamba.exe shell hook --shell powershell)" + # mamba shell init --shell powershell + # mamba shell reinit --shell powershell + # shell: bash -el {0} + + # - name: Set up Environment and Install Dependencies + # run: | + # conda activate test + # mamba create -n pytest python=3.${{matrix.python-minor-version}} qgis=3.${{matrix.qgis-minor-version}} --yes + # mamba activate pytest + # mamba install --file requirements.txt --yes + # mamba install pytest --yes + # shell: bash -el {0} + # - name: Set up Conda + # # uses: conda-incubator/setup-miniconda@v3 + # uses: mamba-org/setup-micromamba@v1 + # # with: + # # python-version: 3.${{matrix.python-minor-version}} + # # channels: conda-forge + # - run: | + # mamba init + # mamba activate test + # # conda install qgis=3.${{matrix.qgis-minor-version}} + # mamba install qgis=3.${{matrix.qgis-minor-version}} + # mamba install --file requirements.txt + # mamba install pytest + # shell: bash -el {0} + + # - name: Check Path in windows + # if: matrix.os == 'windows-latest' + # run: | + # conda activate test + # $conda_env_path = (Split-Path -Path (Split-Path -Path (Get-Command python).Path)) + + # # Update PATH to include the "Library\bin" directory for the conda environment + # Add-Content -Path $Env:GITHUB_ENV -Value "PATH=$conda_env_path\Library\bin;$Env:PATH" + # shell: powershell + + # - name: Tests + # run: | + # mamba activate test + # pytest tests/ + # shell: bash -el {0} diff --git a/requirements-ga.txt b/requirements-ga.txt new file mode 100644 index 0000000..0669354 --- /dev/null +++ b/requirements-ga.txt @@ -0,0 +1,14 @@ +geopandas >= 0.14.4 +scikit-learn >= 1.5.1 +psutil >= 5.0.0 +# from torchgeo +rasterio >= 1.2 +rtree >= 0.9 +einops >= 0.3 +fiona >= 1.8.19 +kornia >= 0.6.9 +numpy >= 1.19.3 +pyproj >= 3.3 +shapely >= 1.7.1 +timm >= 0.4.12 +pytest diff --git a/requirements.txt b/requirements.txt index 9e22d50..a2d31d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ psutil >= 5.0.0 # torchgeo == 0.5.2 # from torchgeo rasterio >= 1.2 -rtree <= 0.9 +rtree >= 1 einops >= 0.3 fiona >= 1.8.19 kornia >= 0.6.9 diff --git a/tests/__init__.py b/tests/__init__.py index 2c76a82..c0a9a56 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,8 +4,13 @@ import os PYTHON_VERSION = sys.version_info SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PLUGIN_ROOT_DIR = os.path.realpath(os.path.abspath(os.path.join(SCRIPT_DIR, ".."))) +QGIS_PYTHON_DIR = os.path.realpath(os.path.abspath(os.path.join(PLUGIN_ROOT_DIR, ".."))) PACKAGES_INSTALL_DIR = os.path.join( PLUGIN_ROOT_DIR, f"python{PYTHON_VERSION.major}.{PYTHON_VERSION.minor}" ) sys.path.append(PACKAGES_INSTALL_DIR) # TODO: check for a less intrusive way to do this + +qgis_python_path = os.getenv("PYTHONPATH") +if qgis_python_path and qgis_python_path not in sys.path: + sys.path.append(qgis_python_path) diff --git a/tests/test_common.py b/tests/test_common.py index 22c5b22..792cc55 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,4 +1,5 @@ import os +import pytest from pathlib import Path import tempfile import unittest @@ -12,6 +13,8 @@ from ..similarity import SimilarityAlgorithm from ..clustering import ClusterAlgorithm from ..reduction import ReductionAlgorithm from ..utils.misc import get_file_md5_hash, remove_files_with_extensions +from ..utils.geo import validate_geotiff + INPUT = os.path.join(Path(__file__).parent.parent.absolute(), "assets", "test.tif") OUTPUT = os.path.join(tempfile.gettempdir(), "iamap_test") @@ -36,6 +39,8 @@ class TestReductionAlgorithm(unittest.TestCase): "d7a32c6b7a4cee1af9c73607561d7b25", "e04f8c86d9aad81dd9c625b9cd8f9824", ] + output_size = 4405122 + output_wh = (968,379) out_name = "proj.tif" def setUp(self): @@ -48,22 +53,25 @@ class TestReductionAlgorithm(unittest.TestCase): self.default_parameters, self.context, self.feedback ) expected_result_path = os.path.join(self.algorithm.output_dir, self.out_name) - result_file_hash = get_file_md5_hash(expected_result_path) + @pytest.mark.parametrize("output_file", expected_result_path, "expected_output_size", self.output_size, "expected_wh", self.output_wh) + def test_geotiff_validity(output_file): + validate_geotiff(output_file) remove_files_with_extensions(self.algorithm.output_dir, EXTENSIONS_TO_RM) - assert result_file_hash in self.possible_hashes class TestClusteringAlgorithm(TestReductionAlgorithm): algorithm = ClusterAlgorithm() - possible_hashes = ["0c47b0c4b4c13902db5da3ee6e5d4aef"] + # possible_hashes = ["0c47b0c4b4c13902db5da3ee6e5d4aef"] out_name = "cluster.tif" + output_size = 4405122 class TestSimAlgorithm(TestReductionAlgorithm): algorithm = SimilarityAlgorithm() default_parameters = {"INPUT": INPUT, "OUTPUT": OUTPUT, "TEMPLATE": TEMPLATE} - possible_hashes = ["f76eb1f0469725b49fe0252cfe86829a"] + # possible_hashes = ["f76eb1f0469725b49fe0252cfe86829a"] out_name = "similarity.tif" + output_size = 1468988 class TestMLAlgorithm(TestReductionAlgorithm): @@ -74,8 +82,9 @@ class TestMLAlgorithm(TestReductionAlgorithm): "TEMPLATE": TEMPLATE_RF, "GT_COL": GT_COL, } - possible_hashes = ["bd22d66180347e043fca58d494876184"] + # possible_hashes = ["bd22d66180347e043fca58d494876184"] out_name = "ml.tif" + output_size = 367520 if __name__ == "__main__": diff --git a/tests/test_encoder.py b/tests/test_encoder.py index f386f34..ead5a66 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -16,6 +16,7 @@ from ..tg.datasets import RasterDataset from ..encoder import EncoderAlgorithm from ..utils.misc import get_file_md5_hash +from ..utils.geo import validate_geotiff INPUT = os.path.join(Path(__file__).parent.parent.absolute(), "assets", "test.tif") @@ -59,42 +60,11 @@ class TestEncoderAlgorithm(unittest.TestCase): self.default_parameters, self.context, self.feedback ) expected_result_path = os.path.join(self.algorithm.output_subdir, "merged.tif") - result_file_hash = get_file_md5_hash(expected_result_path) - - ## different rasterio versions lead to different hashes ? - ## GPU and quantization as well - possible_hashes = [ - "0fb32cc57a0dd427d9f0165ec6d5418f", - "48c3a78773dbc2c4c7bb7885409284ab", - "431e034b842129679b99a067f2bd3ba4", - "60153535214eaa44458db4e297af72b9", - "f1394d1950f91e4f8277a8667ae77e85", - "a23837caa3aca54aaca2974d546c5123", - "43ac54811a1892f81a4793de2426b43f", - ] - assert result_file_hash in possible_hashes + @pytest.mark.parametrize("output_file", expected_result_path) + def test_geotiff_validity(output_file): + validate_geotiff(output_file) os.remove(expected_result_path) - @pytest.mark.slow - def test_data_types(self): - self.algorithm.initAlgorithm() - parameters = self.default_parameters - parameters["OUT_DTYPE"] = 1 - _ = self.algorithm.processAlgorithm(parameters, self.context, self.feedback) - expected_result_path = os.path.join(self.algorithm.output_subdir, "merged.tif") - result_file_hash = get_file_md5_hash(expected_result_path) - - ## different rasterio versions lead to different hashes ? - possible_hashes = [ - "ef0c4b0d57f575c1cd10c0578c7114c0", - "ebfad32752de71c5555bda2b40c19b2e", - "d3705c256320b7190dd4f92ad2087247", - "65fa46916d6d0d08ad9656d7d7fabd01", - "43ac54811a1892f81a4793de2426b43f", - ] - if result_file_hash in possible_hashes: - os.remove(expected_result_path) - assert result_file_hash in possible_hashes def test_timm_create_model(self): archs = [ @@ -173,5 +143,4 @@ if __name__ == "__main__": test_encoder.test_timm_create_model() test_encoder.test_RasterDataset() test_encoder.test_valid_parameters() - test_encoder.test_data_types() test_encoder.test_cuda() diff --git a/utils/geo.py b/utils/geo.py index a0847c0..cfd410f 100644 --- a/utils/geo.py +++ b/utils/geo.py @@ -1,5 +1,7 @@ +import os from typing import Callable, Union import rasterio +import rasterio.errors import geopandas as gpd import numpy as np from rasterio.merge import merge @@ -181,6 +183,41 @@ def get_unique_col_name(gdf, base_name="fold"): return column_name +def validate_geotiff(output_file, expected_output_size=4428850, expected_wh=(60,24)): + """ + tests geotiff validity by opening with rasterio, + checking if the file weights as expected and has the correct width and height. + Additionaly, it is checked if there is more than one value in the raster. + """ + + expected_size_min = .8*expected_output_size + expected_size_max = 1.2*expected_output_size + # 1. Check if the output file is a valid GeoTIFF + try: + with rasterio.open(output_file) as src: + assert src.meta['driver'] == 'GTiff', "File is not a valid GeoTIFF." + width = src.width + height = src.height + # 2. Read the data and check width/height + assert width == expected_wh[0], f"Expected width {expected_wh[0]}, got {width}." + assert height == expected_wh[1], f"Expected height {expected_wh[1]}, got {height}." + # 3. Read the data and check for unique values + data = src.read(1) # Read the first band + unique_values = np.unique(data) + + assert len(unique_values) > 1, "The GeoTIFF contains only one unique value." + + except rasterio.errors.RasterioIOError: + print("The file could not be opened as a GeoTIFF, indicating it is invalid.") + assert False + + # 4. Check if the file size is within the expected range + file_size = os.path.getsize(output_file) + assert expected_size_min <= file_size <= expected_size_max, ( + f"File size {file_size} is outside the expected range." + ) + return + if __name__ == "__main__": gdf = gpd.read_file("assets/ml_poly.shp") print(gdf) -- GitLab