kitty/kitty_tests/child.py

100 lines
3.9 KiB
Python

#!/usr/bin/env python
# License: GPL v3 Copyright: 2026, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess
from kitty.child import memory_used_by_process_tree_rooted_at
from kitty.constants import is_macos, kitty_exe
from . import BaseTest
class ChildMemoryTest(BaseTest):
def _spawn_allocating_child(self, alloc_bytes: int) -> subprocess.Popen:
p = subprocess.Popen(
[kitty_exe(), '+runpy', f'''\
import sys, time
buf = bytearray({alloc_bytes})
for i in range(0, {alloc_bytes}, 4096):
buf[i] = 1
sys.stdout.write("ready\\n")
sys.stdout.flush()
time.sleep(300)
'''],
stdout=subprocess.PIPE,
)
line = p.stdout.readline().strip()
p.stdout.close()
if line != b'ready':
p.kill()
p.wait()
raise AssertionError(f'Unexpected output from allocating child: {line!r}')
return p
def _terminate(self, p: subprocess.Popen) -> None:
p.terminate()
p.wait()
def test_memory_returns_positive_for_live_process(self):
mem = memory_used_by_process_tree_rooted_at(os.getpid())
self.assertGreater(mem, 0)
def test_memory_returns_minus_one_for_nonexistent_pid(self):
self.ae(memory_used_by_process_tree_rooted_at(99999999), -1)
def test_memory_accounts_for_child_allocation(self):
# Verify that a child's known resident allocation shows up in the
# measurement. check_if_cgroup_root=True falls back to per-process
# tree walk when pid is not the cgroup root (the common case when
# running under a shared session cgroup), so this exercises the tree
# walk path on Linux and the always-tree-walk path on macOS.
alloc = 20 * 1024 * 1024 # 20 MiB
child = self._spawn_allocating_child(alloc)
try:
mem = memory_used_by_process_tree_rooted_at(child.pid, check_if_cgroup_root=True)
self.assertGreater(
mem, alloc // 2,
f'Expected at least {alloc // 2} bytes for a {alloc}-byte allocation, got {mem}',
)
finally:
self._terminate(child)
def test_memory_of_parent_tree_includes_child(self):
# Measuring the current process tree must yield more than measuring
# the child alone, because the test runner itself occupies memory.
alloc = 20 * 1024 * 1024 # 20 MiB
child = self._spawn_allocating_child(alloc)
try:
mem_child = memory_used_by_process_tree_rooted_at(child.pid, check_if_cgroup_root=True)
mem_tree = memory_used_by_process_tree_rooted_at(os.getpid(), check_if_cgroup_root=True)
self.assertGreater(
mem_tree, mem_child,
'Parent tree memory should exceed child-only memory',
)
finally:
self._terminate(child)
def test_memory_cgroup_path_returns_positive(self):
# The fast cgroup path (check_if_cgroup_root=False, the default) must
# return a usable value on Linux.
if is_macos:
self.skipTest('cgroup not available on macOS')
mem = memory_used_by_process_tree_rooted_at(os.getpid())
self.assertGreater(mem, 0)
def test_memory_cgroup_and_tree_walk_both_positive(self):
# Both the cgroup path and the tree-walk path should give positive
# results for a process that is alive.
if is_macos:
self.skipTest('cgroup path not applicable on macOS')
alloc = 10 * 1024 * 1024 # 10 MiB
child = self._spawn_allocating_child(alloc)
try:
mem_cgroup = memory_used_by_process_tree_rooted_at(child.pid, check_if_cgroup_root=False)
mem_walk = memory_used_by_process_tree_rooted_at(child.pid, check_if_cgroup_root=True)
self.assertGreater(mem_cgroup, 0)
self.assertGreater(mem_walk, 0)
finally:
self._terminate(child)