-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_tests_safe.py
More file actions
120 lines (99 loc) · 3.36 KB
/
run_tests_safe.py
File metadata and controls
120 lines (99 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Run pytest with a memory cap.
Monitors the pytest process every 2 seconds. If RSS memory exceeds
MEMORY_LIMIT_MB the process tree is killed and a clear error is printed.
Usage:
python scripts/run_tests_safe.py [pytest args...]
Examples:
python scripts/run_tests_safe.py Tests/runner Tests/benchmark -q
python scripts/run_tests_safe.py Tests/ -q --tb=short
python scripts/run_tests_safe.py Tests/ -q --tb=short --limit 4096
"""
from __future__ import annotations
import argparse
import subprocess
import sys
import threading
import time
try:
import psutil
except ImportError:
sys.exit("psutil is required: pip install psutil")
DEFAULT_LIMIT_MB = 3072 # 3 GB — adjust if your machine has less RAM
def _monitor(proc: subprocess.Popen, limit_mb: int, stop: threading.Event) -> None:
"""Background thread: kill proc if its RSS exceeds limit_mb."""
try:
ps = psutil.Process(proc.pid)
except psutil.NoSuchProcess:
return
while not stop.is_set():
try:
# Sum RSS of pytest + all child processes (spawned workers etc.)
children = ps.children(recursive=True)
total_rss = ps.memory_info().rss
for child in children:
try:
total_rss += child.memory_info().rss
except psutil.NoSuchProcess:
pass
total_mb = total_rss / (1024 * 1024)
if total_mb > limit_mb:
print(
f"\n{'='*70}\n"
f" MEMORY LIMIT EXCEEDED: {total_mb:.0f} MB > {limit_mb} MB\n"
f" Killing pytest process tree ...\n"
f"{'='*70}\n",
flush=True,
)
# Kill process tree
try:
for child in ps.children(recursive=True):
child.kill()
ps.kill()
except psutil.NoSuchProcess:
pass
return
except psutil.NoSuchProcess:
return
time.sleep(2)
def main() -> int:
parser = argparse.ArgumentParser(
description="Run pytest with a memory cap (kills process if RSS exceeds limit).",
add_help=True,
)
parser.add_argument(
"--limit",
type=int,
default=DEFAULT_LIMIT_MB,
metavar="MB",
help=f"Memory limit in MB (default: {DEFAULT_LIMIT_MB})",
)
# Unknown args are forwarded to pytest
args, pytest_args = parser.parse_known_args()
cmd = [sys.executable, "-m", "pytest"] + pytest_args
print(
f"Running: {' '.join(cmd)}\n"
f"Memory limit: {args.limit} MB\n"
)
stop_event = threading.Event()
proc = subprocess.Popen(cmd)
monitor_thread = threading.Thread(
target=_monitor, args=(proc, args.limit, stop_event), daemon=True
)
monitor_thread.start()
try:
proc.wait()
except KeyboardInterrupt:
print("\nInterrupted — killing pytest ...", flush=True)
try:
ps = psutil.Process(proc.pid)
for child in ps.children(recursive=True):
child.kill()
ps.kill()
except psutil.NoSuchProcess:
pass
finally:
stop_event.set()
monitor_thread.join(timeout=5)
return proc.returncode
if __name__ == "__main__":
sys.exit(main())