Files
psxrecomp/runtime/tests/test_debug_input_route.py
T

154 lines
7.5 KiB
Python

"""Exercise the actual debug route handlers/getters without SDL or a game.
Extract the unchanged function bodies from debug_server.c; stub only JSON and
responses. Live game qualification must additionally check native pad input.
"""
import argparse
from pathlib import Path
import re
import subprocess
import tempfile
def function(source, name):
found = re.search(r"^(?:static )?(?:int|void) " + name + r"\([^\n]*\)\s*\{.*?^\}",
source, re.M | re.S)
if not found:
raise ValueError("Missing source function: " + name)
return found[0]
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--cc', required=True)
args = parser.parse_args()
source = (Path(__file__).resolve().parents[1] / 'src/debug_server.c').read_text()
preamble = r'''
#include <assert.h>
#include "input_route.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define PSX_BSS
static uint64_t s_frame_count;
typedef struct { unsigned unused; } PsxFrameFingerprint;
#define PSX_FRAME_FINGERPRINT_INIT {0}
static int s_fp_reset_on_load;
static PsxFrameFingerprint s_fp;
static unsigned s_fp_head,s_fp_total;
static int s_input_override=-1,s_input_override_p2=-1;
static int s_input_frames,s_input_frames_p2,s_axis_override,s_pad_type_override=-1;
static int s_pad_connected_override[2]={-1,-1};
static int s_extra_active[8],s_extra_frames[8];
static InputRoute s_route;
static unsigned s_input_route_port=1;
static int s_input_route_wait_load;
static unsigned char s_axis_st[4],s_host_axes[4],s_host_lt,s_host_rt;
static int s_host_layer;
static uint16_t s_host_buttons;
static char response[512];
static void send_ok(int id){(void)id;strcpy(response,"ok");}
static void send_err(int id,const char *text){(void)id;snprintf(response,sizeof response,"ERROR:%s",text);}
static void send_fmt(const char *format,...){va_list v;va_start(v,format);vsnprintf(response,sizeof response,format,v);va_end(v);}
static int json_get_int(const char *json,const char *key,int fallback){
char name[80];snprintf(name,sizeof name,"\"%s\"",key);
const char *p=strstr(json,name);if(!p)return fallback;
p=strchr(p,':');assert(p);return atoi(p+1);
}
'''
body = '\n'.join(function(source, name) for name in (
'handle_clear_input', 'handle_input_route_clear', 'handle_input_route_append',
'handle_input_route_start', 'handle_input_route_stop', 'handle_input_route_status',
'input_route_loaded', 'debug_server_note_savestate_loaded',
'input_route_poll_port', 'debug_server_get_input_override',
'debug_server_get_input_override_port2'))
checks = r'''
static void queue(void){
handle_input_route_clear(1,"{}");
handle_input_route_append(1,"{\"frames\":2,\"buttons\":32767}");
handle_input_route_append(1,"{\"frames\":1,\"buttons\":65535}");
}
int main(void){
queue();s_input_override=123;s_input_override_p2=456;
handle_input_route_start(1,"{\"port\":2,\"at_frame\":10}");
assert(strstr(response,"\"port\":2"));
assert(s_input_override==123&&s_input_override_p2==-1);
s_frame_count=9;
assert(debug_server_get_input_override_port2()==-1);
assert(s_route.remaining==2);
s_frame_count=10;
assert(debug_server_get_input_override()==123);
assert(s_route.remaining==2);
assert(debug_server_get_input_override_port2()==32767);
assert(s_route.remaining==1);
++s_frame_count;
assert(debug_server_get_input_override()==123);
assert(debug_server_get_input_override_port2()==32767);
assert(s_route.index==1&&s_route.remaining==1);
++s_frame_count;
assert(debug_server_get_input_override_port2()==65535);
assert(!s_route.active);
assert(debug_server_get_input_override_port2()==-1);
queue();handle_input_route_start(1,"{\"port\":2}");
handle_clear_input(1,"{\"port\":1}");assert(s_route.active);
handle_clear_input(1,"{\"port\":2}");assert(!s_route.active);
queue();handle_input_route_start(1,"{}");
assert(s_input_route_port==1);s_input_override_p2=456;
assert(debug_server_get_input_override_port2()==456&&s_route.remaining==2);
assert(debug_server_get_input_override()==32767);
handle_clear_input(1,"{\"port\":2}");assert(s_route.active);
handle_input_route_status(1,"{}");assert(strstr(response,"\"port\":1"));
handle_input_route_append(1,"{\"frames\":1,\"buttons\":0}");
assert(strstr(response,"ERROR"));
handle_input_route_stop(1,"{}");assert(!s_route.active);
handle_input_route_start(1,"{\"port\":3}");
assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"port\":2}");
handle_clear_input(1,"{}");assert(!s_route.active);
handle_input_route_clear(1,"{}");handle_input_route_start(1,"{}");
assert(strstr(response,"empty"));
queue();handle_input_route_start(1,"{\"port\":2,\"after_load\":1}");
assert(s_route.active&&s_input_route_wait_load);
assert(strstr(response,"\"waiting_for_load\":true"));
for(unsigned n=0;n<20;++n){++s_frame_count;assert(debug_server_get_input_override_port2()==-1);}
assert(s_route.remaining==2);
handle_input_route_status(1,"{}");assert(strstr(response,"\"waiting_for_load\":true"));
/* Route admission is independent of optional fingerprint reset. */
debug_server_note_savestate_loaded();assert(!s_input_route_wait_load);
assert(debug_server_get_input_override_port2()==32767&&s_route.remaining==1);
debug_server_note_savestate_loaded(); /* An ordinary later load does not restart it. */
assert(s_route.remaining==1);
assert(debug_server_get_input_override_port2()==32767);
assert(debug_server_get_input_override_port2()==65535&&!s_route.active);
for(unsigned port=1;port<=2;++port){
queue();handle_input_route_start(1,port==1?"{\"after_load\":1}":"{\"port\":2,\"after_load\":1}");
handle_clear_input(1,port==1?"{\"port\":2}":"{\"port\":1}");assert(s_input_route_wait_load);
handle_clear_input(1,port==1?"{\"port\":1}":"{\"port\":2}");assert(!s_input_route_wait_load&&!s_route.active);
}
queue();handle_input_route_start(1,"{\"after_load\":1}");
handle_input_route_stop(1,"{}");debug_server_note_savestate_loaded();assert(!s_route.active);
queue();handle_input_route_start(1,"{\"after_load\":1}");
handle_input_route_clear(1,"{}");debug_server_note_savestate_loaded();assert(!s_route.active);
queue();handle_input_route_start(1,"{\"after_load\":2}");assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"after_load\":1,\"at_frame\":10}");assert(strstr(response,"ERROR")&&!s_route.active);
handle_input_route_start(1,"{\"after_load\":1}");s_fp_reset_on_load=1;s_fp_head=4;s_fp_total=5;
debug_server_note_savestate_loaded();assert(!s_input_route_wait_load&&!s_fp_reset_on_load&&!s_fp_head&&!s_fp_total);
puts("debug input route: P2 timing, isolation, deferred start, clearing and legacy P1 pass");
return 0;
}
'''
with tempfile.TemporaryDirectory(prefix='psx-input-route-') as directory:
path = Path(directory) / 'route.c'
path.write_text(preamble + body + checks,encoding='utf-8',newline='\n')
executable = Path(directory) / 'route.exe'
subprocess.run([args.cc, '-std=c11', '-Wall', '-Wextra', '-Werror', str(path),
'-I',str(Path(__file__).resolve().parents[1]/'include'),
str(Path(__file__).resolve().parents[1]/'src/input_route.c'),'-o', str(executable)], check=True)
subprocess.run([str(executable)], check=True)
if __name__ == '__main__':
main()