diff --git a/.github/scripts/set_test_env.sh b/.github/scripts/set_test_env.sh new file mode 100644 index 000000000..43b61e0ea --- /dev/null +++ b/.github/scripts/set_test_env.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +# Configure CI test environment variables for Clawpack workflows. +# Usage: +# source .github/scripts/set_test_env.sh + +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) + # Removed -flto -march=native + export FFLAGS="-O1 -fopenmp -funroll-loops -finline-functions -ftree-vectorize -fstack-protector-strong" + 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 3cd0bf439..4e2a026d8 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -2,10 +2,9 @@ name: Test AMRClaw 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 AMRClaw 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 --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 + + 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 AMRClaw branch - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v6.0.2 with: path: amrclaw @@ -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}/amrclaw - # 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}/amrclaw - pytest + pytest --basetemp=${CLAW}/amrclaw/pytest_tmp -vv -s -o addopts="" - name: Upload test results if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: test_results - path: ${{ env.CLAW }}/amrclaw/*_output + name: test_results_${{ matrix.os }}_${{ matrix.toolchain.compiler }}_${{ matrix.build }} + path: ${{ env.CLAW }}/amrclaw/pytest_tmp if-no-files-found: ignore diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index 37da1938d..000000000 --- a/examples/Makefile +++ /dev/null @@ -1,11 +0,0 @@ - -# Targets that do not correspond to file names: -.PHONY: tests clobber - -tests: - python run_tests.py - -clobber: - rm -f run_tests_output.txt run_tests_results.txt - rm -f */*.o */xamr */*.data */*.pyc - rm -rf */_output diff --git a/examples/acoustics_1d_adjoint/adjoint/test_acoustics_1d_adjoint.py b/examples/acoustics_1d_adjoint/adjoint/test_acoustics_1d_adjoint.py index f5ca606a1..fb46e33dd 100644 --- a/examples/acoustics_1d_adjoint/adjoint/test_acoustics_1d_adjoint.py +++ b/examples/acoustics_1d_adjoint/adjoint/test_acoustics_1d_adjoint.py @@ -1,45 +1,32 @@ +#!/usr/bin/env python """ -Test for the adjoint problem for 2D acoustics +Test for the adjoint problem for 1D acoustics """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test -import clawpack.amrclaw.data as data -class Acoustics1DAdjointTest(test.AMRClawRegressionTest): +def set_adjoint_data(runner): + """Set the adjoint data for the test case""" + runner.set_data() - def runTest(self, save=False): +@pytest.mark.regression +@pytest.mark.adjoint +def test_acoustics_1d_adjoint(tmp_path: Path, save: bool): + """Acoustics 1D adjoint test""" - # Write out data files - self.load_rundata() - - # self.rundata.clawdata.num_output_times = 10 - # self.rundata.clawdata.tfinal = 1.0 + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.write_data() + runner.build_executable() + runner.run_code() - # self.rundata.clawdata.use_fwaves = False + runner.check_gauge(gauge_id=0, save=save) + runner.check_gauge(gauge_id=1, save=save) - self.write_rundata_objects() - self.run_code() - - # Perform Tests - self.check_gauges(save=save, gauge_id=0) - self.check_gauges(save=save, gauge_id=1) - - self.success = True - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics1DAdjointTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_1d_adjoint/test_acoustics_1d_adjoint_forward.py b/examples/acoustics_1d_adjoint/test_acoustics_1d_adjoint_forward.py index 192627aad..d44663e2a 100644 --- a/examples/acoustics_1d_adjoint/test_acoustics_1d_adjoint_forward.py +++ b/examples/acoustics_1d_adjoint/test_acoustics_1d_adjoint_forward.py @@ -1,78 +1,38 @@ +#!/usr/bin/env python """ Regression tests for 1D acoustics with adjoint flagging. """ from pathlib import Path -import sys -import shutil -import unittest +import pytest import clawpack.amrclaw.test as test -import clawpack.clawutil.runclaw - -from adjoint.test_acoustics_1d_adjoint import Acoustics1DAdjointTest - - -class Acoustics1DAdjointForwardTest(test.AMRClawRegressionTest): - r"""Basic test for a 1D acoustics adjoint-flagging forward problem test case""" - - - def runTest(self, save=False): - - # Run adjoint problem - try: - adjoint_run = Acoustics1DAdjointTest() - adjoint_run.setUp() - adjoint_run.runTest() - - # Copy output to local directory - adjoint_output = Path(self.temp_path) / "_adjoint_output" - - if Path(adjoint_output).exists(): - shutil.rmtree(adjoint_output) - shutil.copytree(adjoint_run.temp_path, adjoint_output) - finally: - adjoint_run.tearDown() - - # Write out data files - self.load_rundata() - - # self.rundata.clawdata.num_output_times = 1 - # self.rundata.clawdata.tfinal = 0.5 - - # self.rundata.amrdata.flag2refine_tol = 0.05 - - # self.rundata.gaugedata.gauges = [] - # self.rundata.gaugedata.gauges.append([0, -1.0, 0., 1e9]) - # self.rundata.gaugedata.gauges.append([1, -2.5, 0., 1e9]) - # self.rundata.gaugedata.gauges.append([2, -1.75, 0., 1e9]) - - # Look for adjoint data - self.rundata.adjointdata.adjoint_outdir = adjoint_output - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=0) - self.check_gauges(save=save, gauge_id=1) - # self.check_gauges(save=save, gauge_id=2) - - self.success = True - - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics1DAdjointForwardTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() +import clawpack.clawutil.test as clawtest + +@pytest.mark.regression +@pytest.mark.adjoint_forward +def test_acoustics_1d_adjoint_forward(tmp_path: Path, save: bool): + """Test for a 1D acoustics adjoint-flagging forward problem test case""" + + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + adjoint_path = runner.test_path / "adjoint" + adjoint_output = tmp_path / "_adjoint_output" + + clawtest.run_example_for_test( + test.AMRClawTestRunner, + adjoint_output, + adjoint_path, + ) + + runner.set_data() + runner.rundata.adjointdata.adjoint_outdir = adjoint_output + runner.write_data() + + runner.build_executable() + runner.run_code() + + runner.check_gauge(0, save=save) + runner.check_gauge(1, save=save) + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_1d_homogeneous/test_acoustics_1d_homogeneous.py b/examples/acoustics_1d_homogeneous/test_acoustics_1d_homogeneous.py index 06c54ae5e..241283d99 100644 --- a/examples/acoustics_1d_homogeneous/test_acoustics_1d_homogeneous.py +++ b/examples/acoustics_1d_homogeneous/test_acoustics_1d_homogeneous.py @@ -1,51 +1,33 @@ +#!/usr/bin/env python """ Regression tests for 1D acoustics in a homogeneous medium. """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test - -class Acoustics1DTest_Homogeneous(test.AMRClawRegressionTest): +def test_acoustics_1d_homogeneous(tmp_path: Path, save: bool): """Basic test for a 1D acoustics test case in a homogeneous medium""" + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_output_times = 1 + runner.rundata.clawdata.tfinal = 0.200000 + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([0, 0.0, 0., 0.8]) + runner.rundata.gaugedata.gauges.append([1, 0.6, 0., 0.8]) + runner.write_data() - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 0.200000 - - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([0, 0.0, 0., 0.8]) - self.rundata.gaugedata.gauges.append([1, 0.6, 0., 0.8]) - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=0) - self.check_gauges(save=save, gauge_id=1) + runner.build_executable() - self.success = True + runner.run_code() + runner.check_gauge(save=save, gauge_id=0) + runner.check_gauge(save=save, gauge_id=1) -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics1DTest_Homogeneous() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_2d_adjoint/adjoint/test_acoustics_2d_adjoint.py b/examples/acoustics_2d_adjoint/adjoint/test_acoustics_2d_adjoint.py index 0d8ea0b2a..579512328 100644 --- a/examples/acoustics_2d_adjoint/adjoint/test_acoustics_2d_adjoint.py +++ b/examples/acoustics_2d_adjoint/adjoint/test_acoustics_2d_adjoint.py @@ -1,47 +1,40 @@ +#!/usr/bin/env python """ Test for the adjoint problem for 2D acoustics """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test -import clawpack.amrclaw.data as data +from clawpack.clawutil.test import run_example_for_test -class Acoustics2DAdjointTest(test.AMRClawRegressionTest): +def configure_2d_adjoint(runner): + clawdata = runner.rundata.clawdata + gaugedata = runner.rundata.gaugedata - def runTest(self, save=False): + clawdata.num_output_times = 30 + clawdata.tfinal = 3.0 - # Write out data files - self.load_rundata() + gaugedata.gauges = [ + [1, 1.0, 1.0, 0.0, 10.0], + [2, 3.5, 0.5, 0.0, 10.0], + ] - self.rundata.clawdata.num_output_times = 30 - self.rundata.clawdata.tfinal = 3.0 +@pytest.mark.regression +@pytest.mark.adjoint +def test_acoustics_2d_adjoint(tmp_path: Path, save: bool): + adjoint_path = Path(__file__).parent - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 1.0, 1.0, 0., 10.]) - self.rundata.gaugedata.gauges.append([2, 3.5, 0.5, 0., 10.]) + runner = run_example_for_test( + test.AMRClawTestRunner, + tmp_path, + adjoint_path, + configure_runner=configure_2d_adjoint, + ) - self.write_rundata_objects() + runner.check_gauge(gauge_id=1, save=save) + runner.check_gauge(gauge_id=2, save=save) - self.run_code() - - # Perform Tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - self.success = True - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics2DAdjointTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_2d_adjoint/test_acoustics_2d_adjoint_forward.py b/examples/acoustics_2d_adjoint/test_acoustics_2d_adjoint_forward.py index 17ff24eed..2c06a8bd2 100644 --- a/examples/acoustics_2d_adjoint/test_acoustics_2d_adjoint_forward.py +++ b/examples/acoustics_2d_adjoint/test_acoustics_2d_adjoint_forward.py @@ -1,80 +1,75 @@ +#!/usr/bin/env python """ Regression tests for 2D acoustics with adjoint flagging. """ from pathlib import Path -import sys -import shutil -import unittest +import pytest import clawpack.amrclaw.test as test +from clawpack.clawutil.test import run_example_for_test -from adjoint.test_acoustics_2d_adjoint import Acoustics2DAdjointTest +def configure_2d_adjoint(runner): + clawdata = runner.rundata.clawdata + gaugedata = runner.rundata.gaugedata -class Acoustics2DAdjointForwardTest(test.AMRClawRegressionTest): - r"""Basic test for a 2D acoustics adjoint-flagging forward problem test case""" + clawdata.num_output_times = 30 + clawdata.tfinal = 3.0 + gaugedata.gauges = [ + [1, 1.0, 1.0, 0.0, 10.0], + [2, 3.5, 0.5, 0.0, 10.0], + ] - def runTest(self, save=False): - - # Run adjoint problem - try: - adjoint_run = Acoustics2DAdjointTest() - adjoint_run.setUp() - adjoint_run.runTest() - - # Copy output to local directory - adjoint_output = Path(self.temp_path) / "_adjoint_output" +def configure_2d_forward(adjoint_output: Path): + def _configure(runner): + clawdata = runner.rundata.clawdata + gaugedata = runner.rundata.gaugedata + amrdata = runner.rundata.amrdata - if Path(adjoint_output).exists(): - shutil.rmtree(adjoint_output) - shutil.copytree(adjoint_run.temp_path, adjoint_output) - finally: - adjoint_run.tearDown() - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 3.0 + clawdata.num_output_times = 1 + clawdata.tfinal = 3.0 # Test gauges - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 1.0, 1.0, 0., 1e9]) - self.rundata.gaugedata.gauges.append([2, 3.5, 0.5, 0., 1e9]) + gaugedata.gauges = [] + gaugedata.gauges.append([1, 1.0, 1.0, 0., 1e9]) + gaugedata.gauges.append([2, 3.5, 0.5, 0., 1e9]) # AMR parameters - self.rundata.amrdata.amr_levels_max = 2 - self.rundata.amrdata.refinement_ratios_x = [2] - self.rundata.amrdata.refinement_ratios_y = [2] - self.rundata.amrdata.refinement_ratios_t = [2] - self.rundata.amrdata.flag_richardson_tol = 1e-5 - self.rundata.amrdata.flag2refine_tol = 0.02 - - # Look for adjoint data - self.rundata.adjointdata.adjoint_outdir = adjoint_output.resolve() - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - self.success = True - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics2DAdjointForwardTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() + amrdata.amr_levels_max = 2 + amrdata.refinement_ratios_x = [2] + amrdata.refinement_ratios_y = [2] + amrdata.refinement_ratios_t = [2] + amrdata.flag_richardson_tol = 1e-5 + amrdata.flag2refine_tol = 0.02 + + runner.rundata.adjointdata.adjoint_outdir = adjoint_output + + return _configure + +@pytest.mark.regression +@pytest.mark.adjoint_forward +def test_acoustics_2d_adjoint_forward(tmp_path: Path, save: bool): + example_path = Path(__file__).parent + adjoint_path = example_path / "adjoint" + adjoint_output = tmp_path / "_adjoint_output" + + run_example_for_test( + test.AMRClawTestRunner, + adjoint_output, + adjoint_path, + configure_runner=configure_2d_adjoint, + ) + + runner = run_example_for_test( + test.AMRClawTestRunner, + tmp_path, + example_path, + configure_runner=configure_2d_forward(adjoint_output), + ) + + runner.check_gauge(gauge_id=1, save=save) + runner.check_gauge(gauge_id=2, save=save) + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/acoustics_2d_radial/test_acoustics_2d_radial.py b/examples/acoustics_2d_radial/test_acoustics_2d_radial.py index 08a290176..469e9d099 100644 --- a/examples/acoustics_2d_radial/test_acoustics_2d_radial.py +++ b/examples/acoustics_2d_radial/test_acoustics_2d_radial.py @@ -1,50 +1,34 @@ +#!/usr/bin/env python """ Regression tests for 2D acoustics. """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test - -class Acoustics2DRadialTest(test.AMRClawRegressionTest): +def test_acoustics_2d_radial(tmp_path: Path, save: bool): r"""Basic test for a 2D acoustics test case""" + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_output_times = 1 + runner.rundata.clawdata.tfinal = 0.2 + runner.rundata.regiondata.regions.append([1,1,0,1e10,-1.,1.,-1.,1]) + runner.rundata.regiondata.regions.append([1,3,0,1e10,-1.,1.,-.2,.2]) + runner.write_data() - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 0.2 - self.rundata.regiondata.regions.append([1,1,0,1e10,-1.,1.,-1.,1]) - self.rundata.regiondata.regions.append([1,3,0,1e10,-1.,1.,-.2,.2]) - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=0) - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - self.success = True + runner.build_executable() + # Run code + runner.run_code() + # Perform tests + runner.check_gauge(save=save, gauge_id=0) + runner.check_gauge(save=save, gauge_id=1) + runner.check_gauge(save=save, gauge_id=2) -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Acoustics2DRadialTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/advection_1d_example1/test_advection_1d_example1.py b/examples/advection_1d_example1/test_advection_1d_example1.py index e971bfb2c..5a34f444a 100644 --- a/examples/advection_1d_example1/test_advection_1d_example1.py +++ b/examples/advection_1d_example1/test_advection_1d_example1.py @@ -1,91 +1,76 @@ +#!/usr/bin/env python """ Regression tests for 1D advection;. """ -import sys -import unittest -import numpy +from pathlib import Path +import pytest +import numpy as np import clawpack.amrclaw.test as test import clawpack.pyclaw.gauges as gauges - -class Advection1DTest(test.AMRClawRegressionTest): - """Basic test for a 1D acoustics advection case""" - - - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_cells[0] = 40 - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 0.200000 - - # Set up 3 gauges at the same location with different formats - gauge_location = 0.2 - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([0, gauge_location, 0, 1e9]) # ascii - self.rundata.gaugedata.gauges.append([1, gauge_location, 0, 1e9]) # binary32 - self.rundata.gaugedata.gauges.append([2, gauge_location, 0, 1e9]) # binary64 - - # Set per-gauge formats: ascii, binary32, binary64 - self.rundata.gaugedata.file_format = ['ascii', 'binary32', 'binary64'] - - self.rundata.amrdata.refinement_ratios_x = [2, 2] - self.rundata.amrdata.refinement_ratios_t = [2, 2] - self.rundata.amrdata.flag2refine_tol = 0.1 - self.rundata.amrdata.regrid_buffer_width = 2 - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Load all three gauge files - gauge_ascii = gauges.GaugeSolution(0, path=self.temp_path) - gauge_binary32 = gauges.GaugeSolution(1, path=self.temp_path) - gauge_binary64 = gauges.GaugeSolution(2, path=self.temp_path) - - # Compare all formats against each other - # Use ASCII as reference for comparisons - indices = [0] # Compare first component (q[0]) - - # binary32 vs ascii: binary32 has ~7 decimal digits precision - numpy.testing.assert_allclose( - gauge_binary32.q[indices[0], :], - gauge_ascii.q[indices[0], :], - rtol=1e-7, atol=1e-7, - err_msg="binary32 gauge does not match ascii gauge") - - # binary64 vs ascii: binary64 has ~15-17 decimal digits precision - numpy.testing.assert_allclose( - gauge_binary64.q[indices[0], :], - gauge_ascii.q[indices[0], :], - rtol=1e-15, atol=1e-15, - err_msg="binary64 gauge does not match ascii gauge") - - # binary32 vs binary64: limited by binary32 precision - numpy.testing.assert_allclose( - gauge_binary32.q[indices[0], :], - gauge_binary64.q[indices[0], :], - rtol=1e-7, atol=1e-7, - err_msg="binary32 gauge does not match binary64 gauge") - - self.success = True - - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Advection1DTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +def test_advection_1d_example1(tmp_path: Path, save: bool): + r"""Basic test for a 1D advection test case""" + + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_cells[0] = 40 + runner.rundata.clawdata.num_output_times = 1 + runner.rundata.clawdata.tfinal = 0.200000 + + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([0, 0.2, 0, 1e9]) + runner.rundata.gaugedata.gauges.append([1, 0.9, 0, 1e9]) + runner.rundata.gaugedata.gauges.append([2, 0.2, 0, 1e9]) + runner.rundata.gaugedata.gauges.append([3, 0.2, 0, 1e9]) + + runner.rundata.gaugedata.file_format = ['ascii', 'ascii', + 'binary32', 'binary64'] + + runner.rundata.amrdata.refinement_ratios_x = [2, 2] + runner.rundata.amrdata.refinement_ratios_t = [2, 2] + runner.rundata.amrdata.flag2refine_tol = 0.1 + runner.rundata.amrdata.regrid_buffer_width = 2 + runner.write_data() + + runner.build_executable() + + runner.run_code() + + # Original gauge check + runner.check_gauge(save=save, gauge_id=1) + + # Testing for gauge formatting: load all three gauge files + gauge_ascii = gauges.GaugeSolution(0, path=runner.temp_path) + gauge_binary32 = gauges.GaugeSolution(2, path=runner.temp_path) + gauge_binary64 = gauges.GaugeSolution(3, path=runner.temp_path) + + # Compare all formats against each other + # Use ASCII as reference for comparisons + indices = [0] # Compare first component (q[0]) + + # binary32 vs ascii: binary32 has ~7 decimal digits precision + np.testing.assert_allclose( + gauge_binary32.q[indices[0], :], + gauge_ascii.q[indices[0], :], + rtol=1e-7, atol=1e-7, + err_msg="binary32 gauge does not match ascii gauge") + + # binary64 vs ascii: binary64 has ~15-17 decimal digits precision + np.testing.assert_allclose( + gauge_binary64.q[indices[0], :], + gauge_ascii.q[indices[0], :], + rtol=1e-15, atol=1e-15, + err_msg="binary64 gauge does not match ascii gauge") + + # binary32 vs binary64: limited by binary32 precision + np.testing.assert_allclose( + gauge_binary32.q[indices[0], :], + gauge_binary64.q[indices[0], :], + rtol=1e-7, atol=1e-7, + err_msg="binary32 gauge does not match binary64 gauge") + +if __name__ == "__main__": + 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 a15ce992e..ee35d53b4 100644 --- a/examples/advection_2d_annulus/test_advection_2d_annulus.py +++ b/examples/advection_2d_annulus/test_advection_2d_annulus.py @@ -1,52 +1,34 @@ +#!/usr/bin/env python """ Regression tests for 2D advection on an annulus. """ -import sys -import unittest +from pathlib import Path +import pytest import numpy as np import clawpack.amrclaw.test as test - -class Advection2DAnnulusTest(test.AMRClawRegressionTest): +def test_advection_2d_annulus(tmp_path: Path, save: bool): r"""Basic test for a 2D advection on an annulus test case""" + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_output_times = 1 + runner.rundata.clawdata.tfinal = 0.500000 + runner.rundata.regiondata.regions.append([1, 2, 0.0, 10.0, 0.2, 1.0, 0.0, 2.0 * np.pi]) + runner.rundata.regiondata.regions.append([3, 3, 0.0, 10.0, 0.5, 1.0, 0.0, 0.5 * np.pi]) - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 0.500000 - - self.rundata.regiondata.regions.append([1, 2, 0.0, 10.0, 0.2, 1.0, 0.0, 2.0 * np.pi]) - self.rundata.regiondata.regions.append([3, 3, 0.0, 10.0, 0.5, 1.0, 0.0, 0.5 * np.pi]) - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) + runner.write_data() - self.success = True + runner.build_executable() + runner.run_code() + runner.check_gauge(save=save, gauge_id=1) + runner.check_gauge(save=save, gauge_id=2) -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 +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/advection_2d_inflow/Makefile b/examples/advection_2d_inflow/Makefile index 6a8de4129..f46063188 100644 --- a/examples/advection_2d_inflow/Makefile +++ b/examples/advection_2d_inflow/Makefile @@ -56,7 +56,8 @@ SOURCES = \ $(CLAW)/riemann/src/rpn2_advection.f90 \ $(CLAW)/riemann/src/rpt2_advection.f90 -# Replace special source if TEST is defined +# When TEST is defined, replace the normal inflow helper sources with +# deterministic test-specific versions used by the regression test. ifdef TEST EXCLUDE_SOURCES = bc2amr.f90 qinit.f90 SOURCES := $(subst bc2amr.f90, test_bc2amr.f, $(SOURCES)) diff --git a/examples/advection_2d_inflow/test_advection_2d_inflow.py b/examples/advection_2d_inflow/test_advection_2d_inflow.py index 5e4409025..635c29012 100644 --- a/examples/advection_2d_inflow/test_advection_2d_inflow.py +++ b/examples/advection_2d_inflow/test_advection_2d_inflow.py @@ -3,86 +3,34 @@ Regression tests for 2D advection on an annulus. """ -import sys -import os -import unittest -import subprocess -import inspect -import shutil +from pathlib import Path +import pytest import clawpack.amrclaw.test as test - -class Advection2DBoundaryTest(test.AMRClawRegressionTest): +def test_advection_2d_inflow(tmp_path: Path, save: bool): r"""Basic test for a 2D advection on an annulus test case""" - - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.probdata.add_param('u', 0.5, 'ubar advection velocity') - self.rundata.probdata.add_param('v', 1.0, 'vbar advection velocity') - - self.rundata.clawdata.output_style = 3 - self.rundata.clawdata.output_step_interval = 10 - self.rundata.clawdata.total_steps = 10 - - self.rundata.clawdata.bc_lower[1] = 'extrap' # opposed to user - - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 0.08, 0.9, 0., 10.]) - self.rundata.gaugedata.gauges.append([2, 0.06, 0.391, 0., 10.]) - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - self.success = True - - - def build_executable(self, executable_name="xamr"): - r"""Build executable by running `make .exe` in test directory. - - Moves the resulting executable to the temporary directory. - - - """ - - try: - self.stdout.write("Test path and class info:\n") - self.stdout.write(" class: %s\n" % str(self.__class__)) - self.stdout.write(" class file: %s\n" % str(inspect.getfile(self.__class__))) - self.stdout.write(" test path: %s\n" % str(self.test_path)) - self.stdout.write(" temp path: %s\n" % str(self.temp_path)) - subprocess.check_call("cd %s ; make TEST=T .exe" % self.test_path, - stdout=self.stdout, - stderr=self.stderr, - shell=True) - except subprocess.CalledProcessError as e: - self.tearDown() - raise e - - self.executable_name = executable_name - shutil.move(os.path.join(self.test_path, self.executable_name), - self.temp_path) - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Advection2DBoundaryTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.probdata.add_param('u', 0.5, 'ubar advection velocity') + runner.rundata.probdata.add_param('v', 1.0, 'vbar advection velocity') + runner.rundata.clawdata.output_style = 3 + runner.rundata.clawdata.output_step_interval = 10 + runner.rundata.clawdata.total_steps = 10 + runner.rundata.clawdata.bc_lower[1] = 'extrap' # opposed to user + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([1, 0.08, 0.9, 0., 10.]) + runner.rundata.gaugedata.gauges.append([2, 0.06, 0.391, 0., 10.]) + runner.write_data() + + runner.build_executable(make_vars={'TEST': True}, verbose=True) + + runner.run_code() + + runner.check_gauge(save=save, gauge_id=1) + runner.check_gauge(save=save, gauge_id=2) + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/advection_2d_square/test_advection_2d_square.py b/examples/advection_2d_square/test_advection_2d_square.py index bcb2e8d87..f2ef4026b 100644 --- a/examples/advection_2d_square/test_advection_2d_square.py +++ b/examples/advection_2d_square/test_advection_2d_square.py @@ -1,83 +1,61 @@ +#!/usr/bin/env python """ Regression tests for 2D advection on a square. """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test import clawpack.amrclaw.data as data - -class Advection2DSquareTest(test.AMRClawRegressionTest): - r"""Basic test for a 2D advection test case""" - - - def runTest(self, save=False): - - # Write out data files - self.load_rundata() - - self.rundata.clawdata.num_output_times = 1 - self.rundata.clawdata.tfinal = 0.4 - - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 0.65, 0.4, 0., 10.]) - self.rundata.gaugedata.gauges.append([2, 0.2, 0.8, 0., 10.]) - - # Test newer flagregions - - # The entire domain restricted to level 1 for illustration: - # Note that this is a rectangle specified in the new way: - # (other regions below will force/allow more refinement) - flagregion = data.FlagRegion(num_dim=2) - flagregion.name = 'Region_domain' - flagregion.minlevel = 1 - flagregion.maxlevel = 1 - flagregion.t1 = 0. - flagregion.t2 = 1e9 - flagregion.spatial_region_type = 1 # Rectangle - flagregion.spatial_region = [0.,1.,0.,1.] # = [x1,x2,y1,y2] - self.rundata.flagregiondata.flagregions.append(flagregion) - - # Another rectangle specified in the new way: - flagregion = data.FlagRegion(num_dim=2) - flagregion.name = 'Region_3levels' - flagregion.minlevel = 1 - flagregion.maxlevel = 3 - flagregion.t1 = 0. - flagregion.t2 = 1e9 - flagregion.spatial_region_type = 1 # Rectangle - flagregion.spatial_region = [0.,1.,0.,0.7] - self.rundata.flagregiondata.flagregions.append(flagregion) - - self.write_rundata_objects() - - # Run code - self.run_code() - - # Perform tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - # self.check_gauges(save=save, gauge_num=1, - # regression_data_path='regression_data_test2.txt') - # self.check_gauges(save=save, gauge_num=2, - # regression_data_path='regression_data_test3.txt') - - self.success = True - - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Advection2DSquareTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +def test_advection_2d_square(tmp_path: Path, save: bool): + """Basic test for a 2D advection test case""" + + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_output_times = 1 + runner.rundata.clawdata.tfinal = 0.4 + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([1, 0.65, 0.4, 0., 10.]) + runner.rundata.gaugedata.gauges.append([2, 0.2, 0.8, 0., 10.]) + + # Test newer flagregions + + # The entire domain restricted to level 1 for illustration: + # Note that this is a rectangle specified in the new way: + # (other regions below will force/allow more refinement) + flagregion = data.FlagRegion(num_dim=2) + flagregion.name = 'Region_domain' + flagregion.minlevel = 1 + flagregion.maxlevel = 1 + flagregion.t1 = 0. + flagregion.t2 = 1e9 + flagregion.spatial_region_type = 1 # Rectangle + flagregion.spatial_region = [0.,1.,0.,1.] # = [x1,x2,y1,y2] + runner.rundata.flagregiondata.flagregions.append(flagregion) + + # Another rectangle specified in the new way: + flagregion = data.FlagRegion(num_dim=2) + flagregion.name = 'Region_3levels' + flagregion.minlevel = 1 + flagregion.maxlevel = 3 + flagregion.t1 = 0. + flagregion.t2 = 1e9 + flagregion.spatial_region_type = 1 # Rectangle + flagregion.spatial_region = [0.,1.,0.,0.7] + runner.rundata.flagregiondata.flagregions.append(flagregion) + + runner.write_data() + + runner.build_executable() + + runner.run_code() + + runner.check_gauge(save=save, gauge_id=1) + runner.check_gauge(save=save, gauge_id=2) + + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/advection_3d_swirl/test_advection_3d_swirl.py b/examples/advection_3d_swirl/test_advection_3d_swirl.py index 45b2120b2..7649dcb79 100644 --- a/examples/advection_3d_swirl/test_advection_3d_swirl.py +++ b/examples/advection_3d_swirl/test_advection_3d_swirl.py @@ -2,55 +2,31 @@ Regression tests for swril 3D advection. """ -from __future__ import absolute_import -import sys -import os -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test +def test_advection_3d_swirl(tmp_path: Path, save: bool): + r"""Basic test for a 3D advection on a swirl test case""" -class Advection3DSwirlTest(test.AMRClawRegressionTest): - r"""Basic test for a 3D advection test case""" + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_data() + runner.rundata.clawdata.num_output_times = 2 + runner.rundata.clawdata.tfinal = .1 + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([1, 0.55, 0.4, 0.4, 0., 1e9]) + runner.rundata.gaugedata.gauges.append([2, 0.45, 0.6, 0.4, 0., 1e9]) + runner.write_data() + runner.build_executable() - def runTest(self, save=False): + runner.run_code() - # Write out data files - self.load_rundata() + runner.check_gauge(gauge_id=1, save=save) + runner.check_gauge(gauge_id=2, save=save) - self.rundata.clawdata.num_output_times = 2 - self.rundata.clawdata.tfinal = .1 - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 0.55, 0.4, 0.4, 0., 1e9]) - self.rundata.gaugedata.gauges.append([2, 0.45, 0.6, 0.4, 0., 1e9]) - - 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.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) - - self.success = True - - - -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Advection3DSwirlTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() \ No newline at end of file +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/compare_gauges.py b/examples/compare_gauges.py deleted file mode 100644 index 8cf78b7dc..000000000 --- a/examples/compare_gauges.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Compare gauges between two different runs, useful for regression testing. - -Requires visclaw@c3bc5aab82 or later for gaugetools.compare_gauges. -""" - -from __future__ import absolute_import -from __future__ import print_function -from clawpack.visclaw import gaugetools - -outdir1 = '_output_original' -outdir2 = '_output' - -tol = 0. # tolerance for comparison - -matches = gaugetools.compare_gauges(outdir1, outdir2, - gaugenos='all', q_components='all', - tol=tol, verbose=True, plot=False) - -if not matches: - print("*** Warning: results to not all match to tol = %g" % tol) - print("*** You might want to call gaugetools.compare_gauges with ") - print(" plot=True to view differences.") - - diff --git a/examples/conftest.py b/examples/conftest.py new file mode 100644 index 000000000..179542851 --- /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/examples/euler_1d_wcblast/test_euler_1d_wcblast.py b/examples/euler_1d_wcblast/test_euler_1d_wcblast.py index c00f8503e..2af147981 100644 --- a/examples/euler_1d_wcblast/test_euler_1d_wcblast.py +++ b/examples/euler_1d_wcblast/test_euler_1d_wcblast.py @@ -1,53 +1,36 @@ +#!/usr/bin/env python """ Regression tests for 1D advection;. """ -import sys -import unittest +from pathlib import Path +import pytest import clawpack.amrclaw.test as test - -class Euler1DTest(test.AMRClawRegressionTest): +def test_euler_1d_wcblast(tmp_path: Path, save: bool): """Basic test for a 1D Euler case""" + runner = test.AMRClawTestRunner(tmp_path, test_path=Path(__file__).parent) + + runner.set_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.015 - - self.rundata.gaugedata.gauges = [] - self.rundata.gaugedata.gauges.append([1, 0.3, 0, 1e9]) - self.rundata.gaugedata.gauges.append([2, 0.85, 0, 1e9]) - - # amrdata.max1d = 500 - - self.write_rundata_objects() - - # Run code - self.run_code() + runner.rundata.clawdata.num_output_times = 2 + runner.rundata.clawdata.tfinal = 0.015 - # Perform tests - self.check_gauges(save=save, gauge_id=1) - self.check_gauges(save=save, gauge_id=2) + runner.rundata.gaugedata.gauges = [] + runner.rundata.gaugedata.gauges.append([1, 0.3, 0, 1e9]) + runner.rundata.gaugedata.gauges.append([2, 0.85, 0, 1e9]) + runner.write_data() - self.success = True + runner.build_executable() + # Run code + runner.run_code() + # Perform tests + runner.check_gauge(save=save, gauge_id=1) + runner.check_gauge(save=save, gauge_id=2) -if __name__=="__main__": - if len(sys.argv) > 1: - if bool(sys.argv[1]): - # Fake the setup and save out output - test = Euler1DTest() - try: - test.setUp() - test.runTest(save=True) - finally: - test.tearDown() - sys.exit(0) - unittest.main() +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__])) diff --git a/examples/fix_Makefile.py b/examples/fix_Makefile.py deleted file mode 100644 index 76050e86b..000000000 --- a/examples/fix_Makefile.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Fix Makefile for changes to support dimensional splitting, based -on amrclaw@260e3598e. - -Modified for 3d -""" - -from __future__ import absolute_import -from __future__ import print_function -import os - -f = open('Makefile').read() -f = f.replace('stepgrid.f \\\n ', 'stepgrid.f \\\n $(AMRLIB)/stepgrid_dimSplit.f \\\n ') -print(f.find('$(AMRLIB)/step3.f \\\n ')) -f = f.replace('$(AMRLIB)/step3.f \\\n ', '$(AMRLIB)/step3.f \\\n $(AMRLIB)/step3x.f \\\n $(AMRLIB)/step3y.f \\\n $(AMRLIB)/step3z.f \\\n ') -f = f.replace('flux3.f \\\n ', 'flux3.f \\\n $(AMRLIB)/flux3_dimSplit.f \\\n ') - -os.system('mv Makefile Makefile_orig') -open('Makefile','w').write(f) -print('Modified Makefile, original in Makefile_orig') - diff --git a/examples/fix_Makefile_gauges.py b/examples/fix_Makefile_gauges.py deleted file mode 100644 index ac132173b..000000000 --- a/examples/fix_Makefile_gauges.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Fix Makefile for changes to gauges_module.f90: remove dumpgauges.f -on amrclaw@77612a61b3 -""" - -from __future__ import absolute_import -from __future__ import print_function -import os - -f = open('Makefile').read() -f = f.replace(' $(AMRLIB)/dumpgauge.f \\\n','') - -os.system('mv Makefile Makefile_orig') -open('Makefile','w').write(f) -print('Modified Makefile, original in Makefile_orig') - diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..9bd5b2b4c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,9 @@ +[pytest] +addopts = -m "not slow and not adjoint" +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 + adjoint: tests that are adjoint problems, may want to skip in some cases + adjoint_forward: direct adjoint tests that also flex adjoint computation diff --git a/src/2d/adjoint_module.f90 b/src/2d/adjoint_module.f90 index 8423e01d2..8db51f57e 100644 --- a/src/2d/adjoint_module.f90 +++ b/src/2d/adjoint_module.f90 @@ -23,7 +23,7 @@ module adjoint_module integer :: totnum_adjoints, & counter, innerprod_index real(kind=8) :: trange_start, trange_final, levtol(maxlv) - character(len=365), allocatable :: adj_files(:) + character(len=512), allocatable :: adj_files(:) logical :: adjoint_flagging real(kind=8), allocatable, dimension(:) :: errors integer, allocatable, dimension(:) :: eptr @@ -40,7 +40,7 @@ subroutine read_adjoint_data() ! Function Arguments character(len=*), parameter :: adjointfile = 'adjoint.data' - character(len=400) :: adjoint_output + character(len=512) :: adjoint_output logical :: fileExists integer :: iunit, k real(kind=8) :: t1,t2 @@ -339,4 +339,4 @@ subroutine select_snapshots(time,mask_selecta) end subroutine select_snapshots -end module adjoint_module \ No newline at end of file +end module adjoint_module diff --git a/src/python/amrclaw/test.py b/src/python/amrclaw/test.py index 37c19075e..3aa8c90b1 100644 --- a/src/python/amrclaw/test.py +++ b/src/python/amrclaw/test.py @@ -1,49 +1,24 @@ r""" -Execute nosetests in all subdirectories, to run a series of quick -regression tests. +Defines the AMRClaw Clawpack Test Runner class for running PyTest based +regression tests in AMRClaw. -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 __future__ import absolute_import +from pathlib import Path import os -import glob +from typing import Optional -import clawpack.clawutil.test -import clawpack.pyclaw.util +import clawpack.clawutil.test as test -# Clean library files whenever this module is used +# Set environment variable to avoid warning about missing CLAW variable 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,"amrclaw","src","2d"), - os.path.join(CLAW,"amrclaw","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) +class AMRClawTestRunner(test.ClawpackTestRunner): - -class AMRClawRegressionTest(clawpack.clawutil.test.ClawpackRegressionTest): - - r"""Base AMRClaw regression test setup derived from ClawpackRegressionTest - - """ - - __doc__ += clawpack.pyclaw.util.add_parent_doc( - clawpack.clawutil.test.ClawpackRegressionTest) - - - def build_executable(self, executable_name="xamr"): - r"""Build executable by running `make .exe` in test directory. - - Moves the resulting executable to the temporary directory. - - - """ - - super(AMRClawRegressionTest, self).build_executable( - executable_name=executable_name) + def __init__(self, path: Path, test_path: Optional[Path]=None): + super(AMRClawTestRunner, self).__init__(path, test_path=test_path) + self.executable_name = 'xamr'