From 97ccdf62e50940c7e5fa338e7953699edb225dfc Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 15:57:23 -0600 Subject: [PATCH 01/10] DAS-2431: Updates tests to be architecture independent. You can now run the tests in docker or in your local environment. ARM vs AMD will not affect the outputs at the test level. --- tests/unit/test_coordinate_utilities.py | 10 +++++----- tests/unit/test_projection_utilities.py | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_coordinate_utilities.py b/tests/unit/test_coordinate_utilities.py index f651d74..df62051 100644 --- a/tests/unit/test_coordinate_utilities.py +++ b/tests/unit/test_coordinate_utilities.py @@ -1165,7 +1165,7 @@ def test_get_dimension_order_and_dim_values(self): self.lat_arr, self.lon_arr, row_indices, crs, is_row=True ) self.assertEqual(y_x_order, True) - self.assertListEqual(dim_values, expected_dim_values) + np.testing.assert_allclose(dim_values, expected_dim_values, rtol=1e-7) with self.subTest('Get y_x order when projected_dim is changing across column'): col_indices = [[0, 0], [0, 9]] expected_dim_values = [-17299990.048985746, 17213152.396759935] @@ -1173,7 +1173,7 @@ def test_get_dimension_order_and_dim_values(self): self.lat_arr, self.lon_arr, col_indices, crs, is_row=False ) self.assertEqual(y_x_order, True) - self.assertListEqual(dim_values, expected_dim_values) + np.testing.assert_allclose(dim_values, expected_dim_values, rtol=1e-7) with self.subTest('Get x_y order when projected_dim is changing across row'): row_indices = [[0, 0], [4, 0]] expected_dim_values = [-17299990.048985746, 17213152.396759935] @@ -1185,7 +1185,7 @@ def test_get_dimension_order_and_dim_values(self): is_row=True, ) self.assertEqual(y_x_order, False) - self.assertListEqual(dim_values, expected_dim_values) + np.testing.assert_allclose(dim_values, expected_dim_values, rtol=1e-7) with self.subTest('Get x_y order when projected_dim is changing across column'): col_indices = [[0, 0], [0, 9]] expected_dim_values = [7341677.255608977, -7341677.255608977] @@ -1197,7 +1197,7 @@ def test_get_dimension_order_and_dim_values(self): is_row=False, ) self.assertEqual(y_x_order, False) - self.assertListEqual(dim_values, expected_dim_values) + np.testing.assert_allclose(dim_values, expected_dim_values, rtol=1e-7) with self.subTest('Get y_x order when projected_dims are not varying'): lat_arr = np.array( [ @@ -1235,7 +1235,7 @@ def test_get_dimension_order_and_dim_values(self): self.lat_arr_3d, self.lon_arr_3d, row_indices, crs, is_row=True ) self.assertEqual(y_x_order, True) - self.assertListEqual(dim_values, expected_dim_values) + np.testing.assert_allclose(dim_values, expected_dim_values, rtol=1e-7) def test_create_dimension_arrays_from_coordinates( self, diff --git a/tests/unit/test_projection_utilities.py b/tests/unit/test_projection_utilities.py index 54652c1..4a36913 100644 --- a/tests/unit/test_projection_utilities.py +++ b/tests/unit/test_projection_utilities.py @@ -85,6 +85,17 @@ def read_geojson(geojson_base_name: str): return geojson_content + def assertDictAlmostEqual(self, dict1, dict2, places=7): + """Test the float values of dictionaries as almost equal.""" + self.assertEqual(dict1.keys(), dict2.keys()) + for key in dict1: + self.assertAlmostEqual( + dict1[key], + dict2[key], + places=places, + msg=f'Mismatch in {key}', + ) + @patch('hoss.projection_utilities.get_grid_mapping_attributes') def test_get_variable_crs(self, mock_get_grid_mapping_attributes): """Ensure a `pyproj.CRS` object can be instantiated from the given @@ -359,7 +370,7 @@ def test_get_projected_x_y_extents(self): } with self.subTest('Bounding box input'): - self.assertDictEqual( + self.assertDictAlmostEqual( get_projected_x_y_extents( x_values, y_values, crs, bounding_box=bounding_box ), @@ -367,7 +378,7 @@ def test_get_projected_x_y_extents(self): ) with self.subTest('Shape file input'): - self.assertDictEqual( + self.assertDictAlmostEqual( get_projected_x_y_extents( x_values, y_values, crs, shape_file=polygon_path ), @@ -403,7 +414,7 @@ def test_get_projected_x_y_extents_whole_earth(self): 'y_max': 12702440.710450241, } with self.subTest('Whole Earth LAEA - Bounding box input'): - self.assertDictEqual( + self.assertDictAlmostEqual( get_projected_x_y_extents( x_values, y_values, crs, bounding_box=whole_earth_bbox ), @@ -411,7 +422,7 @@ def test_get_projected_x_y_extents_whole_earth(self): ) with self.subTest('Whole Earth LAEA - Shape file input'): - self.assertDictEqual( + self.assertDictAlmostEqual( get_projected_x_y_extents( x_values, y_values, crs, shape_file=polygon_path ), @@ -1160,7 +1171,7 @@ def test_get_x_y_extents_from_geographic_points(self): 'y_max': 1670250.0136418417, } - self.assertDictEqual( + self.assertDictAlmostEqual( get_x_y_extents_from_geographic_points(points, crs), expected_x_y_extents ) From bcc329f376d3e2cd88a1757b95f4cc5ec00a35a1 Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 15:58:21 -0600 Subject: [PATCH 02/10] DAS-2431: Fixes test so that fixutre is not modified after test. This was modifying the test and it could only be run one time before generating errors. --- tests/unit/test_dimension_utilities.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_dimension_utilities.py b/tests/unit/test_dimension_utilities.py index 13e71b1..9a85f10 100644 --- a/tests/unit/test_dimension_utilities.py +++ b/tests/unit/test_dimension_utilities.py @@ -1,5 +1,6 @@ from logging import getLogger from os.path import exists +from pathlib import Path from shutil import copy, rmtree from tempfile import mkdtemp from unittest import TestCase @@ -677,8 +678,12 @@ def test_write_bounds(self): and in a nested group. """ + + test_filename = Path(self.temp_dir) / 'ATL16_prefetch_group.nc4' + copy('tests/data/ATL16_prefetch_group.nc4', test_filename) + varinfo_prefetch = VarInfoFromDmr('tests/data/ATL16_prefetch_group.dmr') - prefetch_dataset = Dataset('tests/data/ATL16_prefetch_group.nc4', 'r+') + prefetch_dataset = Dataset(test_filename, 'r+') # Expected variable contents in file. expected_bounds_data = self.bounds_array From 7ad24740700b07c6d5872808dad7923f01f906dc Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 16:27:57 -0600 Subject: [PATCH 03/10] DAS-2431: Update coverage/tests for local and CI and Docker. This just uses pytest-cov to generate coverage and relocates the output to the same place whether you run from docker or the commandline. --- bin/run-test | 2 +- tests/pip_test_requirements.txt | 12 +++++------- tests/run_tests.sh | 15 +++++---------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/bin/run-test b/bin/run-test index 0c10269..e4cc442 100755 --- a/bin/run-test +++ b/bin/run-test @@ -24,6 +24,6 @@ mkdir -p coverage # Run the tests in a Docker container with mounted volumes for XML report # output and test coverage reporting docker run --platform linux/amd64 --rm \ - -v $(pwd)/test-reports:/home/tests/reports \ + -v $(pwd)/test-reports:/home/tests/test-reports \ -v $(pwd)/coverage:/home/tests/coverage \ ghcr.io/nasa/harmony-opendap-subsetter-test "$@" diff --git a/tests/pip_test_requirements.txt b/tests/pip_test_requirements.txt index 1ad2d6b..1b5d68f 100644 --- a/tests/pip_test_requirements.txt +++ b/tests/pip_test_requirements.txt @@ -1,7 +1,5 @@ -coverage~=7.9.2 -pre-commit~=4.2.0 -pycodestyle~=2.14.0 -pylint ~= 3.3.7 -pytest~=8.3.3 -pytest-mock==3.14.0 -unittest-xml-reporting~=3.2.0 +pycodestyle ~= 2.14.0 +pylint ~= 3.3.9 +pytest ~= 8.3.3 +pytest-mock == 3.14.0 +pytest-cov == 7.0.0 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 3a6c825..4a5a3b2 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash #################################### # # A script invoked by the test Dockerfile to run the Python `unittest` suite @@ -14,12 +14,10 @@ # Exit status used to report back to caller STATUS=0 -export HDF5_DISABLE_VERSION_CHECK=1 -# Run all tests using pytest (will also run unittest tests), producing JUnit -# compatible output -echo "\nRunning tests..." -coverage run -m pytest tests/ --junitxml=tests/reports/pytest-results.xml -v +echo -e "\nRunning tests..." +pytest ./tests -s --cov=hoss --junitxml=test-reports/pytest-results.xml --cov-report=html:coverage --cov-report term + RESULT=$? if [ "$RESULT" -ne "0" ]; then @@ -27,10 +25,7 @@ if [ "$RESULT" -ne "0" ]; then echo "ERROR: tests generated errors" fi -echo "\n" -echo "Test Coverage Estimates" -coverage report --omit="tests/*" -coverage html --omit="tests/*" -d /home/tests/coverage +echo -e "\n" # Run pylint # Ignored errors/warnings: From c920cea18b4438079c4de9a63b97bac27d5dfa1d Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 16:32:31 -0600 Subject: [PATCH 04/10] DAS-2431: Update template for fixversion --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8e6f556..833d0b4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,3 +14,4 @@ A short description of the changes in this PR. * [ ] `docker/service_version.txt` updated if publishing a release. * [ ] Tests added/updated and passing. * [ ] Documentation updated (if needed). +* [ ] Jira ticket updated with expected fixversion hoss-X.Y.Z From 086ff3624773cf16583c4c5649a31d173f9e4e94 Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 16:46:24 -0600 Subject: [PATCH 05/10] DAS-2431: Fling some directories. --- tests/run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 4a5a3b2..d211229 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -15,8 +15,9 @@ STATUS=0 + echo -e "\nRunning tests..." -pytest ./tests -s --cov=hoss --junitxml=test-reports/pytest-results.xml --cov-report=html:coverage --cov-report term +pytest ./tests -s --cov=hoss --junitxml=./test-reports/pytest-results.xml --cov-report=html:./coverage --cov-report term RESULT=$? From c3916148b42d1a951b9139847c9b5dfeeb498abb Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 16:57:06 -0600 Subject: [PATCH 06/10] DAS-2431: fix mounts --- bin/run-test | 4 ++-- tests/run_tests.sh | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/run-test b/bin/run-test index e4cc442..b29b1f0 100755 --- a/bin/run-test +++ b/bin/run-test @@ -24,6 +24,6 @@ mkdir -p coverage # Run the tests in a Docker container with mounted volumes for XML report # output and test coverage reporting docker run --platform linux/amd64 --rm \ - -v $(pwd)/test-reports:/home/tests/test-reports \ - -v $(pwd)/coverage:/home/tests/coverage \ + -v $(pwd)/test-reports:/home/test-reports \ + -v $(pwd)/coverage:/home/coverage \ ghcr.io/nasa/harmony-opendap-subsetter-test "$@" diff --git a/tests/run_tests.sh b/tests/run_tests.sh index d211229..abc3f70 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -14,10 +14,11 @@ # Exit status used to report back to caller STATUS=0 - +export HDF5_DISABLE_VERSION_CHECK=1 echo -e "\nRunning tests..." -pytest ./tests -s --cov=hoss --junitxml=./test-reports/pytest-results.xml --cov-report=html:./coverage --cov-report term + +pytest ./tests -s --cov=hoss --junitxml=test-reports/pytest-results.xml --cov-report=html:coverage --cov-report term RESULT=$? From 2e6344bb88c242957ed65b88b0ca1d573a1a8502 Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Mon, 20 Oct 2025 17:11:24 -0600 Subject: [PATCH 07/10] DAS-2431: Update CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8501142..1104850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [unrleased] - 2025-10-20 + +### Changed + +- Updated tests to be less dependent on architecture when comparing floats. +- Fixed test that modified a source file fixture. +- Changed infrastructure so that local and Docker runs of the tests produce output in same locations. +- GitHub once again captures the artifacts from the tests and coverage. + + ## [v1.1.12] - 2025-10-15 ### Added From 36394c803d590e39d4db2b8af236dcd5540e13ed Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Tue, 21 Oct 2025 08:00:22 -0600 Subject: [PATCH 08/10] DAS-2431: Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1104850..03075cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [unrleased] - 2025-10-20 +## [unreleased] - 2025-10-20 ### Changed From a4678bc530fbff40c3f6113e0dcb5ba07e0f2f99 Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Tue, 21 Oct 2025 10:31:06 -0600 Subject: [PATCH 09/10] DAS-2431: Update README for installing pre-commit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84f0326..176450f 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,8 @@ checking the repository for some coding standard best practices. These include: To enable these checks: ```bash -# Install pre-commit Python package as part of test requirements: -pip install -r tests/pip_test_requirements.txt +# Install pre-commit Python package: +pip install pre-commit # Install the git hook scripts: pre-commit install From 41533975c09ffe8f128ea980b400365168000eff Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Tue, 21 Oct 2025 10:56:18 -0600 Subject: [PATCH 10/10] DAS-2431: assertDictAlmostEqual -> assert_float_dict_almost_equal Moves into utility function. --- tests/unit/test_projection_utilities.py | 24 ++++++++---------------- tests/utilities.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/tests/unit/test_projection_utilities.py b/tests/unit/test_projection_utilities.py index 4a36913..2695b9d 100644 --- a/tests/unit/test_projection_utilities.py +++ b/tests/unit/test_projection_utilities.py @@ -44,6 +44,8 @@ is_projection_x_dimension, is_projection_y_dimension, ) +from tests import utilities +from tests.utilities import assert_float_dict_almost_equal class TestProjectionUtilities(TestCase): @@ -85,17 +87,6 @@ def read_geojson(geojson_base_name: str): return geojson_content - def assertDictAlmostEqual(self, dict1, dict2, places=7): - """Test the float values of dictionaries as almost equal.""" - self.assertEqual(dict1.keys(), dict2.keys()) - for key in dict1: - self.assertAlmostEqual( - dict1[key], - dict2[key], - places=places, - msg=f'Mismatch in {key}', - ) - @patch('hoss.projection_utilities.get_grid_mapping_attributes') def test_get_variable_crs(self, mock_get_grid_mapping_attributes): """Ensure a `pyproj.CRS` object can be instantiated from the given @@ -370,7 +361,8 @@ def test_get_projected_x_y_extents(self): } with self.subTest('Bounding box input'): - self.assertDictAlmostEqual( + + assert_float_dict_almost_equal( get_projected_x_y_extents( x_values, y_values, crs, bounding_box=bounding_box ), @@ -378,7 +370,7 @@ def test_get_projected_x_y_extents(self): ) with self.subTest('Shape file input'): - self.assertDictAlmostEqual( + assert_float_dict_almost_equal( get_projected_x_y_extents( x_values, y_values, crs, shape_file=polygon_path ), @@ -414,7 +406,7 @@ def test_get_projected_x_y_extents_whole_earth(self): 'y_max': 12702440.710450241, } with self.subTest('Whole Earth LAEA - Bounding box input'): - self.assertDictAlmostEqual( + assert_float_dict_almost_equal( get_projected_x_y_extents( x_values, y_values, crs, bounding_box=whole_earth_bbox ), @@ -422,7 +414,7 @@ def test_get_projected_x_y_extents_whole_earth(self): ) with self.subTest('Whole Earth LAEA - Shape file input'): - self.assertDictAlmostEqual( + assert_float_dict_almost_equal( get_projected_x_y_extents( x_values, y_values, crs, shape_file=polygon_path ), @@ -1171,7 +1163,7 @@ def test_get_x_y_extents_from_geographic_points(self): 'y_max': 1670250.0136418417, } - self.assertDictAlmostEqual( + assert_float_dict_almost_equal( get_x_y_extents_from_geographic_points(points, crs), expected_x_y_extents ) diff --git a/tests/utilities.py b/tests/utilities.py index ffe1acc..12802ed 100644 --- a/tests/utilities.py +++ b/tests/utilities.py @@ -5,6 +5,7 @@ from typing import List from unittest.mock import MagicMock +import numpy as np from harmony_service_lib.util import bbox_to_geometry from pystac import Asset, Catalog, Item @@ -93,3 +94,22 @@ def create_stac(granules: List[Granule]) -> Catalog: catalog.add_item(item) return catalog + + +def assert_float_dict_almost_equal(dict1, dict2, decimal=8): + """Assert two dicts are almost equal. + + Two dictionaries that have only floating point values are compared for + almost equivalence. + + """ + assert ( + dict1.keys() == dict2.keys() + ), f"Keys mismatch: {dict1.keys()} != {dict2.keys()}" + for key in dict1: + np.testing.assert_almost_equal( + dict1[key], + dict2[key], + decimal=decimal, + err_msg=f'Mismatch in {key}: {dict1[key]} != {dict2[key]}', + )