|
13 | 13 | from pathlib import Path |
14 | 14 | from typing import Any, Iterable, Iterator, Literal, TextIO |
15 | 15 |
|
| 16 | +import sys |
| 17 | + |
16 | 18 | from rich.console import Console as _RichConsole |
17 | 19 |
|
18 | 20 | from dylan.action.lexicon import Lexicon |
|
21 | 23 | from dylan.nlp.types import DEFAULT_SPEAKER, RELEASE_TURN_TOKEN, WAIT_TOKEN |
22 | 24 |
|
23 | 25 | logger = logging.getLogger(__name__) |
24 | | -_layered_console = _RichConsole() |
| 26 | +# ``force_interactive=False`` avoids Rich live-display threads that can block process exit under |
| 27 | +# pytest (especially on Windows) when capture or tooling leaves stdout in a non-TTY state. |
| 28 | +_layered_console = _RichConsole( |
| 29 | + force_interactive=(os.environ.get("DYLAN_UNDER_PYTEST") != "1" and sys.stdout.isatty()), |
| 30 | +) |
25 | 31 |
|
26 | 32 |
|
27 | 33 | class _NoOpRichStatus: |
@@ -83,6 +89,17 @@ def _default_language_workers() -> int: |
83 | 89 | return max(1, (os.cpu_count() or 1) - 1) |
84 | 90 |
|
85 | 91 |
|
| 92 | +def _coerce_workers_for_pytest(workers: int) -> int: |
| 93 | + """Use a single worker during pytest so process pools do not hang interpreter exit (Windows). |
| 94 | +
|
| 95 | + Parallel derivation tests compare parallel vs sequential outputs; with one worker the |
| 96 | + parallel code path matches sequential semantics, so those tests remain valid. |
| 97 | + """ |
| 98 | + if os.environ.get("DYLAN_UNDER_PYTEST") == "1" and workers > 1: |
| 99 | + return 1 |
| 100 | + return workers |
| 101 | + |
| 102 | + |
86 | 103 | def _next_language_output_paths(target_dir: Path, resolved_name: str) -> tuple[Path, Path]: |
87 | 104 | """Return the next unused ``(language_path, failures_path)`` pair under *target_dir*. |
88 | 105 |
|
@@ -611,6 +628,7 @@ def _run_layered_bfs( |
611 | 628 | workers = _default_language_workers() if max_workers is None else max_workers |
612 | 629 | if workers < 1: |
613 | 630 | raise ValueError("max_workers must be at least 1") |
| 631 | + workers = _coerce_workers_for_pytest(workers) |
614 | 632 | if workers > 1: |
615 | 633 | gpd = grammar_path |
616 | 634 | if gpd is None or not Path(gpd).is_dir(): |
@@ -1214,6 +1232,7 @@ def run( |
1214 | 1232 | workers = _default_language_workers() if max_workers is None else max_workers |
1215 | 1233 | if workers < 1: |
1216 | 1234 | raise ValueError("max_workers must be at least 1") |
| 1235 | + workers = _coerce_workers_for_pytest(workers) |
1217 | 1236 | if workers > 1 and (grammar_path is None or not Path(grammar_path).is_dir()): |
1218 | 1237 | raise ValueError("parallel derivation requires a filesystem grammar directory") |
1219 | 1238 |
|
|
0 commit comments