From 660015edf772695bffce33a73facb044f0867692 Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Fri, 25 Sep 2026 11:03:01 +0200 Subject: [PATCH] Fix integration test failing on Windows CI pytest's tmp_path can land on a different drive than the checkout on Windows runners, and Snakemake's relative-path handling then fails with "path is on mount 'D:', start on mount 'C:'". Use a temp directory under the checkout itself instead, so it's always on the same drive as the Snakefile and conda prefix. --- .gitignore | 4 ++ tests/integration_test.py | 135 +++++++++++++++++++++----------------- 2 files changed, 79 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 7a57f9b..92e3c18 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,10 @@ gurobi.log # earth-osm cache data/earth-osm/ +# test_snakemake_integration_testing's working directory; normally cleaned +# up automatically, this is only a backstop if that's ever interrupted +.integration-*/ + # Personal overrides (e.g. a custom Overpass API endpoint/token), passed via # --configfile config/config.local.yaml; never committed. config/config.local.yaml diff --git a/tests/integration_test.py b/tests/integration_test.py index d26e957..1316f42 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -10,6 +10,7 @@ import shutil import subprocess import sys +import tempfile from pathlib import Path import pytest @@ -46,71 +47,85 @@ def test_standard_file_existance(module_path, file): assert Path(module_path / file).exists() -def test_snakemake_integration_testing(module_path, tmp_path): +def test_snakemake_integration_testing(module_path): """Run retrieval from scratch in its Conda environment and validate outputs.""" log_dir = module_path / "tests/integration/logs" log_dir.mkdir(parents=True, exist_ok=True) run_log = log_dir / "integration.log" conda_prefix = module_path / ".snakemake/conda" - try: - with run_log.open("w", encoding="utf-8") as log: - result = subprocess.run( - [ - sys.executable, - "-m", - "snakemake", - "--snakefile", - str(module_path / "tests/integration/Snakefile"), - "--directory", - str(tmp_path), - "--use-conda", - "--conda-prefix", - str(conda_prefix), - "--cores", - "1", - "--show-failed-logs", - ], - stdout=log, - stderr=subprocess.STDOUT, - timeout=600, - check=False, - env={**os.environ, "XDG_CACHE_HOME": str(tmp_path / "cache")}, + + # A directory under module_path, not pytest's tmp_path: on Windows CI, + # tmp_path can land on a different drive than the checkout, and + # Snakemake's relative-path handling then fails with "ValueError: path + # is on mount 'D:', start on mount 'C:'". Keeping the working directory + # on the same drive as the Snakefile/conda prefix avoids that. Every + # assertion that reads from workdir has to stay inside this block - + # TemporaryDirectory deletes it on exit. + with tempfile.TemporaryDirectory( + dir=module_path, prefix=".integration-", ignore_cleanup_errors=True + ) as temporary_directory: + workdir = Path(temporary_directory) + try: + with run_log.open("w", encoding="utf-8") as log: + result = subprocess.run( + [ + sys.executable, + "-m", + "snakemake", + "--snakefile", + str(module_path / "tests/integration/Snakefile"), + "--directory", + str(workdir), + "--use-conda", + "--conda-prefix", + str(conda_prefix), + "--cores", + "1", + "--show-failed-logs", + ], + stdout=log, + stderr=subprocess.STDOUT, + timeout=600, + check=False, + env={**os.environ, "XDG_CACHE_HOME": str(workdir / "cache")}, + ) + finally: + if (workdir / "logs").exists(): + shutil.copytree(workdir / "logs", log_dir, dirs_exist_ok=True) + + assert result.returncode == 0, run_log.read_text(encoding="utf-8") + output_dir = workdir / "resources/grid-builder/osm/retrieve" + for feature in ("substations_way", "lines_way"): + payload = json.loads( + (output_dir / f"benin_{feature}.json").read_text(encoding="utf-8") ) - finally: - if (tmp_path / "logs").exists(): - shutil.copytree(tmp_path / "logs", log_dir, dirs_exist_ok=True) - - assert result.returncode == 0, run_log.read_text(encoding="utf-8") - output_dir = tmp_path / "resources/grid-builder/osm/retrieve" - for feature in ("substations_way", "lines_way"): - payload = json.loads( - (output_dir / f"benin_{feature}.json").read_text(encoding="utf-8") - ) - elements = payload["elements"] - assert elements, f"No {feature} records retrieved" - assert all(item["geometry"] for item in elements) - - build_dir = tmp_path / "resources/grid-builder/osm/build" - for component in ("buses", "lines", "transformers"): - with (build_dir / "csv" / f"{component}.csv").open(encoding="utf-8") as file: - rows = list(csv.DictReader(file)) - assert rows or component == "transformers" - - with (build_dir / "csv" / "lines.csv").open(encoding="utf-8") as file: - built_lines = list(csv.DictReader(file)) - assert all(row["bus0"] != row["bus1"] for row in built_lines) - assert all(float(row["voltage_kv"]) >= 220 for row in built_lines) - - for geojson_name in ("stations_polygon", "buses_polygon"): - polygons = json.loads( - (build_dir / "geojson" / f"{geojson_name}.geojson").read_text( + elements = payload["elements"] + assert elements, f"No {feature} records retrieved" + assert all(item["geometry"] for item in elements) + + build_dir = workdir / "resources/grid-builder/osm/build" + for component in ("buses", "lines", "transformers"): + with (build_dir / "csv" / f"{component}.csv").open( encoding="utf-8" + ) as file: + rows = list(csv.DictReader(file)) + assert rows or component == "transformers" + + with (build_dir / "csv" / "lines.csv").open(encoding="utf-8") as file: + built_lines = list(csv.DictReader(file)) + assert all(row["bus0"] != row["bus1"] for row in built_lines) + assert all(float(row["voltage_kv"]) >= 220 for row in built_lines) + + for geojson_name in ("stations_polygon", "buses_polygon"): + polygons = json.loads( + (build_dir / "geojson" / f"{geojson_name}.geojson").read_text( + encoding="utf-8" + ) ) - ) - assert polygons["type"] == "FeatureCollection" - - country_log = (tmp_path / "logs/grid-builder/retrieve_osm_pbf/benin.log").read_text( - encoding="utf-8" - ) - assert "Wrote" in country_log - assert str(conda_prefix) in country_log, "Retrieval must use its Conda Python" + assert polygons["type"] == "FeatureCollection" + + country_log = ( + workdir / "logs/grid-builder/retrieve_osm_pbf/benin.log" + ).read_text(encoding="utf-8") + assert "Wrote" in country_log + assert str(conda_prefix) in country_log, "Retrieval must use its Conda Python"