From cce419afa870abf97da9334c8272b63bb215e5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:05:36 +0000 Subject: [PATCH] r.mfilter: Avoid reopening NamedTemporaryFile on Windows in testsuite On Windows, a file opened via tempfile.NamedTemporaryFile() cannot be reopened by another process while the Python handle is still open, so r.mfilter failed to read the filter file created this way. Write the filter contents to a path from grass.script.tempfile() instead, which does not hold the file open, and drop the @xfail_windows markers this fix should resolve. Extracted and reworked as a standalone change from PR #2616 (by Stefan Blumentrath / @ninsbl), which combined this fix with many other Windows-specific testsuite changes and has been open, unrebased, since 2022. Re-derived against current main rather than cherry-picked, because @ninsbl's branch predates unrelated fixes already merged to this same test file (a docstring/variance correction and other @xfail_windows additions), which a direct cherry-pick would have clobbered. This has not been verified on an actual Windows run; CI on this PR should confirm whether the @xfail_windows markers can indeed be removed. Analysis of PR #2616 assisted by AI (Claude), including identifying this as an easily isolated, self-contained candidate for its own PR; verify before merging. Co-Authored-By: Claude Sonnet 5 --- raster/r.mfilter/testsuite/test_r_mfilter.py | 52 ++++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/raster/r.mfilter/testsuite/test_r_mfilter.py b/raster/r.mfilter/testsuite/test_r_mfilter.py index 403a80a57f9..ad1f2453db6 100644 --- a/raster/r.mfilter/testsuite/test_r_mfilter.py +++ b/raster/r.mfilter/testsuite/test_r_mfilter.py @@ -1,7 +1,8 @@ -from tempfile import NamedTemporaryFile +from pathlib import Path + +import grass.script as gs from grass.gunittest.case import TestCase from grass.gunittest.main import test -from grass.gunittest.utils import xfail_windows class TestNeighbors(TestCase): @@ -185,10 +186,9 @@ class TestNeighbors(TestCase): def create_filter(self, options): """Create a temporary filter file with the given name and options.""" - f = NamedTemporaryFile() - f.write(options) - f.flush() - return f + filter_file = gs.tempfile(create=False) + Path(filter_file).write_bytes(options) + return filter_file @classmethod def setUpClass(cls): @@ -203,7 +203,6 @@ def tearDownClass(cls): "g.remove", flags="f", type="raster", name=",".join(cls.to_remove) ) - @xfail_windows def test_sequential(self): """Test output with sequential filter type.""" test_case = "test_sequential" @@ -216,16 +215,15 @@ def test_sequential(self): "r.mfilter", input="elevation", output=output, - filter=filter.name, + filter=filter, ) self.assertModule( "r.mfilter", input="elevation", output=output_threaded, - filter=filter.name, + filter=filter, nprocs=4, ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case], @@ -237,7 +235,6 @@ def test_sequential(self): precision=1e-5, ) - @xfail_windows def test_parallel(self): """Test output with parallel filter type.""" test_case = "test_parallel" @@ -250,16 +247,15 @@ def test_parallel(self): "r.mfilter", input="elevation", output=output, - filter=filter.name, + filter=filter, ) self.assertModule( "r.mfilter", input="elevation", output=output_threaded, - filter=filter.name, + filter=filter, nprocs=4, ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case], @@ -271,7 +267,6 @@ def test_parallel(self): precision=1e-5, ) - @xfail_windows def test_sequential_null(self): """Test output with sequential filter type with null mode enabled.""" test_case = "test_sequential_null" @@ -284,16 +279,15 @@ def test_sequential_null(self): "r.mfilter", input="lakes", output=output, - filter=filter.name, + filter=filter, ) self.assertModule( "r.mfilter", input="lakes", output=output_z, - filter=filter.name, + filter=filter, flags="z", ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case][False], @@ -305,7 +299,6 @@ def test_sequential_null(self): precision=1e-5, ) - @xfail_windows def test_parallel_null(self): """Test output with parallel filter type with null mode enabled.""" test_case = "test_parallel_null" @@ -320,31 +313,30 @@ def test_parallel_null(self): "r.mfilter", input="lakes", output=output, - filter=filter.name, + filter=filter, ) self.assertModule( "r.mfilter", input="lakes", output=output_threaded, - filter=filter.name, + filter=filter, nprocs=4, ) self.assertModule( "r.mfilter", input="lakes", output=output_z, - filter=filter.name, + filter=filter, flags="z", ) self.assertModule( "r.mfilter", input="lakes", output=output_z_threaded, - filter=filter.name, + filter=filter, flags="z", nprocs=4, ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case][False], @@ -366,7 +358,6 @@ def test_parallel_null(self): precision=1e-5, ) - @xfail_windows def test_multiple_filters(self): """Test output with multiple filters.""" test_case = "test_multiple_filters" @@ -379,16 +370,15 @@ def test_multiple_filters(self): "r.mfilter", input="elevation", output=output, - filter=filter.name, + filter=filter, ) self.assertModule( "r.mfilter", input="elevation", output=output_threaded, - filter=filter.name, + filter=filter, nprocs=4, ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case], @@ -400,7 +390,6 @@ def test_multiple_filters(self): precision=1e-5, ) - @xfail_windows def test_repeated_filters(self): """Test output with repeated filters.""" test_case = "test_repeated_filters" @@ -413,18 +402,17 @@ def test_repeated_filters(self): "r.mfilter", input="elevation", output=output, - filter=filter.name, + filter=filter, repeat=3, ) self.assertModule( "r.mfilter", input="elevation", output=output_threaded, - filter=filter.name, + filter=filter, repeat=3, nprocs=4, ) - filter.close() self.assertRasterFitsUnivar( raster=output, reference=self.test_results[test_case],