FDS/OS 1.0

This commit is contained in:
2026-09-21 22:29:23 +08:00
commit 99bc3d15c5
430 changed files with 34876 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
"""Event-driven serial/QMP support for isolated ARM integration tests."""
import json
import base64
import binascii
import hashlib
import os
from pathlib import Path
import re
import selectors
import socket
import subprocess
import time
PROJECT = Path(__file__).resolve().parents[1]
class VM:
def __init__(self, work, name, image, initramfs=None, extra=(), system_usb=False):
self.work = Path(work)
self.name = name
self.qmp_path = self.work/(name+'.qmp')
self.log = (self.work/(name+'.log')).open('wb')
self.errors = (self.work/(name+'.stderr.log')).open('wb')
self.data = bytearray()
self.position = 0
command = [str(PROJECT/'tools/in-void'), '--isolated-network', 'qemu-system-aarch64',
'-machine', 'virt', '-cpu', 'max', '-accel', 'tcg', '-m', '1024', '-smp', '2',
'-nodefaults', '-display', 'none', '-serial', 'stdio', '-nic', 'none', '-no-reboot',
'-qmp', f'unix:{self.qmp_path},server=on,wait=off',
'-kernel', str(PROJECT/'out/kernel/boot/kernel_2712.img'),
'-initrd', str(initramfs or PROJECT/'out/fds-initramfs.img'),
'-append', 'console=ttyAMA0 rdinit=/init ro quiet loglevel=3',
'-drive', f'file={image},if=none,id=system,format=raw,readonly=on',
*(['-device', 'qemu-xhci,id=xhci,addr=05.0', '-device', 'usb-storage,id=systemusb,drive=system,bus=xhci.0,port=1'] if system_usb else ['-device', 'virtio-blk-pci,drive=system']), *extra]
self.child = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self.errors)
self.selector = selectors.DefaultSelector()
self.selector.register(self.child.stdout, selectors.EVENT_READ)
def expect(self, pattern, timeout=120):
deadline = time.monotonic()+timeout
expression = re.compile(pattern, re.MULTILINE | re.DOTALL)
while True:
match = expression.search(self.data, self.position)
if match:
self.position = match.end()
return match
remaining = deadline-time.monotonic()
if remaining <= 0: raise AssertionError(f'{self.name}: missing {pattern!r}; inspect serial log')
for key, _ in self.selector.select(remaining):
chunk = os.read(key.fd, 65536)
if not chunk: raise AssertionError(f'{self.name}: VM exited before expected output; inspect {self.errors.name}')
self.data.extend(chunk)
self.log.write(chunk)
self.log.flush()
assert len(self.data) < 4*1024*1024, 'Unexpected serial output flood'
assert b'Kernel panic' not in self.data and b'FDS_STAGE0_ERROR' not in self.data, 'Guest boot failed'
def send(self, command):
self.child.stdin.write(command.encode()+b'\n')
self.child.stdin.flush()
def capture(self, command, ok=True, timeout=180):
"""Execute once; retry only the checked serial transfer after log noise."""
self.send('( '+command+' ) >/tmp/fds-test-response 2>&1; printf "%s" "$?" >/tmp/fds-test-status; printf "\\nVM_SAVED\\n"')
self.expect(rb'^VM_SAVED\r?\n', timeout=timeout)
for _ in range(20):
self.send('printf "\\nVM_BEGIN\\n"; base64 -w0 /tmp/fds-test-response; printf "\\n"; sha256sum /tmp/fds-test-response; cat /tmp/fds-test-status; printf "\\nVM_END\\n"')
frame=self.expect(rb'^VM_BEGIN\r?\n(.*?)\r?\nVM_END\r?\n').group(1).decode().replace('\r','')
try:
encoded,checksum,status=frame.split('\n')
payload=base64.b64decode(encoded, validate=True)
if hashlib.sha256(payload).hexdigest()!=checksum.split()[0]:continue
except (ValueError, binascii.Error):continue
assert (status=='0')==ok, (command,status,payload)
return payload.decode().strip()
raise AssertionError('Repeated serial transfer corruption')
def qmp(self, execute, arguments=None):
with socket.socket(socket.AF_UNIX) as control:
control.settimeout(10)
control.connect(str(self.qmp_path))
with control.makefile('rwb') as protocol:
# A device-deletion event can arrive while a fresh monitor
# connection is being greeted. It is not a command response.
while True:
greeting = json.loads(protocol.readline())
if 'QMP' in greeting: break
assert 'event' in greeting, greeting
def request(name, args):
protocol.write((json.dumps({'execute': name, 'arguments': args})+'\n').encode())
protocol.flush()
while True:
result = json.loads(protocol.readline())
if 'event' in result: continue
assert 'return' in result, result
return result['return']
request('qmp_capabilities', {})
return request(execute, arguments or {})
def close(self):
try:
if self.child.poll() is None:
self.qmp('quit')
self.child.wait(timeout=10)
finally:
if self.child.poll() is None:
self.child.kill()
self.child.wait()
self.selector.close()
self.log.close()
self.errors.close()
def __enter__(self): return self
def __exit__(self, *_): self.close()