diff --git a/noxfile.py b/noxfile.py index 7b489ef63..3e25b468c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -21,13 +21,14 @@ BEHAVE_PARALLEL_URL = ( def _default_processes() -> int: + env_override = os.environ.get("TEST_PROCESSES") + if env_override: + return int(env_override) try: cpus = len(os.sched_getaffinity(0)) or 1 except AttributeError: cpus = os.cpu_count() or 1 - # Cap parallelism to avoid excessive resource usage in constrained - # environments (CI containers, cgroups, etc.) - return min(cpus, 8) + return cpus def _behave_parallel_args(posargs: list[str]) -> list[str]: @@ -46,10 +47,7 @@ def _pabot_parallel_args(posargs: list[str]) -> list[str]: ) if has_custom_processes: return [] - env_override = os.environ.get("PABOT_PROCESSES") - if env_override: - return ["--processes", env_override] - return ["--processes", str(min(_default_processes(), 2))] + return ["--processes", str(_default_processes())] def _split_pabot_args(posargs: list[str]) -> tuple[list[str], list[str]]: @@ -474,43 +472,59 @@ COVERAGE_THRESHOLD = 97 def coverage_report(session: nox.Session): """Generate coverage report from Behave tests. - Runs behave under coverage in serial mode to ensure accurate data - collection. Coverage threshold is enforced at >=97%. + Runs behave tests in parallel via behave-parallel (same approach as + unit_tests) with each worker collecting coverage independently using + ``coverage run --parallel-mode``. After all workers finish, the + per-process data files are merged with ``coverage combine``. + + Coverage threshold is enforced at >=97%. On success, emits: COVERAGE OK: % (threshold: 97%) On failure, emits: COVERAGE FAILED: % < 97% threshold Both are single-line, CI-parseable summary strings. """ session.install("-e", ".[tests]") + _install_behave_parallel(session) os.makedirs("build", exist_ok=True) session.env["PYTHONPATH"] = str(Path("src").resolve()) session.env["COVERAGE_RCFILE"] = "pyproject.toml" session.env["COVERAGE_FILE"] = "build/.coverage" + # Tell behave-parallel workers to run under coverage --parallel-mode + session.env["BEHAVE_PARALLEL_COVERAGE"] = "1" # Clean up any existing coverage data session.run("coverage", "erase") - # Run behave under coverage (serial mode for reliable data collection) - behave_args = [ - "coverage", - "run", - "--source=src", - "-m", - "behave", - "-q", - "--tags=-discovery", - "--no-capture", - ] + # Run behave tests in parallel; each worker collects coverage separately + behave_cmd = session.bin + "/behave-parallel" + parallel_args = _behave_parallel_args(session.posargs) if session.posargs and session.posargs[0].endswith(".feature"): - behave_args.extend(session.posargs) + args = [ + behave_cmd, + "-q", + "--tags=-discovery", + "--no-capture", + *parallel_args, + *session.posargs, + ] else: - behave_args.append("features/") - behave_args.extend(session.posargs) + args = [ + behave_cmd, + "-q", + "--tags=-discovery", + "--no-capture", + *parallel_args, + "features/", + *session.posargs, + ] - session.run(*behave_args) + session.run(*args) + + # Merge per-worker coverage data files into a single build/.coverage + session.run("coverage", "combine") # Always generate HTML and XML reports before checking threshold session.run("coverage", "html")