Skip to content

Commit 9feb293

Browse files
committed
test-petsc-ordering integrated test
Checks the global ordering of cells for consistency as number of processors and nxpe is varied.
1 parent 4920c86 commit 9feb293

5 files changed

Lines changed: 391 additions & 0 deletions

File tree

tests/integrated/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_subdirectory(test-naulin-laplace)
3434
add_subdirectory(test-options-netcdf)
3535
add_subdirectory(test-petsc_laplace)
3636
add_subdirectory(test-petsc_laplace_MAST-grid)
37+
add_subdirectory(test-petsc-ordering)
3738
add_subdirectory(test-restart-io)
3839
add_subdirectory(test-restarting)
3940
add_subdirectory(test-slepc-solver)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bout_add_integrated_test(
2+
test-petsc-ordering
3+
SOURCES test_petsc_ordering.cxx
4+
REQUIRES BOUT_HAS_PETSC
5+
USE_RUNTEST USE_DATA_BOUT_INP
6+
PROCESSORS 4
7+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Input file for test_petsc_ordering integrated test.
2+
# Uses a small grid large enough to exercise MPI decomposition on 4 ranks
3+
# (nx=10 gives 2 interior x-points per rank when NXPE=4; ny and nz similarly).
4+
5+
[mesh]
6+
nx = 10 # 8 interior + 2 guard (mxg=1 each side)
7+
ny = 8
8+
nz = 4
9+
10+
ixseps1 = nx
11+
ixseps2 = nx
12+
13+
[output]
14+
enabled = false # Suppress data file output; we only need stdout
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
# requires: petsc
3+
# cores: 4
4+
"""
5+
Integrated test: Ordering equivalence between PetscCellMapping
6+
and the SNES solver's globalIndex traversal.
7+
8+
Runs test_petsc_ordering with 1, 2, and 4 MPI ranks and checks that:
9+
- The executable exits with status 0.
10+
- The output contains "ordering_check=PASS".
11+
- No "MISMATCH" or "SHIFT_NOT_CONSTANT" lines appear.
12+
"""
13+
14+
import argparse
15+
import re
16+
import subprocess
17+
import sys
18+
19+
from boututils.run_wrapper import build_and_log
20+
21+
22+
def parse_args():
23+
parser = argparse.ArgumentParser(description=__doc__)
24+
parser.add_argument(
25+
"--executable",
26+
default="./test_petsc_ordering",
27+
help="Path to the test executable",
28+
)
29+
parser.add_argument(
30+
"--mpirun", default="mpirun", help="MPI launcher (mpirun, srun, ...)"
31+
)
32+
parser.add_argument(
33+
"--nprocs",
34+
type=int,
35+
nargs="+",
36+
default=[1, 2, 4],
37+
help="List of MPI rank counts to test",
38+
)
39+
parser.add_argument(
40+
"--timeout", type=int, default=120, help="Per-run timeout in seconds"
41+
)
42+
return parser.parse_args()
43+
44+
45+
def run_case(mpirun, executable, nproc, nxpe, timeout):
46+
"""Launch one run and return (stdout+stderr, returncode)."""
47+
cmd = [mpirun, "-n", str(nproc), executable, f"nxpe={nxpe}"]
48+
try:
49+
result = subprocess.run(
50+
cmd,
51+
stdout=subprocess.PIPE,
52+
stderr=subprocess.STDOUT,
53+
timeout=timeout,
54+
text=True,
55+
)
56+
return result.stdout, result.returncode
57+
except subprocess.TimeoutExpired:
58+
return f"TIMED OUT after {timeout}s\n", -1
59+
except FileNotFoundError as e:
60+
return f"Could not launch: {e}\n", -1
61+
62+
63+
def check_output(output, nproc):
64+
"""
65+
Inspect the combined output for pass/fail markers.
66+
Returns a list of failure strings (empty means pass).
67+
"""
68+
failures = []
69+
70+
# Must contain the summary pass marker
71+
if "ordering_check=PASS" not in output:
72+
if "ordering_check=FAIL" in output:
73+
failures.append("ordering_check=FAIL found in output")
74+
else:
75+
failures.append("ordering_check marker not found in output")
76+
77+
# Must not contain any mismatch lines
78+
mismatch_lines = [
79+
line for line in output.splitlines() if line.startswith("MISMATCH")
80+
]
81+
if mismatch_lines:
82+
failures.append(
83+
f"{len(mismatch_lines)} MISMATCH line(s) found:\n"
84+
+ "\n".join(f" {line}" for line in mismatch_lines)
85+
)
86+
87+
# Must not contain any shift-not-constant lines
88+
shift_lines = [
89+
line for line in output.splitlines() if line.startswith("SHIFT_NOT_CONSTANT")
90+
]
91+
if shift_lines:
92+
failures.append(
93+
f"{len(shift_lines)} SHIFT_NOT_CONSTANT line(s) found:\n"
94+
+ "\n".join(f" {line}" for line in shift_lines)
95+
)
96+
97+
# Extract and report the numeric summary for informational purposes
98+
for marker in ("total_mismatches", "total_shift_failures"):
99+
m = re.search(rf"{marker}=(\d+)", output)
100+
if m:
101+
count = int(m.group(1))
102+
if count != 0:
103+
failures.append(f"{marker}={count} (expected 0)")
104+
105+
return failures
106+
107+
108+
def main():
109+
args = parse_args()
110+
111+
build_and_log("PETSc ordering test")
112+
113+
overall_pass = True
114+
115+
for nproc in args.nprocs:
116+
for nxpe in [1, 2, 4]:
117+
if nxpe > nproc:
118+
break
119+
print(f"\n{'=' * 60}")
120+
print(f"Running with {nproc} MPI rank(s), nxpe={nxpe}")
121+
print(f"{'=' * 60}")
122+
123+
output, returncode = run_case(
124+
args.mpirun, args.executable, nproc, nxpe, args.timeout
125+
)
126+
127+
case_failures = []
128+
129+
if returncode != 0:
130+
# Note: MPI task can exit non-zero for reasons not connected to test
131+
print(f"Warning: Non-zero exit code: {returncode}")
132+
133+
case_failures.extend(check_output(output, nproc))
134+
135+
if case_failures:
136+
print(output) # Only print output on failure
137+
overall_pass = False
138+
print(f"FAIL (nproc={nproc}, nxpe={nxpe}):")
139+
for f in case_failures:
140+
print(f" - {f}")
141+
else:
142+
print(f"PASS (nproc={nproc}, nxpe={nxpe})")
143+
144+
print(f"\n{'=' * 60}")
145+
if overall_pass:
146+
print("ALL CASES PASSED")
147+
return 0
148+
else:
149+
print("ONE OR MORE CASES FAILED")
150+
return 1
151+
152+
153+
if __name__ == "__main__":
154+
sys.exit(main())

0 commit comments

Comments
 (0)