From 0c5b6600dccf3ede4b85c96cbea9a2a04169a842 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:02:32 -0500 Subject: [PATCH 01/20] Switch to new pytest runner --- ...egression_data_test2.txt => frame0001.txt} | 0 ...egression_data_test3.txt => frame0002.txt} | 0 .../test_acoustics_1d_heterogeneous.py | 49 ++----- examples/conftest.py | 10 ++ src/python/classic/test.py | 131 +++++++++++++++--- 5 files changed, 139 insertions(+), 51 deletions(-) rename examples/acoustics_1d_heterogeneous/regression_data/{regression_data_test2.txt => frame0001.txt} (100%) rename examples/acoustics_1d_heterogeneous/regression_data/{regression_data_test3.txt => frame0002.txt} (100%) create mode 100644 examples/conftest.py diff --git a/examples/acoustics_1d_heterogeneous/regression_data/regression_data_test2.txt b/examples/acoustics_1d_heterogeneous/regression_data/frame0001.txt similarity index 100% rename from examples/acoustics_1d_heterogeneous/regression_data/regression_data_test2.txt rename to examples/acoustics_1d_heterogeneous/regression_data/frame0001.txt diff --git a/examples/acoustics_1d_heterogeneous/regression_data/regression_data_test3.txt b/examples/acoustics_1d_heterogeneous/regression_data/frame0002.txt similarity index 100% rename from examples/acoustics_1d_heterogeneous/regression_data/regression_data_test3.txt rename to examples/acoustics_1d_heterogeneous/regression_data/frame0002.txt diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index dd43352..7882638 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -2,49 +2,28 @@ Regression tests for a 1D heterogeneous acoustics test """ -from __future__ import absolute_import import sys -import unittest +import pytest import clawpack.classic.test as test +def test_acoustics_1d_heterogeneous(tmp_path, save): -class Acoustics1DHeterogeneousTest(test.ClassicRegressionTest): - r"""Basic test for an 1D heterogeneous acoustics test case""" + ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) - def runTest(self, save=False): + ctr.set_data() + ctr.rundata.clawdata.num_output_times = 2 + ctr.rundata.clawdata.tfinal = 5.0 + ctr.rundata.clawdata.output_t0 = False + ctr.write_data() - # Write out data files - self.load_rundata() - - # Modify data for test run - self.rundata.clawdata.num_output_times = 2 - self.rundata.clawdata.tfinal = 5.0 - self.rundata.clawdata.output_t0 = False - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_frame(indices=[0, 1], save=save, frame_num=1, - file_name='regression_data_test2.txt') - self.check_frame(indices=[0, 1], save=save, frame_num=2, - file_name='regression_data_test3.txt') - - self.success = True + ctr.executable_name = 'xclaw' + ctr.build_executable() + ctr.run_code() + ctr.check_frame(1, indices=(0, 1), save=save) + ctr.check_frame(2, indices=(0, 1), save=save) if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics1DHeterogeneousTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file + pytest.main([__file__]) \ No newline at end of file diff --git a/examples/conftest.py b/examples/conftest.py new file mode 100644 index 0000000..1795428 --- /dev/null +++ b/examples/conftest.py @@ -0,0 +1,10 @@ +"""Configuration for PyTest""" + +import pytest + +def pytest_addoption(parser): + parser.addoption('--save', action='store_true') + +@pytest.fixture +def save(request): + return request.config.getoption("--save", False) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index 21bbae1..3471513 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -6,32 +6,131 @@ results and looking for errors. """ +from pathlib import Path import os -import glob +import sys +import subprocess +import importlib +import inspect +import shutil +import pytest -import clawpack.clawutil.test -import clawpack.pyclaw.util +import numpy as np + +import clawpack.clawutil.runclaw as runclaw +import clawpack.clawutil.claw_git_status as claw_git_status +import clawpack.pyclaw.solution as solution # Clean library files whenever this module is used if "CLAW" in os.environ: - CLAW = os.environ["CLAW"] + CLAW = Path(os.environ["CLAW"]) else: raise ValueError("Need to set CLAW environment variable.") -for lib_path in [os.path.join(CLAW,"classic","src","1d"), - os.path.join(CLAW,"classic","src","2d"), - os.path.join(CLAW,"classic","src","3d")]: - for path in glob.glob(os.path.join(lib_path,"*.o")): - os.remove(path) - for path in glob.glob(os.path.join(lib_path,"*.mod")): - os.remove(path) +for lib_path in (CLAW / "classic" / "src" / "1d").glob("*.o"): + lib_path.unlink() +for lib_path in (CLAW / "classic" / "src" / "2d").glob("*.o"): + lib_path.unlink() +for lib_path in (CLAW / "classic" / "src" / "2d").glob("*.o"): + lib_path.unlink() + + +class ClawpackClassicTestRunner: + + def __init__(self, path, caller_path): + + self.temp_path = path + # :TODO: see if there's a way to get this automatically + self.test_path = Path(caller_path).parent + self.executable_name = 'xclaw' + + + def set_data(self, setrun_module=None): + + sys.path.insert(0, self.test_path) + if not setrun_module: + setrun_module = 'setrun' + if setrun_module in sys.modules: + del(sys.modules[setrun_module]) + setrun = importlib.import_module(setrun_module) + self.rundata = setrun.setrun() + sys.path.pop(0) + + + def write_data(self, path=None): + + if not path: + path = self.temp_path + self.rundata.write(out_dir=path) + + + def build_executable(self, make_level='default', FFLAGS=None, LFLAGS=None): + + # Assumes GCC CLI + if not FFLAGS: + FFLAGS = os.environ.get('FFLAGS', "-O2 -fopenmp") + if not LFLAGS: + LFLAGS = os.environ.get('LFLAGS', FFLAGS) + + if make_level.lower() == "new": + cmd = "".join((f"cd {self.test_path} ; make new ", + f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) + elif make_level.lower() == "default": + # clean up *.o and *.mod files in test path only + for path in self.test_path.glob("*.o"): + path.unlink() + for path in self.test_path.glob("*.mod"): + path.unlink() + cmd = "".join((f"cd {self.test_path} ; make .exe ", + f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) + + elif make_level.lower() == "exe": + cmd = "".join((f"cd {self.test_path} ; make .exe ", + f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) + else: + raise ValueError(f"Invaled make_level={make_level} given.") + + try: + subprocess.run(cmd, shell=True, check=True) + except subprocess.CalledProcessError as e: + self.clean_up() + raise e + + shutil.move(self.test_path / self.executable_name, self.temp_path) + + + def run_code(self): + runclaw.runclaw(xclawcmd=self.temp_path / self.executable_name, + rundir=self.temp_path, + outdir=self.temp_path, + overwrite=True, + restart=False) + + + def clean_up(self): + pass + + + def check_frame(self, frame, indices=(0), regression_path=None, save=False, **kwargs): + if not(isinstance(indices, tuple) or isinstance(indices, list)): + indices = tuple(indices) -class ClassicRegressionTest(clawpack.clawutil.test.ClawpackRegressionTest): + if not regression_path: + regression_path = self.test_path / "regression_data" - r"""Base Classic regression test setup derived from ClawpackRegressionTest + # Load test output data + sol = solution.Solution(frame, path=self.temp_path) + sol_sums = sol.q[indices, ...].sum(axis=1) - """ + # Load regression data + regression_data = regression_path / f"frame{str(frame).zfill(4)}.txt" + if save: + np.savetxt(regression_data, sol_sums) + claw_git_status.make_git_status_file(outdir=regression_path) + regression_sum = np.loadtxt(regression_data) - __doc__ += clawpack.pyclaw.util.add_parent_doc( - clawpack.clawutil.test.ClawpackRegressionTest) + # Compare data + kwargs.setdefault('rtol', 1e-14) + kwargs.setdefault('atol', 1e-8) + np.testing.assert_allclose(sol_sums, regression_sum, **kwargs) \ No newline at end of file From 5005d712b00b992ddc4e9212c6450bac0e89c8a1 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:07:42 -0500 Subject: [PATCH 02/20] Minor adjustment to testing action --- .github/workflows/testing.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8fd6dde..ee9abd2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -20,6 +20,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.10" + - name: Install dependencies run: | sudo apt-get update @@ -32,15 +33,16 @@ jobs: with: repository: clawpack/clawpack submodules: true + - name: Checkout classic branch uses: actions/checkout@v4.1.5 with: - repository: clawpack/classic path: classic + - name: Install clawpack run: | - pip install --no-build-isolation --editable $CLAW - # pip install --user -e $CLAW + cd ${CLAW} + pip install --no-build-isolation --editable . - name: Lint with flake8 run: | @@ -49,7 +51,16 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest run: | cd ${CLAW}/classic - pytest + pytest --basetemp=./test_output + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test_results + path: ${{ env.CLAW }}/geoclaw/*_output + if-no-files-found: ignore From df20f4aed1d6aef0b007d4021e35f3acc7ce8d18 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:08:17 -0500 Subject: [PATCH 03/20] Add gauge check --- src/python/classic/test.py | 54 +++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index 3471513..e35529b 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -133,4 +133,56 @@ def check_frame(self, frame, indices=(0), regression_path=None, save=False, **kw # Compare data kwargs.setdefault('rtol', 1e-14) kwargs.setdefault('atol', 1e-8) - np.testing.assert_allclose(sol_sums, regression_sum, **kwargs) \ No newline at end of file + np.testing.assert_allclose(sol_sums, regression_sum, **kwargs) + + + def check_gauge(self, gauge_id, indices=(0), regression_path=None, save=False, **kwargs): + r"""Basic test to assert gauge equality + + :Input: + - *save* (bool) - If *True* will save the output from this test to + the file *regresion_data.txt*. Default is *False*. + - *indices* (tuple) - Contains indices to compare in the gague + comparison. Defaults to *(0)*. + - *rtol* (float) - Relative tolerance used in the comparison, default + is *1e-14*. Note that the old *tolerance* input is now synonymous + with this parameter. + - *atol* (float) - Absolute tolerance used in the comparison, default + is *1e-08*. + """ + + if not(isinstance(indices, tuple) or isinstance(indices, list)): + indices = tuple(indices) + + if not regression_path: + regression_path = self.test_path / "regression_data" + + # Load test output data + gauge = gauges.GaugeSolution(gauge_id, path=self.temp_path) + + # Load regression data + if save: + shutil.copy(self.temp_path / f"gauge{str(gauge_id).zfill(5)}.txt", + regression_path) + claw_git_status.make_git_status_file(outdir=regression_path) + regression_gauge = gauges.GaugeSolution(gauge_id, path=regression_path) + + # Compare data + kwargs.setdefault('rtol', 1e-14) + kwargs.setdefault('atol', 1e-8) + try: + for n in indices: + np.testing.assert_allclose(gauge.q[n, :], + regression_gauge.q[n, :], + **kwargs) + except AssertionError as e: + err_msg = "\n".join((e.args[0], + "Gauge Match Failed for gauge = %s" % gauge_id)) + err_msg = "\n".join((err_msg, " failures in fields:")) + failure_indices = [] + for n in indices: + if ~np.allclose(gauge.q[n, :], regression_gauge.q[n, :], + **kwargs): + failure_indices.append(str(n)) + index_str = ", ".join(failure_indices) + raise AssertionError(" ".join((err_msg, index_str))) \ No newline at end of file From 548cf236dc8ae6455a801ff49bfcc3db59b048df Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:22:13 -0500 Subject: [PATCH 04/20] Add missing gauge import --- src/python/classic/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index e35529b..ec2c8f1 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -20,6 +20,7 @@ import clawpack.clawutil.runclaw as runclaw import clawpack.clawutil.claw_git_status as claw_git_status import clawpack.pyclaw.solution as solution +import clawpack.pyclaw.gauges as gauges # Clean library files whenever this module is used if "CLAW" in os.environ: From 7f1d8ab0b59742668dec1a170c35c9ff1e133907 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:41:02 -0500 Subject: [PATCH 05/20] Convert 2d annulus test --- ...egression_data_test2.txt => frame0001.txt} | 0 ...egression_data_test3.txt => frame0002.txt} | 0 .../test_advection_2d_annulus.py | 48 +++++-------------- src/python/classic/test.py | 4 +- 4 files changed, 15 insertions(+), 37 deletions(-) rename examples/advection_2d_annulus/regression_data/{regression_data_test2.txt => frame0001.txt} (100%) rename examples/advection_2d_annulus/regression_data/{regression_data_test3.txt => frame0002.txt} (100%) diff --git a/examples/advection_2d_annulus/regression_data/regression_data_test2.txt b/examples/advection_2d_annulus/regression_data/frame0001.txt similarity index 100% rename from examples/advection_2d_annulus/regression_data/regression_data_test2.txt rename to examples/advection_2d_annulus/regression_data/frame0001.txt diff --git a/examples/advection_2d_annulus/regression_data/regression_data_test3.txt b/examples/advection_2d_annulus/regression_data/frame0002.txt similarity index 100% rename from examples/advection_2d_annulus/regression_data/regression_data_test3.txt rename to examples/advection_2d_annulus/regression_data/frame0002.txt diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index 8edde85..17a618f 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -2,49 +2,27 @@ Regression tests for 2D advection on an annulus. """ -from __future__ import absolute_import import sys -import unittest +import pytest import clawpack.classic.test as test +def test_advection_2d_annulus(tmp_path, save): -class Advection2DAnnulusTest(test.ClassicRegressionTest): - r"""Basic test for a 2D advection test case""" + ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) + ctr.set_data() + ctr.rundata.clawdata.num_output_times = 2 + ctr.rundata.clawdata.tfinal = 0.5 + ctr.write_data() - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 2 - self.rundata.clawdata.tfinal = 0.5 - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_frame(save=save, frame_num=1, - file_name='regression_data_test2.txt') - self.check_frame(save=save, frame_num=2, - file_name='regression_data_test3.txt') - - self.success = True + ctr.executable_name = 'xclaw' + ctr.build_executable() + ctr.run_code() + ctr.check_frame(1, save=save) + ctr.check_frame(2, save=save) if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Advection2DAnnulusTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file + pytest.main([__file__]) \ No newline at end of file diff --git a/src/python/classic/test.py b/src/python/classic/test.py index ec2c8f1..5a4ca37 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -112,7 +112,7 @@ def clean_up(self): pass - def check_frame(self, frame, indices=(0), regression_path=None, save=False, **kwargs): + def check_frame(self, frame, indices=(0,), regression_path=None, save=False, **kwargs): if not(isinstance(indices, tuple) or isinstance(indices, list)): indices = tuple(indices) @@ -122,7 +122,7 @@ def check_frame(self, frame, indices=(0), regression_path=None, save=False, **kw # Load test output data sol = solution.Solution(frame, path=self.temp_path) - sol_sums = sol.q[indices, ...].sum(axis=1) + sol_sums = [sol.q[i, ...].sum() for i in indices] # Load regression data regression_data = regression_path / f"frame{str(frame).zfill(4)}.txt" From c3910b50154278acccf65fc49306b03759738a9c Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Wed, 21 May 2025 23:45:36 -0500 Subject: [PATCH 06/20] Convert 3d heterogeneous test --- ...egression_data_test2.txt => frame0001.txt} | 0 ...egression_data_test3.txt => frame0002.txt} | 0 .../test_acoustics_3d_heterogeneous.py | 45 ++++++------------- 3 files changed, 13 insertions(+), 32 deletions(-) rename examples/acoustics_3d_heterogeneous/regression_data/{regression_data_test2.txt => frame0001.txt} (100%) rename examples/acoustics_3d_heterogeneous/regression_data/{regression_data_test3.txt => frame0002.txt} (100%) diff --git a/examples/acoustics_3d_heterogeneous/regression_data/regression_data_test2.txt b/examples/acoustics_3d_heterogeneous/regression_data/frame0001.txt similarity index 100% rename from examples/acoustics_3d_heterogeneous/regression_data/regression_data_test2.txt rename to examples/acoustics_3d_heterogeneous/regression_data/frame0001.txt diff --git a/examples/acoustics_3d_heterogeneous/regression_data/regression_data_test3.txt b/examples/acoustics_3d_heterogeneous/regression_data/frame0002.txt similarity index 100% rename from examples/acoustics_3d_heterogeneous/regression_data/regression_data_test3.txt rename to examples/acoustics_3d_heterogeneous/regression_data/frame0002.txt diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index 4409ca0..3c977fb 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -8,43 +8,24 @@ import clawpack.classic.test as test -class Acoustics3DHeterogeneousTest(test.ClassicRegressionTest): +def test_acoustics_3d_heterogeneous(tmp_path, save): r"""Basic test for a 3D heterogeneous acoustics test.""" + ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) - def runTest(self, save=False): + ctr.set_data() + ctr.rundata.clawdata.num_cells = [20, 20, 20] + ctr.rundata.clawdata.num_output_times = 2 + ctr.rundata.clawdata.tfinal = 1.0 + ctr.write_data() - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_cells = [20, 20, 20] - self.rundata.clawdata.num_output_times = 2 - self.rundata.clawdata.tfinal = 1.0 - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_frame(save=save, indices=[0, 1, 2], frame_num=1, - file_name='regression_data_test2.txt') - self.check_frame(save=save, indices=[0, 1, 2], frame_num=2, - file_name='regression_data_test3.txt') - - self.success = True + ctr.executable_name = 'xclaw' + ctr.build_executable() + ctr.run_code() + ctr.check_frame(1, indices=(0, 1, 2), save=save) + ctr.check_frame(2, indices=(0, 1, 2), save=save) if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics3DHeterogeneousTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file + pytest.main([__file__]) \ No newline at end of file From 907b6cec37d43980f2ab9dc9362d92ab5c02742b Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 28 Jul 2025 12:13:57 -0400 Subject: [PATCH 07/20] Multiple test in one run now working --- examples/acoustics_1d_heterogeneous/setrun.py | 1 - .../test_acoustics_1d_heterogeneous.py | 9 ++-- .../test_acoustics_3d_heterogeneous.py | 12 ++--- .../test_advection_2d_annulus.py | 9 ++-- src/python/classic/test.py | 54 ++++++++++--------- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/examples/acoustics_1d_heterogeneous/setrun.py b/examples/acoustics_1d_heterogeneous/setrun.py index c0c4bc4..02b7c07 100644 --- a/examples/acoustics_1d_heterogeneous/setrun.py +++ b/examples/acoustics_1d_heterogeneous/setrun.py @@ -6,7 +6,6 @@ """ -from __future__ import absolute_import import os import numpy as np diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index 7882638..7c45783 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -2,14 +2,13 @@ Regression tests for a 1D heterogeneous acoustics test """ -import sys -import pytest +from pathlib import Path import clawpack.classic.test as test -def test_acoustics_1d_heterogeneous(tmp_path, save): +def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): - ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) + ctr = test.ClawpackClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_output_times = 2 @@ -26,4 +25,4 @@ def test_acoustics_1d_heterogeneous(tmp_path, save): ctr.check_frame(2, indices=(0, 1), save=save) if __name__=="__main__": - pytest.main([__file__]) \ No newline at end of file + pytest.main([__file__]) diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index 3c977fb..9db4449 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -2,16 +2,14 @@ Regression tests for 3D heterogeneous acoustics problem. """ -import sys -import unittest +from pathlib import Path import clawpack.classic.test as test - -def test_acoustics_3d_heterogeneous(tmp_path, save): +def test_acoustics_3d_heterogeneous(tmp_path: Path, save: bool): r"""Basic test for a 3D heterogeneous acoustics test.""" - ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) + ctr = test.ClawpackClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_cells = [20, 20, 20] @@ -27,5 +25,5 @@ def test_acoustics_3d_heterogeneous(tmp_path, save): ctr.check_frame(1, indices=(0, 1, 2), save=save) ctr.check_frame(2, indices=(0, 1, 2), save=save) -if __name__=="__main__": - pytest.main([__file__]) \ No newline at end of file +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index 17a618f..70cd2e1 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -2,14 +2,13 @@ Regression tests for 2D advection on an annulus. """ -import sys -import pytest +from pathlib import Path import clawpack.classic.test as test -def test_advection_2d_annulus(tmp_path, save): +def test_advection_2d_annulus(tmp_path: Path, save: bool): - ctr = test.ClawpackClassicTestRunner(tmp_path, __file__) + ctr = test.ClawpackClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_output_times = 2 @@ -25,4 +24,4 @@ def test_advection_2d_annulus(tmp_path, save): ctr.check_frame(2, save=save) if __name__=="__main__": - pytest.main([__file__]) \ No newline at end of file + pytest.main([__file__]) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index 5a4ca37..a284203 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -11,9 +11,10 @@ import sys import subprocess import importlib -import inspect import shutil -import pytest +import inspect +import random +import string import numpy as np @@ -32,30 +33,32 @@ lib_path.unlink() for lib_path in (CLAW / "classic" / "src" / "2d").glob("*.o"): lib_path.unlink() -for lib_path in (CLAW / "classic" / "src" / "2d").glob("*.o"): +for lib_path in (CLAW / "classic" / "src" / "3d").glob("*.o"): lib_path.unlink() - class ClawpackClassicTestRunner: - def __init__(self, path, caller_path): + def __init__(self, path: Path): self.temp_path = path - # :TODO: see if there's a way to get this automatically - self.test_path = Path(caller_path).parent + self.test_path = Path(Path(inspect.stack()[1].filename).absolute()).parent self.executable_name = 'xclaw' - def set_data(self, setrun_module=None): + def set_data(self, setrun_path: Path = None): + r"""Set the rundata for the test.""" + + if not setrun_path: + setrun_path = self.test_path / "setrun.py" - sys.path.insert(0, self.test_path) - if not setrun_module: - setrun_module = 'setrun' - if setrun_module in sys.modules: - del(sys.modules[setrun_module]) - setrun = importlib.import_module(setrun_module) - self.rundata = setrun.setrun() - sys.path.pop(0) + mod_name = '_'.join(("setrun", + "".join(random.choices(string.ascii_letters + + string.digits, k=32)))) + spec = importlib.util.spec_from_file_location(mod_name, setrun_path) + setrun_module = importlib.util.module_from_spec(spec) + sys.modules[mod_name] = setrun_module + spec.loader.exec_module(setrun_module) + self.rundata = setrun_module.setrun() def write_data(self, path=None): @@ -101,6 +104,9 @@ def build_executable(self, make_level='default', FFLAGS=None, LFLAGS=None): def run_code(self): + print(f"clawcmd={self.temp_path / self.executable_name}") + print(f"rundir={self.temp_path}") + print(f"outdir={self.temp_path}") runclaw.runclaw(xclawcmd=self.temp_path / self.executable_name, rundir=self.temp_path, outdir=self.temp_path, @@ -141,12 +147,12 @@ def check_gauge(self, gauge_id, indices=(0), regression_path=None, save=False, * r"""Basic test to assert gauge equality :Input: - - *save* (bool) - If *True* will save the output from this test to + - *save* (bool) - If *True* will save the output from this test to the file *regresion_data.txt*. Default is *False*. - - *indices* (tuple) - Contains indices to compare in the gague + - *indices* (tuple) - Contains indices to compare in the gague comparison. Defaults to *(0)*. - - *rtol* (float) - Relative tolerance used in the comparison, default - is *1e-14*. Note that the old *tolerance* input is now synonymous + - *rtol* (float) - Relative tolerance used in the comparison, default + is *1e-14*. Note that the old *tolerance* input is now synonymous with this parameter. - *atol* (float) - Absolute tolerance used in the comparison, default is *1e-08*. @@ -174,16 +180,16 @@ def check_gauge(self, gauge_id, indices=(0), regression_path=None, save=False, * try: for n in indices: np.testing.assert_allclose(gauge.q[n, :], - regression_gauge.q[n, :], + regression_gauge.q[n, :], **kwargs) except AssertionError as e: - err_msg = "\n".join((e.args[0], + err_msg = "\n".join((e.args[0], "Gauge Match Failed for gauge = %s" % gauge_id)) err_msg = "\n".join((err_msg, " failures in fields:")) failure_indices = [] for n in indices: - if ~np.allclose(gauge.q[n, :], regression_gauge.q[n, :], + if ~np.allclose(gauge.q[n, :], regression_gauge.q[n, :], **kwargs): failure_indices.append(str(n)) index_str = ", ".join(failure_indices) - raise AssertionError(" ".join((err_msg, index_str))) \ No newline at end of file + raise AssertionError(" ".join((err_msg, index_str))) From 00b635d82d6482594461ff1c29d5c7c9f8406560 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 28 Jul 2025 12:17:43 -0400 Subject: [PATCH 08/20] Add pytest import --- .../test_acoustics_1d_heterogeneous.py | 2 +- .../test_acoustics_3d_heterogeneous.py | 2 +- examples/advection_2d_annulus/test_advection_2d_annulus.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index 7c45783..d6cfb38 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -3,7 +3,7 @@ """ from pathlib import Path - +import pytest import clawpack.classic.test as test def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index 9db4449..c2c89a6 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -3,7 +3,7 @@ """ from pathlib import Path - +import pytest import clawpack.classic.test as test def test_acoustics_3d_heterogeneous(tmp_path: Path, save: bool): diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index 70cd2e1..d1e91a4 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -3,7 +3,7 @@ """ from pathlib import Path - +import pytest import clawpack.classic.test as test def test_advection_2d_annulus(tmp_path: Path, save: bool): From 4b60fe088a56277862f3afbc42f48070ad9bbe79 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 28 Jul 2025 12:23:37 -0400 Subject: [PATCH 09/20] Always upload artifacts --- .github/workflows/testing.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ee9abd2..71a1c5a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -58,9 +58,10 @@ jobs: pytest --basetemp=./test_output - name: Upload test results - if: failure() + # if: failure() - Always upload artifacts now uses: actions/upload-artifact@v4 with: name: test_results - path: ${{ env.CLAW }}/geoclaw/*_output + path: ./test_output + # path: ${{ env.CLAW }}/geoclaw/*_output if-no-files-found: ignore From 737368a1ffa2922bc05054e1be96ebe9c58185ed Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 28 Jul 2025 12:34:21 -0400 Subject: [PATCH 10/20] Change test artifact path to absolute --- .github/workflows/testing.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 71a1c5a..fdd1ba0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -62,6 +62,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: test_results - path: ./test_output - # path: ${{ env.CLAW }}/geoclaw/*_output + path: ${{ env.CLAW }}/classic/test_output if-no-files-found: ignore From 220dfca0709073cada9662158e516f8ca63e6707 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 28 Jul 2025 12:35:40 -0400 Subject: [PATCH 11/20] Change name of action to test --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index fdd1ba0..64a97f6 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -13,7 +13,7 @@ env: CLAW: ${{ github.workspace }} jobs: - build: + test: runs-on: ubuntu-latest steps: - name: Set up Python 3.10 From 1fc859cbc0a441582c8747c84fbef69502cd7021 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 12 Sep 2025 10:20:11 -0400 Subject: [PATCH 12/20] Move new test runner to clawutil --- src/python/classic/test.py | 171 +------------------------------------ 1 file changed, 3 insertions(+), 168 deletions(-) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index a284203..410fd3d 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -8,20 +8,8 @@ from pathlib import Path import os -import sys -import subprocess -import importlib -import shutil -import inspect -import random -import string -import numpy as np - -import clawpack.clawutil.runclaw as runclaw -import clawpack.clawutil.claw_git_status as claw_git_status -import clawpack.pyclaw.solution as solution -import clawpack.pyclaw.gauges as gauges +import clawpack.clawutil.test as test # Clean library files whenever this module is used if "CLAW" in os.environ: @@ -36,160 +24,7 @@ for lib_path in (CLAW / "classic" / "src" / "3d").glob("*.o"): lib_path.unlink() -class ClawpackClassicTestRunner: +class ClawpackClassicTestRunner(test.ClawpackTestRunner): def __init__(self, path: Path): - - self.temp_path = path - self.test_path = Path(Path(inspect.stack()[1].filename).absolute()).parent - self.executable_name = 'xclaw' - - - def set_data(self, setrun_path: Path = None): - r"""Set the rundata for the test.""" - - if not setrun_path: - setrun_path = self.test_path / "setrun.py" - - mod_name = '_'.join(("setrun", - "".join(random.choices(string.ascii_letters - + string.digits, k=32)))) - spec = importlib.util.spec_from_file_location(mod_name, setrun_path) - setrun_module = importlib.util.module_from_spec(spec) - sys.modules[mod_name] = setrun_module - spec.loader.exec_module(setrun_module) - self.rundata = setrun_module.setrun() - - - def write_data(self, path=None): - - if not path: - path = self.temp_path - self.rundata.write(out_dir=path) - - - def build_executable(self, make_level='default', FFLAGS=None, LFLAGS=None): - - # Assumes GCC CLI - if not FFLAGS: - FFLAGS = os.environ.get('FFLAGS', "-O2 -fopenmp") - if not LFLAGS: - LFLAGS = os.environ.get('LFLAGS', FFLAGS) - - if make_level.lower() == "new": - cmd = "".join((f"cd {self.test_path} ; make new ", - f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) - elif make_level.lower() == "default": - # clean up *.o and *.mod files in test path only - for path in self.test_path.glob("*.o"): - path.unlink() - for path in self.test_path.glob("*.mod"): - path.unlink() - cmd = "".join((f"cd {self.test_path} ; make .exe ", - f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) - - elif make_level.lower() == "exe": - cmd = "".join((f"cd {self.test_path} ; make .exe ", - f"FFLAGS='{FFLAGS}' LFLAGS='{LFLAGS}'")) - else: - raise ValueError(f"Invaled make_level={make_level} given.") - - try: - subprocess.run(cmd, shell=True, check=True) - except subprocess.CalledProcessError as e: - self.clean_up() - raise e - - shutil.move(self.test_path / self.executable_name, self.temp_path) - - - def run_code(self): - print(f"clawcmd={self.temp_path / self.executable_name}") - print(f"rundir={self.temp_path}") - print(f"outdir={self.temp_path}") - runclaw.runclaw(xclawcmd=self.temp_path / self.executable_name, - rundir=self.temp_path, - outdir=self.temp_path, - overwrite=True, - restart=False) - - - def clean_up(self): - pass - - - def check_frame(self, frame, indices=(0,), regression_path=None, save=False, **kwargs): - - if not(isinstance(indices, tuple) or isinstance(indices, list)): - indices = tuple(indices) - - if not regression_path: - regression_path = self.test_path / "regression_data" - - # Load test output data - sol = solution.Solution(frame, path=self.temp_path) - sol_sums = [sol.q[i, ...].sum() for i in indices] - - # Load regression data - regression_data = regression_path / f"frame{str(frame).zfill(4)}.txt" - if save: - np.savetxt(regression_data, sol_sums) - claw_git_status.make_git_status_file(outdir=regression_path) - regression_sum = np.loadtxt(regression_data) - - # Compare data - kwargs.setdefault('rtol', 1e-14) - kwargs.setdefault('atol', 1e-8) - np.testing.assert_allclose(sol_sums, regression_sum, **kwargs) - - - def check_gauge(self, gauge_id, indices=(0), regression_path=None, save=False, **kwargs): - r"""Basic test to assert gauge equality - - :Input: - - *save* (bool) - If *True* will save the output from this test to - the file *regresion_data.txt*. Default is *False*. - - *indices* (tuple) - Contains indices to compare in the gague - comparison. Defaults to *(0)*. - - *rtol* (float) - Relative tolerance used in the comparison, default - is *1e-14*. Note that the old *tolerance* input is now synonymous - with this parameter. - - *atol* (float) - Absolute tolerance used in the comparison, default - is *1e-08*. - """ - - if not(isinstance(indices, tuple) or isinstance(indices, list)): - indices = tuple(indices) - - if not regression_path: - regression_path = self.test_path / "regression_data" - - # Load test output data - gauge = gauges.GaugeSolution(gauge_id, path=self.temp_path) - - # Load regression data - if save: - shutil.copy(self.temp_path / f"gauge{str(gauge_id).zfill(5)}.txt", - regression_path) - claw_git_status.make_git_status_file(outdir=regression_path) - regression_gauge = gauges.GaugeSolution(gauge_id, path=regression_path) - - # Compare data - kwargs.setdefault('rtol', 1e-14) - kwargs.setdefault('atol', 1e-8) - try: - for n in indices: - np.testing.assert_allclose(gauge.q[n, :], - regression_gauge.q[n, :], - **kwargs) - except AssertionError as e: - err_msg = "\n".join((e.args[0], - "Gauge Match Failed for gauge = %s" % gauge_id)) - err_msg = "\n".join((err_msg, " failures in fields:")) - failure_indices = [] - for n in indices: - if ~np.allclose(gauge.q[n, :], regression_gauge.q[n, :], - **kwargs): - failure_indices.append(str(n)) - index_str = ", ".join(failure_indices) - raise AssertionError(" ".join((err_msg, index_str))) + super(ClawpackClassicTestRunner, self).__init__(path) From c15a1d58d4358435b5d6e23ceec08bfd461c99aa Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 12 Sep 2025 10:48:26 -0400 Subject: [PATCH 13/20] Cleanup names and minor bugs --- .../test_acoustics_1d_heterogeneous.py | 2 +- .../test_acoustics_3d_heterogeneous.py | 2 +- .../advection_2d_annulus/test_advection_2d_annulus.py | 2 +- src/python/classic/test.py | 11 +++++------ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index d6cfb38..c20a187 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -8,7 +8,7 @@ def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): - ctr = test.ClawpackClassicTestRunner(tmp_path) + ctr = test.ClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_output_times = 2 diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index c2c89a6..cd62911 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -9,7 +9,7 @@ def test_acoustics_3d_heterogeneous(tmp_path: Path, save: bool): r"""Basic test for a 3D heterogeneous acoustics test.""" - ctr = test.ClawpackClassicTestRunner(tmp_path) + ctr = test.ClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_cells = [20, 20, 20] diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index d1e91a4..49a8191 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -8,7 +8,7 @@ def test_advection_2d_annulus(tmp_path: Path, save: bool): - ctr = test.ClawpackClassicTestRunner(tmp_path) + ctr = test.ClassicTestRunner(tmp_path) ctr.set_data() ctr.rundata.clawdata.num_output_times = 2 diff --git a/src/python/classic/test.py b/src/python/classic/test.py index 410fd3d..e233d66 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -1,9 +1,8 @@ r""" -Execute nosetests in all subdirectories, to run a series of quick -regression tests. +Defines the Classic Clawpack Test Runner class for running PyTest based +regression tests in classic clawpack. -Sends output and result/errors to separate files to simplify checking -results and looking for errors. +Refer to the documentation for PyTest to manage output and reporting. """ from pathlib import Path @@ -24,7 +23,7 @@ for lib_path in (CLAW / "classic" / "src" / "3d").glob("*.o"): lib_path.unlink() -class ClawpackClassicTestRunner(test.ClawpackTestRunner): +class ClassicTestRunner(test.ClawpackTestRunner): def __init__(self, path: Path): - super(ClawpackClassicTestRunner, self).__init__(path) + super(ClassicTestRunner, self).__init__(path) From 86b888692bcac431d7d9a42953af88e66d96f57e Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 15 Sep 2025 11:54:53 -0400 Subject: [PATCH 14/20] Add test_path input option --- src/python/classic/test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index e233d66..c037c75 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -7,6 +7,7 @@ from pathlib import Path import os +from typing import Optional import clawpack.clawutil.test as test @@ -25,5 +26,5 @@ class ClassicTestRunner(test.ClawpackTestRunner): - def __init__(self, path: Path): - super(ClassicTestRunner, self).__init__(path) + def __init__(self, path: Path, test_path: Optional[Path]=None): + super(ClassicTestRunner, self).__init__(path, test_path=test_path) From cfb5fcc625867bbc17e72d3ed7248b38d03a5cfd Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 16 Mar 2026 13:55:07 -0400 Subject: [PATCH 15/20] Remove object cleaning from Python and depend on Makefiles Also adds more comments to the example test in acoustics_1d_heterogenous. --- .../test_acoustics_1d_heterogeneous.py | 40 +++++++++++-------- src/python/classic/test.py | 9 +---- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index c20a187..9c35b69 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ Regression tests for a 1D heterogeneous acoustics test """ @@ -7,22 +8,29 @@ import clawpack.classic.test as test def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): - - ctr = test.ClassicTestRunner(tmp_path) - - ctr.set_data() - ctr.rundata.clawdata.num_output_times = 2 - ctr.rundata.clawdata.tfinal = 5.0 - ctr.rundata.clawdata.output_t0 = False - ctr.write_data() - - ctr.executable_name = 'xclaw' - ctr.build_executable() - - ctr.run_code() - - ctr.check_frame(1, indices=(0, 1), save=save) - ctr.check_frame(2, indices=(0, 1), save=save) + # Create an example-local runner. tmp_path keeps all generated files and + # solver output out of the source tree. + runner = test.ClassicTestRunner(tmp_path, test_path=Path(__file__).parent) + + # Load the default example configuration from setrun.py and make the run + # smaller/faster for regression testing. + runner.set_data() + runner.rundata.clawdata.num_output_times = 2 + runner.rundata.clawdata.tfinal = 5.0 + runner.rundata.clawdata.output_t0 = False + + # Write the input data files into the temporary run directory. + runner.write_data() + + # Build using the example Makefile, then move the executable into tmp_path. + runner.build_executable() + + # Run the code with both input files and output files in tmp_path. + runner.run_code() + + # Compare selected output frames against saved regression baselines. + runner.check_frame(1, indices=(0, 1), save=save) + runner.check_frame(2, indices=(0, 1), save=save) if __name__=="__main__": pytest.main([__file__]) diff --git a/src/python/classic/test.py b/src/python/classic/test.py index c037c75..a8c471f 100644 --- a/src/python/classic/test.py +++ b/src/python/classic/test.py @@ -6,8 +6,8 @@ """ from pathlib import Path -import os from typing import Optional +import os import clawpack.clawutil.test as test @@ -17,13 +17,6 @@ else: raise ValueError("Need to set CLAW environment variable.") -for lib_path in (CLAW / "classic" / "src" / "1d").glob("*.o"): - lib_path.unlink() -for lib_path in (CLAW / "classic" / "src" / "2d").glob("*.o"): - lib_path.unlink() -for lib_path in (CLAW / "classic" / "src" / "3d").glob("*.o"): - lib_path.unlink() - class ClassicTestRunner(test.ClawpackTestRunner): def __init__(self, path: Path, test_path: Optional[Path]=None): From 750ba6415f3a8b29db80e364d051c95ad3cf04cf Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Mon, 16 Mar 2026 14:04:58 -0400 Subject: [PATCH 16/20] Add testing matrix --- .github/workflows/testing.yml | 148 +++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 20 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 64a97f6..591b915 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -1,4 +1,4 @@ -name: Test Classic +name: Test Classic Clawpack on: push: @@ -6,6 +6,8 @@ on: pull_request: branches: [ "master" ] + workflow_dispatch: + permissions: contents: read @@ -13,54 +15,160 @@ env: CLAW: ${{ github.workspace }} jobs: - test: - runs-on: ubuntu-latest + tests: + name: > + ${{ matrix.os }} - ${{ matrix.toolchain.compiler }} ${{ matrix.build }} - py ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false # Probably want to turn this off for a large matrix + matrix: + os: [ubuntu-latest, macos-latest] + build: [debug, opt] + python-version: [3.12] + toolchain: + - {compiler: gcc, version: 14} + - {compiler: gcc, version: 15} + # - {compiler: intel, version: '2025.0'} + # - {compiler: intel-classic, version: '2021.10'} + # - {compiler: nvidia-hpc, version: '25.1'} # does not build python + # - {compiler: lfortran, version: '0.45.0'} # lfortran not yet supported + # - {compiler: flang, version: '20.1.0'} # flang not yet supported + # include: + # 3.8 does not seem to work. + # - os: ubuntu-latest + # build: opt + # python-version: 3.8 + # toolchain: {compiler: gcc, version: 14} + exclude: + - os: ubuntu-latest + toolchain: {compiler: gcc, version: 15} + - os: macos-latest + toolchain: {compiler: gcc, version: 14} + steps: - - name: Set up Python 3.10 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: "3.10" - - - name: Install dependencies + python-version: ${{ matrix.python-version }} + + - name: Set up compilers + uses: fortran-lang/setup-fortran@v1 + id: setup-fortran + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install python dependencies run: | - sudo apt-get update - sudo apt-get install gfortran python -m pip install --upgrade pip pip install flake8 meson-python ninja pytest numpy - - name: Checkout clawpack + - name: Checkout Clawpack uses: actions/checkout@v4.1.5 with: repository: clawpack/clawpack submodules: true - - name: Checkout classic branch + - name: Checkout Classic branch uses: actions/checkout@v4.1.5 with: - path: classic + path: classic - - name: Install clawpack + - name: Install clawpack python run: | - cd ${CLAW} pip install --no-build-isolation --editable . - name: Lint with flake8 + if: ${{ matrix.build == 'debug' }} run: | cd ${CLAW}/classic # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude dev # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - + - name: Test with pytest run: | + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="-O0 -g -fcheck=all -fbacktrace -fbounds-check -ffpe-trap=invalid,zero,overflow -finit-real=nan -finit-integer=nan -Wall -Wunderflow -Wextra -Wconversion -Wuninitialized -Warray-bounds -Wshadow -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter -Wno-unused-label -Wno-unused-but-set-variable" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="-O1 -fopenmp -funroll-loops -finline-functions -ftree-vectorize -fstack-protector-strong -flto -march=native" + export OMP_NUM_THREADS=2 + else + echo "Unknown build type: ${{ matrix.build }}" + exit 1 + fi + elif [ "${{ matrix.toolchain.compiler }}" = "intel-classic" ]; then + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="-O0 -debug all -check all -warn all,nodec,interfaces -gen_interfaces -traceback -fpe0 -ftrapuv -init=snan,arrays -check bounds" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="-O -qopenmp -unroll -finline-functions -inline-forceinline -ipo -ip" + export OMP_NUM_THREADS=2 + fi + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="-O0 -debug all -check all -warn all,nodec,interfaces -gen_interfaces -traceback -fpe0 -ftrapuv -init=snan,arrays -check bounds" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="-O -qopenmp -unroll -finline-functions -inline-forceinline -ipo -ip" + export OMP_NUM_THREADS=2 + else + echo "Unknown build type: ${{ matrix.build }}" + exit 1 + fi + elif [ "${{ matrix.toolchain.compiler }}" = "nvidia-hpc" ]; then + echo "nvidia-hpc compiler not yet supported" + exit 1 + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="-g -O0 -check all -traceback" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="-O1 -qopenmp" + export OMP_NUM_THREADS=2 + else + echo "Unknown build type: ${{ matrix.build }}" + exit 1 + fi + elif [ "${{ matrix.toolchain.compiler }}" = "flang" ]; then + echo "flang compiler not yet supported" + exit 1 + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="" + export OMP_NUM_THREADS=2 + else + echo "Unknown build type: ${{ matrix.build }}" + exit 1 + fi + elif [ "${{ matrix.toolchain.compiler }}" = "lfortran" ]; then + if [ "${{ matrix.build }}" = "debug" ]; then + export FFLAGS="" + export OMP_NUM_THREADS=1 + elif [ "${{ matrix.build }}" = "opt" ]; then + export FFLAGS="--fast --openmp" + export OMP_NUM_THREADS=2 + else + echo "Unknown build type: ${{ matrix.build }}" + exit 1 + fi + else + echo "Unknown compiler: ${{ matrix.toolchain.compiler }}" + exit 1 + fi + echo "FFLAGS: $FFLAGS" + echo "OMP_NUM_THREADS: $OMP_NUM_THREADS" cd ${CLAW}/classic - pytest --basetemp=./test_output - + pytest + - name: Upload test results - # if: failure() - Always upload artifacts now + if: failure() uses: actions/upload-artifact@v4 with: name: test_results - path: ${{ env.CLAW }}/classic/test_output + path: ${{ env.CLAW }}/classic/*_output if-no-files-found: ignore From 5345d1863d1d0bf846f17549bcc69b7696a2464f Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 3 Apr 2026 14:07:45 -0400 Subject: [PATCH 17/20] Fix CLI test calling --- .../test_acoustics_1d_heterogeneous.py | 2 +- .../test_acoustics_3d_heterogeneous.py | 2 +- examples/advection_2d_annulus/test_advection_2d_annulus.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index 9c35b69..d5a2972 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -33,4 +33,4 @@ def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): runner.check_frame(2, indices=(0, 1), save=save) if __name__=="__main__": - pytest.main([__file__]) + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index cd62911..9150929 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -26,4 +26,4 @@ def test_acoustics_3d_heterogeneous(tmp_path: Path, save: bool): ctr.check_frame(2, indices=(0, 1, 2), save=save) if __name__ == "__main__": - pytest.main([__file__]) + raise SystemExit(pytest.main([__file__])) diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index 49a8191..bf8f6a7 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -24,4 +24,4 @@ def test_advection_2d_annulus(tmp_path: Path, save: bool): ctr.check_frame(2, save=save) if __name__=="__main__": - pytest.main([__file__]) + raise SystemExit(pytest.main([__file__])) From 565fe1e31e635e4647da2bfabf6401cc385eea26 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 3 Apr 2026 14:23:40 -0400 Subject: [PATCH 18/20] Add testing marks --- .../test_acoustics_1d_heterogeneous.py | 1 + .../test_acoustics_3d_heterogeneous.py | 1 + examples/advection_2d_annulus/test_advection_2d_annulus.py | 1 + pytest.ini | 7 +++++++ 4 files changed, 10 insertions(+) create mode 100644 pytest.ini diff --git a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py index d5a2972..faad758 100644 --- a/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py +++ b/examples/acoustics_1d_heterogeneous/test_acoustics_1d_heterogeneous.py @@ -7,6 +7,7 @@ import pytest import clawpack.classic.test as test +@pytest.mark.regression def test_acoustics_1d_heterogeneous(tmp_path: Path, save: bool): # Create an example-local runner. tmp_path keeps all generated files and # solver output out of the source tree. diff --git a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py index 9150929..fa2f9c5 100644 --- a/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py +++ b/examples/acoustics_3d_heterogeneous/test_acoustics_3d_heterogeneous.py @@ -6,6 +6,7 @@ import pytest import clawpack.classic.test as test +@pytest.mark.regression def test_acoustics_3d_heterogeneous(tmp_path: Path, save: bool): r"""Basic test for a 3D heterogeneous acoustics test.""" diff --git a/examples/advection_2d_annulus/test_advection_2d_annulus.py b/examples/advection_2d_annulus/test_advection_2d_annulus.py index bf8f6a7..baa4a03 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -6,6 +6,7 @@ import pytest import clawpack.classic.test as test +@pytest.mark.regression def test_advection_2d_annulus(tmp_path: Path, save: bool): ctr = test.ClassicTestRunner(tmp_path) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e30443e --- /dev/null +++ b/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +addopts = -m "not slow" +markers = + python: pure Python unit tests + regression: example-based regression tests + remote: tests that require fetching a file from the web + slow: slower tests not intended for the default quick CI path From 4ffbbeeb3da974edb17761e116a46a24e548a542 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Fri, 3 Apr 2026 14:38:22 -0400 Subject: [PATCH 19/20] Add enhancements to testing This brings up to date testing for classic with what was done in GeoClaw. This currently does not have any python tests, and if they are added, the testing.yml would need to be updated. There's also no slow test functionality. --- .github/scripts/set_test_env.sh | 83 +++++++++++++++++ .github/workflows/testing.yml | 154 ++++++++++---------------------- 2 files changed, 130 insertions(+), 107 deletions(-) create mode 100644 .github/scripts/set_test_env.sh diff --git a/.github/scripts/set_test_env.sh b/.github/scripts/set_test_env.sh new file mode 100644 index 0000000..5d6ad5e --- /dev/null +++ b/.github/scripts/set_test_env.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +# Configure CI test environment variables for Clawpack workflows. +# Usage: +# source .github/scripts/set_test_env.sh regression + +set -euo pipefail + +test_group="${1:-}" +compiler="${2:-}" +build="${3:-}" + +if [[ -z "${test_group}" || -z "${compiler}" || -z "${build}" ]]; then + echo "Usage: source .github/scripts/set_test_env.sh " + return 1 2>/dev/null || exit 1 +fi + +case "${test_group}" in + regression|slow) + case "${compiler}" in + gcc) + case "${build}" in + debug) + export FFLAGS="-O0 -g -fcheck=all -fbacktrace -fbounds-check -ffpe-trap=invalid,zero,overflow -finit-real=nan -finit-integer=nan -Wall -Wunderflow -Wextra -Wconversion -Wuninitialized -Warray-bounds -Wshadow -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter -Wno-unused-label -Wno-unused-but-set-variable" + export OMP_NUM_THREADS=1 + ;; + opt) + export FFLAGS="-O1 -fopenmp -funroll-loops -finline-functions -ftree-vectorize -fstack-protector-strong -flto -march=native" + export OMP_NUM_THREADS=2 + ;; + *) + echo "Unknown build type: ${build}" + return 1 2>/dev/null || exit 1 + ;; + esac + ;; + intel|intel-classic) + case "${build}" in + debug) + export FFLAGS="-O0 -debug all -check all -warn all,nodec,interfaces -gen_interfaces -traceback -fpe0 -ftrapuv -init=snan,arrays -check bounds" + export OMP_NUM_THREADS=1 + ;; + opt) + export FFLAGS="-O -qopenmp -unroll -finline-functions -inline-forceinline -ipo -ip" + export OMP_NUM_THREADS=2 + ;; + *) + echo "Unknown build type: ${build}" + return 1 2>/dev/null || exit 1 + ;; + esac + ;; + lfortran) + case "${build}" in + debug) + export FFLAGS="" + export OMP_NUM_THREADS=1 + ;; + opt) + export FFLAGS="--fast --openmp" + export OMP_NUM_THREADS=2 + ;; + *) + echo "Unknown build type: ${build}" + return 1 2>/dev/null || exit 1 + ;; + esac + ;; + nvidia-hpc|flang) + echo "${compiler} compiler not yet supported" + return 1 2>/dev/null || exit 1 + ;; + *) + echo "Unknown compiler: ${compiler}" + return 1 2>/dev/null || exit 1 + ;; + esac + ;; + *) + echo "Unknown test group: ${test_group}" + return 1 2>/dev/null || exit 1 + ;; +esac diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 591b915..6fcdbdc 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -2,10 +2,9 @@ name: Test Classic Clawpack on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] - + branches: ["master"] workflow_dispatch: permissions: @@ -15,30 +14,50 @@ env: CLAW: ${{ github.workspace }} jobs: - tests: + python-lint: name: > - ${{ matrix.os }} - ${{ matrix.toolchain.compiler }} ${{ matrix.build }} - py ${{ matrix.python-version }} + python-lint - ubuntu-latest - py ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.12', '3.13'] + + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6.2.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Install python dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + + - name: Checkout Classic branch + uses: actions/checkout@v6.0.2 + + - name: Lint with flake8 + run: | + cd ${CLAW} + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + regression-tests: + name: > + regression - ${{ matrix.os }} - ${{ matrix.toolchain.compiler }} ${{ matrix.build }} - py ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false # Probably want to turn this off for a large matrix + fail-fast: false matrix: os: [ubuntu-latest, macos-latest] build: [debug, opt] - python-version: [3.12] + python-version: ['3.12'] toolchain: - {compiler: gcc, version: 14} - {compiler: gcc, version: 15} - # - {compiler: intel, version: '2025.0'} - # - {compiler: intel-classic, version: '2021.10'} - # - {compiler: nvidia-hpc, version: '25.1'} # does not build python - # - {compiler: lfortran, version: '0.45.0'} # lfortran not yet supported - # - {compiler: flang, version: '20.1.0'} # flang not yet supported - # include: - # 3.8 does not seem to work. - # - os: ubuntu-latest - # build: opt - # python-version: 3.8 - # toolchain: {compiler: gcc, version: 14} exclude: - os: ubuntu-latest toolchain: {compiler: gcc, version: 15} @@ -47,12 +66,12 @@ jobs: steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Set up compilers - uses: fortran-lang/setup-fortran@v1 + uses: fortran-lang/setup-fortran@v1.9.0 id: setup-fortran with: compiler: ${{ matrix.toolchain.compiler }} @@ -64,13 +83,13 @@ jobs: pip install flake8 meson-python ninja pytest numpy - name: Checkout Clawpack - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v6.0.2 with: repository: clawpack/clawpack submodules: true - name: Checkout Classic branch - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v6.0.2 with: path: classic @@ -78,97 +97,18 @@ jobs: run: | pip install --no-build-isolation --editable . - - name: Lint with flake8 - if: ${{ matrix.build == 'debug' }} + - name: Run regression tests run: | cd ${CLAW}/classic - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude dev - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Test with pytest - run: | - if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="-O0 -g -fcheck=all -fbacktrace -fbounds-check -ffpe-trap=invalid,zero,overflow -finit-real=nan -finit-integer=nan -Wall -Wunderflow -Wextra -Wconversion -Wuninitialized -Warray-bounds -Wshadow -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter -Wno-unused-label -Wno-unused-but-set-variable" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="-O1 -fopenmp -funroll-loops -finline-functions -ftree-vectorize -fstack-protector-strong -flto -march=native" - export OMP_NUM_THREADS=2 - else - echo "Unknown build type: ${{ matrix.build }}" - exit 1 - fi - elif [ "${{ matrix.toolchain.compiler }}" = "intel-classic" ]; then - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="-O0 -debug all -check all -warn all,nodec,interfaces -gen_interfaces -traceback -fpe0 -ftrapuv -init=snan,arrays -check bounds" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="-O -qopenmp -unroll -finline-functions -inline-forceinline -ipo -ip" - export OMP_NUM_THREADS=2 - fi - elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="-O0 -debug all -check all -warn all,nodec,interfaces -gen_interfaces -traceback -fpe0 -ftrapuv -init=snan,arrays -check bounds" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="-O -qopenmp -unroll -finline-functions -inline-forceinline -ipo -ip" - export OMP_NUM_THREADS=2 - else - echo "Unknown build type: ${{ matrix.build }}" - exit 1 - fi - elif [ "${{ matrix.toolchain.compiler }}" = "nvidia-hpc" ]; then - echo "nvidia-hpc compiler not yet supported" - exit 1 - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="-g -O0 -check all -traceback" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="-O1 -qopenmp" - export OMP_NUM_THREADS=2 - else - echo "Unknown build type: ${{ matrix.build }}" - exit 1 - fi - elif [ "${{ matrix.toolchain.compiler }}" = "flang" ]; then - echo "flang compiler not yet supported" - exit 1 - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="" - export OMP_NUM_THREADS=2 - else - echo "Unknown build type: ${{ matrix.build }}" - exit 1 - fi - elif [ "${{ matrix.toolchain.compiler }}" = "lfortran" ]; then - if [ "${{ matrix.build }}" = "debug" ]; then - export FFLAGS="" - export OMP_NUM_THREADS=1 - elif [ "${{ matrix.build }}" = "opt" ]; then - export FFLAGS="--fast --openmp" - export OMP_NUM_THREADS=2 - else - echo "Unknown build type: ${{ matrix.build }}" - exit 1 - fi - else - echo "Unknown compiler: ${{ matrix.toolchain.compiler }}" - exit 1 - fi + source .github/scripts/set_test_env.sh regression "${{ matrix.toolchain.compiler }}" "${{ matrix.build }}" echo "FFLAGS: $FFLAGS" echo "OMP_NUM_THREADS: $OMP_NUM_THREADS" - cd ${CLAW}/classic - pytest + pytest --basetemp=${CLAW}/classic/pytest_tmp -vv -s -o addopts="" -m "regression" - name: Upload test results if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: test_results - path: ${{ env.CLAW }}/classic/*_output + name: test_results_${{ matrix.os }}_${{ matrix.toolchain.compiler }}_${{ matrix.build }} + path: ${{ env.CLAW }}/classic/pytest_tmp if-no-files-found: ignore From f459d5e7e25e71bfcd41005d64b87cf04e703436 Mon Sep 17 00:00:00 2001 From: Kyle Mandli Date: Sat, 4 Apr 2026 09:51:04 -0400 Subject: [PATCH 20/20] Add acoustics 1d example1 test --- .../regression_data/frame0001.txt | 2 ++ .../regression_data/frame0002.txt | 2 ++ .../test_acoustics_1d_example1.py | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 examples/acoustics_1d_example1/regression_data/frame0001.txt create mode 100644 examples/acoustics_1d_example1/regression_data/frame0002.txt create mode 100644 examples/acoustics_1d_example1/test_acoustics_1d_example1.py diff --git a/examples/acoustics_1d_example1/regression_data/frame0001.txt b/examples/acoustics_1d_example1/regression_data/frame0001.txt new file mode 100644 index 0000000..0aaff20 --- /dev/null +++ b/examples/acoustics_1d_example1/regression_data/frame0001.txt @@ -0,0 +1,2 @@ +1.253314136595804307e+01 +-6.266570676735773837e+00 diff --git a/examples/acoustics_1d_example1/regression_data/frame0002.txt b/examples/acoustics_1d_example1/regression_data/frame0002.txt new file mode 100644 index 0000000..5031a1f --- /dev/null +++ b/examples/acoustics_1d_example1/regression_data/frame0002.txt @@ -0,0 +1,2 @@ +1.018560194718946428e-33 +-5.092800973594732139e-34 diff --git a/examples/acoustics_1d_example1/test_acoustics_1d_example1.py b/examples/acoustics_1d_example1/test_acoustics_1d_example1.py new file mode 100644 index 0000000..3066656 --- /dev/null +++ b/examples/acoustics_1d_example1/test_acoustics_1d_example1.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +from pathlib import Path +import pytest + +import clawpack.classic.test as test + + +def test_acoustics_1d_example1(tmp_path: Path, save: bool): + runner = test.ClassicTestRunner(tmp_path, + test_path=Path(__file__).parent) + + runner.set_data() + + runner.rundata.clawdata.num_output_times = 2 + runner.rundata.clawdata.tfinal = 1.0 + runner.rundata.clawdata.output_t0 = False + + runner.write_data() + + runner.executable_name = "xclaw" + runner.build_executable() + runner.run_code() + + runner.check_frame(1, indices=(0, 1), save=save) + runner.check_frame(2, indices=(0, 1), save=save) + +if __name__=="__main__": + raise SystemExit(pytest.main([__file__]))