diff --git a/tools/regtesting/do_regtest.py b/tools/regtesting/do_regtest.py index e6d7da39f1..fe7ac5d644 100755 --- a/tools/regtesting/do_regtest.py +++ b/tools/regtesting/do_regtest.py @@ -6,7 +6,7 @@ from asyncio import Semaphore from asyncio.subprocess import PIPE, STDOUT, Process from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union import argparse import asyncio import math @@ -23,8 +23,8 @@ try: except: from typing_extensions import Literal # type: ignore -# Some tests do not work with the cp2k shell (which is generally considered a bug). -ALLWAYS_SKIP_DIRS = [ +# Some tests do not work with --keepalive (which is generally considered a bug). +KEEPALIVE_SKIP_DIRS = [ "TMC/regtest", "TMC/regtest_ana_on_the_fly", "TMC/regtest_ana_post_proc", @@ -36,11 +36,11 @@ ALLWAYS_SKIP_DIRS = [ async def main() -> None: parser = argparse.ArgumentParser(description="Runs CP2K regression test suite.") parser.add_argument("--mpiranks", type=int, default=2) - parser.add_argument("--ompthreads", type=int, default=2) + parser.add_argument("--ompthreads", type=int) parser.add_argument("--maxtasks", type=int, default=os.cpu_count()) parser.add_argument("--timeout", type=int, default=400) parser.add_argument("--maxerrors", type=int, default=50) - parser.add_argument("--no-keep-alive", dest="keep_alive", action="store_false") + parser.add_argument("--keepalive", dest="keepalive", action="store_true") parser.add_argument("--debug", action="store_true") parser.add_argument("--restrictdir", action="append") parser.add_argument("arch") @@ -66,7 +66,7 @@ async def main() -> None: print(f"Workers: {cfg.num_workers}") print(f"Timeout [s]: {cfg.timeout}") print(f"Work base dir: {cfg.work_base_dir}") - print(f"Keep alive: {cfg.keep_alive}") + print(f"Keepalive: {cfg.keepalive}") print(f"Debug: {cfg.debug}") print(f"ARCH: {cfg.arch}") print(f"VERSION: {cfg.version}") @@ -117,8 +117,8 @@ async def main() -> None: print(f"Skipping {batch.name} because its requirements are not satisfied.") elif not any(re.match(p, batch.name) for p in cfg.restrictdirs): num_restrictdirs += 1 - elif batch.name in ALLWAYS_SKIP_DIRS: - print(f"Skipping {batch.name} because it doesn't work with the cp2k shell.") + elif cfg.keepalive and batch.name in KEEPALIVE_SKIP_DIRS: + print(f"Skipping {batch.name} because it doesn't work with --keepalive.") else: tasks.append(asyncio.create_task(run_batch(batch, cfg))) # launch @@ -183,7 +183,8 @@ class Config: def __init__(self, args: argparse.Namespace): self.timeout = args.timeout self.use_mpi = args.version.startswith("p") - self.ompthreads = args.ompthreads + default_ompthreads = 2 if "smp" in args.version else 1 + self.ompthreads = args.ompthreads if args.ompthreads else default_ompthreads self.mpiranks = args.mpiranks if self.use_mpi else 1 self.num_workers = int(args.maxtasks / self.ompthreads / self.mpiranks) self.workers = Semaphore(self.num_workers) @@ -192,7 +193,7 @@ class Config: leaf_dir = f"TEST-{args.arch}-{args.version}-{datestamp}" self.work_base_dir = self.cp2k_root / "regtesting" / leaf_dir self.error_summary = self.work_base_dir / "error_summary" - self.keep_alive = args.keep_alive + self.keepalive = args.keepalive self.arch = args.arch self.version = args.version self.debug = args.debug @@ -389,6 +390,26 @@ class Cp2kShell: return self._child.returncode if self._child.returncode else 0 +# ====================================================================================== +async def wait_for_child_process( + child: Process, timeout: int +) -> Tuple[bytes, int, bool]: + try: + output, _ = await asyncio.wait_for(child.communicate(), timeout=timeout) + timeout = False + returncode = child.returncode if child.returncode else 0 + except asyncio.TimeoutError: + timeout = True + returncode = -9 + try: + child.terminate() # Give mpiexec a chance to shutdown + except ProcessLookupError: + pass + output, _ = await child.communicate() + + return output, returncode, timeout + + # ====================================================================================== async def run_batch(batch: Batch, cfg: Config) -> BatchResult: async with cfg.workers: @@ -402,17 +423,7 @@ async def run_unittests(batch: Batch, cfg: Config) -> List[TestResult]: for test in batch.unittests: start_time = time.perf_counter() child = await cfg.launch_exe(test.name, str(cfg.cp2k_root), cwd=batch.workdir) - try: - output, _ = await asyncio.wait_for(child.communicate(), timeout=cfg.timeout) - timeout = False - except asyncio.TimeoutError: - timeout = True - try: - child.terminate() # Give mpiexec a chance to shutdown - except ProcessLookupError: - pass - output, _ = await child.communicate() - + output, returncode, timeout = await wait_for_child_process(child, cfg.timeout) duration = time.perf_counter() - start_time test.out_path.write_bytes(output) output_lines = output.decode("utf8", errors="replace").split("\n") @@ -421,8 +432,8 @@ async def run_unittests(batch: Batch, cfg: Config) -> List[TestResult]: if timeout: error += f"Timeout after {duration} seconds." results.append(TestResult(test, duration, "TIMEOUT", error)) - elif child.returncode != 0: - error += f"Runtime failure with code {child.returncode}." + elif returncode != 0: + error += f"Runtime failure with code {returncode}." results.append(TestResult(test, duration, "RUNTIME FAIL", error)) else: results.append(TestResult(test, duration, "OK")) @@ -432,6 +443,14 @@ async def run_unittests(batch: Batch, cfg: Config) -> List[TestResult]: # ====================================================================================== async def run_regtests(batch: Batch, cfg: Config) -> List[TestResult]: + if cfg.keepalive: + return await run_regtests_keepalive(batch, cfg) + else: + return await run_regtests_classic(batch, cfg) + + +# ====================================================================================== +async def run_regtests_keepalive(batch: Batch, cfg: Config) -> List[TestResult]: shell = Cp2kShell(cfg, batch.workdir) await shell.start() @@ -448,7 +467,7 @@ async def run_regtests(batch: Batch, cfg: Config) -> List[TestResult]: timeout = True returncode = -9 - if returncode != 0 or not cfg.keep_alive: + if returncode != 0: await shell.stop() await shell.start() duration = time.perf_counter() - start_time @@ -459,6 +478,21 @@ async def run_regtests(batch: Batch, cfg: Config) -> List[TestResult]: return results +# ====================================================================================== +async def run_regtests_classic(batch: Batch, cfg: Config) -> List[TestResult]: + results: List[TestResult] = [] + for test in batch.regtests: + start_time = time.perf_counter() + child = await cfg.launch_exe("cp2k", test.inp_fn, cwd=batch.workdir) + output, returncode, timeout = await wait_for_child_process(child, cfg.timeout) + duration = time.perf_counter() - start_time + test.out_path.write_bytes(output) + res = eval_regtest(batch, test, duration, returncode, timeout) + results.append(res) + + return results + + # ====================================================================================== def eval_regtest( batch: Batch, test: Regtest, duration: float, returncode: int, timeout: bool