diff --git a/.ci_fedora.sh b/.ci_fedora.sh
index c865e8a0aa..0f21d156fd 100755
--- a/.ci_fedora.sh
+++ b/.ci_fedora.sh
@@ -23,7 +23,7 @@ then
test . != ".$2" && mpi="$2" || mpi=openmpi
time $cmd pull ghcr.io/boutproject/bout-container-base:main
time $cmd create --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
- --shm-size 256M \
+ --shm-size 1G \
--name mobydick ghcr.io/boutproject/bout-container-base:main \
/tmp/BOUT-dev/.ci_fedora.sh $mpi
time $cmd cp ${TRAVIS_BUILD_DIR:-$(pwd)} mobydick:/tmp/BOUT-dev
@@ -37,9 +37,15 @@ test . != ".$1" && mpi="$1" || mpi=openmpi
cp -a /tmp/BOUT-dev /home/test/
. /etc/profile.d/modules.sh
module load mpi/${1}-x86_64
- export OMPI_MCA_rmaps_base_oversubscribe=yes
+ sudo dnf install -y python3-pytest python3-pytest-xdist
+ # OpenMPI Oversubscription Overrides
+ export OMPI_MCA_rmaps_base_oversubscribe=1
+ export OMPI_MCA_hwloc_base_binding_policy=none
+ export OMPI_MCA_rmaps_base_mapping_policy=core:OVERSUBSCRIBE
+ export PRTE_MCA_rmaps_default_mapping_policy=core:OVERSUBSCRIBE
export PRTE_MCA_rmaps_default_mapping_policy=:oversubscribe
export TRAVIS=true
+ export CI=true
# Try limiting openmp threads
export FLEXIBLAS=NETLIB
export MKL_NUM_THREADS=1
diff --git a/.ci_with_cmake.sh b/.ci_with_cmake.sh
index 71ea2204e2..c664d3d66e 100755
--- a/.ci_with_cmake.sh
+++ b/.ci_with_cmake.sh
@@ -13,7 +13,7 @@ fi
cmake --build build --target build-check -j 2
cd build
-ctest --output-on-failure --timeout 300
+cmake --build . --target check
export LD_LIBRARY_PATH=/home/runner/local/lib:$LD_LIBRARY_PATH
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ff11a2a4c9..92171444e0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -133,39 +133,48 @@ jobs:
echo CMake options: ${{ matrix.config.cmake_options }}
- name: Install dependencies
- run: sudo apt update &&
- sudo apt install -y
- libfftw3-dev
- libnetcdf-dev
- libnetcdf-c++4-dev
- netcdf-bin
- python3
- python3-pip
- python3-pytest
- python3-numpy
- python3-scipy
- lcov
- openmpi-bin
- libopenmpi-dev
- petsc-dev
- slepc-dev
- liblapack-dev
- libparpack2-dev
- libhypre-dev
+ run: |
+ sudo apt update
+ sudo apt install -y \
+ libfftw3-dev \
+ libnetcdf-dev \
+ libnetcdf-c++4-dev \
+ netcdf-bin \
+ lcov \
+ openmpi-bin \
+ libopenmpi-dev \
+ petsc-dev \
+ slepc-dev \
+ liblapack-dev \
+ libparpack2-dev \
+ libhypre-dev
- uses: actions/checkout@v6
with:
submodules: true
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+ with:
+ enable-cache: true
+
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- cache: 'pip'
- - name: Install pip packages
+ - name: Setup virtual environment with uv
run: |
- python -m pip install --upgrade pip setuptools
- python -m pip install -r requirements.txt
+ # uv sync automatically creates a .venv and installs the groups
+ uv sync --no-dev --group pytest --inexact
+
+ # Install the core requirements into the same venv
+ uv pip install -r requirements.txt
+
+ # Put the venv in the PATH
+ echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
+
+ # Force CMake to recognize the virtual environment
+ echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
- name: Cache Zenodo test data
uses: actions/cache@v5
@@ -190,7 +199,9 @@ jobs:
run: BUILD_ADIOS2=${{ matrix.config.build_adios2 }} ./.build_adios2_for_ci.sh
- name: Build BOUT++
- run: UNIT_ONLY=${{ matrix.config.unit_only }} ./.ci_with_cmake.sh ${{ matrix.config.cmake_options }}
+ run: |
+ UNIT_ONLY=${{ matrix.config.unit_only }} ./.ci_with_cmake.sh ${{ matrix.config.cmake_options }} \
+ -DPython3_EXECUTABLE=$GITHUB_WORKSPACE/.venv/bin/python
Fedora:
# This is its own job as it doesn't use most of the steps of the
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 67e2337210..d2258cca77 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -741,6 +741,7 @@ option(
OFF
)
if(BOUT_TESTS)
+ set(CMAKE_CTEST_ARGUMENTS "--output-on-failure" "--timeout" "300")
enable_testing()
# Targets for just building the tests
# Tests need to add themselves as dependencies to these targets
@@ -768,7 +769,8 @@ if(BOUT_TESTS)
endif()
add_custom_target(
- check-integrated-tests COMMAND ctest -R "test-" --output-on-failure
+ check-integrated-tests
+ COMMAND bash "${PROJECT_BINARY_DIR}/tests/run_integrated_tests.sh"
)
add_dependencies(check-integrated-tests build-check-integrated-tests)
@@ -782,6 +784,30 @@ if(BOUT_TESTS)
add_dependencies(check check-unit-tests)
endif()
+ # Copy the pytest config file
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/tests/conftest.py"
+ "${PROJECT_BINARY_DIR}/tests/conftest.py"
+ )
+
+ # Copy the pyproject.toml file
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/pyproject.toml"
+ "${PROJECT_BINARY_DIR}/pyproject.toml"
+ )
+
+ # Copy the pytest config file for the boutpp tests
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/tests/integrated/test-boutpp/conftest.py"
+ "${PROJECT_BINARY_DIR}/tests/integrated/test-boutpp/conftest.py"
+ )
+
+ # Copy the pytest shell script
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/tests/run_integrated_tests.sh"
+ "${PROJECT_BINARY_DIR}/tests/run_integrated_tests.sh" @ONLY
+ )
+
endif()
option(BOUT_BUILD_EXAMPLES "Build the examples" ON)
diff --git a/cmake/BOUT++functions.cmake b/cmake/BOUT++functions.cmake
index 9b64f643c6..c5abb1169c 100644
--- a/cmake/BOUT++functions.cmake
+++ b/cmake/BOUT++functions.cmake
@@ -172,7 +172,9 @@ endfunction()
#
function(bout_add_integrated_or_mms_test BUILD_CHECK_TARGET TESTNAME)
set(options USE_RUNTEST USE_DATA_BOUT_INP)
- set(oneValueArgs EXECUTABLE_NAME PROCESSORS DOWNLOAD DOWNLOAD_NAME)
+ set(oneValueArgs EXECUTABLE_NAME PROCESSORS DOWNLOAD DOWNLOAD_NAME
+ PYTHON_TEST_FILE
+ )
set(multiValueArgs SOURCES EXTRA_FILES REQUIRES CONFLICTS TESTARGS
EXTRA_DEPENDS
)
@@ -256,16 +258,29 @@ function(bout_add_integrated_or_mms_test BUILD_CHECK_TARGET TESTNAME)
# Set the actual test command
if(BOUT_TEST_OPTIONS_USE_RUNTEST)
- add_test(NAME ${TESTNAME} COMMAND ./runtest ${BOUT_TEST_OPTIONS_TESTARGS})
+ if(BOUT_TEST_OPTIONS_PYTHON_TEST_FILE)
+ # It's an integrated test with a specific python file
+ add_test(NAME ${TESTNAME}
+ COMMAND pytest ${BOUT_TEST_OPTIONS_PYTHON_TEST_FILE}
+ ${BOUT_TEST_OPTIONS_TESTARGS}
+ )
+ else()
+ # It's an MMS test still using the 'runtest' script
+ add_test(NAME ${TESTNAME} COMMAND ./runtest ${BOUT_TEST_OPTIONS_TESTARGS})
+ endif()
+
set_tests_properties(
${TESTNAME} PROPERTIES ENVIRONMENT
PYTHONPATH=${BOUT_PYTHONPATH}:$ENV{PYTHONPATH}
)
- bout_copy_file(runtest)
else()
add_test(NAME ${TESTNAME} COMMAND ${TESTNAME} ${BOUT_TEST_OPTIONS_TESTARGS})
endif()
+ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/runtest)
+ bout_copy_file(runtest)
+ endif()
+
set_tests_properties(
${TESTNAME} PROPERTIES PROCESSORS ${BOUT_TEST_OPTIONS_PROCESSORS}
PROCESSOR_AFFINITY ON
@@ -286,11 +301,23 @@ endfunction()
# Add a new integrated test. See `bout_add_integrated_or_mms_test` for arguments
function(bout_add_integrated_test TESTNAME)
+ # Construct the Python test filename
+ string(REGEX REPLACE "^(test-)?(.+)$" "test_\\2.py" TEST_FILENAME
+ "${TESTNAME}"
+ )
+ string(REPLACE "-" "_" TEST_FILENAME "${TEST_FILENAME}")
+ string(REPLACE "test_test_" "test_" TEST_FILENAME "${TEST_FILENAME}")
+
bout_add_integrated_or_mms_test(
- build-check-integrated-tests ${TESTNAME} ${ARGV}
+ build-check-integrated-tests ${TESTNAME} PYTHON_TEST_FILE ${TEST_FILENAME}
+ ${ARGV}
)
-endfunction()
+ # Only copy the file if the test wasn't skipped due to missing requirements
+ if(TARGET ${TESTNAME})
+ bout_copy_file(${TEST_FILENAME})
+ endif()
+endfunction()
# Add a new MMS test. See `bout_add_integrated_or_mms_test` for arguments
function(bout_add_mms_test TESTNAME)
bout_add_integrated_or_mms_test(build-check-mms-tests ${TESTNAME} ${ARGV})
diff --git a/externalpackages/googletest b/externalpackages/googletest
index 8736d2cd5c..d72f9c8aea 160000
--- a/externalpackages/googletest
+++ b/externalpackages/googletest
@@ -1 +1 @@
-Subproject commit 8736d2cd5c1dcba41170ed2fddca14021d4916c3
+Subproject commit d72f9c8aea6817cdf1ca0ac10887f328de7f3da2
diff --git a/manual/sphinx/developer_docs/contributing.rst b/manual/sphinx/developer_docs/contributing.rst
index 062415af04..02dda06a0a 100644
--- a/manual/sphinx/developer_docs/contributing.rst
+++ b/manual/sphinx/developer_docs/contributing.rst
@@ -196,6 +196,23 @@ formatters and run them according to the next section. Usually this is just:
which will format just the parts of C++ files that have changed since ``next``.
+The "integrated" tests are run via pytest . To install the required packages using `uv
+`_ (from the top of your BOUT++ directory):
+
+.. code-block:: console
+
+ uv sync --group pytest --inexact
+
+They can be run via cmake in the same way as the other tests:
+
+.. code-block:: console
+
+ cmake --build build --target check-integrated-tests
+
+or directly, by running the script `tests/run_integrated_tests.sh`.
+
+For test isolation, pytest automatically copies each test to a temporary directory when running them.
+
Formatting and Linters
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/pyproject.toml b/pyproject.toml
index 3e75bb39c5..4a76c03fd2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,6 +5,9 @@ requires = []
build-backend = "backend"
backend-path = ["tools/pylib/_boutpp_build/"]
+[tool.pytest.ini_options]
+console_output_style = "plain"
+
[dependency-groups]
dev = [
"cmake~=4.2",
@@ -19,6 +22,10 @@ dev = [
"sphinx-lint~=1.0",
"sync-with-uv~=0.5.0",
]
+pytest = [
+ "pytest~=8.4",
+ "pytest-xdist~=3.8",
+]
maint = [
"pygithub~=2.8",
"ruamel-yaml~=0.19",
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000000..f04f85e562
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,60 @@
+import pytest
+import shutil
+import os
+import subprocess
+import time
+from pathlib import Path
+
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers",
+ "serial: mark that the test should not be run concurrently with other. ",
+ )
+
+
+@pytest.fixture
+def test_dir(request) -> Path:
+ return Path(request.fspath).parent
+
+
+@pytest.fixture(scope="function", autouse=True)
+def copy_and_cwd_to_unique_tmp_dir(request, tmp_path_factory, monkeypatch):
+ """
+ For each test function, create a unique temporary copy of the test directory
+ and change cwd to it.
+ """
+
+ test_file_dir = Path(request.fspath).parent
+
+ if not test_file_dir.is_dir():
+ pytest.fail(f"Expected test directory '{test_file_dir}' not found")
+
+ # Create a unique temp dir for this test
+ run_dir = tmp_path_factory.mktemp(test_file_dir.name)
+
+ # Copy the original test directory into it
+ shutil.copytree(test_file_dir, run_dir, dirs_exist_ok=True)
+
+ # Change working directory to the copy
+ monkeypatch.chdir(run_dir)
+
+
+@pytest.fixture
+def assert_success_in_shell(test_dir):
+
+ def inner_function(command: str):
+ # MPI oversubscribe for communications test
+ os.environ["OMPI_MCA_rmaps_base_oversubscribe"] = "1" # Allows 18 procs
+ start = time.time()
+ result = subprocess.run(
+ command, shell=True, capture_output=True, text=True, timeout=600
+ )
+ elapsed = time.time() - start
+ assert result.returncode == 0, (
+ f"Failed after {elapsed:.3f}s in {test_dir}\n"
+ f"Stderr: {result.stderr}\n"
+ f"Output: {result.stdout}"
+ )
+
+ return inner_function
diff --git a/tests/integrated/test-backtrace/runtest b/tests/integrated/test-backtrace/runtest
deleted file mode 100755
index c4020e6d99..0000000000
--- a/tests/integrated/test-backtrace/runtest
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python3
-
-# Test enabling/disabling exception backtrace from environment variable
-
-# requires all_tests
-
-from boututils.run_wrapper import build_and_log, shell
-import os
-
-build_and_log("backtrace environment variable test")
-
-try:
- del os.environ["BOUT_SHOW_BACKTRACE"]
-except KeyError:
- pass
-
-success = True
-errors = []
-
-_, output = shell("BOUT_SHOW_BACKTRACE=0 ./boutexcept", pipe=True)
-
-if "troublemaker" in output:
- success = False
- print(
- f"Fail: detected offending function name in output when not expected:\n{output}"
- )
-
-_, output = shell("./boutexcept", pipe=True)
-
-if "troublemaker" not in output:
- success = False
- print(
- f"Fail: did not detect offending function name in output when expected:\n{output}"
- )
-
-if success:
- print("=> All BoutException backtrace tests passed")
- exit(0)
-
-print("=> Some failed tests")
-exit(1)
diff --git a/tests/integrated/test-backtrace/test_backtrace.py b/tests/integrated/test-backtrace/test_backtrace.py
new file mode 100755
index 0000000000..4572010c62
--- /dev/null
+++ b/tests/integrated/test-backtrace/test_backtrace.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+# Test enabling/disabling exception backtrace from environment variable
+
+# requires all_tests
+
+from boututils.run_wrapper import shell
+import os
+
+
+def test_backtrace():
+ try:
+ del os.environ["BOUT_SHOW_BACKTRACE"]
+ except KeyError:
+ pass
+
+ _, output = shell(["BOUT_SHOW_BACKTRACE=0 ./boutexcept"], pipe=True)
+
+ assert "troublemaker" not in output, (
+ f"Fail: detected offending function name in output when not expected\n{output}"
+ )
+
+ _, output = shell(["./boutexcept"], pipe=True)
+
+ assert "troublemaker" in output, (
+ f"Fail: did not detect offending function name in output when expected\n{output}"
+ )
diff --git a/tests/integrated/test-beuler/runtest b/tests/integrated/test-beuler/runtest
deleted file mode 100755
index 00fb93708c..0000000000
--- a/tests/integrated/test-beuler/runtest
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: petsc
-
-from boututils.run_wrapper import shell_safe, launch_safe
-
-from sys import exit
-
-nthreads = 1
-nproc = 1
-
-print("Making solver test")
-shell_safe("make > make.log")
-
-print("Running solver test")
-status, out = launch_safe("./test_beuler", nproc=nproc, mthread=nthreads, pipe=True)
-with open("run.log", "w") as f:
- f.write(out)
-
-if status:
- print(out)
-
-exit(status)
diff --git a/tests/integrated/test-beuler/test_beuler.py b/tests/integrated/test-beuler/test_beuler.py
new file mode 100755
index 0000000000..ab028a9c2e
--- /dev/null
+++ b/tests/integrated/test-beuler/test_beuler.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+# requires: petsc
+
+from boututils.run_wrapper import shell_safe
+
+
+def test_beuler():
+ print("Running solver test")
+ status, out = shell_safe("./test_beuler", pipe=True)
+ with open("run.log", "w") as f:
+ f.write(out)
+
+ assert status == 0, out
diff --git a/tests/integrated/test-bout-override-default-option/test_bout_override_default_option.py b/tests/integrated/test-bout-override-default-option/test_bout_override_default_option.py
new file mode 100644
index 0000000000..1095b40a70
--- /dev/null
+++ b/tests/integrated/test-bout-override-default-option/test_bout_override_default_option.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./runtest")
diff --git a/tests/integrated/test-boutpp/collect-staggered/runtest b/tests/integrated/test-boutpp/collect-staggered/runtest
deleted file mode 100755
index 25477c05ee..0000000000
--- a/tests/integrated/test-boutpp/collect-staggered/runtest
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python3
-import boutpp as bc
-
-# requires boutpp
-# requires not make
-
-bc.init("-q -q -q")
-
-fail = 0
-f = bc.create3D("sin(y)", outloc="YLOW")
-
-dump = bc.Options()
-dump["f3d_evolve"].assignRepeat(f)
-dump["f3d_once"] = f
-bc.writeDefaultOutputFile(dump)
-
-
-fc = bc.create3D("sin(y)", outloc="CENTRE")
-
-fe = bc.Field3D.fromCollect("f3d_evolve", path="data", info=False)
-fo = bc.Field3D.fromCollect("f3d_once", path="data", info=False)
-
-if fe.getLocation() == fc.getLocation():
- print("The loaded field should not be at CELL_CENTRE")
- fail = 1
-
-if fo.getLocation() == fc.getLocation():
- print("The loaded field should not be at CELL_CENTRE")
- fail = 1
-
-
-def compare(a, b):
- diff = a - bc.interp_to(b, a.getLocation())
- err = bc.max(bc.sqrt(diff * diff))
- return err
-
-
-if compare(fc, f) > 1e-3:
- print("Something is wrong. Maybe interpolation is broken")
- fail = 1
-
-if compare(fc, fe) > 1e-3:
- print("Something is wrong. Maybe setting the location from field failed.")
- fail = 1
-
-if compare(fc, fo) > 1e-3:
- print("Something is wrong. Maybe setting the location from field failed.")
- fail = 1
-
-exit(fail)
diff --git a/tests/integrated/test-boutpp/collect-staggered/test_boutpp_collect_staggered.py b/tests/integrated/test-boutpp/collect-staggered/test_boutpp_collect_staggered.py
new file mode 100755
index 0000000000..d88595e434
--- /dev/null
+++ b/tests/integrated/test-boutpp/collect-staggered/test_boutpp_collect_staggered.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+import boutpp as bc
+
+# requires boutpp
+# requires not make
+
+
+def test_boutpp_collect_staggered(run_isolated):
+
+ if run_isolated():
+ return
+
+ bc.init("-q -q -q")
+
+ fail = 0
+ f = bc.create3D("sin(y)", outloc="YLOW")
+
+ dump = bc.Options()
+ dump["f3d_evolve"].assignRepeat(f)
+ dump["f3d_once"] = f
+ bc.writeDefaultOutputFile(dump)
+
+ fc = bc.create3D("sin(y)", outloc="CENTRE")
+
+ fe = bc.Field3D.fromCollect("f3d_evolve", path="data", info=False)
+ fo = bc.Field3D.fromCollect("f3d_once", path="data", info=False)
+
+ if fe.getLocation() == fc.getLocation():
+ print("The loaded field should not be at CELL_CENTRE")
+ fail = 1
+
+ if fo.getLocation() == fc.getLocation():
+ print("The loaded field should not be at CELL_CENTRE")
+ fail = 1
+
+ def compare(a, b):
+ diff = a - bc.interp_to(b, a.getLocation())
+ err = bc.max(bc.sqrt(diff * diff))
+ return err
+
+ if compare(fc, f) > 1e-3:
+ print("Something is wrong. Maybe interpolation is broken")
+ fail = 1
+
+ if compare(fc, fe) > 1e-3:
+ print("Something is wrong. Maybe setting the location from field failed.")
+ fail = 1
+
+ if compare(fc, fo) > 1e-3:
+ print("Something is wrong. Maybe setting the location from field failed.")
+ fail = 1
+
+ assert fail == 0
diff --git a/tests/integrated/test-boutpp/collect/runtest b/tests/integrated/test-boutpp/collect/runtest
deleted file mode 100755
index 31d228d518..0000000000
--- a/tests/integrated/test-boutpp/collect/runtest
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-import boutpp as bc
-import numpy as np
-from boutdata import collect
-
-# requires boutpp
-# requires not make
-
-bc.init("-d input".split(" "))
-
-f = bc.Field3D.fromMesh(None)
-f.setAll(np.array([[[1.0]]]))
-f2 = bc.Field3D.fromMesh(None)
-f2.setAll(np.array([[[2.0]]]))
-print(f.getAll())
-
-dump = bc.Options(f3d=f, f2d=f2)
-bc.writeDefaultOutputFile(dump)
-
-errorlist = ""
-n = collect("f2d", path="input", tind=-1, yguards=True)
-print(n.shape)
-T = collect("f3d", path="input", tind=-1, yguards=True)
-print(n.shape)
-
-print("initialized mesh")
-mesh = bc.Mesh.getGlobal()
-print("initialized mesh")
-
-dens = bc.Field3D.fromMesh(mesh)
-dens[:, :, :] = n.reshape((1, 1, 1))
-temp = bc.Field3D.fromCollect("f3d", path="input")
-pres = dens + temp
-print(slice(None, None, -2).indices(5))
-p = pres[::5, 0, ::-2]
-pn = n + T
-pn = pn.reshape(p.shape)
-if not ((p == pn).all()):
- errorlist += "addition not working\n"
-pres = dens * temp * 1
-p = pres.getAll()
-pn = n * T
-pn = pn.reshape(p.shape)
-if not ((p == pn).all()):
- errorlist += "multiplication not working\n"
-print(p.shape)
-print(pn.shape)
diff --git a/tests/integrated/test-boutpp/collect/test_boutpp_collect.py b/tests/integrated/test-boutpp/collect/test_boutpp_collect.py
new file mode 100755
index 0000000000..89ad9d6b5b
--- /dev/null
+++ b/tests/integrated/test-boutpp/collect/test_boutpp_collect.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+import boutpp as bc
+import numpy as np
+from boutdata import collect
+
+# requires boutpp
+# requires not make
+
+
+def test_boutpp_collect(run_isolated):
+
+ if run_isolated():
+ return
+
+ bc.init("-d input".split(" "))
+
+ f = bc.Field3D.fromMesh(None)
+ f.setAll(np.array([[[1.0]]]))
+ f2 = bc.Field3D.fromMesh(None)
+ f2.setAll(np.array([[[2.0]]]))
+ print(f.getAll())
+
+ dump = bc.Options(f3d=f, f2d=f2)
+ bc.writeDefaultOutputFile(dump)
+
+ errorlist = ""
+ n = collect("f2d", path="input", tind=-1, yguards=True)
+ print(n.shape)
+ T = collect("f3d", path="input", tind=-1, yguards=True)
+ print(n.shape)
+
+ print("initialized mesh")
+ mesh = bc.Mesh.getGlobal()
+ print("initialized mesh")
+
+ dens = bc.Field3D.fromMesh(mesh)
+ dens[:, :, :] = n.reshape((1, 1, 1))
+ temp = bc.Field3D.fromCollect("f3d", path="input")
+ pres = dens + temp
+ print(slice(None, None, -2).indices(5))
+ p = pres[::5, 0, ::-2]
+ pn = n + T
+ pn = pn.reshape(p.shape)
+ if not ((p == pn).all()):
+ errorlist += "addition not working\n"
+ pres = dens * temp * 1
+ p = pres.getAll()
+ pn = n * T
+ pn = pn.reshape(p.shape)
+ if not ((p == pn).all()):
+ errorlist += "multiplication not working\n"
+ print(p.shape)
+ print(pn.shape)
diff --git a/tests/integrated/test-boutpp/conftest.py b/tests/integrated/test-boutpp/conftest.py
new file mode 100644
index 0000000000..09631a1fe6
--- /dev/null
+++ b/tests/integrated/test-boutpp/conftest.py
@@ -0,0 +1,98 @@
+import pytest
+import os
+import sys
+import subprocess
+
+
+@pytest.fixture(autouse=True, scope="function")
+def unique_xdist_group(request):
+ # Unique group per test function (nodeid is unique, e.g., test_file.py::test_func)
+ group_name = (
+ f"boutpp_isolated_{request.node.nodeid.replace('/', '_').replace('::', '_')}"
+ )
+ request.node.add_marker(pytest.mark.xdist_group(name=group_name))
+
+
+@pytest.fixture(scope="function")
+def run_isolated(request):
+ """
+ Spawns a fresh, isolated process for tests.
+ This prevents C++ singleton/MPI conflicts in xdist workers.
+ Returns a function that evaluates to True in the parent (to abort test execution)
+ and False in the child (to run the actual test).
+ """
+ if os.environ.get("BOUT_ISOLATED_RUN") == "1":
+ return lambda: False
+
+ # In the parent process, set up and run the child
+ nodeid = request.node.nodeid
+
+ # Use the absolute path to the test file to avoid directory resolution issues
+ # fspath is the local path to the test file
+ root_dir = str(request.config.rootpath)
+
+ env = os.environ.copy()
+ env["BOUT_ISOLATED_RUN"] = "1"
+
+ # Disable xdist (-p no:xdist) in the subprocess to ensure a clean single-process run
+ # Use -o to disable the cache entirely in the subprocess
+ # Remove -c /dev/null so it stays in the project context
+ cmd = [
+ sys.executable,
+ "-m",
+ "pytest",
+ nodeid,
+ "--rootdir",
+ root_dir,
+ "-p",
+ "no:xdist",
+ "-o",
+ "cache_dir=/tmp/pytest-cache", # Redirect cache to a writable place
+ "-c",
+ str(request.config.inifile or root_dir), # Point to actual config if it exists
+ ]
+
+ result = subprocess.run(cmd, env=env, cwd=root_dir, capture_output=True, text=True)
+
+ if result.returncode != 0:
+ pytest.fail(
+ f"Isolated test failed with exit code {result.returncode}\n"
+ f"--- STDERR ---\n{result.stderr}\n"
+ f"--- STDOUT ---\n{result.stdout}",
+ pytrace=False,
+ )
+
+ # Return True so the parent can exit the test cleanly without skipping.
+ return lambda: True
+
+
+@pytest.fixture(autouse=True)
+def sanitize_openmpi_env():
+ """
+ OpenMPI leaks state via environment variables (PMIX_*, OPAL_*).
+ This fixture removes them so subprocesses called via `mpirun`
+ don't crash thinking they are already initialized.
+ """
+ # Find all problematic OpenMPI state variables
+ bad_prefixes = ("PMIX_", "OPAL_")
+ bad_exact = (
+ "OMPI_COMM_WORLD_SIZE",
+ "OMPI_COMM_WORLD_RANK",
+ "OMPI_COMM_WORLD_LOCAL_RANK",
+ )
+
+ mpi_vars_to_remove = [
+ k for k in os.environ if k.startswith(bad_prefixes) or k in bad_exact
+ ]
+
+ # Save original variables
+ saved_env = {k: os.environ[k] for k in mpi_vars_to_remove}
+
+ # Delete them from the current environment
+ for k in mpi_vars_to_remove:
+ del os.environ[k]
+
+ yield # Run the test
+
+ # Restore the environment after the test finishes
+ os.environ.update(saved_env)
diff --git a/tests/integrated/test-boutpp/legacy-model/runtest b/tests/integrated/test-boutpp/legacy-model/runtest
deleted file mode 100755
index ea5d06a06e..0000000000
--- a/tests/integrated/test-boutpp/legacy-model/runtest
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python3
-import boutpp
-import sys
-
-# requires boutpp
-# requires not make
-
-boutpp.init(sys.argv[1:])
-
-dens = boutpp.create3D("0")
-
-
-def rhs(time):
- n_ddt = dens.ddt()
- n_ddt[:, :, :] = dens * 0
- n_ddt += 1
-
-
-model = boutpp.PhysicsModelBase()
-model.setRhs(rhs)
-model.solve_for(n=dens)
-model.solve()
diff --git a/tests/integrated/test-boutpp/legacy-model/test_boutpp_legacy_model.py b/tests/integrated/test-boutpp/legacy-model/test_boutpp_legacy_model.py
new file mode 100755
index 0000000000..d1f6ea736a
--- /dev/null
+++ b/tests/integrated/test-boutpp/legacy-model/test_boutpp_legacy_model.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+import boutpp
+
+# requires boutpp
+# requires not make
+
+
+def test_boutpp_legacy_model(run_isolated):
+
+ if run_isolated():
+ return
+
+ boutpp.init([])
+
+ dens = boutpp.create3D("0")
+
+ def rhs(time):
+ n_ddt = dens.ddt()
+ n_ddt[:, :, :] = dens * 0
+ n_ddt += 1
+
+ model = boutpp.PhysicsModelBase()
+ model.setRhs(rhs)
+ model.solve_for(n=dens)
+ model.solve()
diff --git a/tests/integrated/test-boutpp/mms-ddz/runtest b/tests/integrated/test-boutpp/mms-ddz/runtest
deleted file mode 100755
index 02e87ae63a..0000000000
--- a/tests/integrated/test-boutpp/mms-ddz/runtest
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python3
-import boutpp
-import numpy as np
-
-# requires boutpp
-# requires not make
-
-errorlist = ""
-boutpp.init("-d data -q -q -q") # +" -f BOUT.settings")
-
-shapes = []
-errors = []
-mmax = 7
-start = 6
-doPlot = False
-nzs = np.logspace(start, mmax, num=mmax - start + 1, base=2)
-for nz in nzs:
- boutpp.setOption("meshz:nz", "%d" % nz, force=True)
- boutpp.setOption("meshz:dz", "2*pi/%d" % nz, force=True)
- mesh = boutpp.Mesh(section="meshz")
- f = boutpp.create3D("sin(z)", mesh)
- sim = boutpp.DDZ(f)
- ana = boutpp.create3D("cos(z)", mesh)
- err = sim - ana
- err = np.max(np.abs(err.getAll()))
- errors.append(err)
-
-if doPlot:
- from matplotlib import pyplot as plt
-
- plt.plot(1 / np.array(nzs), errors, "-o")
- plt.show()
-
-errc = np.log(errors[-2] / errors[-1])
-difc = np.log(nzs[-1] / nzs[-2])
-conv = errc / difc
-
-if 1.9 < conv < 2.1:
- print("The convergence is: %f" % conv)
-else:
- errorlist += "DDZ is not working\n"
-
-if errorlist != "":
- print("Found errors:\n%s" % errorlist)
- exit(1)
diff --git a/tests/integrated/test-boutpp/mms-ddz/test_boutpp_mms_ddz.py b/tests/integrated/test-boutpp/mms-ddz/test_boutpp_mms_ddz.py
new file mode 100755
index 0000000000..1e2421e59b
--- /dev/null
+++ b/tests/integrated/test-boutpp/mms-ddz/test_boutpp_mms_ddz.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+import boutpp
+import numpy as np
+
+# requires boutpp
+# requires not make
+
+
+def test_boutpp_mms_ddz(run_isolated):
+
+ if run_isolated():
+ return
+
+ errorlist = ""
+ boutpp.init("-d data -q -q -q") # +" -f BOUT.settings")
+
+ errors = []
+ mmax = 7
+ start = 6
+ doPlot = False
+ nzs = np.logspace(start, mmax, num=mmax - start + 1, base=2)
+ for nz in nzs:
+ boutpp.setOption("meshz:nz", "%d" % nz, force=True)
+ boutpp.setOption("meshz:dz", "2*pi/%d" % nz, force=True)
+ mesh = boutpp.Mesh(section="meshz")
+ f = boutpp.create3D("sin(z)", mesh)
+ sim = boutpp.DDZ(f)
+ ana = boutpp.create3D("cos(z)", mesh)
+ err = sim - ana
+ err = np.max(np.abs(err.getAll()))
+ errors.append(err)
+
+ if doPlot:
+ from matplotlib import pyplot as plt
+
+ plt.plot(1 / np.array(nzs), errors, "-o")
+ plt.show()
+
+ errc = np.log(errors[-2] / errors[-1])
+ difc = np.log(nzs[-1] / nzs[-2])
+ conv = errc / difc
+
+ if 1.9 < conv < 2.1:
+ print("The convergence is: %f" % conv)
+ else:
+ errorlist += "DDZ is not working\n"
+
+ if errorlist != "":
+ print("Found errors:\n%s" % errorlist)
+ exit(1)
diff --git a/tests/integrated/test-boutpp/print/CMakeLists.txt b/tests/integrated/test-boutpp/print/CMakeLists.txt
index 84b005df89..edc17cd407 100644
--- a/tests/integrated/test-boutpp/print/CMakeLists.txt
+++ b/tests/integrated/test-boutpp/print/CMakeLists.txt
@@ -1,6 +1,5 @@
bout_add_integrated_test(
test-boutpp-print
- USE_RUNTEST
EXTRA_FILES test/BOUT.inp test.py
REQUIRES BOUT_ENABLE_PYTHON
)
diff --git a/tests/integrated/test-boutpp/print/runtest b/tests/integrated/test-boutpp/print/runtest
deleted file mode 100755
index 4b68c692aa..0000000000
--- a/tests/integrated/test-boutpp/print/runtest
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-# requires boutpp
-# requires not make
-
-set -ex
-
-testit() {
- msg="$@"
- python3 test.py $msg > out.log
- grep "$msg" out.log
- grep "$msg" test/BOUT.log.0
-}
-
-testit Can we print to the log from python? 🎉 '__does_it_still__work {} {:s}'
diff --git a/tests/integrated/test-boutpp/print/test_boutpp_print.py b/tests/integrated/test-boutpp/print/test_boutpp_print.py
new file mode 100644
index 0000000000..26934a0080
--- /dev/null
+++ b/tests/integrated/test-boutpp/print/test_boutpp_print.py
@@ -0,0 +1,20 @@
+import subprocess
+
+
+def test_boutpp_print(run_isolated):
+
+ if run_isolated():
+ return
+
+ msg = "Can we print to the log from python? 🎉 __does_it_still__work {} {:s}"
+ cmd = ["python3", "test.py", msg]
+
+ print(f"+ python3 test.py {msg} > out.log")
+ with open("out.log", "w") as f:
+ subprocess.run(cmd, stdout=f, check=True)
+
+ with open("out.log", "r") as f:
+ assert msg in f.read(), f"Error: '{msg}' not found in out.log"
+
+ with open("test/BOUT.log.0", "r") as f:
+ assert msg in f.read(), f"Error: '{msg}' not found in test/BOUT.log.0"
diff --git a/tests/integrated/test-boutpp/simple-model/runtest b/tests/integrated/test-boutpp/simple-model/runtest
deleted file mode 100755
index d1a6b573b7..0000000000
--- a/tests/integrated/test-boutpp/simple-model/runtest
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python3
-
-# requires boutpp
-# requires not make
-
-from boutpp import *
-
-init("-d mini")
-
-
-class MyModel(PhysicsModel):
- def init(self, restart):
- self.n = create3D("dens:function")
- self.solve_for(dens=self.n)
-
- def rhs(self, time):
- self.n.ddt(DDX(self.n))
-
-
-model = MyModel()
-model.solve()
diff --git a/tests/integrated/test-boutpp/simple-model/test_boutpp_simple_model.py b/tests/integrated/test-boutpp/simple-model/test_boutpp_simple_model.py
new file mode 100755
index 0000000000..a443f22cd8
--- /dev/null
+++ b/tests/integrated/test-boutpp/simple-model/test_boutpp_simple_model.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+
+# requires boutpp
+# requires not make
+
+import boutpp
+
+
+def test_boutpp_simple_model(run_isolated):
+
+ if run_isolated():
+ return
+
+ boutpp.init("-d mini")
+
+ class MyModel(boutpp.PhysicsModel):
+ def init(self, restart):
+ self.n = boutpp.create3D("dens:function")
+ self.solve_for(dens=self.n)
+
+ def rhs(self, time):
+ self.n.ddt(boutpp.DDX(self.n))
+
+ model = MyModel()
+ model.solve()
diff --git a/tests/integrated/test-boutpp/slicing/runtest b/tests/integrated/test-boutpp/slicing/runtest
deleted file mode 100755
index 781e3593a4..0000000000
--- a/tests/integrated/test-boutpp/slicing/runtest
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# requires boutpp
-# requires not make
-
-import numpy as np
-import boutpp as bc
-import inspect
-
-bc.init("-d test")
-mesh = bc.Mesh.getGlobal()
-field = bc.Field3D.fromMesh(mesh)
-ndat = np.random.random(field.shape)
-field[:] = ndat
-
-assert np.all(ndat == field[:])
-
-examples = [
- lambda x: x[2],
- lambda x: x[-2],
- lambda x: x[1, 3],
- lambda x: x[1, -1],
- lambda x: x[0],
- lambda x: x[0][2],
- lambda x: x[1:7:2],
- lambda x: x[-2:10],
- lambda x: x[-3:3:-1],
- lambda x: x[5:],
- lambda x: x[1:2],
- lambda x: x[..., 0],
- lambda x: x[:, :, 0],
- lambda x: x[np.array([3, 3, 1, 8])],
- lambda x: x[np.array([3, 3, -3, 8])],
- lambda x: x[np.array([1, -1])],
- lambda x: x[np.array([3, 4])],
- lambda x: x[[0, 1, 2], [0, 1, 0]],
- lambda x: x[1:2, 1:3],
- lambda x: x[1:2, [1, 2]],
- lambda x: x[ndat < 0],
-]
-
-print(field.shape)
-for ex in examples:
- print("testing", inspect.getsource(ex))
- try:
- nout = ex(ndat)
- fout = ex(field)
- assert (
- fout.shape == nout.shape
- ), f"Field3D returned {{ fout.shape }} but numpy {{ nout.shape }}"
- assert np.all(fout == nout), f"data mismatch, {{ fout == nout }}"
- except:
- print("Failed to test", inspect.getsource(ex))
- raise
diff --git a/tests/integrated/test-boutpp/slicing/test_slicing.py b/tests/integrated/test-boutpp/slicing/test_slicing.py
new file mode 100755
index 0000000000..cffc767bf8
--- /dev/null
+++ b/tests/integrated/test-boutpp/slicing/test_slicing.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# requires boutpp
+# requires not make
+import numpy as np
+import numpy.testing as npt
+import boutpp as bc
+import inspect
+
+
+def test_slicing():
+
+ bc.init("-d test")
+
+ mesh = bc.Mesh.getGlobal()
+ field = bc.Field3D.fromMesh(mesh)
+ ndat = np.random.random(field.shape)
+ field[:] = ndat
+
+ npt.assert_allclose(ndat, field[:])
+
+ examples = [
+ lambda x: x[2],
+ lambda x: x[-2],
+ lambda x: x[1, 3],
+ lambda x: x[1, -1],
+ lambda x: x[0],
+ lambda x: x[0][2],
+ lambda x: x[1:7:2],
+ lambda x: x[-2:10],
+ lambda x: x[-3:3:-1],
+ lambda x: x[5:],
+ lambda x: x[1:2],
+ lambda x: x[..., 0],
+ lambda x: x[:, :, 0],
+ lambda x: x[np.array([3, 3, 1, 8])],
+ lambda x: x[np.array([3, 3, -3, 8])],
+ lambda x: x[np.array([1, -1])],
+ lambda x: x[np.array([3, 4])],
+ lambda x: x[[0, 1, 2], [0, 1, 0]],
+ lambda x: x[1:2, 1:3],
+ lambda x: x[1:2, [1, 2]],
+ lambda x: x[ndat < 0],
+ ]
+
+ print(field.shape)
+ for ex in examples:
+ print("testing", inspect.getsource(ex))
+ nout = ex(ndat)
+ fout = ex(field)
+ npt.assert_allclose(
+ fout,
+ nout,
+ err_msg=f"data mismatch, {{ fout == nout }}. Failed to test {inspect.getsource(ex)}",
+ )
diff --git a/tests/integrated/test-code-style/README.md b/tests/integrated/test-code-style/README.md
deleted file mode 100644
index 602c41d8d5..0000000000
--- a/tests/integrated/test-code-style/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Coding Style Test
-=================
-
-Test for a few parts of the coding style.
-For example options should be passed as non-const value for small datatypes (`int`,`char`,`BoutReal`,...)
-
-```C++
-void fu(int a);
-```
\ No newline at end of file
diff --git a/tests/integrated/test-code-style/runtest b/tests/integrated/test-code-style/runtest
deleted file mode 100755
index d392f9110f..0000000000
--- a/tests/integrated/test-code-style/runtest
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-
-#requires not make
-
-BOUT_TOP=../../..
-error=
-
-paths="src include examples tests"
-
-
-# Check that exceptions are caught by reference
-for path in $paths
-do
- catch_errors=$(grep catch[^\(]*\([^\(]*Bout $BOUT_TOP/$path --include=*xx -r|grep -v '&')
- ex=$?
- if test $ex -eq 0
- then
- error=yes
- echo "Found catch by value - not by reference:"
- echo "$catch_errors"
- fi
-done
-
-
-# Return
-if test $error
-then
- echo "-> Errors listed above"
- exit 1
-else
- echo "No Errors detected"
- exit 0
-fi
diff --git a/tests/integrated/test-collect/runtest b/tests/integrated/test-collect/runtest
deleted file mode 100755
index a1ebb8e3c7..0000000000
--- a/tests/integrated/test-collect/runtest
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: all_tests
-
-from boututils.run_wrapper import shell, shell_safe
-
-from boutdata import collect
-import numpy as np
-
-shell_safe("make > make.log")
-
-shell_safe("./test-collect")
-
-# Try collecting data using incorrect case
-# This should be corrected automatically
-a = collect("A", path="data")
-
-if not np.allclose(a, 1.23):
- print("Wrong value => Failed")
- print(a)
- exit(1)
-
-print("Passed")
-exit(0)
diff --git a/tests/integrated/test-collect/test_collect.py b/tests/integrated/test-collect/test_collect.py
new file mode 100755
index 0000000000..eade57557a
--- /dev/null
+++ b/tests/integrated/test-collect/test_collect.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+# requires: all_tests
+
+from boututils.run_wrapper import shell_safe
+
+from boutdata import collect
+import numpy.testing as npt
+
+
+def test_collect():
+
+ shell_safe("./test-collect")
+
+ # Try collecting data using incorrect case
+ # This should be corrected automatically
+ a = collect("A", path="data")
+
+ npt.assert_allclose(a, 1.23, err_msg=f"Wrong value => Failed: {a}")
diff --git a/tests/integrated/test-command-args/runtest b/tests/integrated/test-command-args/test_command_args.py
similarity index 89%
rename from tests/integrated/test-command-args/runtest
rename to tests/integrated/test-command-args/test_command_args.py
index a8dc3f020c..c8f786ef4f 100755
--- a/tests/integrated/test-command-args/runtest
+++ b/tests/integrated/test-command-args/test_command_args.py
@@ -1,25 +1,26 @@
#!/usr/bin/env python3
-from boututils.run_wrapper import build_and_log, launch_safe
-import os
+from boututils.run_wrapper import launch_safe
+from pathlib import Path
import re
import shutil
import unittest
import platform
-OUTPUT_FILE = "stdout.log" if platform.system() == "FreeBSD" else "stderr.log"
+
+OUTPUT_FILE = Path("stdout.log") if platform.system() == "FreeBSD" else "stderr.log"
class TestCommandLineArgs(unittest.TestCase):
command = "./command-args >stdout.log 2>stderr.log"
def makeDirAndCopyInput(self, path):
- os.mkdir(path)
+ Path.mkdir(path)
shutil.copy("BOUT.inp", path)
def setUp(self):
try:
- os.remove(OUTPUT_FILE)
+ Path.unlink(OUTPUT_FILE)
except OSError:
pass
shutil.rmtree("./data", ignore_errors=True)
@@ -52,11 +53,11 @@ def testNoArgumentsDefaultDirectory(self):
self.makeDirAndCopyInput("data")
launch_safe(self.command, pipe=True, nproc=1, mthread=1)
self.assertTrue(
- os.path.exists("data/BOUT.settings"),
+ Path("data/BOUT.settings").exists(),
msg="FAIL: No BOUT.settings file in data directory",
)
self.assertTrue(
- os.path.exists("data/BOUT.log.0"),
+ Path("data/BOUT.log.0").exists(),
msg="FAIL: No BOUT.log.0 file in data directory",
)
@@ -64,11 +65,11 @@ def testShortLogArgument(self):
self.makeDirAndCopyInput("data")
launch_safe(self.command + " -l different.log", pipe=True, nproc=1, mthread=1)
self.assertFalse(
- os.path.exists("data/BOUT.log.0"),
+ Path("data/BOUT.log.0").exists(),
msg="FAIL: BOUT.log.0 file in data directory",
)
self.assertTrue(
- os.path.exists("data/different.log.0"),
+ Path("data/different.log.0").exists(),
msg="FAIL: no different.log.0 file in data directory",
)
@@ -76,22 +77,22 @@ def testLongLogArgument(self):
self.makeDirAndCopyInput("data")
launch_safe(self.command + " --log log", pipe=True, nproc=1, mthread=1)
self.assertFalse(
- os.path.exists("data/BOUT.log.0"),
+ Path("data/BOUT.log.0").exists(),
msg="FAIL: BOUT.log.0 file in data directory",
)
self.assertTrue(
- os.path.exists("data/log.0"), msg="FAIL: no log.0 file in data directory"
+ Path("data/log.0").exists(), msg="FAIL: no log.0 file in data directory"
)
def testDirectoryArgument(self):
self.makeDirAndCopyInput("test")
launch_safe(self.command + " -d test", pipe=True, nproc=1, mthread=1)
self.assertTrue(
- os.path.exists("test/BOUT.settings"),
+ Path("test/BOUT.settings").exists(),
msg="FAIL: No BOUT.settings file in test directory",
)
self.assertTrue(
- os.path.exists("test/BOUT.log.0"),
+ Path("test/BOUT.log.0").exists(),
msg="FAIL: No BOUT.log.0 file in data directory",
)
@@ -114,7 +115,7 @@ def testDirectoryArgumentNonDirectory(self):
with open(OUTPUT_FILE) as f:
contents = f.read()
self.assertIn(
- '"runtest" is not a directory',
+ 'DataDir "runtest" does not exist or is not accessible',
contents,
msg="FAIL: Error message not printed when missing input directory",
)
@@ -199,5 +200,4 @@ def testCommandLineOptionsArePrinted(self):
if __name__ == "__main__":
- build_and_log("Command arguments test")
unittest.main(verbosity=2)
diff --git a/tests/integrated/test-communications/runtest b/tests/integrated/test-communications/runtest
deleted file mode 100755
index 7c89f0630a..0000000000
--- a/tests/integrated/test-communications/runtest
+++ /dev/null
@@ -1,429 +0,0 @@
-#!/usr/bin/env python3
-
-import numpy as np
-from sys import exit
-
-from boututils.datafile import DataFile
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-
-# Cores: 18
-# Requires: netcdf
-
-# Note, the expected values for the communicated cells, which are used to test the
-# results, are shown in corner_communication_diagram.ods
-
-#################################################
-# Test disconnected-double-null diverted topology
-#################################################
-
-nype = 6 # need at least 6 to include all regions
-
-# run with processor splitting at separatrices
-##################################################
-
-# With nxpe=3, both separatrices are on processor boundaries
-nxpe = 3
-command = "./test-communications NXPE=" + str(nxpe)
-
-build_and_log("Communications Test")
-
-# remove old outputs
-shell("rm data/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run.log." + str(nxpe), "w") as f:
- f.write(out)
-
-
-def test(actual, expected, procnum, region, boundary):
- if not np.all(np.array(np.rint(actual).astype(int)) == np.array(expected)):
- print(
- "failed in {} boundary of region {} ({}). Expected: {} Actual: {}".format(
- boundary, procnum, region, expected, actual
- )
- )
- exit(1)
-
-
-region = "lower, inner PF"
-f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [72, 73], 0, region, "inner x")
-test(f[-1, 1:-1], [24, 25], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99], 0, region, "lower y")
-test(f[:, -1], [82, 10, 22, 26], 0, region, "upper y")
-
-region = "lower, inner between separatrices"
-f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [12, 13], 0, region, "inner x")
-test(f[-1, 1:-1], [48, 49], 0, region, "outer x")
-test(f[:, 0], [98, 99, 100, 101], 0, region, "lower y")
-test(f[:, -1], [22, 26, 38, 50], 0, region, "upper y")
-
-region = "lower, inner SOL"
-f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [36, 37], 0, region, "inner x")
-test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
-test(f[:, 0], [100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [38, 50, 62, 86], 0, region, "upper y")
-
-region = "inner core"
-f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [74, 75], 0, region, "inner x")
-test(f[-1, 1:-1], [26, 27], 0, region, "outer x")
-test(f[:, 0], [81, 9, 21, 25], 0, region, "lower y")
-test(f[:, -1], [80, 8, 20, 32], 0, region, "upper y")
-
-region = "inner between separatrices"
-f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [14, 15], 0, region, "inner x")
-test(f[-1, 1:-1], [50, 51], 0, region, "outer x")
-test(f[:, 0], [21, 25, 37, 49], 0, region, "lower y")
-test(f[:, -1], [20, 32, 44, 52], 0, region, "upper y")
-
-region = "inner SOL"
-f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [38, 39], 0, region, "inner x")
-test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
-test(f[:, 0], [37, 49, 61, 85], 0, region, "lower y")
-test(f[:, -1], [44, 52, 64, 88], 0, region, "upper y")
-
-region = "upper inner PF"
-f = DataFile("data/BOUT.dmp.6.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [76, 77], 0, region, "inner x")
-test(f[-1, 1:-1], [28, 29], 0, region, "outer x")
-test(f[:, 0], [79, 7, 19, 31], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107], 0, region, "upper y")
-
-region = "upper inner SOL, nearer PF"
-f = DataFile("data/BOUT.dmp.7.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [16, 17], 0, region, "inner x")
-test(f[-1, 1:-1], [52, 53], 0, region, "outer x")
-test(f[:, 0], [19, 31, 43, 51], 0, region, "lower y")
-test(f[:, -1], [106, 107, 108, 109], 0, region, "upper y")
-
-region = "upper inner SOL, further from PF"
-f = DataFile("data/BOUT.dmp.8.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [40, 41], 0, region, "inner x")
-test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
-test(f[:, 0], [43, 51, 63, 87], 0, region, "lower y")
-test(f[:, -1], [108, 109, 110, 111], 0, region, "upper y")
-
-region = "upper outer PF"
-f = DataFile("data/BOUT.dmp.9.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [78, 79], 0, region, "inner x")
-test(f[-1, 1:-1], [30, 31], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99], 0, region, "lower y")
-test(f[:, -1], [76, 4, 16, 28], 0, region, "upper y")
-
-region = "upper outer SOL, nearer PF"
-f = DataFile("data/BOUT.dmp.10.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [18, 19], 0, region, "inner x")
-test(f[-1, 1:-1], [54, 55], 0, region, "outer x")
-test(f[:, 0], [98, 99, 100, 101], 0, region, "lower y")
-test(f[:, -1], [16, 28, 40, 56], 0, region, "upper y")
-
-region = "upper outer SOL, further from PF"
-f = DataFile("data/BOUT.dmp.11.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [42, 43], 0, region, "inner x")
-test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
-test(f[:, 0], [100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [40, 56, 68, 92], 0, region, "upper y")
-
-region = "outer core"
-f = DataFile("data/BOUT.dmp.12.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [80, 81], 0, region, "inner x")
-test(f[-1, 1:-1], [32, 33], 0, region, "outer x")
-test(f[:, 0], [75, 3, 15, 27], 0, region, "lower y")
-test(f[:, -1], [74, 2, 14, 34], 0, region, "upper y")
-
-region = "outer between separatrices"
-f = DataFile("data/BOUT.dmp.13.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [20, 21], 0, region, "inner x")
-test(f[-1, 1:-1], [56, 57], 0, region, "outer x")
-test(f[:, 0], [15, 27, 39, 55], 0, region, "lower y")
-test(f[:, -1], [14, 34, 46, 58], 0, region, "upper y")
-
-region = "outer SOL"
-f = DataFile("data/BOUT.dmp.14.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [44, 45], 0, region, "inner x")
-test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
-test(f[:, 0], [39, 55, 67, 91], 0, region, "lower y")
-test(f[:, -1], [46, 58, 70, 94], 0, region, "upper y")
-
-region = "lower outer PF"
-f = DataFile("data/BOUT.dmp.15.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [82, 83], 0, region, "inner x")
-test(f[-1, 1:-1], [34, 35], 0, region, "outer x")
-test(f[:, 0], [73, 1, 13, 33], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107], 0, region, "upper y")
-
-region = "lower outer between separatrices"
-f = DataFile("data/BOUT.dmp.16.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [22, 23], 0, region, "inner x")
-test(f[-1, 1:-1], [58, 59], 0, region, "outer x")
-test(f[:, 0], [13, 33, 45, 57], 0, region, "lower y")
-test(f[:, -1], [106, 107, 108, 109], 0, region, "upper y")
-
-region = "lower outer SOL"
-f = DataFile("data/BOUT.dmp.17.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [46, 47], 0, region, "inner x")
-test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
-test(f[:, 0], [45, 57, 69, 93], 0, region, "lower y")
-test(f[:, -1], [108, 109, 110, 111], 0, region, "upper y")
-
-# run with processor splitting not at separatrices
-##################################################
-
-# With nxpe=2, neither separatrix is on a processor boundary
-nxpe = 2
-command = "./test-communications NXPE=" + str(nxpe)
-
-# remove old outputs
-shell("rm data/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run.log." + str(nxpe), "w") as f:
- f.write(out)
-
-region = "lower, inner leg, interior"
-f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [72, 73], 0, region, "inner x")
-test(f[-1, 1:-1], [36, 37], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99, 100], 0, region, "lower y")
-test(f[:, -1], [82, 10, 22, 26, 38], 0, region, "upper y")
-
-region = "lower, inner leg, exterior"
-f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [24, 25], 0, region, "inner x")
-test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
-test(f[:, 0], [99, 100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [26, 38, 50, 62, 86], 0, region, "upper y")
-
-region = "inner, interior"
-f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [74, 75], 0, region, "inner x")
-test(f[-1, 1:-1], [38, 39], 0, region, "outer x")
-test(f[:, 0], [81, 9, 21, 25, 37], 0, region, "lower y")
-test(f[:, -1], [80, 8, 20, 32, 44], 0, region, "upper y")
-
-region = "inner, exterior"
-f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [26, 27], 0, region, "inner x")
-test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
-test(f[:, 0], [25, 37, 49, 61, 85], 0, region, "lower y")
-test(f[:, -1], [32, 44, 52, 64, 88], 0, region, "upper y")
-
-region = "upper, inner leg, interior"
-f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [76, 77], 0, region, "inner x")
-test(f[-1, 1:-1], [40, 41], 0, region, "outer x")
-test(f[:, 0], [79, 7, 19, 31, 43], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107, 108], 0, region, "upper y")
-
-region = "upper, inner leg, exterior"
-f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [28, 29], 0, region, "inner x")
-test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
-test(f[:, 0], [31, 43, 51, 63, 87], 0, region, "lower y")
-test(f[:, -1], [107, 108, 109, 110, 111], 0, region, "upper y")
-
-region = "upper, outer leg, interior"
-f = DataFile("data/BOUT.dmp.6.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [78, 79], 0, region, "inner x")
-test(f[-1, 1:-1], [42, 43], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99, 100], 0, region, "lower y")
-test(f[:, -1], [76, 4, 16, 28, 40], 0, region, "upper y")
-
-region = "upper, outer leg, exterior"
-f = DataFile("data/BOUT.dmp.7.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [30, 31], 0, region, "inner x")
-test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
-test(f[:, 0], [99, 100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [28, 40, 56, 68, 92], 0, region, "upper y")
-
-region = "outer, interior"
-f = DataFile("data/BOUT.dmp.8.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [80, 81], 0, region, "inner x")
-test(f[-1, 1:-1], [44, 45], 0, region, "outer x")
-test(f[:, 0], [75, 3, 15, 27, 39], 0, region, "lower y")
-test(f[:, -1], [74, 2, 14, 34, 46], 0, region, "upper y")
-
-region = "outer, exterior"
-f = DataFile("data/BOUT.dmp.9.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [32, 33], 0, region, "inner x")
-test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
-test(f[:, 0], [27, 39, 55, 67, 91], 0, region, "lower y")
-test(f[:, -1], [34, 46, 58, 70, 94], 0, region, "upper y")
-
-region = "lower, outer leg, interior"
-f = DataFile("data/BOUT.dmp.10.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [82, 83], 0, region, "inner x")
-test(f[-1, 1:-1], [46, 47], 0, region, "outer x")
-test(f[:, 0], [73, 1, 13, 33, 45], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107, 108], 0, region, "upper y")
-
-region = "lower, outer leg, exterior"
-f = DataFile("data/BOUT.dmp.11.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [34, 35], 0, region, "inner x")
-test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
-test(f[:, 0], [33, 45, 57, 69, 93], 0, region, "lower y")
-test(f[:, -1], [107, 108, 109, 110, 111], 0, region, "upper y")
-
-# run with no processor splitting in the x-direction
-####################################################
-
-nxpe = 1
-command = "./test-communications NXPE=" + str(nxpe)
-
-# remove old outputs
-shell("rm data/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run.log." + str(nxpe), "w") as f:
- f.write(out)
-
-region = "lower, inner leg"
-f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [72, 73], 0, region, "inner x")
-test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99, 100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [82, 10, 22, 26, 38, 50, 62, 86], 0, region, "upper y")
-
-region = "inner"
-f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [74, 75], 0, region, "inner x")
-test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
-test(f[:, 0], [81, 9, 21, 25, 37, 49, 61, 85], 0, region, "lower y")
-test(f[:, -1], [80, 8, 20, 32, 44, 52, 64, 88], 0, region, "upper y")
-
-region = "upper, inner leg"
-f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [76, 77], 0, region, "inner x")
-test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
-test(f[:, 0], [79, 7, 19, 31, 43, 51, 63, 87], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107, 108, 109, 110, 111], 0, region, "upper y")
-
-region = "upper, outer leg"
-f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [78, 79], 0, region, "inner x")
-test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
-test(f[:, 0], [96, 97, 98, 99, 100, 101, 102, 103], 0, region, "lower y")
-test(f[:, -1], [76, 4, 16, 28, 40, 56, 68, 92], 0, region, "upper y")
-
-region = "outer"
-f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [80, 81], 0, region, "inner x")
-test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
-test(f[:, 0], [75, 3, 15, 27, 39, 55, 67, 91], 0, region, "lower y")
-test(f[:, -1], [74, 2, 14, 34, 46, 58, 70, 94], 0, region, "upper y")
-
-region = "lower, outer leg"
-f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [82, 83], 0, region, "inner x")
-test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
-test(f[:, 0], [73, 1, 13, 33, 45, 57, 69, 93], 0, region, "lower y")
-test(f[:, -1], [104, 105, 106, 107, 108, 109, 110, 111], 0, region, "upper y")
-
-
-#################################################
-# Test limiter topology
-#################################################
-
-nype = 1
-
-# run with processor splitting at the separatrix
-####################################################
-
-nxpe = 3
-command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
-
-# remove old outputs
-shell("rm data_limiter/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run_limiter.log." + str(nxpe), "w") as f:
- f.write(out)
-
-region = "core"
-f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [12, 13], 0, region, "inner x")
-test(f[-1, 1:-1], [4, 5], 0, region, "outer x")
-test(f[:, 0], [13, 1, 3, 19], 0, region, "lower y")
-test(f[:, -1], [12, 0, 2, 27], 0, region, "upper y")
-
-region = "interior SOL"
-f = DataFile("data_limiter/BOUT.dmp.1.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [2, 3], 0, region, "inner x")
-test(f[-1, 1:-1], [8, 9], 0, region, "outer x")
-test(f[:, 0], [3, 19, 20, 21], 0, region, "lower y")
-test(f[:, -1], [2, 27, 28, 29], 0, region, "upper y")
-
-region = "exterior SOL"
-f = DataFile("data_limiter/BOUT.dmp.2.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [6, 7], 0, region, "inner x")
-test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
-test(f[:, 0], [20, 21, 22, 23], 0, region, "lower y")
-test(f[:, -1], [28, 29, 30, 31], 0, region, "upper y")
-
-# run with processor splitting not at the separatrix
-####################################################
-
-nxpe = 2
-command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
-
-# remove old outputs
-shell("rm data_limiter/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run_limiter.log." + str(nxpe), "w") as f:
- f.write(out)
-
-region = "interior"
-f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [12, 13], 0, region, "inner x")
-test(f[-1, 1:-1], [6, 7], 0, region, "outer x")
-test(f[:, 0], [13, 1, 3, 19, 20], 0, region, "lower y")
-test(f[:, -1], [12, 0, 2, 27, 28], 0, region, "upper y")
-
-region = "exterior"
-f = DataFile("data_limiter/BOUT.dmp.1.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [4, 5], 0, region, "inner x")
-test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
-test(f[:, 0], [19, 20, 21, 22, 23], 0, region, "lower y")
-test(f[:, -1], [27, 28, 29, 30, 31], 0, region, "upper y")
-
-# run with no processor splitting in the x-direction
-####################################################
-
-nxpe = 1
-command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
-
-# remove old outputs
-shell("rm data_limiter/BOUT.dmp.*")
-
-print("Running Communications Test, nproc=" + str(nxpe * nype))
-
-s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
-with open("run_limiter.log." + str(nxpe), "w") as f:
- f.write(out)
-
-region = "all"
-f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
-test(f[0, 1:-1], [12, 13], 0, region, "inner x")
-test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
-test(f[:, 0], [13, 1, 3, 19, 20, 21, 22, 23], 0, region, "lower y")
-test(f[:, -1], [12, 0, 2, 27, 28, 29, 30, 31], 0, region, "upper y")
-
-# If we did not exit already, then all tests passed
-print("Pass")
-exit(0)
diff --git a/tests/integrated/test-communications/test_communications.py b/tests/integrated/test-communications/test_communications.py
new file mode 100755
index 0000000000..75150ccde1
--- /dev/null
+++ b/tests/integrated/test-communications/test_communications.py
@@ -0,0 +1,426 @@
+#!/usr/bin/env python3
+import os
+import numpy as np
+import numpy.testing as npt
+from boututils.datafile import DataFile
+from boututils.run_wrapper import shell, launch_safe
+
+# Cores: 18
+# Requires: netcdf
+
+# Note, the expected values for the communicated cells, which are used to test the
+# results, are shown in corner_communication_diagram.ods
+
+
+def run_test(actual, expected, procnum, region, boundary):
+ npt.assert_allclose(
+ np.array(np.rint(actual).astype(int)),
+ np.array(expected),
+ err_msg=f"failed in {boundary} boundary of region {procnum} ({region}). "
+ f"Expected: {expected} Actual: {actual}",
+ )
+
+
+def test_communications():
+
+ #################################################
+ # Test disconnected-double-null diverted topology
+ #################################################
+
+ nype = 6 # need at least 6 to include all regions
+
+ # run with processor splitting at separatrices
+ ##################################################
+
+ # MPI oversubscribe for communications test
+ os.environ["OMPI_MCA_rmaps_base_oversubscribe"] = "1" # Allows 18 procs
+
+ # With nxpe=3, both separatrices are on processor boundaries
+ nxpe = 3
+ command = "./test-communications NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "lower, inner PF"
+ f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [72, 73], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [24, 25], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99], 0, region, "lower y")
+ run_test(f[:, -1], [82, 10, 22, 26], 0, region, "upper y")
+
+ region = "lower, inner between separatrices"
+ f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [12, 13], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [48, 49], 0, region, "outer x")
+ run_test(f[:, 0], [98, 99, 100, 101], 0, region, "lower y")
+ run_test(f[:, -1], [22, 26, 38, 50], 0, region, "upper y")
+
+ region = "lower, inner SOL"
+ f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [36, 37], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
+ run_test(f[:, 0], [100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [38, 50, 62, 86], 0, region, "upper y")
+
+ region = "inner core"
+ f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [74, 75], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [26, 27], 0, region, "outer x")
+ run_test(f[:, 0], [81, 9, 21, 25], 0, region, "lower y")
+ run_test(f[:, -1], [80, 8, 20, 32], 0, region, "upper y")
+
+ region = "inner between separatrices"
+ f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [14, 15], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [50, 51], 0, region, "outer x")
+ run_test(f[:, 0], [21, 25, 37, 49], 0, region, "lower y")
+ run_test(f[:, -1], [20, 32, 44, 52], 0, region, "upper y")
+
+ region = "inner SOL"
+ f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [38, 39], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
+ run_test(f[:, 0], [37, 49, 61, 85], 0, region, "lower y")
+ run_test(f[:, -1], [44, 52, 64, 88], 0, region, "upper y")
+
+ region = "upper inner PF"
+ f = DataFile("data/BOUT.dmp.6.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [76, 77], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [28, 29], 0, region, "outer x")
+ run_test(f[:, 0], [79, 7, 19, 31], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107], 0, region, "upper y")
+
+ region = "upper inner SOL, nearer PF"
+ f = DataFile("data/BOUT.dmp.7.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [16, 17], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [52, 53], 0, region, "outer x")
+ run_test(f[:, 0], [19, 31, 43, 51], 0, region, "lower y")
+ run_test(f[:, -1], [106, 107, 108, 109], 0, region, "upper y")
+
+ region = "upper inner SOL, further from PF"
+ f = DataFile("data/BOUT.dmp.8.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [40, 41], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
+ run_test(f[:, 0], [43, 51, 63, 87], 0, region, "lower y")
+ run_test(f[:, -1], [108, 109, 110, 111], 0, region, "upper y")
+
+ region = "upper outer PF"
+ f = DataFile("data/BOUT.dmp.9.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [78, 79], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [30, 31], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99], 0, region, "lower y")
+ run_test(f[:, -1], [76, 4, 16, 28], 0, region, "upper y")
+
+ region = "upper outer SOL, nearer PF"
+ f = DataFile("data/BOUT.dmp.10.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [18, 19], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [54, 55], 0, region, "outer x")
+ run_test(f[:, 0], [98, 99, 100, 101], 0, region, "lower y")
+ run_test(f[:, -1], [16, 28, 40, 56], 0, region, "upper y")
+
+ region = "upper outer SOL, further from PF"
+ f = DataFile("data/BOUT.dmp.11.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [42, 43], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
+ run_test(f[:, 0], [100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [40, 56, 68, 92], 0, region, "upper y")
+
+ region = "outer core"
+ f = DataFile("data/BOUT.dmp.12.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [80, 81], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [32, 33], 0, region, "outer x")
+ run_test(f[:, 0], [75, 3, 15, 27], 0, region, "lower y")
+ run_test(f[:, -1], [74, 2, 14, 34], 0, region, "upper y")
+
+ region = "outer between separatrices"
+ f = DataFile("data/BOUT.dmp.13.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [20, 21], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [56, 57], 0, region, "outer x")
+ run_test(f[:, 0], [15, 27, 39, 55], 0, region, "lower y")
+ run_test(f[:, -1], [14, 34, 46, 58], 0, region, "upper y")
+
+ region = "outer SOL"
+ f = DataFile("data/BOUT.dmp.14.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [44, 45], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
+ run_test(f[:, 0], [39, 55, 67, 91], 0, region, "lower y")
+ run_test(f[:, -1], [46, 58, 70, 94], 0, region, "upper y")
+
+ region = "lower outer PF"
+ f = DataFile("data/BOUT.dmp.15.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [82, 83], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [34, 35], 0, region, "outer x")
+ run_test(f[:, 0], [73, 1, 13, 33], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107], 0, region, "upper y")
+
+ region = "lower outer between separatrices"
+ f = DataFile("data/BOUT.dmp.16.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [22, 23], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [58, 59], 0, region, "outer x")
+ run_test(f[:, 0], [13, 33, 45, 57], 0, region, "lower y")
+ run_test(f[:, -1], [106, 107, 108, 109], 0, region, "upper y")
+
+ region = "lower outer SOL"
+ f = DataFile("data/BOUT.dmp.17.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [46, 47], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
+ run_test(f[:, 0], [45, 57, 69, 93], 0, region, "lower y")
+ run_test(f[:, -1], [108, 109, 110, 111], 0, region, "upper y")
+
+ # run with processor splitting not at separatrices
+ ##################################################
+
+ # With nxpe=2, neither separatrix is on a processor boundary
+ nxpe = 2
+ command = "./test-communications NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "lower, inner leg, interior"
+ f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [72, 73], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [36, 37], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99, 100], 0, region, "lower y")
+ run_test(f[:, -1], [82, 10, 22, 26, 38], 0, region, "upper y")
+
+ region = "lower, inner leg, exterior"
+ f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [24, 25], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
+ run_test(f[:, 0], [99, 100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [26, 38, 50, 62, 86], 0, region, "upper y")
+
+ region = "inner, interior"
+ f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [74, 75], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [38, 39], 0, region, "outer x")
+ run_test(f[:, 0], [81, 9, 21, 25, 37], 0, region, "lower y")
+ run_test(f[:, -1], [80, 8, 20, 32, 44], 0, region, "upper y")
+
+ region = "inner, exterior"
+ f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [26, 27], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
+ run_test(f[:, 0], [25, 37, 49, 61, 85], 0, region, "lower y")
+ run_test(f[:, -1], [32, 44, 52, 64, 88], 0, region, "upper y")
+
+ region = "upper, inner leg, interior"
+ f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [76, 77], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [40, 41], 0, region, "outer x")
+ run_test(f[:, 0], [79, 7, 19, 31, 43], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107, 108], 0, region, "upper y")
+
+ region = "upper, inner leg, exterior"
+ f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [28, 29], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
+ run_test(f[:, 0], [31, 43, 51, 63, 87], 0, region, "lower y")
+ run_test(f[:, -1], [107, 108, 109, 110, 111], 0, region, "upper y")
+
+ region = "upper, outer leg, interior"
+ f = DataFile("data/BOUT.dmp.6.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [78, 79], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [42, 43], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99, 100], 0, region, "lower y")
+ run_test(f[:, -1], [76, 4, 16, 28, 40], 0, region, "upper y")
+
+ region = "upper, outer leg, exterior"
+ f = DataFile("data/BOUT.dmp.7.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [30, 31], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
+ run_test(f[:, 0], [99, 100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [28, 40, 56, 68, 92], 0, region, "upper y")
+
+ region = "outer, interior"
+ f = DataFile("data/BOUT.dmp.8.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [80, 81], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [44, 45], 0, region, "outer x")
+ run_test(f[:, 0], [75, 3, 15, 27, 39], 0, region, "lower y")
+ run_test(f[:, -1], [74, 2, 14, 34, 46], 0, region, "upper y")
+
+ region = "outer, exterior"
+ f = DataFile("data/BOUT.dmp.9.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [32, 33], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
+ run_test(f[:, 0], [27, 39, 55, 67, 91], 0, region, "lower y")
+ run_test(f[:, -1], [34, 46, 58, 70, 94], 0, region, "upper y")
+
+ region = "lower, outer leg, interior"
+ f = DataFile("data/BOUT.dmp.10.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [82, 83], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [46, 47], 0, region, "outer x")
+ run_test(f[:, 0], [73, 1, 13, 33, 45], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107, 108], 0, region, "upper y")
+
+ region = "lower, outer leg, exterior"
+ f = DataFile("data/BOUT.dmp.11.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [34, 35], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
+ run_test(f[:, 0], [33, 45, 57, 69, 93], 0, region, "lower y")
+ run_test(f[:, -1], [107, 108, 109, 110, 111], 0, region, "upper y")
+
+ # run with no processor splitting in the x-direction
+ ####################################################
+
+ nxpe = 1
+
+ command = "./test-communications NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "lower, inner leg"
+ f = DataFile("data/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [72, 73], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [84, 85], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99, 100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [82, 10, 22, 26, 38, 50, 62, 86], 0, region, "upper y")
+
+ region = "inner"
+ f = DataFile("data/BOUT.dmp.1.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [74, 75], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [86, 87], 0, region, "outer x")
+ run_test(f[:, 0], [81, 9, 21, 25, 37, 49, 61, 85], 0, region, "lower y")
+ run_test(f[:, -1], [80, 8, 20, 32, 44, 52, 64, 88], 0, region, "upper y")
+
+ region = "upper, inner leg"
+ f = DataFile("data/BOUT.dmp.2.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [76, 77], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [88, 89], 0, region, "outer x")
+ run_test(f[:, 0], [79, 7, 19, 31, 43, 51, 63, 87], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107, 108, 109, 110, 111], 0, region, "upper y")
+
+ region = "upper, outer leg"
+ f = DataFile("data/BOUT.dmp.3.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [78, 79], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [90, 91], 0, region, "outer x")
+ run_test(f[:, 0], [96, 97, 98, 99, 100, 101, 102, 103], 0, region, "lower y")
+ run_test(f[:, -1], [76, 4, 16, 28, 40, 56, 68, 92], 0, region, "upper y")
+
+ region = "outer"
+ f = DataFile("data/BOUT.dmp.4.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [80, 81], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [92, 93], 0, region, "outer x")
+ run_test(f[:, 0], [75, 3, 15, 27, 39, 55, 67, 91], 0, region, "lower y")
+ run_test(f[:, -1], [74, 2, 14, 34, 46, 58, 70, 94], 0, region, "upper y")
+
+ region = "lower, outer leg"
+ f = DataFile("data/BOUT.dmp.5.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [82, 83], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [94, 95], 0, region, "outer x")
+ run_test(f[:, 0], [73, 1, 13, 33, 45, 57, 69, 93], 0, region, "lower y")
+ run_test(f[:, -1], [104, 105, 106, 107, 108, 109, 110, 111], 0, region, "upper y")
+
+ #################################################
+ # Test limiter topology
+ #################################################
+
+ nype = 1
+
+ # run with processor splitting at the separatrix
+ ####################################################
+
+ nxpe = 3
+ command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data_limiter/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run_limiter.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "core"
+ f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [12, 13], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [4, 5], 0, region, "outer x")
+ run_test(f[:, 0], [13, 1, 3, 19], 0, region, "lower y")
+ run_test(f[:, -1], [12, 0, 2, 27], 0, region, "upper y")
+
+ region = "interior SOL"
+ f = DataFile("data_limiter/BOUT.dmp.1.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [2, 3], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [8, 9], 0, region, "outer x")
+ run_test(f[:, 0], [3, 19, 20, 21], 0, region, "lower y")
+ run_test(f[:, -1], [2, 27, 28, 29], 0, region, "upper y")
+
+ region = "exterior SOL"
+ f = DataFile("data_limiter/BOUT.dmp.2.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [6, 7], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
+ run_test(f[:, 0], [20, 21, 22, 23], 0, region, "lower y")
+ run_test(f[:, -1], [28, 29, 30, 31], 0, region, "upper y")
+
+ # run with processor splitting not at the separatrix
+ ####################################################
+
+ nxpe = 2
+ command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data_limiter/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run_limiter.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "interior"
+ f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [12, 13], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [6, 7], 0, region, "outer x")
+ run_test(f[:, 0], [13, 1, 3, 19, 20], 0, region, "lower y")
+ run_test(f[:, -1], [12, 0, 2, 27, 28], 0, region, "upper y")
+
+ region = "exterior"
+ f = DataFile("data_limiter/BOUT.dmp.1.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [4, 5], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
+ run_test(f[:, 0], [19, 20, 21, 22, 23], 0, region, "lower y")
+ run_test(f[:, -1], [27, 28, 29, 30, 31], 0, region, "upper y")
+
+ # run with no processor splitting in the x-direction
+ ####################################################
+
+ nxpe = 1
+ command = "./test-communications -d data_limiter NXPE=" + str(nxpe)
+
+ # remove old outputs
+ shell(["rm data_limiter/BOUT.dmp.*"])
+
+ print("Running Communications Test, nproc=" + str(nxpe * nype))
+
+ s, out = launch_safe(command, nproc=nxpe * nype, pipe=True)
+ with open("run_limiter.log." + str(nxpe), "w") as f:
+ f.write(out)
+
+ region = "all"
+ f = DataFile("data_limiter/BOUT.dmp.0.nc")["f"][0, :, :, 0]
+ run_test(f[0, 1:-1], [12, 13], 0, region, "inner x")
+ run_test(f[-1, 1:-1], [14, 15], 0, region, "outer x")
+ run_test(f[:, 0], [13, 1, 3, 19, 20, 21, 22, 23], 0, region, "lower y")
+ run_test(f[:, -1], [12, 0, 2, 27, 28, 29, 30, 31], 0, region, "upper y")
diff --git a/tests/integrated/test-compile-examples-petsc/README.md b/tests/integrated/test-compile-examples-petsc/README.md
deleted file mode 120000
index 77bd1f7308..0000000000
--- a/tests/integrated/test-compile-examples-petsc/README.md
+++ /dev/null
@@ -1 +0,0 @@
-../test-compile-examples/README.md
\ No newline at end of file
diff --git a/tests/integrated/test-compile-examples-petsc/runtest b/tests/integrated/test-compile-examples-petsc/runtest
deleted file mode 100755
index 0f9625af40..0000000000
--- a/tests/integrated/test-compile-examples-petsc/runtest
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env bash
-
-#requires: all_tests
-#requires: not make
-#requires: petsc
-
-BOUT_TOP=../../..
-export PATH=$(pwd)/$BOUT_TOP/bin:$PATH
-
-error=0
-
-failed=
-for target in $(find $BOUT_TOP/examples/ -name makefile)
-do
- dir=$(dirname ${target})
- if grep ":.*requires_petsc" $target -q;
- then
- echo "Trying to compile ${dir}"
-
- (cd ${dir} && make 2>&1)
- ex=$?
-
- if test $ex -gt 0
- then
- echo $(basename $dir) failed
- error=1
- failed="$failed\n$(basename $dir) failed"
- fi
- else
- echo "Skipping $dir - requires PETSc"
- fi
-done
-
-echo -e $failed
-
-exit $error
diff --git a/tests/integrated/test-compile-examples/README.md b/tests/integrated/test-compile-examples/README.md
deleted file mode 100644
index 2fa005c7c4..0000000000
--- a/tests/integrated/test-compile-examples/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-test-compile-examples
-=====================
-
-This test ensures that all models in examples can be compiled.
-
-This currently works by finding any makefile located beneath
-examples. It skips any directories matching doc in order to skip
-documentation. Building documentation requires often additional
-tools, and is thus skipped. The test is split in two parts, one for
-examples without PETSc, and one for examples that require PETSc.
-
-PETSc detection works by checking the makefile whether any target
-requires `requires_petsc`.
diff --git a/tests/integrated/test-compile-examples/runtest b/tests/integrated/test-compile-examples/runtest
deleted file mode 100755
index 765ea2cbfd..0000000000
--- a/tests/integrated/test-compile-examples/runtest
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env bash
-
-#requires: all_tests
-#requires: not make
-
-BOUT_TOP=../../..
-export PATH=$(pwd)/$BOUT_TOP/bin:$PATH
-
-error=0
-
-failed=
-for target in $(find $BOUT_TOP/examples/ -name makefile | grep -v doc)
-do
- dir=$(dirname ${target})
- if grep ":.*requires_petsc" $target -q;
- then
- echo "Skipping $dir - requires PETSc"
- else
- echo "Trying to compile ${dir}"
-
- (cd ${dir} && make 2>&1)
- ex=$?
-
- if test $ex -gt 0
- then
- dir=${dir#../../../examples/}
- echo $dir failed
- error=1
- failed="$failed\n$dir failed"
- fi
- fi
-done
-
-echo -e $failed
-
-exit $error
diff --git a/tests/integrated/test-coordinates-initialization/test_coordinates_initialization.py b/tests/integrated/test-coordinates-initialization/test_coordinates_initialization.py
new file mode 100644
index 0000000000..64a438d1dc
--- /dev/null
+++ b/tests/integrated/test-coordinates-initialization/test_coordinates_initialization.py
@@ -0,0 +1,6 @@
+import pytest
+
+
+@pytest.mark.serial
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./runtest")
diff --git a/tests/integrated/test-cyclic/runtest b/tests/integrated/test-cyclic/runtest
deleted file mode 100755
index c68f9b033e..0000000000
--- a/tests/integrated/test-cyclic/runtest
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, check it completed successfully
-#
-
-# Requires: netcdf
-# Cores: 4
-
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-from boututils.run_wrapper import build_and_log, shell, launch
-from boutdata.collect import collect
-from sys import stdout, exit
-
-build_and_log("Cyclic Reduction test")
-
-flags = ["", "nsys=2", "nsys=5 periodic", "nsys=7 n=10"]
-
-code = 0 # Return code
-for nproc in [1, 2, 4]:
- cmd = "./test_cyclic"
-
- print(" %d processors...." % (nproc))
- r = 0
- for f in flags:
- stdout.write("\tflags '" + f + "' ... ")
-
- shell("rm data/BOUT.dmp.* 2> err.log")
-
- # Run the case
- status, out = launch(cmd + " " + f, nproc=nproc, mthread=1, pipe=True)
- with open(f"run.log.{nproc}.{r}", "w") as f:
- f.write(out)
-
- r = r + 1
-
- # Find out if it worked
- if status:
- print("PASSED")
- else:
- print("FAILED")
- code = 1
-
-if code == 0:
- print(" => All cyclic reduction tests passed")
-else:
- print(" => Some failed tests")
-
-exit(code)
diff --git a/tests/integrated/test-cyclic/test_cyclic.py b/tests/integrated/test-cyclic/test_cyclic.py
new file mode 100755
index 0000000000..38a2d70012
--- /dev/null
+++ b/tests/integrated/test-cyclic/test_cyclic.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, check it completed successfully
+#
+
+# Requires: netcdf
+# Cores: 4
+
+from boututils.run_wrapper import shell, launch
+from sys import stdout
+
+
+flags = ["", "nsys=2", "nsys=5 periodic", "nsys=7 n=10"]
+
+
+def test_cyclic():
+
+ code = 0 # Return code
+ for nproc in [1, 2, 4]:
+ cmd = "./test_cyclic"
+
+ print(" %d processors...." % (nproc))
+ r = 0
+ for f in flags:
+ stdout.write("\tflags '" + f + "' ... ")
+
+ shell(["rm data/BOUT.dmp.* 2> err.log"])
+
+ # Run the case
+ status, out = launch(cmd + " " + f, nproc=nproc, mthread=1, pipe=True)
+ with open(f"run.log.{nproc}.{r}", "w") as f:
+ f.write(out)
+
+ r = r + 1
+
+ # Find out if it worked
+ if status:
+ print("PASSED")
+ else:
+ print("FAILED")
+ code = 1
+ assert status, f"Test failed for flag={f}"
+
+ if code == 0:
+ print(" => All cyclic reduction tests passed")
+ else:
+ print(" => Some failed tests")
diff --git a/tests/integrated/test-datafilefacade/runtest b/tests/integrated/test-datafilefacade/runtest
deleted file mode 100755
index 213b64e26b..0000000000
--- a/tests/integrated/test-datafilefacade/runtest
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python3
-#
-# Test the DataFileFacade interface by writing to dump files using the SAVE_ONCE macro
-#
-# requires: netcdf
-# cores: 2
-
-from boutdata.collect import collect
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-import numpy
-from sys import exit
-
-build_and_log("DataFileFacade test")
-
-
-success = True
-
-testvars = {
- "f2d": 0.1,
- "f3d": 0.2,
- "fperp": 1.1,
- "v2d_contravariantx": 2.2,
- "v2d_contravarianty": 3.3,
- "v2d_contravariantz": 4.4,
- "v3d_contravariantx": 5.5,
- "v3d_contravarianty": 6.6,
- "v3d_contravariantz": 7.7,
- "v2d_covariant_x": 12.12,
- "v2d_covariant_y": 13.13,
- "v2d_covariant_z": 14.14,
- "v3d_covariant_x": 15.15,
- "v3d_covariant_y": 16.16,
- "v3d_covariant_z": 17.17,
- "integer": 42,
- "boolean": True,
- "real": 3.14,
- "name": "test string",
-}
-
-
-for nproc in [1, 2]:
- # delete any existing output
- shell("rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc")
-
- print(f" {nproc} processor....")
-
- # run the test executable
- s, out = launch_safe("./test-datafile-facade", nproc=nproc, pipe=True)
- with open(f"run.log.{nproc}", "w") as f:
- f.write(out)
-
- # check the results
- for name, expected in testvars.items():
- # check non-evolving version
- result = collect(name, path="data", info=False)
-
- if result.dtype.kind in ("S", "U"):
- if str(result) != expected:
- success = False
- print(
- f"{name} is different: got '{str(result)}', expected '{expected}'"
- )
- continue
-
- if not numpy.allclose(expected, result):
- success = False
- print(f"{name} is different: {numpy.max(numpy.abs(expected - result))}")
-
-if success:
- print("=> All DataFileFacade tests passed")
- # clean up binary files
- shell(
- "rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc data/restart/BOUT.restart.0.nc"
- )
- exit(0)
-
-print("=> Some failed tests")
-exit(1)
diff --git a/tests/integrated/test-datafilefacade/test_datafile_facade.py b/tests/integrated/test-datafilefacade/test_datafile_facade.py
new file mode 100755
index 0000000000..7a4d947f27
--- /dev/null
+++ b/tests/integrated/test-datafilefacade/test_datafile_facade.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Test the DataFileFacade interface by writing to dump files using the SAVE_ONCE macro
+#
+# requires: netcdf
+# cores: 2
+
+from boutdata.collect import collect
+from boututils.run_wrapper import shell, launch_safe
+import numpy
+
+
+testvars = {
+ "f2d": 0.1,
+ "f3d": 0.2,
+ "fperp": 1.1,
+ "v2d_contravariantx": 2.2,
+ "v2d_contravarianty": 3.3,
+ "v2d_contravariantz": 4.4,
+ "v3d_contravariantx": 5.5,
+ "v3d_contravarianty": 6.6,
+ "v3d_contravariantz": 7.7,
+ "v2d_covariant_x": 12.12,
+ "v2d_covariant_y": 13.13,
+ "v2d_covariant_z": 14.14,
+ "v3d_covariant_x": 15.15,
+ "v3d_covariant_y": 16.16,
+ "v3d_covariant_z": 17.17,
+ "integer": 42,
+ "boolean": True,
+ "real": 3.14,
+ "name": "test string",
+}
+
+
+def test_datafile_facade():
+
+ success = True
+
+ for nproc in [1, 2]:
+ # delete any existing output
+ shell(["rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc"])
+
+ print(f" {nproc} processor....")
+
+ # run the test executable
+ s, out = launch_safe("./test-datafile-facade", nproc=nproc, pipe=True)
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
+
+ # check the results
+ for name, expected in testvars.items():
+ # check non-evolving version
+ result = collect(name, path="data", info=False)
+
+ if result.dtype.kind in ("S", "U"):
+ if str(result) != expected:
+ success = False
+ print(
+ f"{name} is different: got '{str(result)}', expected '{expected}'"
+ )
+ continue
+
+ if not numpy.allclose(expected, result):
+ success = False
+ print(f"{name} is different: {numpy.max(numpy.abs(expected - result))}")
+
+ if success:
+ print("=> All DataFileFacade tests passed")
+ # clean up binary files
+ shell(
+ [
+ "rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc data/restart/BOUT.restart.0.nc"
+ ]
+ )
+ assert success, "=> Some failed tests"
diff --git a/tests/integrated/test-dataformat/test_dataformat.py b/tests/integrated/test-dataformat/test_dataformat.py
new file mode 100644
index 0000000000..b11460b2f8
--- /dev/null
+++ b/tests/integrated/test-dataformat/test_dataformat.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./test_dataformat")
diff --git a/tests/integrated/test-delp2/runtest b/tests/integrated/test-delp2/test_delp2.py
similarity index 52%
rename from tests/integrated/test-delp2/runtest
rename to tests/integrated/test-delp2/test_delp2.py
index 326ba32888..b3a16eded9 100755
--- a/tests/integrated/test-delp2/runtest
+++ b/tests/integrated/test-delp2/test_delp2.py
@@ -1,18 +1,16 @@
#!/usr/bin/env python3
-
# requires: fftw
# cores: 4
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
+import pytest
import numpy as np
-from sys import stdout, exit
+from sys import stdout
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
tol = 1e-10 # Absolute tolerance
-build_and_log("Delp2 operator test")
-
# The command to run
exefile = "./test_delp2"
@@ -25,42 +23,32 @@
]
-success = True
+@pytest.mark.parametrize("setting", settings)
+def test_delp2(setting):
-for i, opts in enumerate(settings):
# Read benchmark values
- print("Args: " + opts)
- cmd = exefile + " " + opts
+ print("Args: " + setting)
+ cmd = exefile + " " + setting
s, out = launch_safe(cmd, nproc=1, pipe=True)
- with open("run.log." + str(i) + ".1", "w") as f:
+ file_suffix = ".".join([x.split("=")[-1] for x in setting.split()])
+ with open("run.log." + str(file_suffix) + ".1", "w") as f:
f.write(out)
n0 = collect("n", path="data", info=False)
for nproc in [2, 4]:
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
stdout.write(" %d processor...." % (nproc))
s, out = launch_safe(cmd, nproc=nproc, mthread=1, pipe=True)
- with open("run.log." + str(i) + "." + str(nproc), "w") as f:
+ with open("run.log." + str(file_suffix) + "." + str(nproc), "w") as f:
f.write(out)
# Collect output data
n = collect("n", path="data", info=False)
- if np.shape(n) != np.shape(n0):
- print("Fail, wrong shape")
- success = False
- diff = np.max(np.abs(n - n0))
- if diff > tol:
- print("Fail, maximum difference = " + str(diff))
- success = False
- else:
- print("Pass")
-if success:
- print(" => All Delp2 tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ assert np.shape(n) == np.shape(n0), "Fail, wrong shape"
+
+ diff = np.max(np.abs(n - n0))
+ assert diff <= tol, "Fail, maximum difference = " + str(diff)
diff --git a/tests/integrated/test-drift-instability-staggered/runtest b/tests/integrated/test-drift-instability-staggered/runtest
deleted file mode 120000
index 3b7f341ec9..0000000000
--- a/tests/integrated/test-drift-instability-staggered/runtest
+++ /dev/null
@@ -1 +0,0 @@
-../test-drift-instability/runtest
\ No newline at end of file
diff --git a/tests/integrated/test-drift-instability-staggered/test_drift_instability_staggered.py b/tests/integrated/test-drift-instability-staggered/test_drift_instability_staggered.py
new file mode 120000
index 0000000000..4d475962e6
--- /dev/null
+++ b/tests/integrated/test-drift-instability-staggered/test_drift_instability_staggered.py
@@ -0,0 +1 @@
+../test-drift-instability/test_drift_instability.py
\ No newline at end of file
diff --git a/tests/integrated/test-drift-instability/runtest b/tests/integrated/test-drift-instability/test_drift_instability.py
old mode 100755
new mode 100644
similarity index 86%
rename from tests/integrated/test-drift-instability/runtest
rename to tests/integrated/test-drift-instability/test_drift_instability.py
index 1fae82100b..e8db2d9798
--- a/tests/integrated/test-drift-instability/runtest
+++ b/tests/integrated/test-drift-instability/test_drift_instability.py
@@ -6,15 +6,15 @@
# Requires: not metric_3d
-from boututils.run_wrapper import build_and_log, shell, launch_safe
+import pytest
+import numpy as np
+
+from boututils.run_wrapper import shell, launch_safe
from boututils.calculus import deriv
from boututils.datafile import DataFile
from boututils.linear_regression import linear_regression
from boutdata.collect import collect
-import argparse
-import numpy as np
-from sys import exit
from math import isnan
nthreads = 1
@@ -53,10 +53,10 @@
def run_zeff_case(zeff):
- """Run a single Zeff case"""
+ """Run a single Zeff case and return success flag and details."""
if zeff not in omega_orig:
- raise ValueError(
+ pytest.fail(
f"Zeff value ({zeff}) not in benchmark values. Available values: {list(omega_orig.keys())}"
)
@@ -67,7 +67,7 @@ def run_zeff_case(zeff):
timestep = 1e3
# Delete old output files
- shell("rm -f data/BOUT.dmp.*.nc")
+ shell(["rm -f data/BOUT.dmp.*.nc"])
print("Running drift instability test, zeff = ", zeff)
@@ -123,7 +123,7 @@ def run_zeff_case(zeff):
# wci=9.58e3*(1./d.AA)*1e4*du.Bmag
lpar = (
- np.sum(((grid["Bxy"] / grid["Bpxy"])) * grid["dlthe"]) / grid["nx"]
+ np.sum((grid["Bxy"] / grid["Bpxy"]) * grid["dlthe"]) / grid["nx"]
) # [m], average over flux surfaces
kpar = 2 * np.pi / (1e2 * lpar) # cm-1
spar = (kpar / kperp) ** 2 * wci * wce / (0.51 * nuei) # [1/s]
@@ -192,32 +192,16 @@ def run_zeff_case(zeff):
)
if isnan(omegadiff) or (omegadiff > omega_tol) or (gammadiff > gamma_tol):
- print(" => FAILED")
- return False
-
- print(" => PASSED")
- return True
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser("Run drift-instability test")
- parser.add_argument(
- "-Z",
- "--Zeff-list",
- help=f"List of Zeff values to test, valid values are {list(gamma_orig.keys())}",
- type=int,
- nargs="+",
- default=zlist,
- )
+ return False, omegadiff, gammadiff
- args = parser.parse_args()
+ return True, omegadiff, gammadiff
- build_and_log("resistive drift instability test")
- return_code = 0
- for zeff in args.Zeff_list:
- success = run_zeff_case(zeff)
- if not success:
- return_code = 1
+@pytest.mark.parametrize("zeff", zlist)
+def test_zeff_case(zeff):
+ """Pytest wrapper for running a single Zeff case."""
+ success, omegadiff, gammadiff = run_zeff_case(zeff)
- exit(return_code)
+ assert success, (
+ f"Test failed for Zeff={zeff}: omega diff={omegadiff:.2%}, gamma diff={gammadiff:.2%}"
+ )
diff --git a/tests/integrated/test-fci-boundary/CMakeLists.txt b/tests/integrated/test-fci-boundary/CMakeLists.txt
index b9164ce000..4aa71f96d5 100644
--- a/tests/integrated/test-fci-boundary/CMakeLists.txt
+++ b/tests/integrated/test-fci-boundary/CMakeLists.txt
@@ -1,4 +1,4 @@
-bout_add_mms_test(
+bout_add_integrated_test(
test-fci-boundary
SOURCES get_par_bndry.cxx
USE_RUNTEST USE_DATA_BOUT_INP
diff --git a/tests/integrated/test-fci-boundary/runtest b/tests/integrated/test-fci-boundary/runtest
deleted file mode 100755
index 74a8abf741..0000000000
--- a/tests/integrated/test-fci-boundary/runtest
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python3
-#
-# Python script to run and analyse MMS test
-
-from boututils.run_wrapper import launch_safe
-from boututils.datafile import DataFile
-from boutdata.collect import collect as _collect
-
-from numpy.testing import assert_allclose
-
-
-def collect(*args):
- return _collect(
- *args,
- info=False,
- path=directory,
- xguards=False,
- yguards=False,
- )
-
-
-nprocs = [1]
-mthread = 2
-
-directory = "data"
-
-with DataFile("grid.fci.nc") as grid:
- MXG = grid.get("MXG", default=1)
- xfwd = grid.read("forward_xt_prime")[MXG:-MXG]
- xbwd = grid.read("backward_xt_prime")[MXG:-MXG]
-
-nx = xfwd.shape[0]
-
-regions = {
- "xin_fwd": xfwd < MXG,
- "xout_fwd": xfwd > nx + MXG - 1,
- "xin_bwd": xbwd < MXG,
- "xout_bwd": xbwd > nx + MXG - 1,
-}
-regions = {k: v.astype(int) for k, v in regions.items()}
-
-
-for x in "xout", "xin":
- regions[x] = regions[f"{x}_fwd"] + regions[f"{x}_bwd"]
-for x in "fwd", "bwd":
- regions[x] = regions[f"xin_{x}"] + regions[f"xout_{x}"]
-regions["all"] = regions["xin"] + regions["xout"]
-
-bndrys = {
- "ybndry_-1": regions["xout_bwd"],
- "ybndry_0": regions["xout_fwd"] * 0,
- "ybndry_1": regions["xout_fwd"],
-}
-
-for nproc in nprocs:
- cmd = "./get_par_bndry"
- _, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
-
- for k, v in regions.items():
- data = collect(f"field_{k}")
- assert_allclose(data, v)
- for i in range(-1, 2):
- name = f"ybndry_{i}"
- data = collect(name)
- assert_allclose(bndrys[name], data)
diff --git a/tests/integrated/test-fci-boundary/test_fci_boundary.py b/tests/integrated/test-fci-boundary/test_fci_boundary.py
new file mode 100755
index 0000000000..9752b9efce
--- /dev/null
+++ b/tests/integrated/test-fci-boundary/test_fci_boundary.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Python script to run and analyse MMS test
+
+
+import os
+import pytest
+
+from boututils.run_wrapper import launch_safe
+from boututils.datafile import DataFile
+from boutdata.collect import collect as _collect
+
+from numpy.testing import assert_allclose
+
+if not os.path.exists(os.path.join(os.path.dirname(__file__), "grid.fci.nc")):
+ pytest.skip(
+ "grid.fci.nc not found (Zoidberg likely missing), skipping test.",
+ allow_module_level=True,
+ )
+
+directory = "data"
+
+
+def collect(*args):
+ return _collect(
+ *args,
+ info=False,
+ path=directory,
+ xguards=False,
+ yguards=False,
+ )
+
+
+def test_fci_boundary():
+
+ nprocs = [1]
+ mthread = 2
+
+ with DataFile("grid.fci.nc") as grid:
+ MXG = grid.get("MXG", default=1)
+ xfwd = grid.read("forward_xt_prime")[MXG:-MXG]
+ xbwd = grid.read("backward_xt_prime")[MXG:-MXG]
+
+ nx = xfwd.shape[0]
+
+ regions = {
+ "xin_fwd": xfwd < MXG,
+ "xout_fwd": xfwd > nx + MXG - 1,
+ "xin_bwd": xbwd < MXG,
+ "xout_bwd": xbwd > nx + MXG - 1,
+ }
+ regions = {k: v.astype(int) for k, v in regions.items()}
+
+ for x in "xout", "xin":
+ regions[x] = regions[f"{x}_fwd"] + regions[f"{x}_bwd"]
+ for x in "fwd", "bwd":
+ regions[x] = regions[f"xin_{x}"] + regions[f"xout_{x}"]
+ regions["all"] = regions["xin"] + regions["xout"]
+
+ bndrys = {
+ "ybndry_-1": regions["xout_bwd"],
+ "ybndry_0": regions["xout_fwd"] * 0,
+ "ybndry_1": regions["xout_fwd"],
+ }
+
+ for nproc in nprocs:
+ cmd = "./get_par_bndry"
+ _, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
+
+ for k, v in regions.items():
+ data = collect(f"field_{k}")
+ assert_allclose(data, v)
+ for i in range(-1, 2):
+ name = f"ybndry_{i}"
+ data = collect(name)
+ assert_allclose(bndrys[name], data)
diff --git a/tests/integrated/test-fci-mpi/CMakeLists.txt b/tests/integrated/test-fci-mpi/CMakeLists.txt
index ba126d7e76..ce5ef9f19b 100644
--- a/tests/integrated/test-fci-mpi/CMakeLists.txt
+++ b/tests/integrated/test-fci-mpi/CMakeLists.txt
@@ -1,4 +1,4 @@
-bout_add_mms_test(
+bout_add_integrated_test(
test-fci-mpi
SOURCES fci_mpi.cxx
USE_RUNTEST USE_DATA_BOUT_INP
diff --git a/tests/integrated/test-fci-mpi/runtest b/tests/integrated/test-fci-mpi/runtest
deleted file mode 100755
index 9962567217..0000000000
--- a/tests/integrated/test-fci-mpi/runtest
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python3
-#
-# Python script to run and analyse MPI test
-
-from boututils.run_wrapper import build_and_log, launch_safe
-from boutdata.collect import collect
-import itertools
-import sys
-
-import numpy.testing as npt
-
-# Resolution in x and y
-NLIST = [1, 2, 4]
-MAXCORES = 8
-NSLICES = [1]
-
-build_and_log("FCI MPI test")
-
-COLLECT_KW = dict(info=False, xguards=False, yguards=False, path="data")
-
-
-def run_case(nxpe: int, nype: int, mthread: int):
- cmd = f"./fci_mpi NXPE={nxpe} NYPE={nype} mesh:paralleltransform:xzinterpolation:type={implementation}"
- print(f"Running command: {cmd}")
-
- _, out = launch_safe(cmd, nproc=nxpe * nype, mthread=mthread, pipe=True)
-
- # Save output to log file
- with open(f"run.log.{nxpe}.{nype}.{nslice}.log", "w") as f:
- f.write(out)
-
-
-def test_case(nxpe: int, nype: int, mthread: int, ref: dict) -> bool:
- run_case(nxpe, nype, mthread)
-
- failures = []
-
- for name, val in ref.items():
- try:
- npt.assert_allclose(val, collect(name, **COLLECT_KW))
- except AssertionError as e:
- failures.append((nxpe, nype, name, e))
-
- return failures
-
-
-failures = []
-
-for implementation in ["hermitespline", "monotonichermitespline"]:
- for nslice in NSLICES:
- # reference data!
- run_case(1, 1, MAXCORES)
-
- ref = {}
- for i in range(4):
- for yp in range(1, nslice + 1):
- for y in [-yp, yp]:
- name = f"output_{i}_{y:+d}"
- ref[name] = collect(name, **COLLECT_KW)
-
- for nxpe, nype in itertools.product(NLIST, NLIST):
- if (nxpe, nype) == (1, 1):
- # reference case, done above
- continue
-
- if nxpe * nype > MAXCORES:
- continue
-
- mthread = MAXCORES // (nxpe * nype)
- failures_ = test_case(nxpe, nype, mthread, ref)
- failures.extend(failures_)
-
-
-success = len(failures) == 0
-if success:
- print("\nAll tests passed")
-else:
- print("\nSome tests failed:")
- for nxpe, nype, name, error in failures:
- print("----------")
- print(f"case {nxpe=} {nype=} {name=}\n{error}")
-
-sys.exit(0 if success else 1)
diff --git a/tests/integrated/test-fci-mpi/test_fci_mpi.py b/tests/integrated/test-fci-mpi/test_fci_mpi.py
new file mode 100755
index 0000000000..51d5795d3f
--- /dev/null
+++ b/tests/integrated/test-fci-mpi/test_fci_mpi.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+#
+# Python script to run and analyse MPI test
+
+from boututils.run_wrapper import launch_safe
+from boutdata.collect import collect
+import itertools
+
+import numpy.testing as npt
+
+# Resolution in x and y
+NLIST = [1, 2, 4]
+MAXCORES = 8
+NSLICES = [1]
+
+
+def test_fci_mpi():
+
+ COLLECT_KW = dict(info=False, xguards=False, yguards=False, path="data")
+
+ def run_case(nxpe: int, nype: int, mthread: int):
+
+ cmd = f"./fci_mpi NXPE={nxpe} NYPE={nype} mesh:paralleltransform:xzinterpolation:type={implementation}"
+ print(f"Running command: {cmd}")
+
+ _, out = launch_safe(cmd, nproc=nxpe * nype, mthread=mthread, pipe=True)
+
+ # Save output to log file
+ with open(f"run.log.{nxpe}.{nype}.{nslice}.log", "w") as f:
+ f.write(out)
+
+ def test_case(nxpe: int, nype: int, mthread: int, ref: dict) -> bool:
+ run_case(nxpe, nype, mthread)
+
+ failures = []
+
+ for name, val in ref.items():
+ try:
+ npt.assert_allclose(val, collect(name, **COLLECT_KW))
+ except AssertionError as e:
+ failures.append((nxpe, nype, name, e))
+
+ return failures
+
+ failures = []
+
+ for implementation in ["hermitespline", "monotonichermitespline"]:
+ for nslice in NSLICES:
+ # reference data!
+ run_case(1, 1, MAXCORES)
+
+ ref = {}
+ for i in range(4):
+ for yp in range(1, nslice + 1):
+ for y in [-yp, yp]:
+ name = f"output_{i}_{y:+d}"
+ ref[name] = collect(name, **COLLECT_KW)
+
+ for nxpe, nype in itertools.product(NLIST, NLIST):
+ if (nxpe, nype) == (1, 1):
+ # reference case, done above
+ continue
+
+ if nxpe * nype > MAXCORES:
+ continue
+
+ mthread = MAXCORES // (nxpe * nype)
+ failures_ = test_case(nxpe, nype, mthread, ref)
+ failures.extend(failures_)
+
+ success = len(failures) == 0
+
+ assert success, "\nSome tests failed:"
+
+ if not success:
+ for nxpe, nype, name, error in failures:
+ print("----------")
+ print(f"case {nxpe=} {nype=} {name=}\n{error}")
diff --git a/tests/integrated/test-fieldgroupComm/runtest b/tests/integrated/test-fieldgroupComm/runtest
deleted file mode 100755
index ca6d5f89f3..0000000000
--- a/tests/integrated/test-fieldgroupComm/runtest
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results
-#
-
-# Requires: all_tests
-# Requires: netcdf
-# Requires: not metric_3d
-# Cores: 4
-
-# Variables to compare
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-from numpy import abs, seterr
-from sys import stdout, exit
-
-# Good chance we'll do 0.0/0.0, which generates a warning
-# Ignore this warning
-seterr(divide="ignore", invalid="ignore")
-
-varCorrect = "fld1"
-varsComp = ["fld2", "fld3"]
-name = "FieldGroup comm"
-exeName = "test_fieldgroupcomm"
-tol = 1e-10 # Relative tolerance
-
-
-build_and_log("{nm} test".format(nm=name))
-
-print("Running {nm} test".format(nm=name))
-success = True
-
-for nproc in [1, 2, 4]:
- nxpe = 1
- if nproc > 2:
- nxpe = 2
-
- cmd = "./{exe} ".format(exe=exeName)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors ...." % (nproc))
- s, out = launch_safe(cmd, nproc=nproc, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Analyse result
- # /"Correct" answer
- f1 = collect(varCorrect, path="data", info=False)
- f1max = abs(f1).max()
- # /Two different fields which should be identical to correct
- err = []
- for v in varsComp:
- tmp = collect(v, path="data", info=False)
- err.append(abs((f1 - tmp)).max() / f1max)
-
- for i, e in enumerate(err):
- if e > tol:
- print("Fail, in {i}th comparison relative error is {re}".format(i=i, re=e))
- success = False
-
-
-if success:
- print(" => All {nm} passed".format(nm=name))
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-fieldgroupComm/test_fieldgroupComm.py b/tests/integrated/test-fieldgroupComm/test_fieldgroupComm.py
new file mode 100755
index 0000000000..917fdc0754
--- /dev/null
+++ b/tests/integrated/test-fieldgroupComm/test_fieldgroupComm.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, compare results
+#
+
+# Requires: all_tests
+# Requires: netcdf
+# Requires: not metric_3d
+# Cores: 4
+
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+from numpy import abs, seterr
+
+
+def test_fieldgroupcomm():
+
+ # Good chance we'll do 0.0/0.0, which generates a warning
+ # Ignore this warning
+ seterr(divide="ignore", invalid="ignore")
+
+ varCorrect = "fld1"
+ varsComp = ["fld2", "fld3"]
+ name = "FieldGroup comm"
+ exeName = "test_fieldgroupcomm"
+ tol = 1e-10 # Relative tolerance
+
+ print("Running {nm} test".format(nm=name))
+ success = True
+
+ for nproc in [1, 2, 4]:
+ cmd = "./{exe} ".format(exe=exeName)
+
+ shell("rm data/BOUT.dmp.*.nc")
+
+ print(" %d processors ...." % (nproc))
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ # Analyse result
+ # /"Correct" answer
+ f1 = collect(varCorrect, path="data", info=False)
+ f1max = abs(f1).max()
+ # /Two different fields which should be identical to correct
+ err = []
+ for v in varsComp:
+ tmp = collect(v, path="data", info=False)
+ err.append(abs((f1 - tmp)).max() / f1max)
+
+ for i, e in enumerate(err):
+ if e > tol:
+ print(
+ "Fail, in {i}th comparison relative error is {re}".format(i=i, re=e)
+ )
+ success = False
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-globalfield/test_globalfield.py b/tests/integrated/test-globalfield/test_globalfield.py
new file mode 100755
index 0000000000..ac2d993dd4
--- /dev/null
+++ b/tests/integrated/test-globalfield/test_globalfield.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./test_globalfield")
diff --git a/tests/integrated/test-griddata-yboundary-guards/runtest b/tests/integrated/test-griddata-yboundary-guards/runtest
deleted file mode 100755
index 79adae9759..0000000000
--- a/tests/integrated/test-griddata-yboundary-guards/runtest
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-from netCDF4 import Dataset
-import numpy
-import os.path
-from sys import stdout, exit
-
-# Cores: 6
-# Requires: netcdf
-
-build_and_log("griddata test")
-
-nx = 4
-ny = 24
-blocksize = ny / 6
-
-# first generate some grid files to test
-# double null case:
-for n_yguards in [0, 1, 2]:
- datadir = "data-doublenull-" + str(n_yguards)
- gridname = "grid-doublenull-" + str(n_yguards) + ".nc"
-
- with Dataset(os.path.join(datadir, gridname), "w") as gridfile:
- gridfile.createDimension("x", nx)
- gridfile.createDimension("y", ny + 4 * n_yguards)
-
- gridfile.createVariable("nx", numpy.int32)
- gridfile["nx"][...] = nx
-
- gridfile.createVariable("ny", numpy.int32)
- gridfile["ny"][...] = ny
-
- gridfile.createVariable("y_boundary_guards", numpy.int32)
- gridfile["y_boundary_guards"][...] = n_yguards
-
- gridfile.createVariable("MXG", numpy.int32)
- gridfile["MXG"][...] = 1
-
- gridfile.createVariable("MYG", numpy.int32)
- gridfile["MYG"][...] = 2 if n_yguards == 0 else n_yguards
-
- gridfile.createVariable("ixseps1", numpy.int32)
- gridfile["ixseps1"][...] = nx // 2 - 1
-
- gridfile.createVariable("ixseps2", numpy.int32)
- gridfile["ixseps2"][...] = nx // 2 - 1
-
- gridfile.createVariable("jyseps1_1", numpy.int32)
- gridfile["jyseps1_1"][...] = blocksize - 1
-
- gridfile.createVariable("jyseps2_1", numpy.int32)
- gridfile["jyseps2_1"][...] = 2 * blocksize - 1
-
- gridfile.createVariable("ny_inner", numpy.int32)
- gridfile["ny_inner"][...] = 3 * blocksize
-
- gridfile.createVariable("jyseps1_2", numpy.int32)
- gridfile["jyseps1_2"][...] = 4 * blocksize - 1
-
- gridfile.createVariable("jyseps2_2", numpy.int32)
- gridfile["jyseps2_2"][...] = 5 * blocksize - 1
-
- testdata = numpy.zeros([nx, ny + 4 * n_yguards])
- testdata[:, :] = numpy.arange(ny + 4 * n_yguards)[numpy.newaxis, :]
- gridfile.createVariable("test", float, ("x", "y"))
- gridfile["test"][...] = testdata
-
-# grid files for single-null:
-for n_yguards in [0, 1, 2]:
- datadir = "data-singlenull-" + str(n_yguards)
- gridname = "grid-singlenull-" + str(n_yguards) + ".nc"
-
- with Dataset(os.path.join(datadir, gridname), "w") as gridfile:
- gridfile.createDimension("x", nx)
- gridfile.createDimension("y", ny + 2 * n_yguards)
-
- gridfile.createVariable("nx", numpy.int32)
- gridfile["nx"][...] = nx
-
- gridfile.createVariable("ny", numpy.int32)
- gridfile["ny"][...] = ny
-
- gridfile.createVariable("y_boundary_guards", numpy.int32)
- gridfile["y_boundary_guards"][...] = n_yguards
-
- gridfile.createVariable("MXG", numpy.int32)
- gridfile["MXG"][...] = 1
-
- gridfile.createVariable("MYG", numpy.int32)
- gridfile["MYG"][...] = 2 if n_yguards == 0 else n_yguards
-
- gridfile.createVariable("ixseps1", numpy.int32)
- gridfile["ixseps1"][...] = nx // 2 - 1
-
- gridfile.createVariable("ixseps2", numpy.int32)
- gridfile["ixseps2"][...] = nx // 2 - 1
-
- gridfile.createVariable("jyseps1_1", numpy.int32)
- gridfile["jyseps1_1"][...] = blocksize - 1
-
- gridfile.createVariable("jyseps2_1", numpy.int32)
- gridfile["jyseps2_1"][...] = ny // 2
-
- gridfile.createVariable("ny_inner", numpy.int32)
- gridfile["ny_inner"][...] = ny // 2
-
- gridfile.createVariable("jyseps1_2", numpy.int32)
- gridfile["jyseps1_2"][...] = ny // 2
-
- gridfile.createVariable("jyseps2_2", numpy.int32)
- gridfile["jyseps2_2"][...] = 5 * blocksize - 1
-
- testdata = numpy.zeros([nx, ny + 2 * n_yguards])
- testdata[:, :] = numpy.arange(ny + 2 * n_yguards)[numpy.newaxis, :]
- gridfile.createVariable("test", float, ("x", "y"))
- gridfile["test"][...] = testdata
-
-
-for nproc in [6]:
- stdout.write("Checking %d processors ... " % (nproc))
-
- shell("rm ./data*/BOUT.dmp.*.nc run.log.*")
-
- success = True
-
- # double null tests
- for n_yguards in [0, 1, 2]:
- datadir = "data-doublenull-" + str(n_yguards)
-
- s, out = launch_safe("./test_griddata -d " + datadir, nproc=nproc, pipe=True)
-
- with open("run.log.doublenull." + str(nproc), "a") as f:
- f.write(out)
-
- testfield = collect("test", path=datadir, info=False, yguards=True)
-
- if n_yguards == 0:
- # output has 2 y-guard cells, but grid file did not
- myg = 2
- checkfield = list(numpy.zeros(myg))
- checkfield += list(numpy.arange(ny // 2))
- checkfield += list(numpy.arange(ny // 2) + checkfield[-1] + 1)
- checkfield += list(numpy.zeros(myg) + checkfield[-1])
- else:
- checkfield = []
- checkfield += list(numpy.arange(n_yguards))
- checkfield += list(numpy.arange(ny // 2) + checkfield[-1] + 1)
- checkfield += list(
- numpy.arange(ny // 2) + checkfield[-1] + 1 + 2 * n_yguards
- )
- checkfield += list(numpy.arange(n_yguards) + checkfield[-1] + 1)
- checkfield = numpy.array(checkfield)
-
- # Test value of testfield
- if numpy.max(numpy.abs(testfield - checkfield)) > 1e-13:
- print(
- "Failed: testfield does not match in doublenull case for n_yguards="
- + str(n_yguards)
- )
- success = False
-
- # single null tests
- for n_yguards in [0, 1, 2]:
- datadir = "data-singlenull-" + str(n_yguards)
-
- s, out = launch_safe("./test_griddata -d " + datadir, nproc=nproc, pipe=True)
-
- with open("run.log.singlenull." + str(nproc), "a") as f:
- f.write(out)
-
- testfield = collect("test", path=datadir, info=False, yguards=True)
-
- if n_yguards == 0:
- # output has 2 y-guard cells, but grid file did not
- myg = 2
- checkfield = list(numpy.zeros(myg))
- checkfield += list(numpy.arange(ny))
- checkfield += list(numpy.zeros(myg) + checkfield[-1])
- else:
- checkfield = []
- checkfield += list(numpy.arange(n_yguards))
- checkfield += list(numpy.arange(ny) + checkfield[-1] + 1)
- checkfield += list(numpy.arange(n_yguards) + checkfield[-1] + 1)
- checkfield = numpy.array(checkfield)
-
- # Test value of testfield
- if numpy.max(numpy.abs(testfield - checkfield)) > 1e-13:
- print(
- "Failed: testfield does not match in doublenull case for n_yguards="
- + str(n_yguards)
- )
- success = False
-
- if not success:
- exit(1)
- else:
- print("Passed")
-
-exit(0)
diff --git a/tests/integrated/test-griddata-yboundary-guards/test_griddata_yboundary_guards.py b/tests/integrated/test-griddata-yboundary-guards/test_griddata_yboundary_guards.py
new file mode 100755
index 0000000000..9666a2f762
--- /dev/null
+++ b/tests/integrated/test-griddata-yboundary-guards/test_griddata_yboundary_guards.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+from netCDF4 import Dataset
+import numpy
+from pathlib import Path
+from sys import stdout
+
+# Cores: 6
+# Requires: netcdf
+
+
+def test_griddata_yboundary_guards():
+
+ nx = 4
+ ny = 24
+ blocksize = ny / 6
+
+ # first generate some grid files to test
+ # double null case:
+ for n_yguards in [0, 1, 2]:
+ datadir = Path("data-doublenull-" + str(n_yguards))
+ gridname = "grid-doublenull-" + str(n_yguards) + ".nc"
+ file_path = datadir / gridname
+
+ with Dataset(file_path, "w") as gridfile:
+ gridfile.createDimension("x", nx)
+ gridfile.createDimension("y", ny + 4 * n_yguards)
+
+ gridfile.createVariable("nx", numpy.int32)
+ gridfile["nx"][...] = nx
+
+ gridfile.createVariable("ny", numpy.int32)
+ gridfile["ny"][...] = ny
+
+ gridfile.createVariable("y_boundary_guards", numpy.int32)
+ gridfile["y_boundary_guards"][...] = n_yguards
+
+ gridfile.createVariable("MXG", numpy.int32)
+ gridfile["MXG"][...] = 1
+
+ gridfile.createVariable("MYG", numpy.int32)
+ gridfile["MYG"][...] = 2 if n_yguards == 0 else n_yguards
+
+ gridfile.createVariable("ixseps1", numpy.int32)
+ gridfile["ixseps1"][...] = nx // 2 - 1
+
+ gridfile.createVariable("ixseps2", numpy.int32)
+ gridfile["ixseps2"][...] = nx // 2 - 1
+
+ gridfile.createVariable("jyseps1_1", numpy.int32)
+ gridfile["jyseps1_1"][...] = blocksize - 1
+
+ gridfile.createVariable("jyseps2_1", numpy.int32)
+ gridfile["jyseps2_1"][...] = 2 * blocksize - 1
+
+ gridfile.createVariable("ny_inner", numpy.int32)
+ gridfile["ny_inner"][...] = 3 * blocksize
+
+ gridfile.createVariable("jyseps1_2", numpy.int32)
+ gridfile["jyseps1_2"][...] = 4 * blocksize - 1
+
+ gridfile.createVariable("jyseps2_2", numpy.int32)
+ gridfile["jyseps2_2"][...] = 5 * blocksize - 1
+
+ testdata = numpy.zeros([nx, ny + 4 * n_yguards])
+ testdata[:, :] = numpy.arange(ny + 4 * n_yguards)[numpy.newaxis, :]
+ gridfile.createVariable("test", float, ("x", "y"))
+ gridfile["test"][...] = testdata
+
+ # grid files for single-null:
+ for n_yguards in [0, 1, 2]:
+ datadir = Path("data-singlenull-" + str(n_yguards))
+ gridname = "grid-singlenull-" + str(n_yguards) + ".nc"
+ file_path = datadir / gridname
+
+ with Dataset(file_path, "w") as gridfile:
+ gridfile.createDimension("x", nx)
+ gridfile.createDimension("y", ny + 2 * n_yguards)
+
+ gridfile.createVariable("nx", numpy.int32)
+ gridfile["nx"][...] = nx
+
+ gridfile.createVariable("ny", numpy.int32)
+ gridfile["ny"][...] = ny
+
+ gridfile.createVariable("y_boundary_guards", numpy.int32)
+ gridfile["y_boundary_guards"][...] = n_yguards
+
+ gridfile.createVariable("MXG", numpy.int32)
+ gridfile["MXG"][...] = 1
+
+ gridfile.createVariable("MYG", numpy.int32)
+ gridfile["MYG"][...] = 2 if n_yguards == 0 else n_yguards
+
+ gridfile.createVariable("ixseps1", numpy.int32)
+ gridfile["ixseps1"][...] = nx // 2 - 1
+
+ gridfile.createVariable("ixseps2", numpy.int32)
+ gridfile["ixseps2"][...] = nx // 2 - 1
+
+ gridfile.createVariable("jyseps1_1", numpy.int32)
+ gridfile["jyseps1_1"][...] = blocksize - 1
+
+ gridfile.createVariable("jyseps2_1", numpy.int32)
+ gridfile["jyseps2_1"][...] = ny // 2
+
+ gridfile.createVariable("ny_inner", numpy.int32)
+ gridfile["ny_inner"][...] = ny // 2
+
+ gridfile.createVariable("jyseps1_2", numpy.int32)
+ gridfile["jyseps1_2"][...] = ny // 2
+
+ gridfile.createVariable("jyseps2_2", numpy.int32)
+ gridfile["jyseps2_2"][...] = 5 * blocksize - 1
+
+ testdata = numpy.zeros([nx, ny + 2 * n_yguards])
+ testdata[:, :] = numpy.arange(ny + 2 * n_yguards)[numpy.newaxis, :]
+ gridfile.createVariable("test", float, ("x", "y"))
+ gridfile["test"][...] = testdata
+
+ for nproc in [6]:
+ stdout.write("Checking %d processors ... " % (nproc))
+
+ shell(["rm ./data*/BOUT.dmp.*.nc run.log.*"])
+
+ success = True
+
+ # double null tests
+ for n_yguards in [0, 1, 2]:
+ datadir = "data-doublenull-" + str(n_yguards)
+
+ s, out = launch_safe(
+ "./test_griddata -d " + datadir, nproc=nproc, pipe=True
+ )
+
+ with open("run.log.doublenull." + str(nproc), "a") as f:
+ f.write(out)
+
+ testfield = collect("test", path=datadir, info=False, yguards=True)
+
+ if n_yguards == 0:
+ # output has 2 y-guard cells, but grid file did not
+ myg = 2
+ checkfield = list(numpy.zeros(myg))
+ checkfield += list(numpy.arange(ny // 2))
+ checkfield += list(numpy.arange(ny // 2) + checkfield[-1] + 1)
+ checkfield += list(numpy.zeros(myg) + checkfield[-1])
+ else:
+ checkfield = []
+ checkfield += list(numpy.arange(n_yguards))
+ checkfield += list(numpy.arange(ny // 2) + checkfield[-1] + 1)
+ checkfield += list(
+ numpy.arange(ny // 2) + checkfield[-1] + 1 + 2 * n_yguards
+ )
+ checkfield += list(numpy.arange(n_yguards) + checkfield[-1] + 1)
+ checkfield = numpy.array(checkfield)
+
+ # Test value of testfield
+ if numpy.max(numpy.abs(testfield - checkfield)) > 1e-13:
+ print(
+ "Failed: testfield does not match in doublenull case for n_yguards="
+ + str(n_yguards)
+ )
+ success = False
+
+ # single null tests
+ for n_yguards in [0, 1, 2]:
+ datadir = "data-singlenull-" + str(n_yguards)
+
+ s, out = launch_safe(
+ "./test_griddata -d " + datadir, nproc=nproc, pipe=True
+ )
+
+ with open("run.log.singlenull." + str(nproc), "a") as f:
+ f.write(out)
+
+ testfield = collect("test", path=datadir, info=False, yguards=True)
+
+ if n_yguards == 0:
+ # output has 2 y-guard cells, but grid file did not
+ myg = 2
+ checkfield = list(numpy.zeros(myg))
+ checkfield += list(numpy.arange(ny))
+ checkfield += list(numpy.zeros(myg) + checkfield[-1])
+ else:
+ checkfield = []
+ checkfield += list(numpy.arange(n_yguards))
+ checkfield += list(numpy.arange(ny) + checkfield[-1] + 1)
+ checkfield += list(numpy.arange(n_yguards) + checkfield[-1] + 1)
+ checkfield = numpy.array(checkfield)
+
+ # Test value of testfield
+ if numpy.max(numpy.abs(testfield - checkfield)) > 1e-13:
+ print(
+ "Failed: testfield does not match in doublenull case for n_yguards="
+ + str(n_yguards)
+ )
+ success = False
+
+ assert success
diff --git a/tests/integrated/test-griddata/runtest b/tests/integrated/test-griddata/runtest
deleted file mode 100755
index 6aef955023..0000000000
--- a/tests/integrated/test-griddata/runtest
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python3
-
-from __future__ import print_function
-from __future__ import division
-
-try:
- from builtins import str
-except:
- pass
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
-
-build_and_log("griddata test")
-
-for nproc in [1]:
- stdout.write("Checking %d processors ... " % (nproc))
-
- shell("rm ./data*nc")
- s, out = launch_safe("./test_griddata -d screw", nproc=nproc, pipe=True)
-
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- prefix = "data"
- Rxy = collect("Rxy", prefix=prefix, info=False)
- Bpxy = collect("Bpxy", prefix=prefix, info=False)
- dx = collect("dx", prefix=prefix, info=False)
-
- nx, ny = Rxy.shape
-
- # Handle 3D metric case
- if len(dx.shape) == 3:
- dx = dx[:, :, 0]
-
- rwidth = 0.4
- dr = float(rwidth) / nx
-
- # Test value of dx
- if not np.allclose(dx, dr * Bpxy * Rxy, atol=1e-7):
- print("Failed: dx does not match")
- exit(1)
-
- print("Passed")
-
-exit(0)
diff --git a/tests/integrated/test-griddata/test_griddata.py b/tests/integrated/test-griddata/test_griddata.py
new file mode 100755
index 0000000000..82f86fd459
--- /dev/null
+++ b/tests/integrated/test-griddata/test_griddata.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+import numpy.testing as npt
+from sys import stdout
+
+
+def test_griddata():
+ for nproc in [1]:
+ stdout.write("Checking %d processors ... " % (nproc))
+
+ shell("rm ./data*nc")
+ s, out = launch_safe("./test_griddata -d screw", nproc=nproc, pipe=True)
+
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ prefix = "data"
+ Rxy = collect("Rxy", prefix=prefix, info=False)
+ Bpxy = collect("Bpxy", prefix=prefix, info=False)
+ dx = collect("dx", prefix=prefix, info=False)
+
+ nx, ny = Rxy.shape
+
+ # Handle 3D metric case
+ if len(dx.shape) == 3:
+ dx = dx[:, :, 0]
+
+ rwidth = 0.4
+ dr = float(rwidth) / nx
+
+ # Test value of dx
+ npt.assert_allclose(
+ dx, dr * Bpxy * Rxy, atol=1e-7, err_msg="Failed: dx does not match"
+ )
+
+ print("Passed")
diff --git a/tests/integrated/test-gyro/runtest b/tests/integrated/test-gyro/runtest
deleted file mode 100755
index 7a0209fa57..0000000000
--- a/tests/integrated/test-gyro/runtest
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-# requires: not metric_3d
-# Requires: netcdf
-# Cores: 4
-
-# Variables to compare
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-vars = ["pade1", "pade2"]
-
-tol = 1e-7 # Absolute tolerance, benchmark values are floats
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
-
-build_and_log("Gyro-average inversion test")
-
-# Read benchmark values
-print("Reading benchmark data")
-bmk = {}
-for v in vars:
- bmk[v] = collect(v, path="data", prefix="benchmark", info=False, xguards=False)
-
-print("Running Gyro-average inversion test")
-success = True
-
-for nproc in [1, 2, 4]:
- nxpe = 1
- if nproc > 2:
- nxpe = 2
-
- cmd = "./test_gyro NXPE=" + str(nxpe)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors (nxpe = %d)...." % (nproc, nxpe))
- s, out = launch_safe(cmd, nproc=nproc, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect output data
- for v in vars:
- stdout.write(" Checking variable " + v + " ... ")
- result = collect(v, path="data", info=False, xguards=False)
- # Compare benchmark and output
- if np.shape(bmk[v]) != np.shape(result):
- print("Fail, wrong shape")
- success = False
- diff = np.max(np.abs(bmk[v] - result))
- if diff > tol:
- print("Fail, maximum difference = " + str(diff))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All Gyro-average tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-gyro/test_gyro.py b/tests/integrated/test-gyro/test_gyro.py
new file mode 100755
index 0000000000..5f2f984ea0
--- /dev/null
+++ b/tests/integrated/test-gyro/test_gyro.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+from pathlib import Path
+
+#
+# Run the test, compare results against the benchmark
+#
+
+# requires: not metric_3d
+# Requires: netcdf
+# Cores: 4
+
+import pytest
+import numpy as np
+from sys import stdout
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+# Variables to compare
+variables = ["pade1", "pade2"]
+
+tol = 1e-7 # Absolute tolerance, benchmark values are floats
+
+
+@pytest.fixture(scope="module")
+def benchmark_data():
+ """Reads the benchmark data once for all test iterations in this module."""
+ bmk = {}
+ source_data_dir = str(Path(__file__).parent / "data")
+
+ for v in variables:
+ bmk[v] = collect(
+ v, path=source_data_dir, prefix="benchmark", info=False, xguards=False
+ )
+ return bmk
+
+
+def test_gyro(benchmark_data):
+
+ print("Running Gyro-average inversion test")
+
+ for nproc in [1, 2, 4]:
+ nxpe = 1
+ if nproc > 2:
+ nxpe = 2
+
+ cmd = f"./test_gyro NXPE={nxpe}"
+
+ shell("rm -f data/BOUT.dmp.*.nc")
+
+ print(" %d processors (nxpe = %d)...." % (nproc, nxpe))
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ # Collect output data
+ for v in variables:
+ stdout.write(" Checking variable " + v + " ... ")
+ result = collect(v, path="data", info=False, xguards=False)
+ # Compare benchmark and output
+ np.testing.assert_allclose(
+ benchmark_data[v],
+ result,
+ atol=tol,
+ rtol=0,
+ err_msg="Gyro-average output mismatch",
+ )
diff --git a/tests/integrated/test-include/.gitignore b/tests/integrated/test-include/.gitignore
deleted file mode 100644
index 1ccb766f4e..0000000000
--- a/tests/integrated/test-include/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-test.cxx
diff --git a/tests/integrated/test-include/README.md b/tests/integrated/test-include/README.md
deleted file mode 100644
index 292b5d3a4e..0000000000
--- a/tests/integrated/test-include/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-test-include
-============
-
-This test ensures that all header files from bout can be included on
-their own. It compiles a test file with one header, and if that fails,
-then the headerfile does not include all files it needs.
-
-Checking whether the library builds as whole is not sufficient, as
-either the file might never be included directly, or some other header
-file might always be included first, which fulfils the requirements,
-thus there is this extra test.
diff --git a/tests/integrated/test-include/runtest b/tests/integrated/test-include/runtest
deleted file mode 100755
index 4fa8096c4b..0000000000
--- a/tests/integrated/test-include/runtest
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-
-#requires: all_tests
-#Requires: not make
-
-BOUT_TOP=../../..
-export PATH=$BOUT_TOP/bin:$PATH
-
-error=0
-
-failed=
-for header in $(cd $BOUT_TOP/include ; find |grep hxx\$)
-do
- echo "#include <$header>" > test.cxx
-
- $(bout-config --cxx) $(bout-config --cflags) test.cxx -c
-
- ex=$?
-
- if test $ex -gt 0
- then
- echo $header failed
- error=1
- failed="$failed
-$header failed"
- fi
-
-done
-
-echo $failed
-
-exit $error
diff --git a/tests/integrated/test-initial/runtest b/tests/integrated/test-initial/runtest
deleted file mode 100755
index 25ad7d09f1..0000000000
--- a/tests/integrated/test-initial/runtest
+++ /dev/null
@@ -1,243 +0,0 @@
-#!/usr/bin/env python3
-
-# Test initial conditions
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-
-import configparser
-import itertools
-from scipy.special import erf
-import numpy as np
-import os
-
-# requires not make
-# requires scipy
-# cores: 4
-
-########################################
-# Implementations of BOUT++ functions
-
-
-def bout_round(x):
- """
- BOUT++ rounding
- """
- return x + 0.5 if x > 0.0 else x - 0.5
-
-
-def genRand(seed):
- """
- BOUT++ psuedo random number generator
-
- This PRNG has no memory, i.e. you need to call it with a different
- seed each time
- """
- # Make sure seed is
- if seed < 0.0:
- seed *= -1
-
- # Round the seed to get the number of iterations
- niter = int(11 + (23 + bout_round(seed)) % 79)
-
- # Start x between 0 and 1
- A = 0.01
- B = 1.23456789
- x = (A + np.mod(seed, B)) / (B + 2.0 * A)
-
- # Iterate logistic map
- for i in range(niter):
- x = 3.99 * x * (1.0 - x)
-
- return x
-
-
-def ballooning(x, ball_n=3):
- """
- Ballooning function. Currently too tricky to implement
- """
- raise NotImplementedError("ballooning")
-
-
-def gauss(x, width=1.0):
- """
- Normalised gaussian
- """
- return np.exp(-(x**2) / (2 * width**2)) / np.sqrt(2 * np.pi)
-
-
-def mixmode(x, seed=0.5):
- """
- 14 modes with random phases
- """
- result = 0.0
- for i in range(14):
- phase = np.pi * (2.0 * genRand(seed + i) - 1.0)
- result += (1.0 / (1.0 + np.abs(i - 4.0)) ** 2) * np.cos(i * x + phase)
- return result
-
-
-def heaviside(x):
- """
- Heaviside step function
- """
- return 1 * (x > 0)
-
-
-def tanhhat(x, width, centre, steepness):
- """
- BOUT++ TanhHat function
- """
- return 0.5 * (
- np.tanh(steepness * (x - (centre - width / 2.0)))
- + np.tanh(-steepness * (x - (centre + width / 2.0)))
- )
-
-
-def atan(x, y=None):
- """
- Resolves to either np.arctan or np.arctan depending on the number of arguments
- """
- if y is not None:
- return np.arctan2(x, y)
- else:
- return np.arctan(x)
-
-
-def max(*args):
- """
- Maximum of *args at each point
- """
- current = args[0]
- for arg in args:
- current = np.maximum(arg, current)
- return current
-
-
-def min(*args):
- """
- Minimum of *args at each point
- """
- current = args[0]
- for arg in args:
- current = np.minimum(arg, current)
- return current
-
-
-def fmod(x, denominator=1.0):
- """
- Modulo operator using fmod convention, (rem in Matlab)
- """
- return np.fmod(x, denominator)
-
-
-# Rename functions to match BOUT++ naming
-# Mostly just alternative names to numpy functions
-abs = np.abs
-asin = np.arcsin
-acos = np.arccos
-ballooning = ballooning
-cos = np.cos
-cosh = np.cosh
-exp = np.exp
-tanh = np.tanh
-H = heaviside
-log = np.log
-power = np.power
-sin = np.sin
-sinh = np.sinh
-sqrt = np.sqrt
-tan = np.tan
-TanhHat = tanhhat
-pi = np.pi
-
-########################################
-# Running the test
-
-# Some parameters
-success = True
-tolerance = 1e-13
-cmd = "./test_initial"
-datadir = "data"
-inputfile = os.path.join(datadir, "BOUT.inp")
-
-# Read the input file
-config = configparser.ConfigParser()
-with open(inputfile, "r") as f:
- config.read_file(itertools.chain(["[global]"], f), source=inputfile)
-
-# Find the variables that have a "function" option
-varlist = [key for key, values in config.items() if "function" in values]
-
-# Remove the coordinate arrays
-for coord in ["var_x", "var_y", "var_z"]:
- varlist.remove(coord)
-
-build_and_log("initial conditions test")
-
-nprocs = [1, 2, 3, 4]
-for nproc in nprocs:
- status, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
- with open("run.log.{}".format(nproc), "w") as f:
- f.write(out)
-
- if status != 0:
- print("=> Could not run test")
- print(status)
- exit(status)
-
- # Collect the coordinate arrays separately
- x = collect("var_x", xguards=True, yguards=True, path=datadir, info=False)
- y = collect("var_y", xguards=True, yguards=True, path=datadir, info=False)
- z = collect("var_z", xguards=True, yguards=True, path=datadir, info=False)
-
- # Evaluate the functions
- for var in varlist:
- function = config[var]["function"]
- function = function.replace("^", "**")
- if ":" in function:
- print(
- "{} contains reference to variable - not possible to resolve at this time".format(
- var
- )
- )
- continue
- try:
- analytic = eval(function)
- except NotImplementedError as err:
- print("{} not implemented, skipping".format(err.args[0]))
- else:
- data = collect(var, xguards=True, yguards=True, path=datadir, info=False)
- E2 = np.sqrt(np.mean((analytic - data) ** 2))
- if E2 < tolerance:
- success_string = "PASS"
- else:
- if (var == "mixmode" or var == "mixmode_seed") and E2 < 1e-3:
- import platform
-
- arch = platform.machine()
- if arch == "i686":
- # This can happen due tue excess precision e.g. on X87 architecture
- success_string = "WARNING"
- else:
- print(
- "This should only happen in i686 with an x87 math architecture."
- )
- print("We detected however an %s architecture." % arch)
- success_string = "FAIL"
- success = False
- else:
- success_string = "FAIL"
- success = False
- print(
- "\tChecking {var:<12}: l-2: {err:.4e} ... {success}".format(
- var=var, err=E2, success=success_string
- )
- )
-
-if success:
- print(" => All tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-initial/test_initial.py b/tests/integrated/test-initial/test_initial.py
new file mode 100755
index 0000000000..cf61514fac
--- /dev/null
+++ b/tests/integrated/test-initial/test_initial.py
@@ -0,0 +1,242 @@
+#!/usr/bin/env python3
+
+# Test initial conditions
+
+import pytest
+import configparser
+import itertools
+import numpy as np
+from scipy.special import erf
+from pathlib import Path
+
+from boututils.run_wrapper import launch_safe
+from boutdata.collect import collect
+
+# requires not make
+# requires scipy
+# cores: 4
+
+########################################
+# Implementations of BOUT++ functions
+
+
+def bout_round(x):
+ """
+ BOUT++ rounding
+ """
+ return x + 0.5 if x > 0.0 else x - 0.5
+
+
+def genRand(seed):
+ """
+ BOUT++ psuedo random number generator
+
+ This PRNG has no memory, i.e. you need to call it with a different
+ seed each time
+ """
+ # Make sure seed is
+ if seed < 0.0:
+ seed *= -1
+
+ # Round the seed to get the number of iterations
+ niter = int(11 + (23 + bout_round(seed)) % 79)
+
+ # Start x between 0 and 1
+ A = 0.01
+ B = 1.23456789
+ x = (A + np.mod(seed, B)) / (B + 2.0 * A)
+
+ # Iterate logistic map
+ for i in range(niter):
+ x = 3.99 * x * (1.0 - x)
+
+ return x
+
+
+def ballooning(x, ball_n=3):
+ """
+ Ballooning function. Currently too tricky to implement
+ """
+ raise NotImplementedError("ballooning")
+
+
+def gauss(x, width=1.0):
+ """
+ Normalised gaussian
+ """
+ return np.exp(-(x**2) / (2 * width**2)) / np.sqrt(2 * np.pi)
+
+
+def mixmode(x, seed=0.5):
+ """
+ 14 modes with random phases
+ """
+ result = 0.0
+ for i in range(14):
+ phase = np.pi * (2.0 * genRand(seed + i) - 1.0)
+ result += (1.0 / (1.0 + np.abs(i - 4.0)) ** 2) * np.cos(i * x + phase)
+ return result
+
+
+def heaviside(x):
+ """
+ Heaviside step function
+ """
+ return 1 * (x > 0)
+
+
+def tanhhat(x, width, centre, steepness):
+ """
+ BOUT++ TanhHat function
+ """
+ return 0.5 * (
+ np.tanh(steepness * (x - (centre - width / 2.0)))
+ + np.tanh(-steepness * (x - (centre + width / 2.0)))
+ )
+
+
+def atan(x, y=None):
+ """
+ Resolves to either np.arctan or np.arctan depending on the number of arguments
+ """
+ if y is not None:
+ return np.arctan2(x, y)
+ else:
+ return np.arctan(x)
+
+
+def max(*args):
+ """
+ Maximum of *args at each point
+ """
+ current = args[0]
+ for arg in args:
+ current = np.maximum(arg, current)
+ return current
+
+
+def min(*args):
+ """
+ Minimum of *args at each point
+ """
+ current = args[0]
+ for arg in args:
+ current = np.minimum(arg, current)
+ return current
+
+
+def fmod(x, denominator=1.0):
+ """
+ Modulo operator using fmod convention, (rem in Matlab)
+ """
+ return np.fmod(x, denominator)
+
+
+# Rename functions to match BOUT++ naming
+# Mostly just alternative names to numpy functions
+abs = np.abs
+asin = np.arcsin
+acos = np.arccos
+ballooning = ballooning
+cos = np.cos
+cosh = np.cosh
+exp = np.exp
+tanh = np.tanh
+H = heaviside
+log = np.log
+power = np.power
+sin = np.sin
+sinh = np.sinh
+sqrt = np.sqrt
+tan = np.tan
+TanhHat = tanhhat
+pi = np.pi
+erf = erf
+
+
+def test_initial():
+ # Running the test
+
+ # Some parameters
+ success = True
+ tolerance = 1e-13
+ cmd = "./test_initial"
+ datadir = Path("data")
+ inputfile = datadir / "BOUT.inp"
+
+ # Read the input file
+ config = configparser.ConfigParser()
+ with open(inputfile, "r") as f:
+ config.read_file(itertools.chain(["[global]"], f), source=inputfile)
+
+ # Find the variables that have a "function" option
+ varlist = [key for key, values in config.items() if "function" in values]
+
+ # Remove the coordinate arrays
+ for coord in ["var_x", "var_y", "var_z"]:
+ varlist.remove(coord)
+
+ nprocs = [1, 2, 3, 4]
+ for nproc in nprocs:
+ status, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
+ with open("run.log.{}".format(nproc), "w") as f:
+ f.write(out)
+
+ if status != 0:
+ print(status)
+ pytest.fail("=> Could not run test")
+
+ # Collect the coordinate arrays separately
+ x = collect("var_x", xguards=True, yguards=True, path=datadir, info=False)
+ y = collect("var_y", xguards=True, yguards=True, path=datadir, info=False)
+ z = collect("var_z", xguards=True, yguards=True, path=datadir, info=False)
+
+ # Evaluate the functions
+ for var in varlist:
+ function = config[var]["function"]
+ function = function.replace("^", "**")
+ if ":" in function:
+ print(
+ "{} contains reference to variable - not possible to resolve at this time".format(
+ var
+ )
+ )
+ continue
+
+ context = {"x": x, "y": y, "z": z}
+ try:
+ analytic = eval(function, globals(), context)
+ except NotImplementedError as err:
+ print("{} not implemented, skipping".format(err.args[0]))
+ else:
+ data = collect(
+ var, xguards=True, yguards=True, path=datadir, info=False
+ )
+ E2 = np.sqrt(np.mean((analytic - data) ** 2))
+ if E2 < tolerance:
+ success_string = "PASS"
+ else:
+ if (var == "mixmode" or var == "mixmode_seed") and E2 < 1e-3:
+ import platform
+
+ arch = platform.machine()
+ if arch == "i686":
+ # This can happen due tue excess precision e.g. on X87 architecture
+ success_string = "WARNING"
+ else:
+ print(
+ "This should only happen in i686 with an x87 math architecture."
+ )
+ print("We detected however an %s architecture." % arch)
+ success_string = "FAIL"
+ success = False
+ else:
+ success_string = "FAIL"
+ success = False
+ print(
+ "\tChecking {var:<12}: l-2: {err:.4e} ... {success}".format(
+ var=var, err=E2, success=success_string
+ )
+ )
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-integrate/test_integrate.py b/tests/integrated/test-integrate/test_integrate.py
new file mode 100755
index 0000000000..d8b4073b66
--- /dev/null
+++ b/tests/integrated/test-integrate/test_integrate.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./test_integrate")
diff --git a/tests/integrated/test-interchange-instability/runtest b/tests/integrated/test-interchange-instability/runtest
deleted file mode 100755
index 0f75157aaf..0000000000
--- a/tests/integrated/test-interchange-instability/runtest
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-# requires: not metric_3d
-# requires: netcdf
-# cores: 2
-
-from __future__ import print_function
-from __future__ import division
-
-nproc = 2 # Number of processors to run on
-reltol = 1.0e-3 # Allowed relative tolerance in growth-rate
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
-
-nthreads = 1
-
-build_and_log("interchange instability test")
-
-# Delete old output files
-shell("rm data_1/BOUT.dmp.*")
-shell("rm data_10/BOUT.dmp.*")
-
-
-def growth_rate(path, nproc, log=False):
- pipe = False
- if log != False:
- pipe = True
- s, out = launch_safe(
- "./2fluid -d " + path, nproc=nproc, mthread=nthreads, pipe=pipe
- )
- if pipe:
- f = open(log, "w")
- f.write(out)
- f.close()
- # Get time base
- tarr = collect("t_array", path=path)
- nt = len(tarr)
- wci = collect("wci", path=path)
- tarr = tarr / wci
-
- # Read density
- Ni = collect("Ni", path=path, xind=5, yind=30, info=False)
-
- # Calculate RMS value in Z
- Nirms = np.zeros(nt)
- for t in np.arange(nt):
- Nirms[t] = np.sqrt(np.mean(Ni[t, 0, 0, :] ** 2))
-
- # Get the growth rate
- ln = np.log(Nirms)
- return (ln[nt - 5] - 8.0 * ln[nt - 4] + 8.0 * ln[nt - 2] - ln[nt - 1]) / (
- 6.0 * (tarr[nt - 2] - tarr[nt - 4])
- )
-
-
-code = 0 # Return code
-
-# Run case 2
-
-print("Test case 1: R = 1m")
-growth = growth_rate("data_1", nproc, log="run_1.log")
-print(" Log file run_1.log")
-orig = (
- 2.148177e05 # 24th October 2011, revision c4f7ec92786b333a5502c5256b5e602ba867090f
-)
-analytic = 2.2e5
-print(" Growth-rate = %e, original = %e, analytic = %e" % (growth, orig, analytic))
-absdev = abs(growth - orig)
-reldev = absdev / orig
-print(" Deviation from original: %e (%e %%)" % (absdev, reldev * 100.0))
-
-if reldev > reltol:
- print(" => Failed")
- exit(1)
-else:
- print(" => Passed")
-
-# Run case 2
-
-print("Test case 2: R = 10m")
-growth = growth_rate("data_10", nproc, log="run_10.log")
-print(" Log file run_10.log")
-# orig = 65570. # 24th October 2011, revision c4f7ec92786b333a5502c5256b5e602ba867090f
-orig = 6.457835e04 # 25th April 2014, revision fd032da
-analytic = 6.3e4
-print(" Growth-rate = %e, original = %e, analytic = %e" % (growth, orig, analytic))
-absdev = abs(growth - orig)
-reldev = absdev / orig
-print(" Deviation from original: %e (%e %%)" % (absdev, reldev * 100.0))
-
-if reldev > reltol:
- print(" => Failed")
- code = 1
-else:
- print(" => Passed")
-
-exit(code)
diff --git a/tests/integrated/test-interchange-instability/test_interchange_instability.py b/tests/integrated/test-interchange-instability/test_interchange_instability.py
new file mode 100755
index 0000000000..2bc9b1c461
--- /dev/null
+++ b/tests/integrated/test-interchange-instability/test_interchange_instability.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, compare results against the benchmark
+#
+
+# requires: not metric_3d
+# requires: netcdf
+# cores: 2
+
+import numpy as np
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+nproc = 2 # Number of processors to run on
+reltol = 1.0e-3 # Allowed relative tolerance in growth-rate
+
+nthreads = 1
+
+
+def test_interchange_instability():
+
+ # Delete old output files
+ shell("rm data_1/BOUT.dmp.*")
+ shell("rm data_10/BOUT.dmp.*")
+
+ def growth_rate(path, nproc, log=False):
+ pipe = False
+ if log:
+ pipe = True
+ s, out = launch_safe(
+ "./2fluid -d " + path, nproc=nproc, mthread=nthreads, pipe=pipe
+ )
+ if pipe:
+ f = open(log, "w")
+ f.write(out)
+ f.close()
+ # Get time base
+ tarr = collect("t_array", path=path)
+ nt = len(tarr)
+ wci = collect("wci", path=path)
+ tarr = tarr / wci
+
+ # Read density
+ Ni = collect("Ni", path=path, xind=5, yind=30, info=False)
+
+ # Calculate RMS value in Z
+ Nirms = np.zeros(nt)
+ for t in np.arange(nt):
+ Nirms[t] = np.sqrt(np.mean(Ni[t, 0, 0, :] ** 2))
+
+ # Get the growth rate
+ ln = np.log(Nirms)
+ return (ln[nt - 5] - 8.0 * ln[nt - 4] + 8.0 * ln[nt - 2] - ln[nt - 1]) / (
+ 6.0 * (tarr[nt - 2] - tarr[nt - 4])
+ )
+
+ # Run case 2
+
+ print("Test case 1: R = 1m")
+ growth = growth_rate("data_1", nproc, log="run_1.log")
+ print(" Log file run_1.log")
+ orig = 2.148177e05 # 24th October 2011, revision c4f7ec92786b333a5502c5256b5e602ba867090f
+ analytic = 2.2e5
+ print(
+ " Growth-rate = %e, original = %e, analytic = %e" % (growth, orig, analytic)
+ )
+ absdev = abs(growth - orig)
+ reldev = absdev / orig
+ print(" Deviation from original: %e (%e %%)" % (absdev, reldev * 100.0))
+
+ assert reldev <= reltol, " => Failed"
+
+ # Run case 2
+
+ print("Test case 2: R = 10m")
+ growth = growth_rate("data_10", nproc, log="run_10.log")
+ print(" Log file run_10.log")
+ # orig = 65570. # 24th October 2011, revision c4f7ec92786b333a5502c5256b5e602ba867090f
+ orig = 6.457835e04 # 25th April 2014, revision fd032da
+ analytic = 6.3e4
+ print(
+ " Growth-rate = %e, original = %e, analytic = %e" % (growth, orig, analytic)
+ )
+ absdev = abs(growth - orig)
+ reldev = absdev / orig
+ print(" Deviation from original: %e (%e %%)" % (absdev, reldev * 100.0))
+
+ assert reldev <= reltol, " => Failed"
diff --git a/tests/integrated/test-interpolate-z/runtest b/tests/integrated/test-interpolate-z/runtest
deleted file mode 100755
index 1b461d8d4f..0000000000
--- a/tests/integrated/test-interpolate-z/runtest
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata import collect
-from numpy import sqrt, max, abs, mean, array, log, polyfit
-from sys import stdout, exit
-
-# Display the plots as well as saving to file
-show_plot = False
-
-# List of NX values to use
-nxlist = [16, 32, 64, 128]
-
-# Only testing 2D (x, z) slices, so only need one processor
-nproc = 1
-
-# Variables to compare
-varlist = ["a", "b", "c"]
-markers = ["bo", "r^", "kx"]
-labels = [r"$" + var + r"$" for var in varlist]
-
-methods = {
- "hermitespline": 4,
-}
-
-
-build_and_log("ZInterpolation test")
-
-print("Running ZInterpolation test")
-success = True
-
-for method in methods:
- print("------------------------------")
- print("Using {} interpolation".format(method))
-
- error_2 = {}
- error_inf = {}
- for var in varlist:
- error_2[var] = [] # L2 error (RMS)
- error_inf[var] = [] # Maximum error
-
- for nx in nxlist:
- dx = 1.0 / (nx)
-
- args = (
- " mesh:nx={nx4} mesh:dx={dx} MZ={nx} zinterpolation:type={method}".format(
- nx4=nx + 4, dx=dx, nx=nx, method=method
- )
- )
-
- cmd = "./test_interpolate" + args
-
- shell("rm data/BOUT.dmp.*.nc")
-
- s, out = launch_safe(cmd, nproc=nproc, pipe=True)
- with open("run.log.{}.{}".format(method, nx), "w") as f:
- f.write(out)
-
- # Collect output data
- for var in varlist:
- interp = collect(var + "_interp", path="data", xguards=False, info=False)
- solution = collect(
- var + "_solution", path="data", xguards=False, info=False
- )
-
- E = interp - solution
-
- l2 = float(sqrt(mean(E**2)))
- linf = float(max(abs(E)))
-
- error_2[var].append(l2)
- error_inf[var].append(linf)
-
- print("{0:s} : l-2 {1:.8f} l-inf {2:.8f}".format(var, l2, linf))
-
- dx = 1.0 / array(nxlist)
-
- for var in varlist:
- fit = polyfit(log(dx), log(error_2[var]), 1)
- order = fit[0]
- stdout.write("{0:s} Convergence order = {1:.2f}".format(var, order))
-
- # Make sure scaling is at least 90% of expected order
- if order < 0.9 * methods[method]:
- print("............ FAIL")
- success = False
- else:
- print("............ PASS")
-
- if False:
- try:
- import matplotlib.pyplot as plt
-
- # Plot errors
- plt.figure()
-
- for var, mark, label in zip(varlist, markers, labels):
- plt.plot(dx, error_2[var], "-" + mark, label=label)
- plt.plot(dx, error_inf[var], "--" + mark)
-
- plt.legend(loc="upper left")
- plt.grid()
-
- plt.yscale("log")
- plt.xscale("log")
-
- plt.xlabel(r"Mesh spacing $\delta x$")
- plt.ylabel("Error norm")
- plt.title("Error scaling for {}".format(method))
-
- name = "error_scaling_{}.pdf".format(method)
- plt.savefig(name)
- print("Plot saved to {}".format(name))
-
- if show_plot:
- plt.show()
- plt.close()
- except ImportError:
- print("No matplotlib available")
- else:
- print("Plotting disabled")
-
-print("------------------------------")
-if success:
- print(" => All Interpolation tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-interpolate-z/test_interpolate_z.py b/tests/integrated/test-interpolate-z/test_interpolate_z.py
new file mode 100755
index 0000000000..7fdc02ea1b
--- /dev/null
+++ b/tests/integrated/test-interpolate-z/test_interpolate_z.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, compare results against the benchmark
+#
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata import collect
+from numpy import sqrt, max, abs, mean, array, log, polyfit
+from sys import stdout
+
+# Display the plots as well as saving to file
+show_plot = False
+
+# List of NX values to use
+nxlist = [16, 32, 64, 128]
+
+# Only testing 2D (x, z) slices, so only need one processor
+nproc = 1
+
+# Variables to compare
+varlist = ["a", "b", "c"]
+markers = ["bo", "r^", "kx"]
+labels = [r"$" + var + r"$" for var in varlist]
+
+methods = {
+ "hermitespline": 4,
+}
+
+
+def test_interpolate_z():
+
+ print("Running ZInterpolation test")
+ success = True
+
+ for method in methods:
+ print("------------------------------")
+ print("Using {} interpolation".format(method))
+
+ error_2 = {}
+ error_inf = {}
+ for var in varlist:
+ error_2[var] = [] # L2 error (RMS)
+ error_inf[var] = [] # Maximum error
+
+ for nx in nxlist:
+ dx = 1.0 / (nx)
+
+ args = " mesh:nx={nx4} mesh:dx={dx} MZ={nx} zinterpolation:type={method}".format(
+ nx4=nx + 4, dx=dx, nx=nx, method=method
+ )
+
+ cmd = "./test_interpolate" + args
+
+ shell(["rm data/BOUT.dmp.*.nc"])
+
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ with open("run.log.{}.{}".format(method, nx), "w") as f:
+ f.write(out)
+
+ # Collect output data
+ for var in varlist:
+ interp = collect(
+ var + "_interp", path="data", xguards=False, info=False
+ )
+ solution = collect(
+ var + "_solution", path="data", xguards=False, info=False
+ )
+
+ E = interp - solution
+
+ l2 = float(sqrt(mean(E**2)))
+ linf = float(max(abs(E)))
+
+ error_2[var].append(l2)
+ error_inf[var].append(linf)
+
+ print("{0:s} : l-2 {1:.8f} l-inf {2:.8f}".format(var, l2, linf))
+
+ dx = 1.0 / array(nxlist)
+
+ for var in varlist:
+ fit = polyfit(log(dx), log(error_2[var]), 1)
+ order = fit[0]
+ stdout.write("{0:s} Convergence order = {1:.2f}".format(var, order))
+
+ # Make sure scaling is at least 90% of expected order
+ if order < 0.9 * methods[method]:
+ print("............ FAIL")
+ success = False
+ else:
+ print("............ PASS")
+
+ if False:
+ try:
+ import matplotlib.pyplot as plt
+
+ # Plot errors
+ plt.figure()
+
+ for var, mark, label in zip(varlist, markers, labels):
+ plt.plot(dx, error_2[var], "-" + mark, label=label)
+ plt.plot(dx, error_inf[var], "--" + mark)
+
+ plt.legend(loc="upper left")
+ plt.grid()
+
+ plt.yscale("log")
+ plt.xscale("log")
+
+ plt.xlabel(r"Mesh spacing $\delta x$")
+ plt.ylabel("Error norm")
+ plt.title("Error scaling for {}".format(method))
+
+ name = "error_scaling_{}.pdf".format(method)
+ plt.savefig(name)
+ print("Plot saved to {}".format(name))
+
+ if show_plot:
+ plt.show()
+ plt.close()
+ except ImportError:
+ print("No matplotlib available")
+ else:
+ print("Plotting disabled")
+
+ print("------------------------------")
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-interpolate/runtest b/tests/integrated/test-interpolate/test_interpolate.py
similarity index 89%
rename from tests/integrated/test-interpolate/runtest
rename to tests/integrated/test-interpolate/test_interpolate.py
index 91503f5f9e..07a06a6c19 100755
--- a/tests/integrated/test-interpolate/runtest
+++ b/tests/integrated/test-interpolate/test_interpolate.py
@@ -4,11 +4,13 @@
# Run the test, compare results against the benchmark
#
-from boututils.run_wrapper import build_and_log, shell, launch_safe
+import pytest
+from boututils.run_wrapper import shell, launch_safe
from boutdata import collect
import boutconfig
from numpy import sqrt, max, abs, mean, array, log, polyfit
-from sys import stdout, exit
+from sys import stdout
+
# Display the plots as well as saving to file
show_plot = False
@@ -30,12 +32,12 @@
}
-build_and_log("Interpolation test")
+@pytest.mark.parametrize("method", methods)
+def test_interpolate(method):
-print("Running Interpolation test")
-success = True
+ print("Running Interpolation test")
+ success = True
-for method in methods:
print("------------------------------")
print("Using {} interpolation".format(method))
@@ -54,7 +56,7 @@
cmd = "./test_interpolate" + args
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
s, out = launch_safe(cmd, nproc=nproc, pipe=True)
with open("run.log.{}.{}".format(method, nx), "w") as f:
@@ -135,10 +137,5 @@ def myplot(f, lbl=None):
else:
print("Plotting disabled")
-print("------------------------------")
-if success:
- print(" => All Interpolation tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ print("------------------------------")
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-invertable-operator/runtest b/tests/integrated/test-invertable-operator/test_invertable_operator.py
similarity index 73%
rename from tests/integrated/test-invertable-operator/runtest
rename to tests/integrated/test-invertable-operator/test_invertable_operator.py
index 47fc5f1b53..3a8379f323 100755
--- a/tests/integrated/test-invertable-operator/runtest
+++ b/tests/integrated/test-invertable-operator/test_invertable_operator.py
@@ -8,28 +8,22 @@
# Run the test, compare results against expected value
#
-from __future__ import print_function
-from __future__ import division
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
+import pytest
+from boututils.run_wrapper import shell, launch_safe
from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
nprocs = [1, 2] # Number of processors to run on
reltol = 1.0e-3 # Allowed relative tolerance
nthreads = 1
-build_and_log("invertable operator test")
-
# Delete old output files
-shell("rm data/BOUT.dmp.*")
+shell(["rm data/BOUT.dmp.*"])
def run(path, nproc, log=False):
pipe = False
- if log != False:
+ if log:
pipe = True
s, out = launch_safe(
"./invertable_operator -d " + path, nproc=nproc, mthread=nthreads, pipe=pipe
@@ -47,7 +41,7 @@ def run(path, nproc, log=False):
print(
" => Failed (verification step - value is {s})".format(s=passVerification)
)
- exit(1)
+ pytest.fail()
if maxRelErrLaplacians > reltol:
print(
@@ -55,11 +49,10 @@ def run(path, nproc, log=False):
s=maxRelErrLaplacians
)
)
- exit(1)
+ pytest.fail()
-for np in nprocs:
- run("data", np)
+def test_invertable_operator():
-print(" => All tests passed")
-exit(0)
+ for np in nprocs:
+ run("data", np)
diff --git a/tests/integrated/test-invpar/runtest b/tests/integrated/test-invpar/runtest
deleted file mode 100755
index 53f6015fe9..0000000000
--- a/tests/integrated/test-invpar/runtest
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, check it completed successfully
-#
-
-# requires: not metric_3d
-# Requires: netcdf
-# Cores: 4
-
-from boututils.run_wrapper import build_and_log, shell, launch
-from boutdata.collect import collect
-from sys import stdout, exit
-
-build_and_log("parallel inversion test")
-
-flags_src = [
- dict(acoef=1, bcoef=0, ccoef=0, dcoef=0, ecoef=0),
- dict(acoef=1.5, bcoef="'2.*sin(2*y)'"),
- dict(acoef=1),
- dict(acoef=1, bcoef=2),
- dict(ccoef=1.793),
- dict(ccoef=3, bcoef=0),
- dict(dcoef=3.5),
- dict(ecoef=-1),
- dict(input_field="'ballooning(exp(-y*y)*cos(z)*gauss(x,0.2))'"),
-]
-
-flags = ""
-for i, f in enumerate(flags_src):
- fl = {"acoef": 1}
- fl.update(f)
- for k, v in fl.items():
- flags += f" {k}_{i}={v}"
-
-regions = ["", " mesh:ixseps1=0 mesh:ixseps2=0"]
-flags = [flags + r for r in regions]
-
-code = 0 # Return code
-for nproc in [1, 2, 4]:
- cmd = "./test_invpar"
-
- print(" %d processors...." % (nproc))
- r = 0
- for f in flags:
- shell("rm data/BOUT.dmp.* 2> err.log")
-
- # Run the case
- s, _ = launch(cmd + " -q -q -q " + f, nproc=nproc, mthread=1)
-
- code += s
-
- if s == 0:
- print("PASSED")
- else:
- print("FAILED")
-
-
-if code == 0:
- print(" => All inversion tests passed")
-else:
- print(" => Some failed tests")
-
-exit(code)
diff --git a/tests/integrated/test-invpar/test_invpar.py b/tests/integrated/test-invpar/test_invpar.py
new file mode 100755
index 0000000000..c7fd0fc565
--- /dev/null
+++ b/tests/integrated/test-invpar/test_invpar.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, check it completed successfully
+#
+
+# requires: not metric_3d
+# Requires: netcdf
+# Cores: 4
+
+from boututils.run_wrapper import shell, launch
+
+
+flags_src = [
+ dict(acoef=1, bcoef=0, ccoef=0, dcoef=0, ecoef=0),
+ dict(acoef=1.5, bcoef="'2.*sin(2*y)'"),
+ dict(acoef=1),
+ dict(acoef=1, bcoef=2),
+ dict(ccoef=1.793),
+ dict(ccoef=3, bcoef=0),
+ dict(dcoef=3.5),
+ dict(ecoef=-1),
+ dict(input_field="'ballooning(exp(-y*y)*cos(z)*gauss(x,0.2))'"),
+]
+
+
+def test_invpar():
+ global i, f, r, code
+ flags = ""
+ for i, f in enumerate(flags_src):
+ fl = {"acoef": 1}
+ fl.update(f)
+ for k, v in fl.items():
+ flags += f" {k}_{i}={v}"
+
+ regions = ["", " mesh:ixseps1=0 mesh:ixseps2=0"]
+ flags = [flags + r for r in regions]
+
+ code = 0 # Return code
+ for nproc in [1, 2, 4]:
+ cmd = "./test_invpar"
+
+ print(" %d processors...." % (nproc))
+ r = 0
+ for f in flags:
+ shell(["rm data/BOUT.dmp.* 2> err.log"])
+
+ # Run the case
+ s, _ = launch(cmd + " -q -q -q " + f, nproc=nproc, mthread=1)
+
+ code += s
+
+ if s == 0:
+ print("PASSED")
+ else:
+ print("FAILED")
+ assert s == 0, f"Test failed for flag={f}"
+
+ if code == 0:
+ print(" => All inversion tests passed")
+ else:
+ print(" => Some failed tests")
diff --git a/tests/integrated/test-laplace-hypre3d/runtest b/tests/integrated/test-laplace-hypre3d/runtest
deleted file mode 100755
index b50c5993b7..0000000000
--- a/tests/integrated/test-laplace-hypre3d/runtest
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: hypre
-
-from boutdata import collect
-from boututils.run_wrapper import launch_safe, build_and_log
-from sys import exit
-
-test_directories = [
- ("data_slab_core", 1),
- ("data_slab_sol", 1),
- ("data_circular_core", 1),
- ("data_circular_core-sol", 1),
-]
-
-tolerance = 1.0e-6
-
-build_and_log("Laplace 3D with Hypre")
-
-success = True
-for directory, nproc in test_directories:
- command = "test-laplace3d -d " + directory
- print("running on", nproc, "processors:", command)
- launch_safe(command, nproc=nproc)
-
- error_max = collect("error_max", path=directory, info=False)
-
- if error_max > tolerance:
- print(directory + " failed with maximum error {}".format(error_max))
- success = False
- else:
- print(directory + " passed with maximum error {}".format(error_max))
-
-if success:
- print("All passed")
- exit(0)
-else:
- exit(1)
diff --git a/tests/integrated/test-laplace-hypre3d/test_laplace_hypre3d.py b/tests/integrated/test-laplace-hypre3d/test_laplace_hypre3d.py
new file mode 100755
index 0000000000..6c5f87dc24
--- /dev/null
+++ b/tests/integrated/test-laplace-hypre3d/test_laplace_hypre3d.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+
+# requires: hypre
+
+from boutdata import collect
+from boututils.run_wrapper import launch_safe
+
+test_directories = [
+ ("data_slab_core", 1),
+ ("data_slab_sol", 1),
+ ("data_circular_core", 1),
+ ("data_circular_core-sol", 1),
+]
+
+tolerance = 1.0e-6
+
+
+def test_laplace_hypre3d():
+
+ success = True
+ for directory, nproc in test_directories:
+ command = "test-laplace3d -d " + directory
+ print("running on", nproc, "processors:", command)
+ launch_safe(command, nproc=nproc)
+
+ error_max = collect("error_max", path=directory, info=False)
+
+ if error_max > tolerance:
+ print(directory + " failed with maximum error {}".format(error_max))
+ success = False
+ else:
+ print(directory + " passed with maximum error {}".format(error_max))
+
+ assert success
diff --git a/tests/integrated/test-laplace-petsc3d/runtest b/tests/integrated/test-laplace-petsc3d/runtest
deleted file mode 100755
index 7b0d55f357..0000000000
--- a/tests/integrated/test-laplace-petsc3d/runtest
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: petsc
-
-from boutdata import collect
-from boututils.run_wrapper import launch, build_and_log
-from sys import exit
-
-test_directories = [
- ("data_slab_core", 1),
- ("data_slab_sol", 1),
- ("data_circular_core", 1),
- ("data_circular_core-sol", 1),
-]
-
-tolerance = 1.0e-6
-
-build_and_log("Laplace 3D with PETSc")
-
-success = True
-errors = {}
-
-for directory, nproc in test_directories:
- command = f"./test-laplace3d -d {directory}"
- print("running on", nproc, "processors:", command)
- status, output = launch(command, nproc=nproc, pipe=True)
-
- if status:
- print("FAILED")
- print(output)
- errors[directory] = ""
- continue
-
- error_max = collect("error_max", path=directory, info=False)
- if error_max > tolerance:
- errors[directory] = error_max
-
-print("**********")
-
-if errors:
- print("Some failures:")
- for name, error in errors.items():
- print(f"{name}, max error: {error}")
- exit(1)
-
-print("All passed")
-exit(0)
diff --git a/tests/integrated/test-laplace-petsc3d/test_laplace_petsc3d.py b/tests/integrated/test-laplace-petsc3d/test_laplace_petsc3d.py
new file mode 100755
index 0000000000..3a255d48a7
--- /dev/null
+++ b/tests/integrated/test-laplace-petsc3d/test_laplace_petsc3d.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# requires: petsc
+
+from boutdata import collect
+from boututils.run_wrapper import launch
+
+
+test_directories = [
+ ("data_slab_core", 1),
+ ("data_slab_sol", 1),
+ ("data_circular_core", 1),
+ ("data_circular_core-sol", 1),
+]
+
+tolerance = 1.0e-6
+
+
+def test_laplace_petsc3d():
+
+ errors = {}
+
+ for directory, nproc in test_directories:
+ command = f"./test-laplace3d -d {directory}"
+ print("running on", nproc, "processors:", command)
+ status, output = launch(command, nproc=nproc, pipe=True)
+
+ if status:
+ print("FAILED")
+ print(output)
+ errors[directory] = ""
+ continue
+
+ error_max = collect("error_max", path=directory, info=False)
+ if error_max > tolerance:
+ errors[directory] = error_max
+
+ print("**********")
+
+ if errors:
+ print("Some failures:")
+ for name, error in errors.items():
+ print(f"{name}, max error: {error}")
+
+ assert not errors
diff --git a/tests/integrated/test-laplace/runtest b/tests/integrated/test-laplace/runtest
deleted file mode 100755
index 8704447d66..0000000000
--- a/tests/integrated/test-laplace/runtest
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-# requires: not metric_3d
-# Requires: netcdf
-# Cores: 4
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect, create_cache
-import numpy.testing as npt
-from sys import exit
-
-
-# Variables to compare
-vars = [
- "flag0",
- "flag3",
- "flagis",
- "flagos",
- "flag0a",
- "flag3a",
- "flagisa",
- "flagosa",
- "flag0ac",
- "flag3ac",
- "flagisac",
- "flagosac",
- "flag0ad",
- "flag3ad",
- "flagisad",
- "flagosad",
-]
-tol = 1e-6 # Absolute tolerance
-
-build_and_log("Laplacian inversion test")
-
-# Read benchmark values
-print("Reading benchmark data")
-bmk = {v: collect(v, path="data", prefix="benchmark", info=False) for v in vars}
-
-print("Running Laplacian inversion test")
-success = True
-
-for solver in ["cyclic", "pcr", "pcr_thomas"]:
- for nproc in [1, 2, 4]:
- nxpe = 1
- if nproc > 2:
- nxpe = 2
-
- cmd = f"./test_laplace NXPE={nxpe} laplace:type={solver}"
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(f" {solver} solver with {nproc} processors ({nxpe=})....")
- s, out = launch_safe(cmd, nproc=nproc, mthread=1, pipe=True)
- with open(f"run.log.{nproc}", "w") as f:
- f.write(out)
-
- cache = create_cache(path="data", prefix="BOUT.dmp")
-
- # Collect output data
- for v in vars:
- print(f" Checking variable {v} ...", end="")
- result = collect(v, path="data", info=False, datafile_cache=cache)
- # Compare benchmark and output
- try:
- npt.assert_allclose(result, bmk[v], atol=tol, rtol=tol)
- print("Pass")
- except AssertionError as e:
- print(f"Fail: {e}")
- success = False
-
- # Only check FieldPerps on one processor because reading them in is
- # quite annoying on mutliple cores due to mismatched global y indices
- if nproc == 1:
- for v in ["flag0_perp", "flag3_perp"]:
- print(f" Checking variable {v} ...", end="")
- result = collect(v, path="data", info=False, datafile_cache=cache)
- # Compare benchmark and output
- try:
- npt.assert_allclose(
- result, bmk[v.replace("_perp", "")][:, 0, :], atol=tol, rtol=tol
- )
- print("Pass")
- except AssertionError as e:
- print(f"Fail: {e}")
- success = False
-
-if success:
- print(" => All Laplacian inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-laplace/test_laplace.py b/tests/integrated/test-laplace/test_laplace.py
new file mode 100755
index 0000000000..254485a6a0
--- /dev/null
+++ b/tests/integrated/test-laplace/test_laplace.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+from pathlib import Path
+
+#
+# Run the test, compare results against the benchmark
+#
+
+# requires: not metric_3d
+# Requires: netcdf
+# Cores: 4
+
+import pytest
+import numpy.testing as npt
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect, create_cache
+
+
+# Variables to compare
+vars = [
+ "flag0",
+ "flag3",
+ "flagis",
+ "flagos",
+ "flag0a",
+ "flag3a",
+ "flagisa",
+ "flagosa",
+ "flag0ac",
+ "flag3ac",
+ "flagisac",
+ "flagosac",
+ "flag0ad",
+ "flag3ad",
+ "flagisad",
+ "flagosad",
+]
+tol = 1e-6 # Absolute tolerance
+
+
+@pytest.fixture(scope="module")
+def benchmark_data():
+ """Fixture to load benchmark data once for the entire test session."""
+
+ source_data_dir = str(Path(__file__).parent / "data")
+ return {
+ v: collect(v, path=source_data_dir, prefix="benchmark", info=False)
+ for v in vars
+ }
+
+
+@pytest.mark.parametrize("solver", ["cyclic", "pcr", "pcr_thomas"])
+@pytest.mark.parametrize("nproc", [1, 2, 4])
+def test_laplace(solver, nproc, benchmark_data):
+
+ nxpe = 2 if nproc > 2 else 1
+ cmd = f"./test_laplace NXPE={nxpe} laplace:type={solver}"
+
+ shell("rm data/BOUT.dmp.*.nc")
+
+ s, out = launch_safe(cmd, nproc=nproc, mthread=1, pipe=True)
+
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
+
+ cache = create_cache(path="data", prefix="BOUT.dmp")
+
+ # Collect output data
+ for v in vars:
+ result = collect(v, path="data", info=False, datafile_cache=cache)
+ npt.assert_allclose(
+ result,
+ benchmark_data[v],
+ atol=tol,
+ rtol=tol,
+ err_msg=f"Failed checking variable: {v}",
+ )
+
+ # Only check FieldPerps on one processor because reading them in is
+ # quite annoying on multiple cores due to mismatched global y indices
+ if nproc == 1:
+ for v in ["flag0_perp", "flag3_perp"]:
+ result = collect(v, path="data", info=False, datafile_cache=cache)
+
+ # Compare benchmark and output
+ npt.assert_allclose(
+ result,
+ benchmark_data[v.replace("_perp", "")][:, 0, :],
+ atol=tol,
+ rtol=tol,
+ err_msg=f"Failed checking perpendicular variable: {v}",
+ )
diff --git a/tests/integrated/test-laplace2/test_laplace2.py b/tests/integrated/test-laplace2/test_laplace2.py
new file mode 100755
index 0000000000..2c5a409aeb
--- /dev/null
+++ b/tests/integrated/test-laplace2/test_laplace2.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./test_laplace")
diff --git a/tests/integrated/test-laplacexy-fv/runtest b/tests/integrated/test-laplacexy-fv/test_laplacexy_fv.py
similarity index 61%
rename from tests/integrated/test-laplacexy-fv/runtest
rename to tests/integrated/test-laplacexy-fv/test_laplacexy_fv.py
index f87a12e2ab..03f12c04b7 100755
--- a/tests/integrated/test-laplacexy-fv/runtest
+++ b/tests/integrated/test-laplacexy-fv/test_laplacexy_fv.py
@@ -8,11 +8,12 @@
# requires: petsc
# cores: 8
-from boututils.run_wrapper import build_and_log, shell, shell_safe, launch, launch_safe
+import pytest
+from boututils.run_wrapper import shell, launch, launch_safe
from boutdata.collect import collect
-from sys import exit
tol = 5.0e-8
+nproc = 8
argslist = [
"laplacexy:core_bndry_dirichlet=true laplacexy:pf_bndry_dirichlet=true laplacexy:y_bndry=dirichlet "
@@ -37,49 +38,36 @@
"f:bndry_xin=neumann f:bndry_xout=dirichlet f:bndry_yup=neumann f:bndry_ydown=neumann b:function=.1",
]
-build_and_log("LaplaceXY inversion test")
-print("Running LaplaceXY inversion test")
-success = True
+@pytest.mark.parametrize("args", argslist)
+def test_laplacexy_fv(args):
-for nproc in [8]:
- print(" %d processors...." % nproc)
- for args in argslist:
- cmd = "./test-laplacexy " + args
+ print("Running LaplaceXY inversion test")
+ cmd = f"./test-laplacexy {args}"
- shell("rm data/BOUT.dmp.*.nc > /dev/null 2>&1")
+ shell(["rm data/BOUT.dmp.*.nc > /dev/null 2>&1"])
- s, out = launch(
- cmd + " laplacexy:pctype=hypre", nproc=nproc, pipe=True, verbose=True
+ s, out = launch(
+ f"{cmd} laplacexy:pctype=hypre", nproc=nproc, pipe=True, verbose=True
+ )
+
+ if s == 134:
+ # PETSc did not recognise pctype option, probably means it
+ # was not compiled with hypre, so skip tests that need
+ # hypre to converge
+ print(
+ "hypre not available as pre-conditioner in PETSc. Re-running with "
+ + "pctype=shell..."
)
- if s == 134:
- # PETSc did not recognise pctype option, probably means it
- # was not compiled with hypre, so skip tests that need
- # hypre to converge
- print(
- "hypre not available as pre-conditioner in PETSc. Re-running with "
- + "pctype=shell..."
- )
- _, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
+ _, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
- f = open("run.log." + str(nproc), "w")
+ with open(f"run.log.{nproc}", "w") as f:
f.write(out)
- f.close()
- # Collect output data
- error = collect("max_error", path="data", info=False)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > tol:
- print("Fail, maximum error is = " + str(error))
- success = False
- else:
- print("Pass")
+ # Collect output data
+ error = collect("max_error", path="data", info=False)
-if success:
- print(" => All LaplaceXY inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ assert error > 0, f"Convergence error: max_error is {error}"
+ assert error <= tol, (
+ f"Fail: maximum error is {error}, which exceeds tolerance {tol}"
+ )
diff --git a/tests/integrated/test-laplacexy-short/runtest b/tests/integrated/test-laplacexy-short/test_laplacexy_short.py
similarity index 68%
rename from tests/integrated/test-laplacexy-short/runtest
rename to tests/integrated/test-laplacexy-short/test_laplacexy_short.py
index 3325a32f4d..135c04141b 100755
--- a/tests/integrated/test-laplacexy-short/runtest
+++ b/tests/integrated/test-laplacexy-short/test_laplacexy_short.py
@@ -8,11 +8,12 @@
# requires: petsc
# cores: 8
-from boututils.run_wrapper import build_and_log, shell, shell_safe, launch, launch_safe
+import pytest
+from boututils.run_wrapper import shell, launch, launch_safe
from boutdata.collect import collect
-from sys import exit
tol = 5.0e-8
+nproc = 8
argslist = [
"laplacexy:core_bndry_dirichlet=true laplacexy:pf_bndry_dirichlet=true laplacexy:y_bndry=dirichlet "
@@ -45,49 +46,33 @@
"f:bndry_xin=neumann f:bndry_xout=dirichlet f:bndry_yup=free_o3 f:bndry_ydown=free_o3 b:function=.1",
]
-build_and_log("LaplaceXY inversion test")
-print("Running LaplaceXY inversion test")
-success = True
+@pytest.mark.parametrize("args", argslist)
+def test_laplacexy_short(args):
-for nproc in [8]:
- print(" %d processors...." % nproc)
- for args in argslist:
- cmd = "./test-laplacexy " + args
+ cmd = f"./test-laplacexy {args}"
- shell("rm data/BOUT.dmp.*.nc > /dev/null 2>&1")
+ shell(["rm data/BOUT.dmp.*.nc > /dev/null 2>&1"])
- s, out = launch(
- cmd + " laplacexy:pctype=hypre", nproc=nproc, pipe=True, verbose=True
+ s, out = launch(
+ f"{cmd} laplacexy:pctype=hypre", nproc=nproc, pipe=True, verbose=True
+ )
+ if s == 134:
+ # PETSc did not recognise pctype option, probably means it
+ # was not compiled with hypre, so skip tests that need
+ # hypre to converge
+ print(
+ "hypre not available as pre-conditioner in PETSc. Re-running with "
+ + "pctype=shell..."
)
- if s == 134:
- # PETSc did not recognise pctype option, probably means it
- # was not compiled with hypre, so skip tests that need
- # hypre to converge
- print(
- "hypre not available as pre-conditioner in PETSc. Re-running with "
- + "pctype=shell..."
- )
- _, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
+ _, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
- f = open("run.log." + str(nproc), "w")
+ with open(f"run.log.{nproc}", "w") as f:
f.write(out)
- f.close()
- # Collect output data
- error = collect("max_error", path="data", info=False)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > tol:
- print("Fail, maximum error is = " + str(error))
- success = False
- else:
- print("Pass")
+ error = collect("max_error", path="data", info=False)
-if success:
- print(" => All LaplaceXY inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ assert error > 0, f"Convergence error: max_error is {error}"
+ assert error <= tol, (
+ f"Fail: maximum error is {error}, which exceeds tolerance {tol}"
+ )
diff --git a/tests/integrated/test-laplacexy/plotmetrics.py b/tests/integrated/test-laplacexy/plotmetrics.py
index 3100856679..313fcfda5f 100755
--- a/tests/integrated/test-laplacexy/plotmetrics.py
+++ b/tests/integrated/test-laplacexy/plotmetrics.py
@@ -3,7 +3,6 @@
from boutdata import collect
from boututils.datafile import DataFile
from matplotlib import pyplot
-import numpy
from sys import exit
# Set default colormap
diff --git a/tests/integrated/test-laplacexy/plotresult.py b/tests/integrated/test-laplacexy/plotresult.py
index ccf48baa55..9b9dfcc44f 100755
--- a/tests/integrated/test-laplacexy/plotresult.py
+++ b/tests/integrated/test-laplacexy/plotresult.py
@@ -3,7 +3,6 @@
from boutdata import collect
from boututils.datafile import DataFile
from matplotlib import pyplot
-import numpy
from sys import exit
# Set default colormap
diff --git a/tests/integrated/test-laplacexy/runtest b/tests/integrated/test-laplacexy/test_laplacexy.py
similarity index 66%
rename from tests/integrated/test-laplacexy/runtest
rename to tests/integrated/test-laplacexy/test_laplacexy.py
index d2f99c5100..750600a6cb 100755
--- a/tests/integrated/test-laplacexy/runtest
+++ b/tests/integrated/test-laplacexy/test_laplacexy.py
@@ -9,9 +9,8 @@
# requires: all_tests
# cores: 8
-from boututils.run_wrapper import build_and_log, shell, shell_safe, launch, launch_safe
+from boututils.run_wrapper import shell, launch, launch_safe
from boutdata.collect import collect
-from sys import exit
tol_orth = 5.0e-8
@@ -52,53 +51,49 @@
"f:bndry_xin=neumann f:bndry_xout=dirichlet f:bndry_yup=free_o3 f:bndry_ydown=free_o3 b:function=.1 laplacexy:pctype=hypre",
]
-build_and_log("LaplaceXY inversion test")
-
-print("Running LaplaceXY inversion test")
-success = True
-
-for nproc in [8]:
- print(" %d processors...." % nproc)
- for nonorth, tol in [(False, tol_orth), (True, tol_nonorth)]:
- for args in argslist:
- if not nonorth:
- args += " mesh:g12=0."
-
- cmd = "./test-laplacexy " + args
-
- shell("rm data/BOUT.dmp.*.nc > /dev/null 2>&1")
-
- if "hypre" in args:
- s, out = launch(cmd, nproc=nproc, pipe=True, verbose=True)
- if s == 134:
- # PETSc did not recognise pctype option, probably means it
- # was not compiled with hypre, so skip tests that need
- # hypre to converge
- print(
- "hypre not available as pre-conditioner in PETSc. Skipping..."
- )
- continue
- else:
- s, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
-
- f = open("run.log." + str(nproc), "w")
- f.write(out)
- f.close()
-
- # Collect output data
- error = collect("max_error", path="data", info=False)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > tol:
- print("Fail, maximum error is = " + str(error))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All LaplaceXY inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+
+def test_laplacexy():
+
+ print("Running LaplaceXY inversion test")
+ success = True
+
+ for nproc in [8]:
+ print(" %d processors...." % nproc)
+ for nonorth, tol in [(False, tol_orth), (True, tol_nonorth)]:
+ for args in argslist:
+ if not nonorth:
+ args += " mesh:g12=0."
+
+ cmd = "./test-laplacexy " + args
+
+ shell(["rm data/BOUT.dmp.*.nc > /dev/null 2>&1"])
+
+ if "hypre" in args:
+ s, out = launch(cmd, nproc=nproc, pipe=True, verbose=True)
+ if s == 134:
+ # PETSc did not recognise pctype option, probably means it
+ # was not compiled with hypre, so skip tests that need
+ # hypre to converge
+ print(
+ "hypre not available as pre-conditioner in PETSc. Skipping..."
+ )
+ continue
+ else:
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
+
+ f = open("run.log." + str(nproc), "w")
+ f.write(out)
+ f.close()
+
+ # Collect output data
+ error = collect("max_error", path="data", info=False)
+ if error <= 0:
+ print("Convergence error")
+ success = False
+ elif error > tol:
+ print("Fail, maximum error is = " + str(error))
+ success = False
+ else:
+ print("Pass")
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-laplacexy2-hypre/runtest b/tests/integrated/test-laplacexy2-hypre/test_laplacexy2_hypre.py
similarity index 70%
rename from tests/integrated/test-laplacexy2-hypre/runtest
rename to tests/integrated/test-laplacexy2-hypre/test_laplacexy2_hypre.py
index 55f5982a0c..8b1a75942d 100755
--- a/tests/integrated/test-laplacexy2-hypre/runtest
+++ b/tests/integrated/test-laplacexy2-hypre/test_laplacexy2_hypre.py
@@ -7,12 +7,12 @@
# requires: hypre
# cores: 8
-from sys import exit
-
+import pytest
from boutdata.collect import collect
-from boututils.run_wrapper import build_and_log, launch_safe, shell
+from boututils.run_wrapper import launch_safe, shell
tol = 5.0e-8
+nproc = 8
argslist = [
"laplacexy:core_bndry_dirichlet=true laplacexy:pf_bndry_dirichlet=true laplacexy:y_bndry_dirichlet=true "
@@ -37,37 +37,25 @@
"f:bndry_xin=neumann f:bndry_xout=dirichlet f:bndry_yup=neumann f:bndry_ydown=neumann b:function=.1",
]
-build_and_log("LaplaceXY inversion test")
-print("Running LaplaceXY inversion test")
-success = True
+@pytest.mark.parametrize("args", argslist)
+def test_laplacexy2_hypre(args):
-for nproc in [8]:
print(" %d processors...." % nproc)
- for args in argslist:
- cmd = "./test-laplacexy " + args
- shell("rm data/BOUT.dmp.*.nc > /dev/null 2>&1")
+ cmd = f"./test-laplacexy {args}"
+
+ shell(["rm data/BOUT.dmp.*.nc > /dev/null 2>&1"])
- s, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
- with open(f"run.log.{nproc}", "w") as f:
- f.write(out)
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
- # Collect output data
- error = collect("max_error", path="data", info=False)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > tol:
- print("Fail, maximum error is = " + str(error))
- success = False
- else:
- print("Pass")
+ # Collect output data
+ error = collect("max_error", path="data", info=False)
-if success:
- print(" => All LaplaceXY inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ assert error > 0, f"Convergence error: maximum error is {error}"
+ assert error <= tol, (
+ f"Fail: maximum error is {error}, which exceeds tolerance {tol}"
+ )
diff --git a/tests/integrated/test-laplacexz/runtest b/tests/integrated/test-laplacexz/runtest
deleted file mode 100755
index bc7592ec3e..0000000000
--- a/tests/integrated/test-laplacexz/runtest
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: not metric_3d
-# requires: petsc
-
-#
-# Run the test, compare results against the benchmark
-#
-
-from boututils.run_wrapper import shell, launch_safe, getmpirun, build_and_log
-from boutdata.collect import collect
-import numpy as np
-from sys import exit
-
-tol = 1e-10 # Absolute tolerance
-
-MPIRUN = getmpirun()
-
-build_and_log("LaplaceXZ test")
-
-print("Running LaplaceXZ test")
-success = True
-
-for nproc in [1, 2, 4]:
- nxpe = nproc
-
- cmd = "./test-laplacexz nxpe=" + str(nxpe)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors (nxpe = %d)...." % (nproc, nxpe))
- s, out = launch_safe(cmd, runcmd=MPIRUN, nproc=nproc, mthread=1, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect output data
- f = collect("f", path="data", info=False)
- f2 = collect("f2", path="data", info=False)
- print(" Checking tolerance... ")
- # Compare benchmark and output
- if np.shape(f) != np.shape(f2):
- print("Fail, wrong shape")
- success = False
- diff = np.max(np.abs(f2 - f))
- if diff > tol:
- print("Fail, maximum difference = " + str(diff))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => LaplaceXZ inversion test passed")
- exit(0)
-
-print(" => LaplaceXZ test failed")
-exit(1)
diff --git a/tests/integrated/test-laplacexz/test_laplacexz.py b/tests/integrated/test-laplacexz/test_laplacexz.py
new file mode 100755
index 0000000000..02a7239285
--- /dev/null
+++ b/tests/integrated/test-laplacexz/test_laplacexz.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+# requires: not metric_3d
+# requires: petsc
+
+#
+# Run the test, compare results against the benchmark
+#
+
+import numpy as np
+import pytest
+
+from boutdata.collect import collect
+from boututils.run_wrapper import shell, launch_safe, getmpirun
+
+
+@pytest.mark.parametrize("nproc", [1, 2, 4])
+def test_laplacexz(nproc):
+
+ tol = 1e-10 # Absolute tolerance
+
+ MPIRUN = getmpirun()
+
+ print(f"Running LaplaceXZ test on {nproc} processors...")
+
+ # Unique data directory per processor configuration
+ cmd = f"./test-laplacexz nxpe={nproc}"
+
+ shell(["rm -f data/BOUT.dmp.*.nc"])
+
+ print(f" {nproc} processors (nxpe = {nproc})....")
+ s, out = launch_safe(cmd, runcmd=MPIRUN, nproc=nproc, mthread=1, pipe=True)
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
+
+ # Collect output data
+ f = collect("f", path="data", info=False)
+ f2 = collect("f2", path="data", info=False)
+ print(" Checking tolerance... ")
+ # Compare benchmark and output
+ np.testing.assert_allclose(
+ f2, f, atol=tol, rtol=0, err_msg="LaplaceXZ output mismatch"
+ )
diff --git a/tests/integrated/test-multigrid_laplace/CMakeLists.txt b/tests/integrated/test-multigrid_laplace/CMakeLists.txt
index b6bb16b8a8..b64b0e166c 100644
--- a/tests/integrated/test-multigrid_laplace/CMakeLists.txt
+++ b/tests/integrated/test-multigrid_laplace/CMakeLists.txt
@@ -3,5 +3,7 @@ bout_add_integrated_test(
SOURCES test_multigrid_laplace.cxx
CONFLICTS BOUT_USE_METRIC_3D
USE_RUNTEST USE_DATA_BOUT_INP
+ EXTRA_FILES data/BOUT_jy4.inp data/BOUT_jy63.inp data/BOUT_jy127.inp
+ data/BOUT_unsheared.inp
PROCESSORS 3
)
diff --git a/tests/integrated/test-multigrid_laplace/runtest b/tests/integrated/test-multigrid_laplace/runtest
deleted file mode 100755
index f325185c46..0000000000
--- a/tests/integrated/test-multigrid_laplace/runtest
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: not metric_3d
-
-#
-# Run the test, check the error
-#
-
-# Cores: 3
-
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-tol = 2e-7 # Absolute tolerance
-numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-from sys import exit
-
-build_and_log("multigrid Laplacian inversion test")
-
-print("Running multigrid Laplacian inversion test")
-success = True
-
-for nproc in [1, 3]:
- # Make sure we don't use too many cores:
- # Reduce number of OpenMP threads when using multiple MPI processes
- mthread = 2
- if nproc > 1:
- mthread = 1
-
- # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the multigrid solver)
- cmd = "./test_multigrid_laplace NXPE=" + str(nproc)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors..." % nproc)
- s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect errors
- errors = [
- collect("max_error" + str(i), path="data") for i in range(1, numTests + 1)
- ]
-
- for i, e in enumerate(errors):
- print("Checking test " + str(i))
- if e < 0.0:
- print("Fail, solver did not converge")
- success = False
- if e > tol:
- print("Fail, maximum absolute error = " + str(e))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All multigrid Laplacian inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-multigrid_laplace/runtest_unsheared b/tests/integrated/test-multigrid_laplace/runtest_unsheared
deleted file mode 100755
index d0dca3a808..0000000000
--- a/tests/integrated/test-multigrid_laplace/runtest_unsheared
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, check the error
-#
-
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-tol = 1e-9 # Absolute tolerance
-numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-
-from boututils.run_wrapper import shell, build_and_log, launch_safe
-from boutdata.collect import collect
-from sys import exit
-
-build_and_log("Making multigrid Laplacian inversion test")
-
-print("Running multigrid Laplacian inversion test")
-success = True
-
-for nproc in [1, 3]:
- # Make sure we don't use too many cores:
- # Reduce number of OpenMP threads when using multiple MPI processes
- mthread = 2
- if nproc > 1:
- mthread = 1
-
- # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the multigrid solver)
- cmd = "./test_multigrid_laplace -f BOUT_unsheared.inp NXPE=" + str(nproc)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors..." % nproc)
- s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect errors
- errors = [
- collect("max_error" + str(i), path="data") for i in range(1, numTests + 1)
- ]
-
- for i, e in enumerate(errors):
- print("Checking test " + str(i))
- if e < 0.0:
- print("Fail, solver did not converge")
- success = False
- if e > tol:
- print("Fail, maximum absolute error = " + str(e))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All multigrid Laplacian inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.py b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.py
new file mode 100755
index 0000000000..6f8c28b30e
--- /dev/null
+++ b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, check the error
+#
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+tol = 2e-6 # Absolute tolerance
+numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
+
+
+def test_multigrid_laplace():
+
+ print("Running multigrid Laplacian inversion test")
+ success = True
+
+ for nproc in [1, 2, 4]:
+ for inputfile in ["BOUT_jy4.inp", "BOUT_jy63.inp", "BOUT_jy127.inp"]:
+ # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the multigrid solver)
+ cmd = f"./test_multigrid_laplace -f {inputfile} NXPE={nproc} input:error_on_unused_options=false"
+
+ shell(["rm data/BOUT.dmp.*.nc"])
+
+ print(" %d processors, input file is %s" % (nproc, inputfile))
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ # Collect errors
+ errors = [
+ collect("max_error" + str(i), path="data")
+ for i in range(1, numTests + 1)
+ ]
+
+ for i, e in enumerate(errors):
+ print("Checking test " + str(i))
+ if e < 0.0:
+ print("Fail, solver did not converge")
+ success = False
+ if e > tol:
+ print("Fail, maximum absolute error = " + str(e))
+ success = False
+ else:
+ print("Pass")
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-multigrid_laplace/test_multigrid_laplace_unsheared.py b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace_unsheared.py
new file mode 100755
index 0000000000..3be9bf1bbc
--- /dev/null
+++ b/tests/integrated/test-multigrid_laplace/test_multigrid_laplace_unsheared.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, check the error
+#
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+tol = 1e-9 # Absolute tolerance
+numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
+
+
+def test_multigrid_laplace_unsheared():
+
+ print("Running multigrid Laplacian inversion test (unsheared)")
+ success = True
+
+ for nproc in [1, 3]:
+ # Make sure we don't use too many cores:
+ # Reduce number of OpenMP threads when using multiple MPI processes
+ mthread = 2
+ if nproc > 1:
+ mthread = 1
+
+ # set nxpe on the command line as we only use solution from one point in y,
+ # so splitting in y-direction is redundant (and also doesn't help test the multigrid solver)
+ inputfile = "BOUT_unsheared.inp"
+ cmd = f"./test_multigrid_laplace -f {inputfile} NXPE={nproc}"
+
+ shell(["rm data/BOUT.dmp.*.nc"])
+
+ print(" %d processors..." % nproc)
+ s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ # Collect errors
+ errors = [
+ collect("max_error" + str(i), path="data") for i in range(1, numTests + 1)
+ ]
+
+ for i, e in enumerate(errors):
+ print("Checking test " + str(i))
+ if e < 0.0:
+ print("Fail, solver did not converge")
+ success = False
+ if e > tol:
+ print("Fail, maximum absolute error = " + str(e))
+ success = False
+ else:
+ print("Pass")
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-naulin-laplace/runtest b/tests/integrated/test-naulin-laplace/runtest
deleted file mode 100755
index 145257c23b..0000000000
--- a/tests/integrated/test-naulin-laplace/runtest
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: not metric_3d
-
-#
-# Run the test, check the error
-#
-
-# Cores: 3
-
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-tol = 2e-7 # Absolute tolerance
-numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-from sys import exit
-
-build_and_log("LaplaceNaulin inversion test")
-
-print("Running LaplaceNaulin inversion test")
-success = True
-
-for nproc in [1, 3]:
- # Make sure we don't use too many cores:
- # Reduce number of OpenMP threads when using multiple MPI processes
- mthread = 2
- if nproc > 1:
- mthread = 1
-
- # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the solver)
- cmd = "./test_naulin_laplace NXPE=" + str(nproc)
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processors..." % nproc)
- s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect errors
- errors = [
- collect("max_error" + str(i), path="data") for i in range(1, numTests + 1)
- ]
-
- for i, e in enumerate(errors):
- print("Checking test " + str(i))
- if e < 0.0:
- print("Fail, solver did not converge")
- success = False
- if e > tol:
- print("Fail, maximum absolute error = " + str(e))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All LaplaceNaulin inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-naulin-laplace/runtest_multiple_grids b/tests/integrated/test-naulin-laplace/runtest_multiple_grids
index f56a80677e..2b802e0eb1 100755
--- a/tests/integrated/test-naulin-laplace/runtest_multiple_grids
+++ b/tests/integrated/test-naulin-laplace/runtest_multiple_grids
@@ -4,21 +4,12 @@
# Run the test, check the error
#
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-tol = 2e-6 # Absolute tolerance
-numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-
-from boututils.run_wrapper import shell, build_and_log, launch_safe
+from boututils.run_wrapper import shell, launch_safe
from boutdata.collect import collect
from sys import exit
-build_and_log("Making LaplaceNaulin inversion test")
+tol = 2e-6 # Absolute tolerance
+numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
print("Running LaplaceNaulin inversion test")
success = True
@@ -28,7 +19,7 @@ for nproc in [1, 2, 4]:
# set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the solver)
cmd = "./test_naulin_laplace -f " + inputfile + " NXPE=" + str(nproc)
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
print(" %d processors, input file is %s" % (nproc, inputfile))
s, out = launch_safe(cmd, nproc=nproc, pipe=True)
diff --git a/tests/integrated/test-naulin-laplace/runtest_unsheared b/tests/integrated/test-naulin-laplace/runtest_unsheared
index a845a9d92f..e5ee732bbb 100755
--- a/tests/integrated/test-naulin-laplace/runtest_unsheared
+++ b/tests/integrated/test-naulin-laplace/runtest_unsheared
@@ -4,21 +4,12 @@
# Run the test, check the error
#
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-
-tol = 1e-9 # Absolute tolerance
-numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-
-from boututils.run_wrapper import shell, build_and_log, launch_safe
+from boututils.run_wrapper import shell, launch_safe
from boutdata.collect import collect
from sys import exit
-build_and_log("LaplaceNaulin inversion test")
+tol = 1e-9 # Absolute tolerance
+numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
print("Running LaplaceNaulin inversion test")
success = True
@@ -33,7 +24,7 @@ for nproc in [1, 3]:
# set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the solver)
cmd = "./test_naulin_laplace -f BOUT_unsheared.inp NXPE=" + str(nproc)
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
print(" %d processors..." % nproc)
s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
diff --git a/tests/integrated/test-multigrid_laplace/runtest_multiple_grids b/tests/integrated/test-naulin-laplace/test_naulin_laplace.py
similarity index 51%
rename from tests/integrated/test-multigrid_laplace/runtest_multiple_grids
rename to tests/integrated/test-naulin-laplace/test_naulin_laplace.py
index 739d15f7d4..52c2e5144d 100755
--- a/tests/integrated/test-multigrid_laplace/runtest_multiple_grids
+++ b/tests/integrated/test-naulin-laplace/test_naulin_laplace.py
@@ -1,37 +1,39 @@
#!/usr/bin/env python3
+# requires: not metric_3d
+
#
# Run the test, check the error
#
-from __future__ import print_function
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
-try:
- from builtins import str
-except:
- pass
+# Cores: 3
-tol = 2e-6 # Absolute tolerance
+tol = 2e-7 # Absolute tolerance
numTests = 4 # We test 4 different boundary conditions (with slightly different inputs for each)
-from boututils.run_wrapper import shell, build_and_log, launch_safe
-from boutdata.collect import collect
-from sys import exit
-build_and_log("Multigrid Laplacian inversion test")
+def test_naulin_laplace():
+
+ print("Running LaplaceNaulin inversion test")
+ success = True
-print("Running multigrid Laplacian inversion test")
-success = True
+ for nproc in [1, 3]:
+ # Make sure we don't use too many cores:
+ # Reduce number of OpenMP threads when using multiple MPI processes
+ mthread = 2
+ if nproc > 1:
+ mthread = 1
-for nproc in [1, 2, 4]:
- for inputfile in ["BOUT_jy4.inp", "BOUT_jy63.inp", "BOUT_jy127.inp"]:
- # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the multigrid solver)
- cmd = "./test_multigrid_laplace -f " + inputfile + " NXPE=" + str(nproc)
+ # set nxpe on the command line as we only use solution from one point in y, so splitting in y-direction is redundant (and also doesn't help test the solver)
+ cmd = "./test_naulin_laplace NXPE=" + str(nproc)
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
- print(" %d processors, input file is %s" % (nproc, inputfile))
- s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ print(" %d processors..." % nproc)
+ s, out = launch_safe(cmd, nproc=nproc, mthread=mthread, pipe=True)
with open("run.log." + str(nproc), "w") as f:
f.write(out)
@@ -51,9 +53,4 @@
else:
print("Pass")
-if success:
- print(" => All multigrid Laplacian inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-options-netcdf/runtest b/tests/integrated/test-options-netcdf/runtest
deleted file mode 100755
index db9adc7f29..0000000000
--- a/tests/integrated/test-options-netcdf/runtest
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env python3
-
-# Note: This test requires NCDF4, whereas on Travis NCDF is used
-# requires: netcdf
-# requires: not legacy_netcdf
-
-from boututils.datafile import DataFile
-from boututils.run_wrapper import build_and_log, shell, launch_safe as launch
-from boutdata.data import BoutOptionsFile
-
-import math
-import numpy as np
-
-build_and_log("options-netcdf test")
-shell("rm -f test-out.ini")
-shell("rm -f test-out.nc")
-
-# Create a NetCDF input file
-with DataFile("test.nc", create=True, format="NETCDF4") as f:
- f.write("int", 42)
- f.write("real", 3.1415)
- f.write("string", "hello")
-
-# run BOUT++
-launch("./test-options-netcdf", nproc=1, mthread=1)
-
-# Check the output INI file
-result = BoutOptionsFile("test-out.ini")
-
-print(result)
-
-assert result["int"] == 42
-assert math.isclose(result["real"], 3.1415)
-assert result["string"] == "hello"
-
-print("Checking saved NetCDF file")
-
-# Check the output NetCDF file
-with DataFile("test-out.nc") as f:
- assert f["int"] == 42
- assert math.isclose(f["real"], 3.1415)
- assert result["string"] == "hello"
-
-print("Checking saved settings.ini")
-
-# Check the settings.ini file, coming from BOUT.inp
-# which is converted to NetCDF, read in, then written again
-settings = BoutOptionsFile("settings.ini")
-
-assert settings["mesh"]["nx"] == 5
-assert settings["mesh"]["ny"] == 2
-
-print("Checking saved fields.nc")
-
-with DataFile("fields.nc") as f:
- assert f["f2d"].shape == (5, 6) # Field2D
- assert f["f3d"].shape == (5, 6, 2) # Field3D
- assert f["fperp"].shape == (5, 2) # FieldPerp
- assert np.allclose(f["f2d"], 1.0)
- assert np.allclose(f["f3d"], 2.0)
- assert np.allclose(f["fperp"], 3.0)
-
-print("Checking saved fields2.nc")
-
-with DataFile("fields2.nc") as f:
- assert f["f2d"].shape == (5, 6) # Field2D
- assert f["f3d"].shape == (5, 6, 2) # Field3D
- assert f["fperp"].shape == (5, 2) # FieldPerp
- assert np.allclose(f["f2d"], 1.0)
- assert np.allclose(f["f3d"], 2.0)
- assert np.allclose(f["fperp"], 3.0)
-
-print(" => Passed")
diff --git a/tests/integrated/test-options-netcdf/test_options_netcdf.py b/tests/integrated/test-options-netcdf/test_options_netcdf.py
new file mode 100755
index 0000000000..f8a4a92b01
--- /dev/null
+++ b/tests/integrated/test-options-netcdf/test_options_netcdf.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+
+# Note: This test requires NCDF4, whereas on Travis NCDF is used
+# requires: netcdf
+# requires: not legacy_netcdf
+
+from boututils.datafile import DataFile
+from boututils.run_wrapper import shell, launch_safe as launch
+from boutdata.data import BoutOptionsFile
+
+import math
+import numpy.testing as npt
+
+
+def test_options_netcdf():
+
+ shell(["rm -f test-out.ini"])
+ shell(["rm -f test-out.nc"])
+
+ # Create a NetCDF input file
+ with DataFile("test.nc", create=True, format="NETCDF4") as f:
+ f.write("int", 42)
+ f.write("real", 3.1415)
+ f.write("string", "hello")
+
+ # run BOUT++
+ launch("./test-options-netcdf", nproc=1, mthread=1)
+
+ # Check the output INI file
+ result = BoutOptionsFile("test-out.ini")
+
+ print(result)
+
+ assert result["int"] == 42
+ assert math.isclose(result["real"], 3.1415)
+ assert result["string"] == "hello"
+
+ print("Checking saved NetCDF file")
+
+ # Check the output NetCDF file
+ with DataFile("test-out.nc") as f:
+ assert f["int"] == 42
+ assert math.isclose(f["real"], 3.1415)
+ assert result["string"] == "hello"
+
+ print("Checking saved settings.ini")
+
+ # Check the settings.ini file, coming from BOUT.inp
+ # which is converted to NetCDF, read in, then written again
+ settings = BoutOptionsFile("settings.ini")
+
+ assert settings["mesh"]["nx"] == 5
+ assert settings["mesh"]["ny"] == 2
+
+ print("Checking saved fields.nc")
+
+ with DataFile("fields.nc") as f:
+ assert f["f2d"].shape == (5, 6) # Field2D
+ assert f["f3d"].shape == (5, 6, 2) # Field3D
+ assert f["fperp"].shape == (5, 2) # FieldPerp
+ npt.assert_allclose(f["f2d"], 1.0)
+ npt.assert_allclose(f["f3d"], 2.0)
+ npt.assert_allclose(f["fperp"], 3.0)
+
+ print("Checking saved fields2.nc")
+
+ with DataFile("fields2.nc") as f:
+ assert f["f2d"].shape == (5, 6) # Field2D
+ assert f["f3d"].shape == (5, 6, 2) # Field3D
+ assert f["fperp"].shape == (5, 2) # FieldPerp
+ npt.assert_allclose(f["f2d"], 1.0)
+ npt.assert_allclose(f["f3d"], 2.0)
+ npt.assert_allclose(f["fperp"], 3.0)
+
+ print(" => Passed")
diff --git a/tests/integrated/test-petsc_laplace/runtest b/tests/integrated/test-petsc_laplace/test_petsc_laplace.py
similarity index 51%
rename from tests/integrated/test-petsc_laplace/runtest
rename to tests/integrated/test-petsc_laplace/test_petsc_laplace.py
index b4379f7754..3ac9a802f1 100755
--- a/tests/integrated/test-petsc_laplace/runtest
+++ b/tests/integrated/test-petsc_laplace/test_petsc_laplace.py
@@ -8,11 +8,10 @@
# requires: all_tests
# cores: 4
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect, create_cache
-
import pathlib
-from sys import exit
+import pytest
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect, create_cache
errors = [
"max_error1",
@@ -24,15 +23,12 @@
]
tol = 2e-4 # Absolute (?) tolerance
-build_and_log("PETSc Laplacian inversion test")
-print("Running PETSc Laplacian inversion test")
-success = True
-
-for nproc in [1, 2, 4]:
+@pytest.mark.parametrize("nproc", [1, 2, 4])
+def test_petsc_laplace(nproc):
cmd = "./test_petsc_laplace"
- shell("rm data/BOUT.dmp.*.nc")
+ shell(["rm data/BOUT.dmp.*.nc"])
print(f" {nproc} processors....")
s, out = launch_safe(cmd, nproc=nproc, pipe=True, verbose=True)
@@ -42,20 +38,9 @@
# Collect output data
for varname in errors:
- print(f" Checking {varname} ... ", end="")
error = collect(varname, path="data", info=False, datafile_cache=cache)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > tol:
- print(f"Fail, maximum error is = {error:e}")
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All PETSc Laplacian inversion tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
+
+ assert error > 0, f"Convergence error for {varname} on {nproc} proc(s)"
+ assert error <= tol, (
+ f"Fail: {varname} maximum error is {error:e} (Tolerance: {tol:e})"
+ )
diff --git a/tests/integrated/test-petsc_laplace_MAST-grid/runtest b/tests/integrated/test-petsc_laplace_MAST-grid/runtest
deleted file mode 100755
index 3be39949c3..0000000000
--- a/tests/integrated/test-petsc_laplace_MAST-grid/runtest
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-# requires: all_tests
-# requires: petsc
-# cores: 4
-
-# Variables to compare
-from __future__ import print_function
-from builtins import str
-
-vars = [
- ["max_error1", 2.0e-4],
- ["max_error2", 1.0e-4],
- ["max_error3", 1.0e-4],
- ["max_error4", 1.0e-4],
- ["max_error5", 2.0e-3],
- ["max_error6", 3.0e-4],
- ["max_error7", 2.0e-4],
- ["max_error8", 1.0e-4],
-]
-# tol = 1e-4 # Absolute (?) tolerance
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-from sys import stdout, exit
-
-build_and_log(
- "PETSc Laplacian inversion test with non-identity metric (taken from grid for MAST SOL)"
-)
-
-print(
- "Running PETSc Laplacian inversion test with non-identity metric (taken from grid for MAST SOL)"
-)
-success = True
-
-for nproc in [1, 2, 4]:
- # nxpe = 1
- # if nproc > 2:
- # nxpe = 2
- for jy in [2, 34, 65, 81, 113]:
- cmd = "./test_petsc_laplace_MAST_grid mesh:file=grids/grid_MAST_SOL_jyis{}.nc".format(
- jy
- )
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" {} processors, grid_MAST_SOL_jyis{}".format(nproc, jy))
- s, out = launch_safe(cmd, nproc=nproc, pipe=True)
- f = open("run.log." + str(nproc), "w")
- f.write(out)
- f.close()
-
- # Collect output data
- for v in vars:
- stdout.write(" Checking " + v[0] + " ... ")
- error = collect(v[0], path="data", info=False)
- if error <= 0:
- print("Convergence error")
- success = False
- elif error > v[1]:
- print("Fail, maximum error is = " + str(error))
- success = False
- else:
- print("Pass")
-
-if success:
- print(
- " => All PETSc Laplacian inversion with non-identity metric (taken from grid for MAST SOL) tests passed"
- )
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-petsc_laplace_MAST-grid/test_petsc_laplace_MAST_grid.py b/tests/integrated/test-petsc_laplace_MAST-grid/test_petsc_laplace_MAST_grid.py
new file mode 100755
index 0000000000..9c67f4bc80
--- /dev/null
+++ b/tests/integrated/test-petsc_laplace_MAST-grid/test_petsc_laplace_MAST_grid.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, compare results against the benchmark
+#
+
+# requires: all_tests
+# requires: petsc
+# cores: 4
+
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+from sys import stdout
+
+# Variables to compare
+vars = [
+ ["max_error1", 2.0e-4],
+ ["max_error2", 1.0e-4],
+ ["max_error3", 1.0e-4],
+ ["max_error4", 1.0e-4],
+ ["max_error5", 2.0e-3],
+ ["max_error6", 3.0e-4],
+ ["max_error7", 2.0e-4],
+ ["max_error8", 1.0e-4],
+]
+# tol = 1e-4 # Absolute (?) tolerance
+
+
+def test_petsc_laplace_MAST_grid():
+
+ print(
+ "Running PETSc Laplacian inversion test with non-identity metric (taken from grid for MAST SOL)"
+ )
+ success = True
+
+ for nproc in [1, 2, 4]:
+ # nxpe = 1
+ # if nproc > 2:
+ # nxpe = 2
+ for jy in [2, 34, 65, 81, 113]:
+ cmd = "./test_petsc_laplace_MAST_grid mesh:file=grids/grid_MAST_SOL_jyis{}.nc".format(
+ jy
+ )
+
+ shell(["rm data/BOUT.dmp.*.nc"])
+
+ print(" {} processors, grid_MAST_SOL_jyis{}".format(nproc, jy))
+ s, out = launch_safe(cmd, nproc=nproc, pipe=True)
+ f = open("run.log." + str(nproc), "w")
+ f.write(out)
+ f.close()
+
+ # Collect output data
+ for v in vars:
+ stdout.write(" Checking " + v[0] + " ... ")
+ error = collect(v[0], path="data", info=False)
+ if error <= 0:
+ print("Convergence error")
+ success = False
+ elif error > v[1]:
+ print("Fail, maximum error is = " + str(error))
+ success = False
+ else:
+ print("Pass")
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-region-iterator/runtest b/tests/integrated/test-region-iterator/runtest
deleted file mode 100755
index 2277ef15e9..0000000000
--- a/tests/integrated/test-region-iterator/runtest
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, check it completed successfully
-#
-
-# requires: netcdf
-# cores: 2
-
-from __future__ import print_function
-
-try:
- from builtins import str
-except:
- pass
-from boututils.run_wrapper import build_and_log, launch_safe
-from boutdata.collect import collect
-from sys import exit
-
-build_and_log("Region Iterator test")
-
-flags = [""]
-cmd = "./test_region_iterator"
-code = 0 # Return code
-pipe = True
-
-for nproc in [1, 2]: # Number of mpi procs
- for mthread in [1]: # Number of omp threads (not yet supported)
- print("\t{n} processors and {m} threads".format(n=nproc, m=mthread))
-
- for f in flags:
- # Run the case
- s, out = launch_safe(cmd + " " + f, nproc=nproc, pipe=pipe)
- if pipe:
- f = open("run.log." + str(nproc) + "." + str(mthread), "w")
- f.write(out)
- f.close()
-
- # If we've got here we know that cmd launched by launch_safe passed
- # as otherwise it raises.
- print("Passed")
-
-if code == 0:
- print(" => All range iterator tests passed")
-else:
- print(" => Some failed tests")
-
-exit(code)
diff --git a/tests/integrated/test-region-iterator/test_region_iterator.py b/tests/integrated/test-region-iterator/test_region_iterator.py
new file mode 100755
index 0000000000..7b5f25599d
--- /dev/null
+++ b/tests/integrated/test-region-iterator/test_region_iterator.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, check it completed successfully
+#
+
+# requires: netcdf
+# cores: 2
+
+from boututils.run_wrapper import launch_safe
+
+
+flags = [""]
+cmd = "./test_region_iterator"
+code = 0 # Return code
+pipe = True
+
+
+def test_region_iterator():
+
+ for nproc in [1, 2]: # Number of mpi procs
+ for mthread in [1]: # Number of omp threads (not yet supported)
+ print("\t{n} processors and {m} threads".format(n=nproc, m=mthread))
+
+ for f in flags:
+ # Run the case
+ s, out = launch_safe(cmd + " " + f, nproc=nproc, pipe=pipe)
+ if pipe:
+ f = open("run.log." + str(nproc) + "." + str(mthread), "w")
+ f.write(out)
+ f.close()
+
+ # If we've got here we know that cmd launched by launch_safe passed
+ # as otherwise it raises.
+ print("Passed")
+
+ assert code == 0, " => Some failed tests"
diff --git a/tests/integrated/test-restart-io/runtest b/tests/integrated/test-restart-io/runtest
deleted file mode 100755
index 00ee907958..0000000000
--- a/tests/integrated/test-restart-io/runtest
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/env python3
-#
-# Test file I/O by loading from restart files and writing to dump files
-#
-# requires: netcdf
-# cores: 4
-
-from boutdata import restart
-from boutdata.collect import collect
-from boututils.boutarray import BoutArray
-from boututils.datafile import DataFile
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-import numpy
-import os
-from sys import exit
-import uuid
-
-nx = 8
-ny = 16
-nz = 4
-mxg = 2
-myg = 2
-
-build_and_log("restart I/O test")
-
-x = numpy.linspace(0.0, 1.0, nx + 2 * mxg)[:, numpy.newaxis, numpy.newaxis]
-y = numpy.linspace(0.0, 1.0, ny + 2 * myg)[numpy.newaxis, :, numpy.newaxis]
-z = numpy.linspace(0.0, 1.0, nz)[numpy.newaxis, numpy.newaxis, :]
-
-testvars = {}
-testvars["f3d"] = BoutArray(
- numpy.exp(numpy.sin(x + y + z)), attributes={"bout_type": "Field3D"}
-)
-testvars["f2d"] = BoutArray(
- numpy.exp(numpy.sin(x + y + 1.0))[:, :, 0], attributes={"bout_type": "Field2D"}
-)
-testvars["fperp_lower"] = BoutArray(
- numpy.exp(numpy.sin(x + z + 2.0))[:, 0, :],
- attributes={"bout_type": "FieldPerp", "yindex_global": 0},
-)
-testvars["fperp_upper"] = BoutArray(
- numpy.exp(numpy.sin(x + z + 3.0))[:, 0, :],
- attributes={"bout_type": "FieldPerp", "yindex_global": ny - 1},
-)
-
-# make restart file
-restartdir = os.path.join("data", "restart")
-try:
- os.mkdir(restartdir)
-except FileExistsError:
- pass
-
-with DataFile(
- os.path.join(restartdir, "BOUT.restart.0.nc"), create=True
-) as base_restart:
- base_restart.write("MXSUB", nx)
- base_restart.write("MYSUB", ny)
- base_restart.write("MZSUB", nz)
- base_restart.write("MXG", mxg)
- base_restart.write("MYG", myg)
- base_restart.write("MZG", 0)
- base_restart.write("nx", nx + 2 * mxg)
- base_restart.write("ny", ny)
- base_restart.write("nz", nz)
- base_restart.write("MZ", nz)
- base_restart.write("NXPE", 1)
- base_restart.write("NYPE", 1)
- base_restart.write("NZPE", 1)
- base_restart.write("tt", 0.0)
- base_restart.write("hist_hi", 0)
- # set BOUT_VERSION to stop collect from changing nz or printing a warning
- base_restart.write("BOUT_VERSION", 4.0)
- base_restart.write("f3d", testvars["f3d"])
- base_restart.write("f2d", testvars["f2d"])
- base_restart.write("fperp_lower", testvars["fperp_lower"])
- base_restart.write("fperp_upper", testvars["fperp_upper"])
-
- # make run_id an array of characters because NetCDF doesn't like generic strings
- run_id_string = "36 character run_id test string ****"
- run_id = numpy.array(list(run_id_string), dtype="S1")
- run_id = BoutArray(run_id, attributes={"bout_type": "string"})
- base_restart.write("run_id", run_id)
- run_restart_from = numpy.array(
- list("36 character run_restart_from string"), dtype="S1"
- )
- run_restart_from = BoutArray(run_restart_from, attributes={"bout_type": "string"})
- base_restart.write("run_restart_from", run_restart_from)
-
-success = True
-
-# Note: expect this to fail for 16 processors, because when there are 2
-# y-points per processor, the fperp_lower FieldPerp is in the grid cells of one
-# set of processors and also in the guard cells of anoether set. This means
-# valid FieldPerps get written to output files with different
-# y-processor-indices, and collect() cannot handle this.
-for nproc in [1, 2, 4]:
- # delete any existing output
- shell("rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc")
-
- # create restart files for the run
- restart.redistribute(nproc, path=restartdir, output="data")
-
- print(" %d processor...." % (nproc))
-
- # run the test executable
- s, out = launch_safe("./test-restart-io", nproc=nproc, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # check the results
- for name in testvars.keys():
- # check non-evolving version
- result = collect(
- name + "_once", path="data", xguards=True, yguards=True, info=False
- )
- testvar = testvars[name]
-
- if not numpy.allclose(testvar, result):
- success = False
- print(f"{name}_once is different: {numpy.max(numpy.abs(testvar - result))}")
- # Don't plot anything by default
- if False:
- from boututils.showdata import showdata
-
- showdata([result, testvar])
- if name == "fperp_lower" or name == "fperp_upper":
- yindex_result = result.attributes["yindex_global"]
- yindex_test = testvar.attributes["yindex_global"]
- if not yindex_result == yindex_test:
- success = False
- print(
- "Fail: yindex_global of "
- + name
- + " is "
- + str(yindex_result)
- + " should be "
- + str(yindex_test)
- )
-
- # check evolving versions
- result = collect(name, path="data", xguards=True, yguards=True, info=False)
-
- for result_timeslice in result:
- if not numpy.all(testvar == result_timeslice):
- success = False
- print(
- f"{name} evolving version is different: {numpy.max(numpy.abs(testvar - result_timeslice))}"
- )
- if name == "fperp_lower" or name == "fperp_upper":
- yindex_result = result.attributes["yindex_global"]
- yindex_test = testvar.attributes["yindex_global"]
- if not yindex_result == yindex_test:
- success = False
- print(
- "Fail: yindex_global of "
- + name
- + " evolving version is "
- + str(yindex_result)
- + " should be "
- + str(yindex_test)
- )
-
- # check the run_id
- run_id = str(collect("run_id", path="data", info=False))
- # check run_id can be converted to a valid UUID
- try:
- uuid.UUID(run_id)
- except ValueError:
- success = False
- print(f"run_id='{run_id}' is not a valid UUID")
- run_restart_from = str(collect("run_restart_from", path="data", info=False))
- if not run_restart_from == run_id_string:
- success = False
- print(
- f"incorrect run_restart_from='{run_restart_from}'. Expected '{run_id_string}'"
- )
-
-if success:
- print("=> All restart I/O tests passed")
- # clean up binary files
- shell(
- "rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc data/restart/BOUT.restart.0.nc"
- )
- exit(0)
-
-print("=> Some failed tests")
-exit(1)
diff --git a/tests/integrated/test-restart-io/test_restart_io.py b/tests/integrated/test-restart-io/test_restart_io.py
new file mode 100755
index 0000000000..f37e56f5c1
--- /dev/null
+++ b/tests/integrated/test-restart-io/test_restart_io.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+#
+# Test file I/O by loading from restart files and writing to dump files
+#
+# requires: netcdf
+# cores: 4
+
+from boutdata import restart
+from boutdata.collect import collect
+from boututils.boutarray import BoutArray
+from boututils.datafile import DataFile
+from boututils.run_wrapper import shell, launch_safe
+import numpy
+import uuid
+from pathlib import Path
+
+nx = 8
+ny = 16
+nz = 4
+mxg = 2
+myg = 2
+
+
+def test_restart_io():
+
+ x = numpy.linspace(0.0, 1.0, nx + 2 * mxg)[:, numpy.newaxis, numpy.newaxis]
+ y = numpy.linspace(0.0, 1.0, ny + 2 * myg)[numpy.newaxis, :, numpy.newaxis]
+ z = numpy.linspace(0.0, 1.0, nz)[numpy.newaxis, numpy.newaxis, :]
+
+ testvars = {}
+ testvars["f3d"] = BoutArray(
+ numpy.exp(numpy.sin(x + y + z)), attributes={"bout_type": "Field3D"}
+ )
+ testvars["f2d"] = BoutArray(
+ numpy.exp(numpy.sin(x + y + 1.0))[:, :, 0], attributes={"bout_type": "Field2D"}
+ )
+ testvars["fperp_lower"] = BoutArray(
+ numpy.exp(numpy.sin(x + z + 2.0))[:, 0, :],
+ attributes={"bout_type": "FieldPerp", "yindex_global": 0},
+ )
+ testvars["fperp_upper"] = BoutArray(
+ numpy.exp(numpy.sin(x + z + 3.0))[:, 0, :],
+ attributes={"bout_type": "FieldPerp", "yindex_global": ny - 1},
+ )
+
+ # make restart file
+ restartdir = Path("data") / "restart"
+ try:
+ Path.mkdir(restartdir)
+ except FileExistsError:
+ pass
+
+ with DataFile(restartdir / "BOUT.restart.0.nc", create=True) as base_restart:
+ base_restart.write("MXSUB", nx)
+ base_restart.write("MYSUB", ny)
+ base_restart.write("MZSUB", nz)
+ base_restart.write("MXG", mxg)
+ base_restart.write("MYG", myg)
+ base_restart.write("MZG", 0)
+ base_restart.write("nx", nx + 2 * mxg)
+ base_restart.write("ny", ny)
+ base_restart.write("nz", nz)
+ base_restart.write("MZ", nz)
+ base_restart.write("NXPE", 1)
+ base_restart.write("NYPE", 1)
+ base_restart.write("NZPE", 1)
+ base_restart.write("tt", 0.0)
+ base_restart.write("hist_hi", 0)
+ # set BOUT_VERSION to stop collect from changing nz or printing a warning
+ base_restart.write("BOUT_VERSION", 4.0)
+ base_restart.write("f3d", testvars["f3d"])
+ base_restart.write("f2d", testvars["f2d"])
+ base_restart.write("fperp_lower", testvars["fperp_lower"])
+ base_restart.write("fperp_upper", testvars["fperp_upper"])
+
+ # make run_id an array of characters because NetCDF doesn't like generic strings
+ run_id_string = "36 character run_id test string ****"
+ run_id = numpy.array(list(run_id_string), dtype="S1")
+ run_id = BoutArray(run_id, attributes={"bout_type": "string"})
+ base_restart.write("run_id", run_id)
+ run_restart_from = numpy.array(
+ list("36 character run_restart_from string"), dtype="S1"
+ )
+ run_restart_from = BoutArray(
+ run_restart_from, attributes={"bout_type": "string"}
+ )
+ base_restart.write("run_restart_from", run_restart_from)
+
+ success = True
+
+ # Note: expect this to fail for 16 processors, because when there are 2
+ # y-points per processor, the fperp_lower FieldPerp is in the grid cells of one
+ # set of processors and also in the guard cells of anoether set. This means
+ # valid FieldPerps get written to output files with different
+ # y-processor-indices, and collect() cannot handle this.
+ for nproc in [1, 2, 4]:
+ # delete any existing output
+ shell("rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc")
+
+ # create restart files for the run
+ restart.redistribute(nproc, path=restartdir, output="data")
+
+ print(" %d processor...." % (nproc))
+
+ # run the test executable
+ s, out = launch_safe("./test-restart-io", nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ # check the results
+ for name in testvars.keys():
+ # check non-evolving version
+ result = collect(
+ name + "_once", path="data", xguards=True, yguards=True, info=False
+ )
+ testvar = testvars[name]
+
+ if not numpy.allclose(testvar, result):
+ success = False
+ print(
+ f"{name}_once is different: {numpy.max(numpy.abs(testvar - result))}"
+ )
+ # Don't plot anything by default
+ if False:
+ from boututils.showdata import showdata
+
+ showdata([result, testvar])
+ if name == "fperp_lower" or name == "fperp_upper":
+ yindex_result = result.attributes["yindex_global"]
+ yindex_test = testvar.attributes["yindex_global"]
+ if not yindex_result == yindex_test:
+ success = False
+ print(
+ "Fail: yindex_global of "
+ + name
+ + " is "
+ + str(yindex_result)
+ + " should be "
+ + str(yindex_test)
+ )
+
+ # check evolving versions
+ result = collect(name, path="data", xguards=True, yguards=True, info=False)
+
+ for result_timeslice in result:
+ if not numpy.all(testvar == result_timeslice):
+ success = False
+ print(
+ f"{name} evolving version is different: {numpy.max(numpy.abs(testvar - result_timeslice))}"
+ )
+ if name == "fperp_lower" or name == "fperp_upper":
+ yindex_result = result.attributes["yindex_global"]
+ yindex_test = testvar.attributes["yindex_global"]
+ if not yindex_result == yindex_test:
+ success = False
+ print(
+ "Fail: yindex_global of "
+ + name
+ + " evolving version is "
+ + str(yindex_result)
+ + " should be "
+ + str(yindex_test)
+ )
+
+ # check the run_id
+ run_id = str(collect("run_id", path="data", info=False))
+ # check run_id can be converted to a valid UUID
+ try:
+ uuid.UUID(run_id)
+ except ValueError:
+ success = False
+ print(f"run_id='{run_id}' is not a valid UUID")
+ run_restart_from = str(collect("run_restart_from", path="data", info=False))
+ if not run_restart_from == run_id_string:
+ success = False
+ print(
+ f"incorrect run_restart_from='{run_restart_from}'. Expected '{run_id_string}'"
+ )
+
+ assert success, "=> Some failed tests"
+
+ # clean up binary files
+ shell(
+ "rm -f data/BOUT.dmp.*.nc data/BOUT.restart.*.nc data/restart/BOUT.restart.0.nc"
+ )
diff --git a/tests/integrated/test-restarting/runtest b/tests/integrated/test-restarting/runtest
deleted file mode 100755
index daf19ebac6..0000000000
--- a/tests/integrated/test-restarting/runtest
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
-
-build_and_log("restart test")
-
-# Run once for 10 timesteps
-s, out = launch_safe("./test_restarting solver:nout=10", nproc=1, pipe=True)
-
-# Read reference data
-f3d_0 = collect("f3d", path="data", info=False)
-f2d_0 = collect("f2d", path="data", info=False)
-
-###########################################
-# Run twice, restarting and appending
-
-print("-> Testing restart append")
-
-shell("rm -f data/BOUT.dmp.0.nc")
-s, out = launch_safe("./test_restarting solver:nout=5", nproc=1, pipe=True)
-s, out = launch_safe(
- "./test_restarting solver:nout=5 restart append", nproc=1, pipe=True
-)
-
-f3d_1 = collect("f3d", path="data", info=False)
-f2d_1 = collect("f2d", path="data", info=False)
-
-success = True
-tolerance = 1e-10
-
-if f3d_1.shape != f3d_0.shape:
- print("Fail: Field3D field has wrong shape")
- success = False
-if f2d_1.shape != f2d_0.shape:
- print("Fail: Field2D field has wrong shape")
- success = False
-
-if not np.allclose(f3d_1, f3d_0, atol=tolerance):
- print("Fail: Field3D values differ")
- success = False
-
-if not np.allclose(f2d_1, f2d_0, atol=tolerance):
- print("Fail: Field2D values differ")
- success = False
-
-###########################################
-# Test restart
-
-print("-> Testing restart")
-
-shell("rm -f data/BOUT.dmp.0.nc")
-s, out = launch_safe("./test_restarting solver:nout=5", nproc=1, pipe=True)
-s, out = launch_safe("./test_restarting solver:nout=5 restart", nproc=1, pipe=True)
-
-f3d_1 = collect("f3d", path="data", info=False)
-f2d_1 = collect("f2d", path="data", info=False)
-
-if f3d_1.shape[0] != 6:
- print("Fail: Field3D has wrong shape")
- success = False
-if f2d_1.shape[0] != 6:
- print("Fail: Field2D has wrong shape")
- success = False
-
-if not np.allclose(f3d_1, f3d_0[5:, :, :, :], atol=tolerance):
- print("Fail: Field3D values differ")
- success = False
-if not np.allclose(f2d_1, f2d_0[5:, :, :], atol=tolerance):
- print("Fail: Field2D values differ")
- success = False
-
-if not success:
- print("=> Some tests failed")
- exit(1)
-
-print("=> Success")
-exit(0)
diff --git a/tests/integrated/test-restarting/test_restarting.py b/tests/integrated/test-restarting/test_restarting.py
new file mode 100755
index 0000000000..b5f51bd073
--- /dev/null
+++ b/tests/integrated/test-restarting/test_restarting.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+
+import numpy as np
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+
+def test_restarting():
+
+ # Run once for 10 timesteps
+ s, out = launch_safe("./test_restarting solver:nout=10", nproc=1, pipe=True)
+
+ # Read reference data
+ f3d_0 = collect("f3d", path="data", info=False)
+ f2d_0 = collect("f2d", path="data", info=False)
+
+ ###########################################
+ # Run twice, restarting and appending
+
+ print("-> Testing restart append")
+
+ shell(["rm -f data/BOUT.dmp.0.nc"])
+ s, out = launch_safe("./test_restarting solver:nout=5", nproc=1, pipe=True)
+ s, out = launch_safe(
+ "./test_restarting solver:nout=5 restart append", nproc=1, pipe=True
+ )
+
+ f3d_1 = collect("f3d", path="data", info=False)
+ f2d_1 = collect("f2d", path="data", info=False)
+
+ success = True
+ tolerance = 1e-10
+
+ if f3d_1.shape != f3d_0.shape:
+ print("Fail: Field3D field has wrong shape")
+ success = False
+ if f2d_1.shape != f2d_0.shape:
+ print("Fail: Field2D field has wrong shape")
+ success = False
+
+ if not np.allclose(f3d_1, f3d_0, atol=tolerance):
+ print("Fail: Field3D values differ")
+ success = False
+
+ if not np.allclose(f2d_1, f2d_0, atol=tolerance):
+ print("Fail: Field2D values differ")
+ success = False
+
+ ###########################################
+ # Test restart
+
+ print("-> Testing restart")
+
+ shell(["rm -f data/BOUT.dmp.0.nc"])
+ s, out = launch_safe("./test_restarting solver:nout=5", nproc=1, pipe=True)
+ s, out = launch_safe("./test_restarting solver:nout=5 restart", nproc=1, pipe=True)
+
+ f3d_1 = collect("f3d", path="data", info=False)
+ f2d_1 = collect("f2d", path="data", info=False)
+
+ if f3d_1.shape[0] != 6:
+ print("Fail: Field3D has wrong shape")
+ success = False
+ if f2d_1.shape[0] != 6:
+ print("Fail: Field2D has wrong shape")
+ success = False
+
+ if not np.allclose(f3d_1, f3d_0[5:, :, :, :], atol=tolerance):
+ print("Fail: Field3D values differ")
+ success = False
+ if not np.allclose(f2d_1, f2d_0[5:, :, :], atol=tolerance):
+ print("Fail: Field2D values differ")
+ success = False
+
+ assert success, "=> Some tests failed"
diff --git a/tests/integrated/test-slepc-solver/runtest b/tests/integrated/test-slepc-solver/runtest
deleted file mode 100755
index baced1bcf3..0000000000
--- a/tests/integrated/test-slepc-solver/runtest
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python3
-
-# requires: slepc
-
-from boutdata.collect import collect
-from boututils.run_wrapper import build_and_log, launch_safe
-from numpy import isclose
-
-build_and_log("SLEPc eigen solver test")
-
-print("Running SLEPc eigen solver test")
-status, out = launch_safe("./test-slepc-solver", nproc=1, pipe=True, verbose=True)
-
-with open("run.log", "w") as f:
- f.write(out)
-
-eigenvalues = collect("t_array", path="data", info=False)
-
-expected_eigenvalues = [0.0, 1.0]
-
-if isclose(expected_eigenvalues, eigenvalues).all():
- print(" => SLEPc test passed")
- exit(0)
-else:
- print(" => SLEPc test failed")
- print(" Eigenvalues:", eigenvalues)
- exit(1)
diff --git a/tests/integrated/test-slepc-solver/test_slepc_solver.py b/tests/integrated/test-slepc-solver/test_slepc_solver.py
new file mode 100755
index 0000000000..2df73df018
--- /dev/null
+++ b/tests/integrated/test-slepc-solver/test_slepc_solver.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+# requires: slepc
+
+from boutdata.collect import collect
+from boututils.run_wrapper import launch_safe
+import numpy.testing as npt
+
+
+def test_slepc_solver():
+
+ print("Running SLEPc eigen solver test")
+ status, out = launch_safe("./test-slepc-solver", nproc=1, pipe=True, verbose=True)
+
+ with open("run.log", "w") as f:
+ f.write(out)
+
+ eigenvalues = collect("t_array", path="data", info=False)
+
+ expected_eigenvalues = [0.0, 1.0]
+
+ npt.assert_allclose(
+ expected_eigenvalues,
+ eigenvalues,
+ err_msg=" => SLEPc test failed\nEigenvalues: {eigenvalues}",
+ )
diff --git a/tests/integrated/test-smooth/runtest b/tests/integrated/test-smooth/runtest
deleted file mode 100755
index 91ae793164..0000000000
--- a/tests/integrated/test-smooth/runtest
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Run the test, compare results against the benchmark
-#
-
-# Requires: netcdf
-# Cores: 4
-
-# Variables to compare
-vars = ["yavg2d", "yavg3d", "sm3d"]
-tol = 1e-7 # Absolute tolerance, benchmark values are floats
-
-from boututils.run_wrapper import build_and_log, shell, launch_safe
-from boutdata.collect import collect
-import numpy as np
-from sys import stdout, exit
-
-build_and_log("smoothing operator test")
-
-# Read benchmark values
-print("Reading benchmark data")
-bmk = {}
-for v in vars:
- bmk[v] = collect(v, path="data", prefix="benchmark", info=False)
-
-print("Running smoothing operator test")
-success = True
-
-for nype in [1, 2]:
- for nxpe in [1, 2]:
- nproc = nxpe * nype
- cmd = "./test_smooth"
-
- shell("rm data/BOUT.dmp.*.nc")
-
- print(" %d processor (%d x %d)...." % (nproc, nxpe, nype))
- s, out = launch_safe(cmd + " NXPE=" + str(nxpe), nproc=nproc, pipe=True)
- with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
- # Collect output data
- for v in vars:
- stdout.write(" Checking variable " + v + " ... ")
- result = collect(v, path="data", info=False)
- # Compare benchmark and output
- if np.shape(bmk[v]) != np.shape(result):
- print("Fail, wrong shape")
- success = False
- continue
-
- diff = np.max(np.abs(bmk[v] - result))
- if diff > tol:
- print("Fail, maximum difference = " + str(diff))
- success = False
- else:
- print("Pass")
-
-if success:
- print(" => All smoothing operator tests passed")
- exit(0)
-else:
- print(" => Some failed tests")
- exit(1)
diff --git a/tests/integrated/test-smooth/test_smooth.py b/tests/integrated/test-smooth/test_smooth.py
new file mode 100755
index 0000000000..40858eb15e
--- /dev/null
+++ b/tests/integrated/test-smooth/test_smooth.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+#
+# Run the test, compare results against the benchmark
+#
+
+# Requires: netcdf
+# Cores: 4
+
+import pytest
+import numpy as np
+import itertools
+from pathlib import Path
+from boututils.run_wrapper import shell, launch_safe
+from boutdata.collect import collect
+
+# Variables to compare
+vars = ["yavg2d", "yavg3d", "sm3d"]
+tol = 1e-7 # Absolute tolerance, benchmark values are floats
+
+
+@pytest.fixture(scope="module")
+def benchmark_data():
+ """Reads the benchmark data once for all test iterations in this module."""
+ bmk = {}
+ source_data_dir = str(Path(__file__).parent / "data")
+
+ for v in vars:
+ bmk[v] = collect(v, path=source_data_dir, prefix="benchmark", info=False)
+ return bmk
+
+
+# Generate test configurations: [(1, 1), (1, 2), (2, 1), (2, 2)]
+PROCESSOR_TOPOLOGIES = list(itertools.product([1, 2], [1, 2]))
+
+
+@pytest.mark.parametrize("nxpe, nype", PROCESSOR_TOPOLOGIES)
+def test_smooth(benchmark_data, nxpe, nype):
+ nproc = nxpe * nype
+ cmd = "./test_smooth"
+
+ # Clean up old data
+ shell(["rm -f data/BOUT.dmp.*.nc"])
+
+ # Run the executable
+ s, out = launch_safe(f"{cmd} NXPE={nxpe}", nproc=nproc, pipe=True)
+
+ # Save log
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
+
+ # Collect output data
+ for v in vars:
+ result = collect(v, path="data", info=False)
+ bmk = benchmark_data[v]
+
+ # Compare benchmark and output
+
+ assert bmk.shape == result.shape, (
+ f"Shape mismatch for variable '{v}' on {nxpe}x{nype} grid"
+ )
+
+ np.testing.assert_allclose(
+ result,
+ bmk,
+ atol=tol,
+ rtol=0,
+ err_msg=f"Data mismatch for variable '{v}' on {nxpe}x{nype} grid",
+ )
diff --git a/tests/integrated/test-snb/test_snb.py b/tests/integrated/test-snb/test_snb.py
new file mode 100644
index 0000000000..1095b40a70
--- /dev/null
+++ b/tests/integrated/test-snb/test_snb.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./runtest")
diff --git a/tests/integrated/test-solver/runtest b/tests/integrated/test-solver/runtest
deleted file mode 100755
index 607f3bb7ea..0000000000
--- a/tests/integrated/test-solver/runtest
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, launch_safe
-
-from sys import exit
-
-nthreads = 1
-nproc = 1
-
-
-build_and_log("Solver test")
-
-print("Running solver test")
-status, out = launch_safe("./test_solver", nproc=nproc, mthread=nthreads, pipe=True)
-with open("run.log", "w") as f:
- f.write(out)
-
-if status:
- print(out)
-
-exit(status)
diff --git a/tests/integrated/test-solver/test_solver.py b/tests/integrated/test-solver/test_solver.py
new file mode 100755
index 0000000000..1b1ff2e79c
--- /dev/null
+++ b/tests/integrated/test-solver/test_solver.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+
+from boututils.run_wrapper import shell_safe
+
+
+def test_solver():
+ print("Running solver test")
+ status, out = shell_safe("./test_solver", pipe=True)
+ with open("run.log", "w") as f:
+ f.write(out)
+
+ assert status == 0, out
diff --git a/tests/integrated/test-squash/runtest b/tests/integrated/test-squash/test_squash.py
similarity index 53%
rename from tests/integrated/test-squash/runtest
rename to tests/integrated/test-squash/test_squash.py
index 8bf358ee8e..ab13380811 100755
--- a/tests/integrated/test-squash/runtest
+++ b/tests/integrated/test-squash/test_squash.py
@@ -1,13 +1,13 @@
#!/usr/bin/env python3
-from boututils.datafile import DataFile
import itertools
import time
import numpy as np
-from boututils.run_wrapper import launch_safe, shell_safe, build_and_log
import argparse
import re
-import os.path
+from boututils.datafile import DataFile
+from boututils.run_wrapper import launch_safe, shell_safe
+from pathlib import Path
# requires: all_tests
# requires: netcdf
@@ -79,51 +79,55 @@ def verify(f1, f2):
raise RuntimeError("data mismatch in ", v, err, v1, v2)
-parser = argparse.ArgumentParser(description="Test the bout-squashoutput wrapper")
-parser.add_argument(
- "executable", help="Path to bout-squashoutput", default="../../../bin", nargs="?"
-)
-args = parser.parse_args()
-
-build_and_log("Squash test")
+def test_squash():
+ parser = argparse.ArgumentParser(description="Test the bout-squashoutput wrapper")
+ parser.add_argument(
+ "executable",
+ help="Path to bout-squashoutput",
+ default="../../../bin",
+ nargs="?",
+ )
+ args = parser.parse_args()
-bout_squashoutput = args.executable + "/bout-squashoutput"
+ bout_squashoutput = Path(args.executable + "/bout-squashoutput")
-if not os.path.exists(bout_squashoutput):
- bout_squashoutput = "bout-squashoutput"
+ if not Path.exists(bout_squashoutput):
+ bout_squashoutput = "bout-squashoutput"
-print("Run once to get normal data")
-timed_shell_safe("./squash -q -q -q solver:nout=2")
-timed_shell_safe("mv data/BOUT.dmp.0.nc f1.nc")
+ print("Run once to get normal data")
+ timed_shell_safe("./squash -q -q -q solver:nout=2")
+ timed_shell_safe("mv data/BOUT.dmp.0.nc f1.nc")
-print("Parallel test")
-timed_shell_safe("rm -f f2.nc")
-timed_launch_safe("./squash -q -q -q solver:nout=2", nproc=4, mthread=1)
-timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ print("Parallel test")
+ timed_shell_safe("rm -f f2.nc")
+ timed_launch_safe("./squash -q -q -q solver:nout=2", nproc=4, mthread=1)
+ timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
-verify("f1.nc", "f2.nc")
+ verify("f1.nc", "f2.nc")
-print("Parallel and in two pieces")
-timed_shell_safe("rm -f f2.nc")
-timed_launch_safe("./squash -q -q -q", nproc=4, mthread=1)
-timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
-timed_launch_safe("./squash -q -q -q restart", nproc=4, mthread=1)
-timed_shell_safe("{} -qdcal 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ print("Parallel and in two pieces")
+ timed_shell_safe("rm -f f2.nc")
+ timed_launch_safe("./squash -q -q -q", nproc=4, mthread=1)
+ timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ timed_launch_safe("./squash -q -q -q restart", nproc=4, mthread=1)
+ timed_shell_safe("{} -qdcal 9 data --outputname ../f2.nc".format(bout_squashoutput))
-verify("f1.nc", "f2.nc")
+ verify("f1.nc", "f2.nc")
-print("Parallel and in two pieces without dump_on_restart")
-timed_shell_safe("rm -f f2.nc")
-timed_launch_safe("./squash -q -q -q", nproc=4, mthread=1)
-timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
-timed_launch_safe("./squash -q -q -q restart dump_on_restart=false", nproc=4, mthread=1)
-timed_shell_safe("{} -qdcal 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ print("Parallel and in two pieces without dump_on_restart")
+ timed_shell_safe("rm -f f2.nc")
+ timed_launch_safe("./squash -q -q -q", nproc=4, mthread=1)
+ timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ timed_launch_safe(
+ "./squash -q -q -q restart dump_on_restart=false", nproc=4, mthread=1
+ )
+ timed_shell_safe("{} -qdcal 9 data --outputname ../f2.nc".format(bout_squashoutput))
-verify("f1.nc", "f2.nc")
+ verify("f1.nc", "f2.nc")
-print("Sequential test")
-timed_shell_safe("rm -f f2.nc")
-timed_shell_safe("./squash -q -q -q solver:nout=2")
-timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
+ print("Sequential test")
+ timed_shell_safe("rm -f f2.nc")
+ timed_shell_safe("./squash -q -q -q solver:nout=2")
+ timed_shell_safe("{} -qdcl 9 data --outputname ../f2.nc".format(bout_squashoutput))
-verify("f1.nc", "f2.nc")
+ verify("f1.nc", "f2.nc")
diff --git a/tests/integrated/test-stopCheck-file/runtest b/tests/integrated/test-stopCheck-file/runtest
deleted file mode 100755
index e959816d11..0000000000
--- a/tests/integrated/test-stopCheck-file/runtest
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-
-# Run the test, compare results against the benchmark
-
-import sys
-
-from boututils.run_wrapper import build_and_log, launch
-from boutdata.collect import collect
-
-nproc = 1
-
-build_and_log("stopCheck test")
-
-check_values = [True, False]
-expected_steps = [1, 11]
-data_dirs = ["data", "dataSecond"]
-stop_files = ["BOUT.stop", "otherStop.check"]
-
-print("Running stopCheck test")
-success = True
-
-for data_dir, stop_file in zip(data_dirs, stop_files):
- for check, expected in zip(check_values, expected_steps):
- executable = "./test_stopCheck"
- command = "{} -d {} stopCheck={} stopCheckName={}".format(
- executable, data_dir, check, stop_file
- )
-
- s, out = launch(
- command,
- nproc=nproc,
- pipe=True,
- )
- with open("run.log.{}.{}".format(data_dir, check), "w") as f:
- f.write(out)
-
- result = collect("t_array", path=data_dir, info=False)
-
- if result.shape[0] != expected:
- print("Fail, wrong shape")
- print("\tOption is {}/{}/{}".format(check, data_dir, stop_file))
- print("\tshape is {}".format(result.shape[0]))
- print("\texpecting {}".format(expected))
- sys.exit(1)
- success = False
- continue
-
-if success:
- print(" => All checkStop tests passed")
- sys.exit(0)
-else:
- print(" => Some failed tests")
- sys.exit(1)
diff --git a/tests/integrated/test-stopCheck-file/test_stopCheck_file.py b/tests/integrated/test-stopCheck-file/test_stopCheck_file.py
new file mode 100755
index 0000000000..9e1b32859d
--- /dev/null
+++ b/tests/integrated/test-stopCheck-file/test_stopCheck_file.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+# Run the test, compare results against the benchmark
+
+import sys
+
+from boututils.run_wrapper import launch
+from boutdata.collect import collect
+
+nproc = 1
+
+
+def test_stopCheck_file():
+
+ check_values = [True, False]
+ expected_steps = [1, 11]
+ data_dirs = ["data", "dataSecond"]
+ stop_files = ["BOUT.stop", "otherStop.check"]
+
+ print("Running stopCheck test")
+ success = True
+
+ for data_dir, stop_file in zip(data_dirs, stop_files):
+ for check, expected in zip(check_values, expected_steps):
+ executable = "./test_stopCheck"
+ command = "{} -d {} stopCheck={} stopCheckName={}".format(
+ executable, data_dir, check, stop_file
+ )
+
+ s, out = launch(
+ command,
+ nproc=nproc,
+ pipe=True,
+ )
+ with open("run.log.{}.{}".format(data_dir, check), "w") as f:
+ f.write(out)
+
+ result = collect("t_array", path=data_dir, info=False)
+
+ if result.shape[0] != expected:
+ print("Fail, wrong shape")
+ print("\tOption is {}/{}/{}".format(check, data_dir, stop_file))
+ print("\tshape is {}".format(result.shape[0]))
+ print("\texpecting {}".format(expected))
+ sys.exit(1)
+ success = False
+ continue
+
+ assert success, " => Some failed tests"
diff --git a/tests/integrated/test-stopCheck/test_stopCheck.py b/tests/integrated/test-stopCheck/test_stopCheck.py
new file mode 100644
index 0000000000..1095b40a70
--- /dev/null
+++ b/tests/integrated/test-stopCheck/test_stopCheck.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./runtest")
diff --git a/tests/integrated/test-subdir/test_subdir.py b/tests/integrated/test-subdir/test_subdir.py
new file mode 100644
index 0000000000..1095b40a70
--- /dev/null
+++ b/tests/integrated/test-subdir/test_subdir.py
@@ -0,0 +1,2 @@
+def test_runtest(assert_success_in_shell):
+ assert_success_in_shell("./runtest")
diff --git a/tests/integrated/test-twistshift-staggered/runtest b/tests/integrated/test-twistshift-staggered/runtest
deleted file mode 100755
index 5bb9963db7..0000000000
--- a/tests/integrated/test-twistshift-staggered/runtest
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-
-from boutdata import collect
-from boututils.run_wrapper import build_and_log, launch_safe
-import numpy
-from sys import exit
-
-datapath = "data"
-nproc = 1
-tol = 1.0e-13
-
-build_and_log("twistshift test")
-
-s, out = launch_safe("./test-twistshift", nproc=nproc, pipe=True)
-with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
-test = collect("test", path=datapath, yguards=True, info=False)
-test_aligned = collect("test_aligned", path=datapath, yguards=True, info=False)
-check = collect("check", path=datapath, yguards=True, info=False)
-
-# from boututils.showdata import showdata
-# showdata([test, test_aligned, check], titles=['test', 'test_aligned', 'check'])
-
-success = True
-
-
-# Check test_aligned is *not* periodic in y
-def test1(ylower, yupper):
- global success
- if numpy.any(
- numpy.abs(test_aligned[:, yupper, :] - test_aligned[:, ylower, :]) < 1.0e-6
- ):
- success = False
- print(
- "Fail - test_aligned should not be periodic jy=%i and jy=%i should be "
- "different" % (yupper, ylower)
- )
-
-
-test1(0, -4)
-test1(1, -3)
-test1(2, -2)
-test1(3, -1)
-
-# Check test_aligned is the same as check
-# Cannot check in guard cells, as the expression used for 'zShift' in the input file is
-# not the same as the corrected zShift used for the transforms in the guard cells
-if numpy.any(numpy.abs(test_aligned[2:-2, 2:-2, :] - check[2:-2, 2:-2, :]) > tol):
- success = False
- print("Fail - test_aligned is different from the expected value")
- print("test_aligned", test_aligned)
- print("check", check)
-
-if success:
- print("Pass")
- exit(0)
-else:
- exit(1)
diff --git a/tests/integrated/test-twistshift-staggered/test_twistshift_staggered.py b/tests/integrated/test-twistshift-staggered/test_twistshift_staggered.py
new file mode 100755
index 0000000000..19eca67f68
--- /dev/null
+++ b/tests/integrated/test-twistshift-staggered/test_twistshift_staggered.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+from boutdata import collect
+from boututils.run_wrapper import launch_safe
+import numpy
+
+datapath = "data"
+nproc = 1
+tol = 1.0e-13
+
+
+def test_twistshift_staggered():
+
+ s, out = launch_safe("./test-twistshift", nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ test_aligned = collect("test_aligned", path=datapath, yguards=True, info=False)
+ check = collect("check", path=datapath, yguards=True, info=False)
+
+ # from boututils.showdata import showdata
+ # showdata([test, test_aligned, check], titles=['test', 'test_aligned', 'check'])
+
+ success = True
+
+ # Check test_aligned is *not* periodic in y
+ def test1(ylower, yupper):
+ global success
+ if numpy.any(
+ numpy.abs(test_aligned[:, yupper, :] - test_aligned[:, ylower, :]) < 1.0e-6
+ ):
+ success = False
+ print(
+ "Fail - test_aligned should not be periodic jy=%i and jy=%i should be "
+ "different" % (yupper, ylower)
+ )
+
+ test1(0, -4)
+ test1(1, -3)
+ test1(2, -2)
+ test1(3, -1)
+
+ # Check test_aligned is the same as check
+ # Cannot check in guard cells, as the expression used for 'zShift' in the input file is
+ # not the same as the corrected zShift used for the transforms in the guard cells
+ if numpy.any(numpy.abs(test_aligned[2:-2, 2:-2, :] - check[2:-2, 2:-2, :]) > tol):
+ success = False
+ print("Fail - test_aligned is different from the expected value")
+ print("test_aligned", test_aligned)
+ print("check", check)
+
+ assert success
diff --git a/tests/integrated/test-twistshift/runtest b/tests/integrated/test-twistshift/runtest
deleted file mode 100755
index c4858970c4..0000000000
--- a/tests/integrated/test-twistshift/runtest
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python3
-
-from boutdata import collect
-from boututils.run_wrapper import build_and_log, launch_safe
-import numpy
-from sys import exit
-
-datapath = "data"
-nproc = 1
-tol = 1.0e-13
-
-build_and_log("twistshift test")
-
-s, out = launch_safe("./test-twistshift", nproc=nproc, pipe=True)
-with open("run.log." + str(nproc), "w") as f:
- f.write(out)
-
-test = collect("test", path=datapath, yguards=True, info=False)
-test_aligned = collect("test_aligned", path=datapath, yguards=True, info=False)
-result = collect("result", path=datapath, yguards=True, info=False)
-
-# from boututils.showdata import showdata
-# showdata([test, test_aligned, result], titles=['test', 'test_aligned', 'result'])
-
-success = True
-
-
-# Check test_aligned is *not* periodic in y
-def test1(ylower, yupper):
- global success
- if numpy.any(
- numpy.abs(test_aligned[:, yupper, :] - test_aligned[:, ylower, :]) < 1.0e-6
- ):
- success = False
- print(
- "Fail - test_aligned should not be periodic jy=%i and jy=%i should be "
- "different",
- yupper,
- ylower,
- )
-
-
-test1(0, -4)
-test1(1, -3)
-test1(2, -2)
-test1(3, -1)
-
-# Check test and result are the same
-if numpy.any(numpy.abs(result - test) > tol):
- print("Fail - result has not been communicated correctly - is different from input")
- success = False
-
-
-# Check result is periodic in y
-def test2(ylower, yupper):
- global success
- if numpy.any(numpy.abs(result[:, yupper, :] - result[:, ylower, :]) > tol):
- success = False
- print(
- "Fail - result should be periodic jy=%i and jy=%i should not be "
- "different",
- yupper,
- ylower,
- )
- print(ylower, result[:, ylower, :])
- print(yupper, result[:, yupper, :])
- print(result[:, ylower, :] - result[:, yupper, :])
-
-
-test2(0, -4)
-test2(1, -3)
-test2(2, -2)
-test2(3, -1)
-
-if success:
- print("Pass")
- exit(0)
-else:
- exit(1)
diff --git a/tests/integrated/test-twistshift/test_twistshift.py b/tests/integrated/test-twistshift/test_twistshift.py
new file mode 100755
index 0000000000..d586244bb4
--- /dev/null
+++ b/tests/integrated/test-twistshift/test_twistshift.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+from boutdata import collect
+from boututils.run_wrapper import launch_safe
+import numpy
+
+datapath = "data"
+nproc = 1
+tol = 1.0e-13
+
+
+def test_twistshift():
+
+ s, out = launch_safe("./test-twistshift", nproc=nproc, pipe=True)
+ with open("run.log." + str(nproc), "w") as f:
+ f.write(out)
+
+ test = collect("test", path=datapath, yguards=True, info=False)
+ test_aligned = collect("test_aligned", path=datapath, yguards=True, info=False)
+ result = collect("result", path=datapath, yguards=True, info=False)
+
+ # from boututils.showdata import showdata
+ # showdata([test, test_aligned, result], titles=['test', 'test_aligned', 'result'])
+
+ success = True
+
+ # Check test_aligned is *not* periodic in y
+ def test1(ylower, yupper):
+ global success
+ if numpy.any(
+ numpy.abs(test_aligned[:, yupper, :] - test_aligned[:, ylower, :]) < 1.0e-6
+ ):
+ success = False
+ print(
+ "Fail - test_aligned should not be periodic jy=%i and jy=%i should be "
+ "different",
+ yupper,
+ ylower,
+ )
+
+ test1(0, -4)
+ test1(1, -3)
+ test1(2, -2)
+ test1(3, -1)
+
+ # Check test and result are the same
+ if numpy.any(numpy.abs(result - test) > tol):
+ print(
+ "Fail - result has not been communicated correctly - is different from input"
+ )
+ success = False
+
+ # Check result is periodic in y
+ def test2(ylower, yupper):
+ global success
+ if numpy.any(numpy.abs(result[:, yupper, :] - result[:, ylower, :]) > tol):
+ success = False
+ print(
+ "Fail - result should be periodic jy=%i and jy=%i should not be "
+ "different",
+ yupper,
+ ylower,
+ )
+ print(ylower, result[:, ylower, :])
+ print(yupper, result[:, yupper, :])
+ print(result[:, ylower, :] - result[:, yupper, :])
+
+ test2(0, -4)
+ test2(1, -3)
+ test2(2, -2)
+ test2(3, -1)
+
+ assert success
diff --git a/tests/integrated/test-unused-options/runtest b/tests/integrated/test-unused-options/runtest
deleted file mode 100755
index f3fad0c404..0000000000
--- a/tests/integrated/test-unused-options/runtest
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, launch_safe
-
-build_and_log("unused options test")
-
-for nproc in [1, 2]:
- print(f"Running with nproc={nproc}...", end="")
- s, out = launch_safe(
- "./test_unused_options",
- nproc=nproc,
- mthread=1,
- pipe=True,
- )
-
- with open(f"run.log.{nproc}", "w") as f:
- f.write(out)
-
- # Check for printed message
- if "SUCCESS" not in out:
- print("failed")
- exit(1)
- print("pass")
diff --git a/tests/integrated/test-unused-options/test_unused_options.py b/tests/integrated/test-unused-options/test_unused_options.py
new file mode 100755
index 0000000000..b147aa353c
--- /dev/null
+++ b/tests/integrated/test-unused-options/test_unused_options.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+from boututils.run_wrapper import launch_safe
+
+
+def test_unused_options():
+
+ for nproc in [1, 2]:
+ print(f"Running with nproc={nproc}...", end="")
+ s, out = launch_safe(
+ "./test_unused_options",
+ nproc=nproc,
+ mthread=1,
+ pipe=True,
+ )
+
+ with open(f"run.log.{nproc}", "w") as f:
+ f.write(out)
+
+ # Check for printed message
+ assert "SUCCESS" in out, "failed"
diff --git a/tests/integrated/test-vec/runtest b/tests/integrated/test-vec/runtest
deleted file mode 100755
index 6d836be804..0000000000
--- a/tests/integrated/test-vec/runtest
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python3
-
-# Cores: 4
-
-from boututils.run_wrapper import build_and_log, launch_safe
-
-build_and_log("vector communication test")
-
-status, out = launch_safe("./testVec", nproc=4, pipe=True)
-
-with open("run.log.4", "w") as f:
- f.write(out)
-
-if status:
- print(" => Test failed")
- exit(status)
-
-print(" => Test passed")
diff --git a/tests/integrated/test-vec/test_vec.py b/tests/integrated/test-vec/test_vec.py
new file mode 100755
index 0000000000..ff2e14d608
--- /dev/null
+++ b/tests/integrated/test-vec/test_vec.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+
+# Cores: 4
+
+from boututils.run_wrapper import launch_safe
+
+
+def test_vec():
+
+ status, out = launch_safe("./testVec", nproc=4, pipe=True)
+
+ with open("run.log.4", "w") as f:
+ f.write(out)
+
+ assert not status, " => Test failed"
diff --git a/tests/integrated/test-yupdown-weights/runtest b/tests/integrated/test-yupdown-weights/runtest
deleted file mode 100755
index 4b59d08cb0..0000000000
--- a/tests/integrated/test-yupdown-weights/runtest
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, launch_safe
-from boutdata.collect import collect
-from sys import stdout, exit
-
-from numpy import max, abs
-
-build_and_log("parallel slices and weights test")
-
-failed = False
-for shifttype in ["shiftedinterp"]:
- s, out = launch_safe(
- "./test_yupdown_weights mesh:paralleltransform:type=" + shifttype,
- nproc=1,
- pipe=True,
- verbose=True,
- )
-
- with open("run.log", "w") as f:
- f.write(out)
-
- vars = [("ddy", "ddy2")]
- for v1, v2 in vars:
- stdout.write("Testing %s and %s ... " % (v1, v2))
- ddy = collect(v1, path="data", xguards=False, yguards=False, info=False)
- ddy2 = collect(v2, path="data", xguards=False, yguards=False, info=False)
-
- diff = max(abs(ddy - ddy2))
-
- if diff < 1e-8:
- print(shifttype + " passed (Max difference %e)" % (diff))
- else:
- print(shifttype + " failed (Max difference %e)" % (diff))
- failed = True
-
-if failed:
- exit(1)
-
-exit(0)
diff --git a/tests/integrated/test-yupdown-weights/test_yupdown_weights.py b/tests/integrated/test-yupdown-weights/test_yupdown_weights.py
new file mode 100755
index 0000000000..ae8754d50d
--- /dev/null
+++ b/tests/integrated/test-yupdown-weights/test_yupdown_weights.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+from boututils.run_wrapper import launch_safe
+from boutdata.collect import collect
+from sys import stdout
+
+from numpy import max, abs
+
+
+def test_yupdown_weights():
+
+ failed = False
+ for shifttype in ["shiftedinterp"]:
+ s, out = launch_safe(
+ "./test_yupdown_weights mesh:paralleltransform:type=" + shifttype,
+ nproc=1,
+ pipe=True,
+ verbose=True,
+ )
+
+ with open("run.log", "w") as f:
+ f.write(out)
+
+ vars = [("ddy", "ddy2")]
+ for v1, v2 in vars:
+ stdout.write("Testing %s and %s ... " % (v1, v2))
+ ddy = collect(v1, path="data", xguards=False, yguards=False, info=False)
+ ddy2 = collect(v2, path="data", xguards=False, yguards=False, info=False)
+
+ diff = max(abs(ddy - ddy2))
+
+ if diff < 1e-8:
+ print(shifttype + " passed (Max difference %e)" % (diff))
+ else:
+ print(shifttype + " failed (Max difference %e)" % (diff))
+ failed = True
+
+ assert not failed
diff --git a/tests/integrated/test-yupdown/runtest b/tests/integrated/test-yupdown/runtest
deleted file mode 100755
index 13d7365b53..0000000000
--- a/tests/integrated/test-yupdown/runtest
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python3
-
-from boututils.run_wrapper import build_and_log, launch_safe
-from boutdata.collect import collect
-from sys import exit
-
-from numpy import max, abs
-
-build_and_log("parallel slices test")
-
-failed = False
-
-for shifttype in ["shifted", "shiftedinterp"]:
- s, out = launch_safe(
- "./test_yupdown mesh:paralleltransform:type=" + shifttype,
- nproc=1,
- pipe=True,
- verbose=True,
- )
-
- with open("run.log", "w") as f:
- f.write(out)
-
- vars = [("ddy", "ddy_check"), ("ddy2", "ddy_check")]
-
- for v, v_check in vars:
- print("Testing %s and %s ... " % (v, v_check))
- ddy = collect(v, path="data", xguards=False, yguards=False, info=False)
- ddy_check = collect(
- v_check, path="data", xguards=False, yguards=False, info=False
- )
-
- diff = max(abs(ddy - ddy_check))
-
- if diff < 2e-5:
- print(shifttype + " passed (Max difference %e)" % (diff))
- else:
- print(shifttype + " failed (Max difference %e)" % (diff))
- failed = True
-
-if failed:
- exit(1)
-exit(0)
diff --git a/tests/integrated/test-yupdown/test_yupdown.py b/tests/integrated/test-yupdown/test_yupdown.py
new file mode 100755
index 0000000000..b2569f4d60
--- /dev/null
+++ b/tests/integrated/test-yupdown/test_yupdown.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+import os
+import pytest
+from boututils.run_wrapper import launch_safe
+from boutdata.collect import collect
+from numpy import max, abs
+
+shift_types = ["shifted", "shiftedinterp"]
+variables = [("ddy", "ddy_check"), ("ddy2", "ddy_check")]
+
+
+def run_case(shift_type, variable):
+ s, out = launch_safe(
+ "./test_yupdown mesh:paralleltransform:type=" + shift_type,
+ nproc=1,
+ pipe=True,
+ verbose=True,
+ )
+
+ with open("run.log", "w") as f:
+ f.write(out)
+
+ v, v_check = variable
+ print("Testing %s and %s ... " % (v, v_check))
+ ddy = collect(v, path="data", xguards=False, yguards=False, info=False)
+ ddy_check = collect(v_check, path="data", xguards=False, yguards=False, info=False)
+
+ diff = max(abs(ddy - ddy_check))
+
+ print(f"shifttype: {shift_type} Max difference {diff}")
+ if diff < 2e-5:
+ return True
+ return False
+
+
+@pytest.mark.parametrize("shift_type, variable", list(zip(shift_types, variables)))
+def test_case(shift_type, variable):
+ # MPI oversubscribe
+ os.environ["OMPI_MCA_rmaps_base_oversubscribe"] = "1" # Allows 18 procs
+
+ success = run_case(shift_type, variable)
+ assert success, f"Test failed for shift_type={shift_type}: , variable={variable}"
diff --git a/tests/integrated/test_suite b/tests/integrated/test_suite
deleted file mode 100755
index 77ad7882c4..0000000000
--- a/tests/integrated/test_suite
+++ /dev/null
@@ -1,366 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Run the test suite, report failures
-#
-# To set command for running parallel jobs, define environment variable
-# e.g. for bash
-# export MPIRUN="mpirun -np"
-# export MPIRUN="aprun -n"
-
-import os
-import sys
-import time
-import glob
-from boututils.run_wrapper import shell
-import threading
-import re
-import fcntl
-import select
-
-##################################################################
-
-import argparse
-
-parser = argparse.ArgumentParser(description="Run or build some tests.")
-parser.add_argument(
- "-g",
- "--get-list",
- action="store_true",
- help="Return a list of tests that would be run/build",
-)
-parser.add_argument(
- "-m", "--make", action="store_true", help="Build the tests, rather then run them."
-)
-parser.add_argument(
- "-a", "--all", action="store_true", dest="all_tests", help="Run all tests"
-)
-parser.add_argument(
- "-b",
- "--set-bool",
- nargs="+",
- metavar=("value1=False", "value2=True"),
- help="Set a bool value for evaluating what scripts can be run.",
-)
-parser.add_argument(
- "-l",
- "--set-list",
- nargs="+",
- metavar=("test1", "test2"),
- help="Set the tests that should be run.",
-)
-parser.add_argument(
- "-j",
- "--jobs",
- nargs=1,
- type=int,
- dest="jobs",
- help="Set the number of cores to use in parallel.",
-)
-
-args = parser.parse_args()
-
-##################################################################
-
-sys.path.append("..")
-from requirements import Requirements
-
-requirements = Requirements()
-
-if args.set_bool is not None:
- lookup = {"false": False, "no": False, "true": True, "yes": True}
- for setbool in args.set_bool:
- k, v = setbool.split("=")
- v = lookup[v.lower()]
- requirements.add(k, v, override=True)
-
-try:
- requirements.add("make", args.make)
-except RuntimeError:
- raise RuntimeError(
- "The make flag needs to be set by passing --make rather then --set-bool make=True"
- )
-
-try:
- requirements.add("all_tests", args.all_tests)
-except RuntimeError:
- raise RuntimeError(
- "The all-tests flag needs to be set by passing --all rather then --set-bool all_tests=True"
- )
-
-##################################################################
-# Parallel stuff
-#
-# We can run in parallel. As some checks require several threads to
-# run, we take this into account when we schedule tests. Given a
-# certain amount of threads that we are told to use, we try to run
-# so many tests in parallel, that the total core count is not higher
-# than this number. If this isn't possible because some jobs require
-# more cores than we have threads, we run the remaining tests in
-# serial. The number of parallel threads is called cost of a test.
-#
-# The beginning of MAKEFLAGS has some flags that tell us what we are
-# supposed to be doing. Most of them we can ignore.
-# Check what we are supposed to be doing:
-if "MAKEFLAGS" in os.environ:
- makeflags = os.environ["MAKEFLAGS"]
- for token in makeflags:
- if token == "n":
- # Print recipies:
- # We ignore this for now ...
- sys.exit(0)
- elif token == "q":
- # Question mode:
- # We are never up-to-date
- sys.exit(1)
- elif token in "ksBd":
- # k - keep going
- # s - silent
- # B - always build (we do)
- # d - debug
- pass
- elif token == " ":
- break
- elif token == "-":
- # This is not from make
- break
- else:
- # Not implemented
- print("mode '%s' not implemented" % token)
- # sys.exit(42)
-else:
- makeflags = ""
-num_threads = 1
-if args.jobs:
- num_threads = args.jobs[0]
-js_read = None
-# if we are running under make, we only have one thread (called job
-# in make terms) for sure, but might be able to run more threads
-# (depending what other jobs are currently running)
-# Additional jobs come in form of `tokens` that we need to give
-# back, if we are finished.
-# https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html
-if "--jobserver-auth=" in makeflags or "--jobserver-fds=" in makeflags:
- try:
- offset = makeflags.index("--jobserver-auth=")
- except ValueError:
- offset = makeflags.index("--jobserver-fds=")
- file_desc = [int(i) for i in re.findall("\d+", makeflags[offset:])[0:2]]
- js_read = file_desc[0]
- js_write = file_desc[1]
- js_tokens = b""
-
-
-class Test(threading.Thread):
- def __init__(self, name, cmd, width, timeout):
- threading.Thread.__init__(self)
- self.name = name
- self.cmd = cmd
- self.width = width
- self.local = threading.local()
- self.cost = self._cost()
- self.timeout = timeout
-
- def run(self):
- # Check requirements
- self.local.req_met, self.local.req_expr = requirements.check(
- self.name + "/runtest"
- )
- self.local.start_time = time.time()
- if not self.local.req_met:
- print(
- "{:{width}}".format(self.name, width=self.width)
- + "S - {0} => False".format(self.local.req_expr)
- )
- sys.stdout.flush()
- self.status = 0
- else:
- # Run test, piping stdout so it is not sent to console
- command = "cd {}; timeout {} {}".format(self.name, self.timeout, self.cmd)
- self.status, self.output = shell(command, pipe=True)
- print("{:{width}}".format(self.name, width=self.width), end="")
- if self.status == 0:
- # ✓ Passed
- print("\u2713", end="") # No newline
- elif self.status == 124:
- # 💤 timeout
- print("\U0001f4a4", end="") # No newline
- self.output += "\n(It is likely that a timeout occured)"
- else:
- # ❌ Failed
- print("\u274c", end="") # No newline
- print(" %7.3f s" % (time.time() - self.local.start_time), flush=True)
-
- def _cost(self):
- if self.cmd == "make":
- return 1
- with open(self.name + "/" + self.cmd, "r", encoding="utf8") as filein:
- contents = filein.read()
- # Find all lines starting with '#cores' or '#Cores:'
- match = re.findall("^\s*\#\s?[Cc]ores:\s*?(\d+)", contents, re.MULTILINE)
- if len(match) > 1:
- raise RuntimeError(
- "Found more then one match for core-count in " + self.name
- )
- if len(match) == 0:
- # default; no mpi
- return 1
- c = int(match[0])
- if c < 0:
- raise RuntimeError(
- "Core-count is %d and thus not positive in " % c + self.name
- )
- return c
-
-
-##################################################################
-
-if args.set_list is None:
- # Get list of directories containing test cases
- try:
- # Requires python >= 3.5
- tests = glob.iglob("**/runtest", recursive=True)
- except TypeError:
- # Fall back - check only a few folders ...
- tests = glob.glob("*/runtest")
- tests += glob.glob("*/*/runtest")
- tests += glob.glob("*/*/*/runtest")
- tests += glob.glob("*/*/*/*/runtest")
-
- tests = [x.rsplit("/", 1)[0] for x in tests]
-
-else:
- # Take the user provided list
- tests = [x.rstrip("/") for x in args.set_list]
-
-
-if args.get_list:
- for test in tests:
- req_met, _ = requirements.check(test + "/runtest")
- if req_met:
- print(test)
- sys.exit(0)
-
-
-# A function to get more threads from the job server
-def get_threads():
- global js_read
- if js_read:
- global cost_remain, js_tokens, avail_threads, num_threads
- try:
- fl = fcntl.fcntl(js_read, fcntl.F_GETFL)
- except OSError:
- print("Warning - we are not part of the job pool 😭, running in serial")
- js_read = None
- return
- isreadable, _, _ = select.select([js_read], [], [], 0)
- if isreadable != []:
- # Only set shortly to non-blocking, and only if we
- # expect we can read. Old make uses blocking pipes, and
- # assumes if it is non-blocking, a job is available.
- fcntl.fcntl(js_read, fcntl.F_SETFL, fl | os.O_NONBLOCK)
- new_token = os.read(js_read, cost_remain - avail_threads)
- fcntl.fcntl(js_read, fcntl.F_SETFL, fl)
- num_threads += len(new_token)
- js_tokens += new_token
- avail_threads += len(new_token)
-
-
-##################################################################
-# Run the actual test
-# Kill tests that take longer than 10 minutes (default)
-timeout = os.environ.get("BOUT_TEST_TIMEOUT", "10m" if not args.all_tests else "30m")
-command = "./runtest" if not args.make else "make"
-
-savepath = os.getcwd() # Save current working directory
-failed = []
-
-start_time = time.time()
-
-test_type = "Making" if args.make else "Running"
-
-print(
- "======= {} {} {} tests ========".format(
- test_type, len(tests), savepath.split("/")[-1]
- )
-)
-
-longest = max([len(s) for s in tests])
-avail_threads = num_threads
-tester = sorted(
- [Test(t, command, longest + 1, timeout) for t in tests],
- key=lambda x: x.cost,
- reverse=True,
-)
-cost_remain = sum(x.cost for x in tester)
-get_threads()
-
-torun = [i for i in range(len(tests))]
-running = []
-
-while len(torun) + len(running):
- if avail_threads:
- # See whether we can start one
- for i in torun[:]:
- if tester[i].cost <= avail_threads:
- torun.remove(i)
- job = tester[i]
- job.start()
- running.append(job)
- avail_threads -= job.cost
- cost_remain -= job.cost
- # if nothing is running, start the first
- if running == []:
- tostart = torun.pop(0)
- job = tester[tostart]
- job.start()
- running.append(job)
- avail_threads -= job.cost
- cost_remain -= job.cost
-
- # Are any jobs finished
- for job in running:
- job.join(0.1)
- if not job.is_alive():
- if job.status:
- failed.append([job.name, job.output])
- avail_threads += job.cost
- running.remove(job)
-
- # Try to get more jobs
- if torun:
- get_threads()
- else:
- # Free not-needed jobs
- if avail_threads and js_read:
- old_tokens = js_tokens[0:avail_threads]
- js_tokens = js_tokens[avail_threads:]
- os.write(js_write, old_tokens)
- num_threads -= avail_threads
- avail_threads = 0
-
-elapsed_time = time.time() - start_time
-
-print("\n")
-
-# Return remaining tokens
-if js_read is not None:
- if js_tokens != "":
- os.write(js_write, js_tokens)
-
-if failed:
- print("======= FAILURES ========")
- for test, output in failed:
- # Note: need Unicode string in case output contains unicode
- print("\n----- {0} -----\n{1}".format(test, output))
-
- print(
- "======= {0} failed in {1:.2f} seconds ========".format(
- len(failed), elapsed_time
- )
- )
-
- sys.exit(1)
-
-else:
- print("======= All tests passed in {0:.2f} seconds =======".format(elapsed_time))
diff --git a/tests/run_integrated_tests.sh b/tests/run_integrated_tests.sh
new file mode 100755
index 0000000000..276a1ea6e5
--- /dev/null
+++ b/tests/run_integrated_tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -e
+
+PROJECT_ROOT="@PROJECT_BINARY_DIR@"
+export PYTHONPATH="@PROJECT_BINARY_DIR@/tools/pylib:@PROJECT_SOURCE_DIR@/tools/pylib:$PYTHONPATH"
+
+# Pre-build the project to prevent concurrent CMake race conditions
+cmake --build "$PROJECT_ROOT"
+
+PYTEST_ARGS=("$@")
+if [ "$CI" == "true" ]; then
+ TEST_FLAGS="-v"
+else
+ TEST_FLAGS="-q"
+fi
+
+# Use the Python executable that CMake discovered during configuration
+"@Python3_EXECUTABLE@" -m pytest -m "not serial" --cache-clear -n auto --dist=loadgroup $TEST_FLAGS "$PROJECT_ROOT/tests/integrated" "${PYTEST_ARGS[@]}"
+"@Python3_EXECUTABLE@" -m pytest -m serial $TEST_FLAGS "$PROJECT_ROOT/tests/integrated" "${PYTEST_ARGS[@]}"
diff --git a/tests/test_suite b/tests/test_suite
deleted file mode 120000
index 860ee3ecc5..0000000000
--- a/tests/test_suite
+++ /dev/null
@@ -1 +0,0 @@
-integrated/test_suite
\ No newline at end of file
diff --git a/tests/test_suite_make b/tests/test_suite_make
deleted file mode 120000
index 083d1d6487..0000000000
--- a/tests/test_suite_make
+++ /dev/null
@@ -1 +0,0 @@
-integrated/test_suite_make
\ No newline at end of file