mirror of
https://github.com/MoHadiShibli/Control4Free.git
synced 2026-10-06 10:00:28 +02:00
The staged spike that worked out the VDA call order is no longer part of the build. It is kept at tag spike-final. - src/main.c is the service only: instance lock, credentials, MBus and pad init, then the web service. src/server.c and include/c4f_server.h are gone with stage 8, and so are the stage-only helpers in src/vda.c: the MBus bind and holds, the assignment and button-map probes, and the GoldHEN klog stream path. The service reads /dev/klog, which is what works. - VERSION holds the only version number. Both Makefiles pass it in, the packager reads it for param.sfo and the package name, and the build and deploy scripts read it for the file they look for. - The payload's log is /data/control4free/control4free.log, not spike.log. - The page drops the protocol 1 branches: the payload has always answered 2. A console slot is a "controller" and a pad plugged into the phone or PC is a "gamepad", so the two no longer read as the same word. - The host tests move to tests/, with tests/run.py to run all four suites. They build the real sources with the PS4 calls stubbed, so they need no console. klog_test now covers the device path only, and asserts that nothing ever connects to GoldHEN's klog server.
108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
"""Suspend and network failure checks against the actual service loop."""
|
|
import os
|
|
from pathlib import Path
|
|
import signal
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import test_web as web
|
|
|
|
|
|
def connect_again():
|
|
end = time.monotonic() + 4
|
|
while time.monotonic() < end:
|
|
try:
|
|
return web.Client()
|
|
except (OSError, AssertionError):
|
|
time.sleep(.05)
|
|
raise AssertionError('listener did not recover')
|
|
|
|
|
|
def main():
|
|
subprocess.run(['clang-18', '-std=gnu11', '-Wall', '-Wextra', '-Werror', '-g',
|
|
'-Iinclude', '-Ivendor/jsmn', 'src/net.c', 'src/web.c',
|
|
'tests/web_stub.c', 'tests/recovery_faults.c', 'build/client.c',
|
|
'-Wl,--wrap=select,--wrap=accept,--wrap=getsockopt', '-o', str(web.BINARY)], check=True)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
fault = Path(directory) / 'fault'
|
|
for name in ('select', 'accept', 'socket'):
|
|
server = web.Server(C4F_TEST_FAULT=str(fault))
|
|
try:
|
|
assert server.c.request('claim', [0])['result']['pads'][0]['mine']
|
|
server.c.input(0, 0x4000)
|
|
time.sleep(.04)
|
|
fault.write_text(name)
|
|
if name == 'accept':
|
|
try:
|
|
socket.create_connection(('127.0.0.1', 4264), timeout=1).close()
|
|
except OSError:
|
|
pass
|
|
end = time.monotonic() + 3
|
|
while not any('network failed' in row for row in server.rows()):
|
|
assert time.monotonic() < end, server.rows()
|
|
time.sleep(.02)
|
|
server.c.close()
|
|
server.c = connect_again()
|
|
assert server.c.request('info')['result']['pads'] == 4
|
|
assert server.c.request('claim', [0])['result']['pads'][0]['mine']
|
|
rows = server.rows()
|
|
split = next(i for i, row in enumerate(rows) if 'network failed' in row)
|
|
assert all(row.split()[3] == '0' for row in rows[split:] if row.startswith('FRAME'))
|
|
assert len([r for r in rows if r.startswith('ADD')]) == 1
|
|
assert any('listener recovered' in row for row in rows)
|
|
print(f'PASS {name} failure: listener returns, old buttons cleared, no duplicate pad', flush=True)
|
|
finally:
|
|
server.close()
|
|
|
|
server = web.Server()
|
|
try:
|
|
server.c.request('claim', [0])
|
|
server.c.input(0, 0x4000)
|
|
time.sleep(.04)
|
|
os.kill(server.p.pid, signal.SIGSTOP)
|
|
time.sleep(6)
|
|
before = len(server.rows())
|
|
os.kill(server.p.pid, signal.SIGCONT)
|
|
time.sleep(.1)
|
|
server.c.close(); server.c = connect_again()
|
|
assert server.c.request('claim', [0])['result']['pads'][0]['mine']
|
|
after = server.rows()[before:]
|
|
assert any('service resumed after a gap' in row for row in after)
|
|
assert all(row.split()[3] == '0' for row in after if row.startswith('FRAME'))
|
|
assert len([r for r in server.rows() if r.startswith('ADD')]) == 1
|
|
print('PASS process suspension: fresh connection, neutral input and retained controller', flush=True)
|
|
finally:
|
|
os.kill(server.p.pid, signal.SIGCONT)
|
|
server.close()
|
|
|
|
server = web.Server(C4F_TEST_KLOG_BUSY='1')
|
|
try:
|
|
assert server.c.request('claim', [0])['error']['code'] == 503
|
|
assert not any(row.startswith('ADD') for row in server.rows())
|
|
assert server.c.request('info')['result']['pads'] == 4
|
|
print('PASS busy log reader: creation refused before any device is added; service stays usable', flush=True)
|
|
finally:
|
|
server.close()
|
|
|
|
server = web.Server()
|
|
try:
|
|
assert not any(row.startswith('KLOG ') for row in server.rows())
|
|
server.c.request('claim', [0])
|
|
os.write(server.log, b'DEVICE_OWNER_CHANGED [DeviceId:0x11030d][UserId:0x1a2b3c4d]\n')
|
|
time.sleep(.04)
|
|
assert server.c.request('status')['result']['pads'][0]['state'] == 'ready'
|
|
assert 'released klog reader' in server.rows()
|
|
server.c.input(0, 0x4000)
|
|
time.sleep(.04)
|
|
assert any(r.startswith('FRAME') and r.split()[3] == '16384' for r in server.rows())
|
|
server.c.request('claim', [0, 1])
|
|
assert len([r for r in server.rows() if r.startswith('KLOG ')]) == 2
|
|
print('PASS klog acquired on demand, released after sign-in, gameplay continues, reopened for next pad', flush=True)
|
|
finally:
|
|
server.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|