Files
StarPilot/tools/ci/tests/test_dev_runtime_review.py
T
firestar5683 694b460f80 Developer Tools
Provide laptop and device build entry points, cache compatibility checks and developer tooling. Keep distribution assets and model packaging available before runtime consumers.

Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

135 lines
4.5 KiB
Python

"""Source-cache transitions that must not require a native build or device."""
import os
import fcntl
from pathlib import Path
import shutil
import signal
import subprocess
import sys
import tempfile
import time
import unittest
from tools.host_runtime import HostRuntime
def git(root: Path, *args: str) -> None:
subprocess.run(['git', *args], cwd=root, check=True, capture_output=True)
class TestHostRuntimeSourceSync(unittest.TestCase):
def setUp(self) -> None:
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name) / 'source'
self.root.mkdir()
git(self.root, 'init', '-q')
git(self.root, 'config', 'user.name', 'Host Runtime Test')
git(self.root, 'config', 'user.email', 'host-runtime@example.invalid')
(self.root / 'README').write_text('fixture\n')
git(self.root, 'add', 'README')
git(self.root, 'commit', '-qm', 'fixture')
self.runtime = HostRuntime(self.root, 'shared', system='Darwin', machine='arm64')
def test_same_size_and_mtime_edit_reaches_cache(self) -> None:
source = self.root / 'README'
self.runtime.sync()
before = source.stat()
source.write_text('changed\n')
os.utime(source, ns=(before.st_atime_ns, before.st_mtime_ns))
self.runtime.sync()
self.assertEqual((self.runtime.work / 'README').read_text(), 'changed\n')
def test_tracked_child_replaced_by_file_reaches_cache(self) -> None:
directory = self.root / 'path'
directory.mkdir()
(directory / 'child').write_text('old\n')
git(self.root, 'add', 'path/child')
git(self.root, 'commit', '-qm', 'child')
self.runtime.sync()
(directory / 'child').unlink()
directory.rmdir()
directory.write_text('new\n')
self.runtime.sync()
cached = self.runtime.work / 'path'
self.assertTrue(cached.is_file())
self.assertEqual(cached.read_text(), 'new\n')
def test_tracked_file_replaced_by_child_reaches_cache(self) -> None:
source = self.root / 'path'
source.write_text('old\n')
git(self.root, 'add', 'path')
git(self.root, 'commit', '-qm', 'file')
self.runtime.sync()
source.unlink()
source.mkdir()
(source / 'child').write_text('new\n')
self.runtime.sync()
cached = self.runtime.work / 'path'
self.assertTrue(cached.is_dir())
self.assertEqual((cached / 'child').read_text(), 'new\n')
def test_wrapper_sigkill_keeps_lock_until_child_exits(self) -> None:
runtime = self.runtime
runtime.work.mkdir(parents=True)
(runtime.venv / 'bin').mkdir(parents=True)
(runtime.venv / 'bin/python').symlink_to(sys.executable)
child_pid_file = self.root / 'child.pid'
source_root = Path(__file__).resolve().parents[3]
wrapper_code = '''import sys
from pathlib import Path
sys.path.insert(0, sys.argv[1])
from tools.host_runtime import HostRuntime
runtime = HostRuntime(sys.argv[2], 'shared', system='Darwin', machine='arm64')
child = "import os,sys,time; from pathlib import Path; Path(sys.argv[1]).write_text(str(os.getpid())); time.sleep(30)"
with runtime.locked():
runtime.launch('python', 1, ['-c', child, sys.argv[3]])
'''
wrapper = subprocess.Popen([sys.executable, '-c', wrapper_code, str(source_root), str(self.root),
str(child_pid_file)], start_new_session=True)
ipc = Path('/tmp') / f'msgq_{runtime.prefix}'
lock_path = runtime.cache / 'lock'
try:
deadline = time.monotonic() + 5
while not child_pid_file.exists() and wrapper.poll() is None and time.monotonic() < deadline:
time.sleep(.02)
self.assertTrue(child_pid_file.exists(), 'child did not start')
wrapper.kill()
wrapper.wait(timeout=3)
with lock_path.open('a+') as contender:
with self.assertRaises(BlockingIOError):
fcntl.flock(contender, fcntl.LOCK_EX | fcntl.LOCK_NB)
finally:
try:
os.killpg(wrapper.pid, signal.SIGTERM)
except ProcessLookupError:
pass
if wrapper.poll() is None:
wrapper.kill()
wrapper.wait(timeout=3)
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
with lock_path.open('a+') as contender:
try:
fcntl.flock(contender, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
time.sleep(.02)
else:
fcntl.flock(contender, fcntl.LOCK_UN)
break
else:
self.fail('owned child did not release the lock after stopping')
if ipc.exists():
shutil.rmtree(ipc)
if __name__ == '__main__':
unittest.main()