Files
RetroArch/samples/network/command_frames/command_frames_test.c
T
Rob LoachandLibretroAdmin be9f97d2cf MCP: review fixes, checkpoints, queued STEPS (#19861)
* MCP: cold paused, held input, stacks, arg types

* MCP: checkpoints, prompt, completions, records

* MCP: completion errors, sized prompt, CI shaders

* MCP: rollback via loader, STOP n, completion fixes

* MCP: ask input_driver whether a replay records

* mcp: Clarify steps, and ensure MCP standards

* MCP: queued STEPS, checkpoint step, 32 steps

* MCP: queued STEPS say how long they sat idle

* MCP: queue ends on load/reset; simplify

* MCP docs: queue stops, idle, list caching

* MCP: shorter STEPS wording

* MCP: notice when port taken, --command, taps

* MCP: drop the play_game prompt and completions

* MCP: queue, tap and reset fixes from a review

* MCP: queue outcomes, hex values, field fixes

* command: --command takes an empty host or port as the default

"CMD;;PORT" and "CMD;HOST;" fill in the default host or port, so an
empty segment after a ";" counts as a host or port field again. A STEPS
list still keeps its own ";" steps, which carry a space, a "|" or are
"checkpoint".

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-19 02:12:59 +02:00

1432 lines
60 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string/stdstring.h>
#include <file/file_path.h>
#include <file/config_file.h>
#include <time/rtime.h>
#include <net/net_compat.h>
#include <net/net_socket.h>
#include <retro_timers.h>
#include "../../../runloop.h"
#include "../../../retroarch.h"
#include "../../../configuration.h"
#include "../../../command.h"
#include "../../../input/input_driver.h"
#include "../../../frontend/frontend_driver.h"
#include "../../../frontend/frontend.h"
#include "../../../runahead.h"
#include "../../../gfx/video_driver.h"
#include "../../../network/netplay/netplay.h"
#include "../../../menu/menu_driver.h"
static unsigned failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "FAIL %u: %s\n", (unsigned)__LINE__, #c); failures++; } } while (0)
static bool blocked;
static unsigned calls, gates;
static void (*loaded_run)(void);
static char reply[1024];
static void counted_run(void) { calls++; loaded_run(); }
static bool fail_save(void *data, size_t size) { (void)data; (void)size; return false; }
bool __real_netplay_driver_ctl(enum rarch_netplay_ctl_state, void*);
bool __wrap_netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
{
if (state == RARCH_NETPLAY_CTL_PRE_FRAME && blocked)
{ gates++; return false; }
return __real_netplay_driver_ctl(state, data);
}
/* ram[7] as the core has it now. Read rather than worked out from the
* frame counter: a runahead or preempt restore moves the counter back
* without moving the byte. */
static unsigned ram7(command_t *cmd)
{
char *sp;
reply[0]=0;
if (!command_read_memory(cmd,"1007 1") || !(sp=strrchr(reply,' ')))
return 0x100;
return (unsigned)strtoul(sp+1,NULL,16);
}
static size_t zero_size(void) { return 0; }
static void receive(command_t *cmd, const char *data, size_t n)
{
(void)cmd;
if (n >= sizeof(reply)) n = sizeof(reply)-1;
memcpy(reply,data,n); reply[n]=0;
}
/* @line sent to the network command interface as a client would, read
* through its own parser; the reply, if any, in @out. */
#define UDP_PORT 55437
static void udp_line(const char *line, char *out, size_t cap)
{
static command_t *net;
static int fd = -1;
static struct addrinfo *to;
int i;
long n = -1;
out[0] = '\0';
if (!net)
{
network_init();
net = command_network_new(UDP_PORT, "127.0.0.1");
fd = socket_init((void**)&to, UDP_PORT, "127.0.0.1",
SOCKET_TYPE_DATAGRAM, AF_INET);
if (fd >= 0)
socket_nonblock(fd);
}
if (!net || fd < 0)
return;
sendto(fd, line, strlen(line), 0, to->ai_addr, to->ai_addrlen);
for (i = 0; i < 200 && n <= 0; i++)
{
net->poll(net);
n = (long)recvfrom(fd, out, cap - 1, 0, NULL, NULL);
if (n <= 0)
retro_sleep(1);
}
out[n > 0 ? n : 0] = '\0';
}
int main(int argc, char **argv)
{
char cfg_path[512];
char *args[7];
FILE *cfg;
unsigned i, before;
int16_t axes[4];
command_t cmd;
runloop_state_t *loop;
if (argc < 2) return 2;
snprintf(cfg_path,sizeof(cfg_path),"%s.cfg",argv[0]);
cfg=fopen(cfg_path,"wb"); if (!cfg) return 2;
fputs("video_driver = \"null\"\naudio_driver = \"null\"\ninput_driver = \"null\"\ninput_joypad_driver = \"null\"\nmenu_driver = \"rgui\"\nvideo_threaded = \"false\"\nvideo_vsync = \"false\"\npause_nonactive = \"false\"\nconfig_save_on_exit = \"false\"\nthreaded_data_runloop_enable = \"false\"\n",cfg);
fclose(cfg);
config_file_set_io_default(config_file_io_filestream());
rtime_init(); retroarch_config_init();
retroarch_ctl(RARCH_CTL_STATE_FREE,NULL);
frontend_driver_init_first(NULL);
args[0]=(char*)"retroarch"; args[1]=(char*)"--config"; args[2]=cfg_path;
args[3]=(char*)"-L"; args[4]=argv[1]; args[5]=NULL;
if (!retroarch_main_init(5,args)) return 2;
for (i=0;i<5;i++) runloop_iterate();
loop=runloop_state_get_ptr();
if (loop->current_core_type == CORE_TYPE_DUMMY) return 2;
loaded_run=loop->current_core.retro_run;
loop->current_core.retro_run=counted_run;
memset(&cmd,0,sizeof(cmd)); cmd.replier=receive;
before=calls; blocked=true;
if (!command_run_frames(&cmd,"3")) return 2;
for (i=0;i<3;i++) runloop_iterate();
printf("blocked: core_calls=%u gate_denials=%u pending=%u reply=%s\n",calls-before,gates,
!!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES),reply);
CHECK(calls == before && gates == 3 && !reply[0]);
CHECK(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES);
blocked=false;
before=calls; reply[0]=0;
for (i=0;i<3;i++) runloop_iterate();
printf("unblocked: core_calls=%u pending=%u reply=%s\n",calls-before,
!!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES),reply);
CHECK(calls - before == 3 && !strcmp(reply, "RUN_FRAMES 3\n"));
CHECK(!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
before=calls;
for (i=0;i<3;i++) runloop_iterate();
CHECK(calls == before && (loop->flags & RUNLOOP_FLAG_PAUSED));
{
settings_t *settings=config_get_ptr();
uint32_t *state=(uint32_t*)loop->current_core.retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM);
unsigned base=*state;
before=calls; reply[0]=0;
runahead_clear_variables(loop);
settings->bools.run_ahead_enabled=true;
settings->bools.run_ahead_secondary_instance=false;
settings->uints.run_ahead_frames=2;
if (!command_run_frames(&cmd,"3")) return 2;
for (i=0;i<3;i++) runloop_iterate();
printf("runahead: core_calls=%u logical_frames=%u pending=%u reply=%s\n",calls-before,*state-base,
!!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES),reply);
CHECK(calls-before == 9 && *state-base == 3 && !strcmp(reply,"RUN_FRAMES 3\n"));
settings->bools.run_ahead_enabled=false;
settings->bools.preemptive_frames_enable=true;
preempt_deinit(loop);
if (!preempt_init(loop)) return 2;
base=*state; before=calls; reply[0]=0;
if (!command_run_frames(&cmd,"3")) return 2;
for (i=0;i<3;i++) runloop_iterate();
printf("preempt: core_calls=%u logical_frames=%u pending=%u reply=%s\n",calls-before,*state-base,
!!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES),reply);
CHECK(calls-before == 3 && *state-base == 3 && !strcmp(reply,"RUN_FRAMES 3\n"));
CHECK(command_set_input(&cmd,"1 b 1"));
base=*state; before=calls; reply[0]=0;
CHECK(command_run_frames(&cmd,"3"));
for (i=0;i<3;i++) runloop_iterate();
printf("preempt replay: core_calls=%u logical_frames=%u reply=%s\n",calls-before,*state-base,reply);
CHECK(calls-before > 3 && *state-base == 3 && !strcmp(reply,"RUN_FRAMES 3\n"));
{
bool (*save)(void*,size_t)=loop->current_core.retro_serialize;
loop->current_core.retro_serialize=fail_save;
before=calls; reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(calls == before && !reply[0] && (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
loop->current_core.retro_serialize=save;
runloop_iterate();
CHECK(calls-before == 1 && !strcmp(reply,"RUN_FRAMES 1\n"));
}
CHECK(command_release_input(&cmd,""));
preempt_deinit(loop);
}
/* STATE answers as STATE, not as the command it runs */
reply[0]=0;
CHECK(!command_state(&cmd,"lo") && !strcmp(reply,"STATE ERROR one of save|load|checkpoint|rollback"));
reply[0]=0;
CHECK(!command_state(&cmd,"loads 1") && !strcmp(reply,"STATE ERROR one of save|load|checkpoint|rollback"));
reply[0]=0;
CHECK(!command_state(&cmd,"load") && !strcmp(reply,"STATE ERROR a slot number, not nothing"));
strlcpy(loop->name.savestate,"command_frames_test.state",
sizeof(loop->name.savestate));
reply[0]=0;
CHECK(command_state(&cmd," save 1"));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strcmp(reply,"STATE save 1"));
remove("command_frames_test.state1");
/* a checkpoint in memory, and the rollback to it */
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strncmp(reply,"STATE ERROR no checkpoint",25));
cmd.error=false;
{
unsigned at;
char want[32];
reply[0]=0;
CHECK(command_state(&cmd,"checkpoint"));
for (i=0;i<8 && !reply[0];i++) { task_queue_check(); }
CHECK(!strcmp(reply,"STATE checkpoint"));
at=ram7(&cmd);
CHECK(command_run_frames(&cmd,"5"));
for (i=0;i<5;i++) runloop_iterate();
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) { task_queue_check(); }
CHECK(!strcmp(reply,"STATE rollback"));
/* the frame counter is the harness core's whole state */
reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
snprintf(want,sizeof(want),"%u",(at + 1) & 0xff);
CHECK(ram7(&cmd) == ((at + 1) & 0xff));
(void)want;
}
#ifdef HAVE_BSV_MOVIE
/* a recording would go on from a state it never saw: refused, as a
* load from a slot is */
reply[0]=0;
CHECK(command_replay(&cmd,"record"));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
if (input_state_get_ptr()->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING)
{
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(!strncmp(reply,"STATE ERROR a replay is recording",33));
reply[0]=0;
command_replay(&cmd,"halt");
for (i=0;i<4;i++) { runloop_iterate(); task_queue_check(); }
}
else
printf("replay recording did not start here: rollback-while-recording not checked\n");
cmd.error=false;
#endif
reply[0]=0;
CHECK(!command_state(&cmd,"checkpoint 3") && strstr(reply,"nothing after"));
cmd.error=false;
/* a reply longer than any fixed buffer still answers as the tool */
{
char long_path[700];
memset(long_path,'d',600);
strlcpy(long_path+600,"/missing.state",sizeof(long_path)-600);
strlcpy(loop->name.savestate,long_path,sizeof(loop->name.savestate));
size_t (*size)(void)=loop->current_core.retro_serialize_size;
/* nothing to serialize: refused at once, naming the path */
loop->current_core.retro_serialize_size=zero_size;
reply[0]=0;
command_state(&cmd,"save 1");
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(strlen(reply) > 600 && !strncmp(reply,"STATE ERROR ",12));
loop->current_core.retro_serialize_size=size;
strlcpy(loop->name.savestate,"command_frames_test.state",
sizeof(loop->name.savestate));
cmd.error=false;
}
/* the other families answer under their own names too */
reply[0]=0;
CHECK(!command_replay(&cmd,"seek 600") && !strncmp(reply,"REPLAY ERROR ",13));
cmd.structured=true;
reply[0]=0;
CHECK(command_menu(&cmd,"up") && !strcmp(reply,"MENU up pressed\n"));
/* a command that answers nothing of its own answers as the tool */
reply[0]=0;
CHECK(command_reinit(&cmd,"audio") && !strcmp(reply,"REINIT audio\n"));
/* RESET is a hotkey and an action: HELP describes it once */
{
char *nl;
unsigned lines = 0;
reply[0]=0;
CHECK(command_help(&cmd,"RESET"));
for (nl = reply; (nl = strchr(nl, '\n')); nl++)
lines++;
CHECK(lines == 1 && strstr(reply,"RESET"));
}
/* CORE stands for LOAD_CORE, START_CORE and UNLOAD_CORE */
reply[0]=0;
CHECK(!command_core(&cmd,"fly") && !strcmp(reply,"CORE ERROR one of load|start|unload"));
reply[0]=0;
CHECK(!command_core(&cmd,"start now") && !strcmp(reply,"CORE ERROR nothing after start"));
reply[0]=0;
CHECK(!command_core(&cmd,"load /no/such/core.so")
&& !strncmp(reply,"CORE ERROR no core at",21));
cmd.error=false;
/* this core has no disks: the hotkeys would do nothing */
reply[0]=0;
CHECK(!command_disk(&cmd,"next")
&& !strcmp(reply,"DISK ERROR this core has no disk control"));
cmd.structured=false;
cmd.error=false;
/* and the commands they run, by name, still answer as themselves */
reply[0]=0;
{
char net_reply[256];
udp_line("SAVE_STATE_SLOT x\n", net_reply, sizeof(net_reply));
CHECK(!strncmp(net_reply,"SAVE_STATE_SLOT ERROR ",22));
}
/* READ_CORE_MEMORY and WRITE_CORE_MEMORY answer with the address and
* the bytes in hexadecimal, at a system address the core maps */
reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 4")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 00 01 02 03\n"));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1004 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1004 04\n"));
reply[0]=0;
CHECK(command_write_memory(&cmd,"1002 ff fe")
&& !strcmp(reply,"WRITE_CORE_MEMORY 1002 2\n"));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 4")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 00 01 FF FE\n"));
/* every byte value formats as two uppercase digits */
CHECK(command_write_memory(&cmd,"1000 00 0f 10 a5 ff"));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 5")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 00 0F 10 A5 FF\n"));
/* a trailing space, or a byte that is not one, writes nothing past
* the bytes given rather than zeroes to the end of the region */
CHECK(command_write_memory(&cmd,"1000 aa "));
reply[0]=0;
CHECK(!command_write_memory(&cmd,"1000 bb zz")
&& strstr(reply,"WRITE_CORE_MEMORY ERROR"));
reply[0]=0;
CHECK(!command_write_memory(&cmd,"1000 1ff")
&& strstr(reply,"WRITE_CORE_MEMORY ERROR"));
cmd.error=false; reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 3")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 AA 0F 10\n"));
/* a line client ending its lines in CRLF writes as one ending in LF */
{
char net_reply[256];
udp_line("WRITE_CORE_MEMORY 1000 ab\r\n", net_reply, sizeof(net_reply));
CHECK(!strcmp(net_reply,"WRITE_CORE_MEMORY 1000 1\n"));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 AB\n"));
CHECK(command_write_memory(&cmd,"1000 aa"));
/* only the \r before a line's end goes: one elsewhere is part of
* the command, not a second one */
udp_line("WRITE_CORE_MEMORY 1000 bb\rWRITE_CORE_MEMORY 1001 cc\n",
net_reply, sizeof(net_reply));
CHECK(!strncmp(net_reply,"WRITE_CORE_MEMORY ERROR",23));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1000 2")
&& !strcmp(reply,"READ_CORE_MEMORY 1000 AA 0F\n"));
}
/* a log cursor is not a slot: past INT_MAX it is still taken */
reply[0]=0;
CHECK(command_get_log(&cmd,"3000000000") && !strstr(reply,"ERROR"));
cmd.error=false;
/* a second space after the command's name is still taken */
reply[0]=0;
CHECK(command_read_memory(&cmd," 1001 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1001 0F\n"));
/* an address or count past 32 bits is refused, not wrapped */
reply[0]=0;
CHECK(!command_read_memory(&cmd,"100001000 1")
&& strstr(reply,"READ_CORE_MEMORY ERROR"));
reply[0]=0;
CHECK(!command_read_memory(&cmd,"1000 4294967297")
&& strstr(reply,"READ_CORE_MEMORY ERROR"));
reply[0]=0;
CHECK(!command_write_memory(&cmd,"100001000 cc")
&& strstr(reply,"WRITE_CORE_MEMORY ERROR"));
cmd.error=false;
CHECK(command_write_memory(&cmd,"1000 00"));
/* a read past the region it is in runs on into the next */
reply[0]=0;
CHECK(command_read_memory(&cmd,"100e 8")
&& !strcmp(reply,"READ_CORE_MEMORY 100e 0E 0F 10 11 12 13 14 15\n"));
/* and one outside every region says so, as it always has */
reply[0]=0;
CHECK(!command_read_memory(&cmd,"20000 1")
&& strstr(reply,"-1"));
/* GET_MEMORY_MAP answers the region READ_CORE_MEMORY takes */
reply[0]=0;
CHECK(command_get_memory_map(&cmd,"")
&& !strcmp(reply,
/* the select mask is RetroArch's, worked out from the
* region where the core leaves it zero */
"1000\t10\t1ff0\t0\tsystem\trw\tlittle\tram\n"
"1010\t10\t1ff0\t0\tsystem\trw\tlittle\tram\n"));
/* GET_INPUT reads what a player holds without changing it, which
* SET_INPUT cannot do and RELEASE_INPUT undoes before answering */
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(command_set_input(&cmd,"1 a 1") && !strcmp(reply,"1\ta\tnone\n"));
reply[0]=0;
CHECK(command_get_input(&cmd,"") && !strcmp(reply,"1\ta\tnone\n"));
reply[0]=0;
CHECK(command_get_input(&cmd,"1") && !strcmp(reply,"1\ta\tnone\n"));
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(command_get_input(&cmd,"") && !strcmp(reply,"1\tnone\tnone\n"));
/* a second space before a count is still taken */
reply[0]=0;
CHECK(command_run_frames(&cmd," 1"));
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\n"));
reply[0]=0;
CHECK(!command_get_input(&cmd,"99") && strstr(reply,"GET_INPUT ERROR"));
/* A core path that is not a core is found out before anything is
* torn down for it: the cfg this test wrote is a real file and not a
* library, and the content running stays running. */
reply[0]=0;
CHECK(!command_load_core(&cmd,"/no/such/core.so")
&& strstr(reply,"no core at"));
reply[0]=0;
CHECK(!command_load_core(&cmd,cfg_path)
&& strstr(reply,"is not a libretro core"));
reply[0]=0;
CHECK(!command_load_core(&cmd,"") && strstr(reply,"a core path"));
{
char req[1024];
snprintf(req,sizeof(req),"%s|%s",cfg_path,cfg_path);
reply[0]=0;
CHECK(!command_load_content(&cmd,req)
&& strstr(reply,"is not a libretro core"));
}
CHECK(loop->flags & RUNLOOP_FLAG_CORE_RUNNING);
CHECK(loop->current_core_type != CORE_TYPE_DUMMY);
/* asking after the core already loaded opens the same library,
* whose retro_set_environment must not be called mid-session */
command_load_core(&cmd,argv[1]);
cmd.error=false; reply[0]=0;
CHECK(command_read_memory(&cmd,"1009 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1009 00\n"));
/* PRESS lets go only of what it pressed itself: a button a client
* holds with SET_INPUT is still held once the press ends. GET_INPUT
* is what makes this checkable. */
CHECK(command_release_input(&cmd,""));
CHECK(command_set_input(&cmd,"1 a 1"));
CHECK(command_press(&cmd,"1 a 2"));
runloop_iterate(); runloop_iterate();
reply[0]=0;
CHECK(command_get_input(&cmd,"") && !strcmp(reply,"1\ta\tnone\n"));
reply[0]=0;
CHECK(command_press(&cmd,"1 a+b 2"));
runloop_iterate(); runloop_iterate();
reply[0]=0;
CHECK(command_get_input(&cmd,"") && !strcmp(reply,"1\ta\tnone\n"));
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(command_press(&cmd,"1 b 1"));
/* b was let go on this boundary: a frame let go, then the tap */
runloop_iterate(); runloop_iterate();
reply[0]=0;
CHECK(command_get_input(&cmd,"") && !strcmp(reply,"1\tnone\tnone\n"));
/* a family of commands as one: the word picks the command, and only
* the ones that take a number take what follows */
reply[0]=0; cmd.error=false;
CHECK(!command_menu(&cmd,"toggle") && strstr(reply,"MENU ERROR one of up|"));
reply[0]=0;
CHECK(!command_menu(&cmd,"up 5") && strstr(reply,"nothing after up"));
reply[0]=0;
CHECK(!command_replay(&cmd,"record 3") && strstr(reply,"nothing after record"));
reply[0]=0;
CHECK(!command_state(&cmd,"keep 1") && strstr(reply,"one of save|load"));
/* a slot past 32 bits is refused, not taken as slot 1 */
reply[0]=0;
CHECK(!command_state(&cmd,"load 4294967297") && strstr(reply,"ERROR"));
reply[0]=0;
CHECK(!command_state(&cmd,"save -1") && strstr(reply,"ERROR"));
/* nor one past INT_MAX, which would be the auto slot's file */
reply[0]=0;
CHECK(!command_state(&cmd,"save 2147483648")
&& !strcmp(reply,"STATE ERROR a slot number, not 2147483648"));
reply[0]=0;
CHECK(!command_state(&cmd,"save 4294967295") && strstr(reply,"STATE ERROR"));
reply[0]=0;
CHECK(!command_replay(&cmd,"play 2147483648")
&& !strcmp(reply,"REPLAY ERROR a slot number, not 2147483648"));
cmd.error=false;
/* WATCH carries a look along with every step. Known bytes first:
* the memory tests above wrote over what retro_init left. */
CHECK(command_write_memory(&cmd,"1001 11 22 33"));
reply[0]=0;
CHECK(command_watch(&cmd,"") && !strcmp(reply,"WATCH none\n"));
reply[0]=0;
CHECK(command_watch(&cmd,"one 1001") && !strcmp(reply,"one\t1001\t17\n"));
CHECK(command_watch(&cmd,"two 1002"));
/* several pairs in one request, taken or refused together */
reply[0]=0;
CHECK(command_watch(&cmd,"two off one 1002 two 1003")
&& !strcmp(reply,"one\t1002\t34\ntwo\t1003\t51\n"));
reply[0]=0;
CHECK(!command_watch(&cmd,"one 1001 x 99000") && strstr(reply,"x: "));
reply[0]=0;
CHECK(command_watch(&cmd,"")
&& !strcmp(reply,"one\t1002\t34\ntwo\t1003\t51\n"));
reply[0]=0;
CHECK(!command_watch(&cmd,"one 1001 two") && strstr(reply,"WATCH ERROR"));
reply[0]=0;
CHECK(command_watch(&cmd,"one 1001 two 1002")
&& !strcmp(reply,"one\t1001\t17\ntwo\t1002\t34\n"));
reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\none\t1001\t17\ntwo\t1002\t34\n"));
reply[0]=0;
CHECK(command_press(&cmd,"1 b 1"));
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 b 1\none\t1001\t17\ntwo\t1002\t34\n"));
/* a failure is its reason, not a reading */
reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
command_run_frames_cancel("tidying up");
CHECK(!strcmp(reply,"RUN_FRAMES ERROR tidying up"));
/* a label keeps its place when its address changes */
reply[0]=0;
CHECK(command_watch(&cmd,"one 1003")
&& !strcmp(reply,"one\t1003\t51\ntwo\t1002\t34\n"));
/* RUN_UNTIL opens with how it ended and closes with its own byte */
reply[0]=0;
CHECK(command_run_until(&cmd,"1002 == 34") && !strcmp(reply,
"MET 0\none\t1003\t51\ntwo\t1002\t34\nVALUE 34\n"));
/* RUN_UNTIL and the watches look the byte up every frame, so a
* core that moves its memory is followed, and one that stops mapping
* it is told apart from a byte that holds a value */
{
uint8_t alt[16];
rarch_memory_descriptor_t *d = &loop->system.mmaps.descriptors[0];
void *was = d->core.ptr;
memcpy(alt,was,sizeof(alt));
alt[2]=99;
reply[0]=0;
CHECK(command_run_until(&cmd,"1002 == 99") && !reply[0]);
d->core.ptr=alt;
runloop_iterate();
CHECK(!strcmp(reply,"MET 1\none\t1003\t51\ntwo\t1002\t99\nVALUE 99\n"));
reply[0]=0;
CHECK(command_run_until(&cmd,"1002 == 1") && !reply[0]);
d->core.ptr=NULL;
runloop_iterate();
CHECK(!strcmp(reply,"RUN_UNTIL ERROR nothing maps the address waited on now")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
reply[0]=0;
CHECK(command_watch(&cmd,"")
&& !strcmp(reply,"one\t1003\tnone\ntwo\t1002\tnone\n"));
d->core.ptr=was;
/* a later RUN_FRAMES carries no VALUE of its own */
reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\none\t1003\t51\ntwo\t1002\t34\n"));
}
reply[0]=0;
CHECK(command_watch(&cmd,"one off") && !strcmp(reply,"two\t1002\t34\n"));
/* and what it refuses */
reply[0]=0;
CHECK(!command_watch(&cmd,"x 99000") && strstr(reply,"WATCH ERROR"));
CHECK(!command_watch(&cmd,"x zz") && strstr(reply,"hexadecimal"));
CHECK(!command_watch(&cmd,"nospace")
&& !strcmp(reply,"WATCH ERROR nospace needs an address or \"off\" after it"));
CHECK(!command_watch(&cmd,"one 1001 odd ") && strstr(reply,"odd needs an address"));
/* past 32 bits is refused, not wrapped onto 1001 */
reply[0]=0;
CHECK(!command_watch(&cmd,"x 100001001") && strstr(reply,"WATCH ERROR"));
CHECK(!command_watch(&cmd,"x -1") && strstr(reply,"hexadecimal"));
/* a tab in a label would split its row */
reply[0]=0;
CHECK(!command_watch(&cmd,"x\ty 1001") && strstr(reply,"WATCH ERROR"));
reply[0]=0;
CHECK(command_watch(&cmd,"two 1002 ") && !strcmp(reply,"two\t1002\t34\n"));
{
unsigned w;
char req[32];
/* room for sixteen, counted from empty */
CHECK(command_watch(&cmd,"two off"));
for (w = 0; w < 16; w++)
{
snprintf(req,sizeof(req),"w%u %x",w,0x1000 + w);
CHECK(command_watch(&cmd,req));
}
reply[0]=0;
CHECK(!command_watch(&cmd,"w16 1009")
&& strstr(reply,"at most 16 bytes watched"));
/* room made earlier in the same request counts */
CHECK(command_watch(&cmd,"w15 off w16 1009 w16 off w15 100f"));
/* but replacing one of the sixteen still works */
CHECK(command_watch(&cmd,"w0 1005"));
for (w = 0; w < 16; w++)
{
snprintf(req,sizeof(req),"w%u off",w);
CHECK(command_watch(&cmd,req));
}
reply[0]=0;
CHECK(command_watch(&cmd,"") && !strcmp(reply,"WATCH none\n"));
}
/* several controls at once */
reply[0]=0;
CHECK(command_set_input(&cmd,"1 b+a 1") && !strcmp(reply,"1\tb a\tnone\n"));
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(command_set_input(&cmd,"1 +a++b+ 1") && !strcmp(reply,"1\tb a\tnone\n"));
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(command_set_input(&cmd,"1 left_x+right_y 2000")
&& !strcmp(reply,"1\tnone\tleft_x=2000 right_y=2000\n"));
CHECK(command_release_input(&cmd,""));
reply[0]=0;
CHECK(!command_set_input(&cmd,"1 + 1") && strstr(reply,"SET_INPUT ERROR"));
/* a combination with something wrong in it holds nothing: the next
* request answers with only what it asked for */
reply[0]=0;
CHECK(!command_set_input(&cmd,"1 a+nope 1") && strstr(reply,"nope"));
reply[0]=0;
CHECK(!command_set_input(&cmd,"1 a+left_x 50")
&& strstr(reply,"SET_INPUT ERROR"));
reply[0]=0;
CHECK(command_set_input(&cmd,"1 start 1")
&& !strcmp(reply,"1\tstart\tnone\n"));
CHECK(command_release_input(&cmd,""));
/* PRESS holds, runs its frames and lets go, answered on the last */
reply[0]=0;
CHECK(command_press(&cmd,"1 b+a 2"));
CHECK(!reply[0] && input_driver_command_held(0,axes) == 0x0101);
runloop_iterate();
CHECK(!reply[0] && input_driver_command_held(0,axes) == 0x0101);
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 b+a 2\n")
&& input_driver_command_held(0,axes) == 0);
/* the controls as resolved, not as written */
reply[0]=0;
CHECK(command_press(&cmd,"1 +a++a+ 1"));
/* a was let go on this boundary: a frame let go, then the press */
runloop_iterate();
CHECK(!reply[0] && input_driver_command_held(0,axes) == 0x0100);
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 a+a 1\n"));
/* an axis is held at a value, so it is SET_INPUT's */
reply[0]=0;
CHECK(!command_press(&cmd,"1 left_x 2") && strstr(reply,"PRESS ERROR"));
CHECK(!command_press(&cmd,"1 a 0") && strstr(reply,"frame count"));
/* a press holds input for every frame it runs, so it is bounded by
* what a client waits for, not by RUN_FRAMES' ten minutes */
reply[0]=0;
CHECK(!command_press(&cmd,"1 a 601") && strstr(reply,"SET_INPUT"));
/* a player past 32 bits is refused, not taken as player 1 */
reply[0]=0;
CHECK(!command_press(&cmd,"4294967297 a 1") && strstr(reply,"a player from 1"));
CHECK(!command_set_input(&cmd,"4294967297 a 1") && strstr(reply,"a player from 1"));
CHECK(!command_set_input(&cmd,"-18446744073709551615 a 1")
&& strstr(reply,"a player from 1"));
cmd.error=false;
CHECK(command_press(&cmd,"1 a 600"));
/* A line interface waits as long as it likes, so RUN_FRAMES keeps
* the ten minutes there; a structured one is told at once rather
* than failing once the frames are already running. */
reply[0]=0;
CHECK(command_run_frames(&cmd,"36000")
&& input_driver_command_held(0,axes) == 0);
command_run_frames_cancel("tidying up");
CHECK(!strcmp(reply,"RUN_FRAMES ERROR tidying up"));
cmd.structured=true;
reply[0]=0;
/* nine seconds of frames, inside the ten a reply is waited for */
CHECK(!command_run_frames(&cmd,"36000")
&& strstr(reply,"a frame count from 1 to 540"));
CHECK(!command_run_frames(&cmd,"541"));
CHECK(command_run_frames(&cmd,"540"));
command_run_frames_cancel("tidying up");
reply[0]=0;
CHECK(!command_press(&cmd,"1 a 541") && strstr(reply,"1 to 540"));
CHECK(!command_run_until(&cmd,"1000 == 0 541") && strstr(reply,"at most 540"));
/* and at 50fps, where 600 frames is twelve seconds */
video_state_get_ptr()->av_info.timing.fps=50.0;
reply[0]=0;
CHECK(!command_run_frames(&cmd,"451") && strstr(reply,"a frame count from 1 to 450"));
CHECK(!command_press(&cmd,"1 a 451") && strstr(reply,"1 to 450"));
CHECK(command_run_frames(&cmd,"450"));
command_run_frames_cancel("tidying up");
video_state_get_ptr()->av_info.timing.fps=60.0;
cmd.structured=false;
cmd.error=false;
/* one at a time: the second replaces the first, which is answered */
reply[0]=0;
CHECK(command_press(&cmd,"1 a 30"));
CHECK(command_press(&cmd,"1 b 1")
&& strstr(reply,"PRESS ERROR replaced by PRESS"));
/* and what the replaced one held was let go of */
CHECK(input_driver_command_held(0,axes) == 0x0001);
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 b 1\n")
&& input_driver_command_held(0,axes) == 0);
/* a press over a button SET_INPUT holds leaves that hold in place;
* a frame first, so B's press is not the one just let go */
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
reply[0]=0;
CHECK(command_set_input(&cmd,"1 a 1"));
CHECK(command_press(&cmd,"1 a+b 1")
&& input_driver_command_held(0,axes) == 0x0101);
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 a+b 1\n")
&& input_driver_command_held(0,axes) == 0x0100);
CHECK(command_release_input(&cmd,""));
/* RUN_FRAMES and PRESS are the same one frame job */
reply[0]=0;
CHECK(command_press(&cmd,"1 a 30"));
CHECK(command_run_frames(&cmd,"1")
&& strstr(reply,"PRESS ERROR replaced by another RUN_FRAMES")
&& input_driver_command_held(0,axes) == 0);
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\n"));
/* RUN_UNTIL waits on a byte the core maps, answered the frame it
* meets what was asked. ram[7] follows the frame counter. */
{
char req[256];
unsigned now;
/* what it already holds: no frame need run */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 == %u",now);
CHECK(command_run_until(&cmd,req));
CHECK(!strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE "))
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
/* three frames away: answered on the third, not before */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 == %u",(now + 3) & 0xff);
CHECK(command_run_until(&cmd,req) && !reply[0]);
runloop_iterate();
CHECK(!reply[0]);
runloop_iterate();
CHECK(!reply[0]);
runloop_iterate();
{
char want[64];
snprintf(want,sizeof(want),"MET 3\nVALUE %u\n",(now + 3) & 0xff);
CHECK(!strcmp(reply,want));
}
/* the frames run out first: how far it got, to be asked again */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 == %u 2",(now + 90) & 0xff);
CHECK(command_run_until(&cmd,req) && !reply[0]);
runloop_iterate();
runloop_iterate();
CHECK(!strncmp(reply,"WAITING 2\nVALUE ",STRLEN_CONST("WAITING 2\nVALUE ")));
/* left to itself it waits nine seconds of the content's frames,
* 540 at this core's 60fps, not the 600 a request may ask for */
{
unsigned n=0;
reply[0]=0;
CHECK(command_run_until(&cmd,"1007 > 255") && !reply[0]);
while (!reply[0] && n < 700) { runloop_iterate(); n++; }
CHECK(n == 540 && !strncmp(reply,"WAITING 540\nVALUE ",
STRLEN_CONST("WAITING 540\nVALUE ")));
}
/* every condition it takes */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 != %u",(now + 1) & 0xff);
CHECK(command_run_until(&cmd,req) && !strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE ")));
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 >= %u",now);
CHECK(command_run_until(&cmd,req) && !strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE ")));
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 <= %u",now);
CHECK(command_run_until(&cmd,req) && !strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE ")));
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 < %u",(now + 1) & 0xff);
CHECK(now == 0xff || (command_run_until(&cmd,req) && !strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE "))));
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 > %u",(now + 0xff) & 0xff);
CHECK(now == 0 || (command_run_until(&cmd,req) && !strncmp(reply,"MET 0\nVALUE ",STRLEN_CONST("MET 0\nVALUE "))));
/* an address past 32 bits, not wrapped onto 1007 */
reply[0]=0;
CHECK(!command_run_until(&cmd,"100001007 == 0")
&& strstr(reply,"hexadecimal address"));
/* a value past what a byte holds, however far past */
reply[0]=0;
CHECK(!command_run_until(&cmd,"1007 == 4294967296")
&& strstr(reply,"0 to 255"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1007 == 0 4294967297")
&& strstr(reply,"at most 600"));
/* and what it refuses */
reply[0]=0;
CHECK(!command_run_until(&cmd,"1007 ~= 5")
&& strstr(reply,"RUN_UNTIL ERROR"));
CHECK(!command_run_until(&cmd,"1007 == 300")
&& strstr(reply,"0 to 255"));
CHECK(!command_run_until(&cmd,"99000 == 0")
&& strstr(reply,"RUN_UNTIL ERROR"));
CHECK(!command_run_until(&cmd,"1007 == 0 601")
&& strstr(reply,"at most 600"));
CHECK(!command_run_until(&cmd,"1007 == 0 5 junk")
&& strstr(reply,"RUN_UNTIL ERROR"));
/* one frame job at a time, as RUN_FRAMES and PRESS are */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1007 == %u",(now + 90) & 0xff);
CHECK(command_run_until(&cmd,req));
CHECK(command_run_frames(&cmd,"1")
&& strstr(reply,"RUN_UNTIL ERROR replaced by another RUN_FRAMES"));
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\n"));
/* two bytes as one value, high:low: ram[6] holds 6 */
{
char want[64];
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1006:1007 == %u",0x600 + ((now + 2) & 0xff));
CHECK(command_run_until(&cmd,req) && !reply[0]);
runloop_iterate(); runloop_iterate();
snprintf(want,sizeof(want),"MET 2\nVALUE %u\n",0x600 + ((now + 2) & 0xff));
CHECK(!strcmp(reply,want));
}
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006:1007 == 65536") && strstr(reply,"0 to 65535"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006: == 1") && strstr(reply,"second hexadecimal"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"99000:1007 == 1") && strstr(reply,"RUN_UNTIL ERROR"));
cmd.error=false;
/* PRESS with until: the press, then on until it holds - a jump
* and its landing in one request */
{
char want[64];
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"1 a 2 until 1007 == %u",(now + 5) & 0xff);
CHECK(command_press(&cmd,req) && !reply[0]);
runloop_iterate();
CHECK((input_driver_command_held(0,axes) & (1u << RETRO_DEVICE_ID_JOYPAD_A)) != 0);
runloop_iterate();
/* let go once its frames have run, while the wait goes on */
CHECK(input_driver_command_held(0,axes) == 0 && !reply[0]);
runloop_iterate(); runloop_iterate(); runloop_iterate();
snprintf(want,sizeof(want),"MET 5\nVALUE %u\n",(now + 5) & 0xff);
CHECK(!strcmp(reply,want));
}
reply[0]=0;
CHECK(command_press(&cmd,"1 a 2 until 1006 == 6"));
runloop_iterate(); runloop_iterate();
CHECK(!strcmp(reply,"MET 2\nVALUE 6\n"));
/* without until it answers as it always has */
reply[0]=0;
CHECK(command_press(&cmd,"1 a 1"));
/* a tap after the last one let go: its own frame, after a gap */
runloop_iterate();
CHECK(!reply[0]);
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 a 1\n"));
reply[0]=0;
CHECK(!command_press(&cmd,"1 a 2 until 99000 == 0") && strstr(reply,"PRESS ERROR"));
reply[0]=0;
CHECK(!command_press(&cmd,"1 a 2 until 1007 ~ 1") && strstr(reply,"after until"));
cmd.error=false;
/* STEPS: a stretch of play as one request */
{
char want[64];
now=ram7(&cmd); reply[0]=0;
CHECK(command_watch(&cmd,"one 1003"));
snprintf(req,sizeof(req),"run 2; press 1 b 1; until 1007 == %u",(now + 6) & 0xff);
reply[0]=0;
CHECK(command_steps(&cmd,req) && !reply[0]);
for (i=0;i<5;i++) runloop_iterate();
CHECK(!reply[0]);
runloop_iterate();
CHECK(!strcmp(reply,"DONE 6\none\t1003\t51\nSTEP 3\n"));
CHECK(command_watch(&cmd,"one off"));
/* stop ends it after the frame where it holds */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 10; stop 1007 == %u",(now + 3) & 0xff);
CHECK(command_steps(&cmd,req));
for (i=0;i<3;i++) runloop_iterate();
CHECK(!strcmp(reply,"STOPPED 3\nSTOP 1\nSTEP 1\n")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
/* what already holds runs no frame */
reply[0]=0;
CHECK(command_steps(&cmd,"until 1006 == 6") && !strcmp(reply,"DONE 0\nSTEP 1\n"));
/* past nine seconds it says how far it got and where */
{
unsigned n=0;
reply[0]=0;
CHECK(command_steps(&cmd,"run 1; until 1007 > 255"));
while (!reply[0] && n < 700) { runloop_iterate(); n++; }
CHECK(n == 540 && !strcmp(reply,"WAITING 540\nSTEP 2\n"));
}
(void)want;
}
/* a watch of two bytes as one value, high:low */
{
char want[64];
CHECK(command_watch(&cmd,"pos 1006:1007"));
now=ram7(&cmd); reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
snprintf(want,sizeof(want),"RUN_FRAMES 1\npos\t1006:1007\t%u\n",0x600 + ((now + 1) & 0xff));
CHECK(!strcmp(reply,want));
CHECK(command_watch(&cmd,"pos off"));
}
/* only the bits a mask names: ram[6] holds 6, 110 in binary */
reply[0]=0;
CHECK(command_run_until(&cmd,"1006 & 0x4 == 4") && !strcmp(reply,"MET 0\nVALUE 4\n"));
reply[0]=0;
CHECK(command_run_until(&cmd,"1006 & 1 == 0") && !strcmp(reply,"MET 0\nVALUE 0\n"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006 & zz == 0") && strstr(reply,"a mask"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006 & 0x 4 == 4") && strstr(reply,"a mask"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006 & 0x+4 == 4") && strstr(reply,"a mask"));
cmd.error=false;
/* any of several stops ends the list */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 20; stop 1006 == 7; stop 1007 == %u",(now + 2) & 0xff);
CHECK(command_steps(&cmd,req));
runloop_iterate(); runloop_iterate();
/* and says which */
CHECK(!strcmp(reply,"STOPPED 2\nSTOP 2\nSTEP 1\n"));
reply[0]=0;
CHECK(!command_steps(&cmd,"run 541") && strstr(reply,"within the 540 frames"));
reply[0]=0;
CHECK(!command_steps(&cmd,"run 4294967295; run 2") && strstr(reply,"within the 540 frames"));
reply[0]=0;
CHECK(!command_steps(&cmd,"run 300; press 1 a 300") && strstr(reply,"step 2"));
/* two presses of one button are two taps: a frame let go between */
{
unsigned want[5] = {1,1,0,1,1};
unsigned k;
bool ok = true;
reply[0]=0;
cmd.error=false;
CHECK(command_steps(&cmd,"press 1 a 2; press 1 a 2"));
for (k=0;k<5;k++)
{
/* held during frame k is what the frame before left */
if (((input_driver_command_held(0,axes) >> RETRO_DEVICE_ID_JOYPAD_A) & 1) != want[k])
ok = false;
runloop_iterate();
}
CHECK(ok && !strcmp(reply,"DONE 5\nSTEP 2\n"));
}
/* an until that already holds between them is no frame: still
* two taps */
{
unsigned want[5] = {1,1,0,1,1};
unsigned k;
bool ok = true;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"press 1 a 2; until 1006 == 6; press 1 a 2"));
for (k=0;k<5;k++)
{
if (((input_driver_command_held(0,axes) >> RETRO_DEVICE_ID_JOYPAD_A) & 1) != want[k])
ok = false;
runloop_iterate();
}
CHECK(ok && !strcmp(reply,"DONE 5\nSTEP 3\n"));
}
/* and so are a press ending one request and the same press
* starting the next */
{
unsigned want[5] = {1,1,0,1,1};
unsigned k;
bool ok = true;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"press 1 a 2"));
for (k=0;k<2;k++)
{
if (((input_driver_command_held(0,axes) >> RETRO_DEVICE_ID_JOYPAD_A) & 1) != want[k])
ok = false;
runloop_iterate();
}
CHECK(!strcmp(reply,"DONE 2\nSTEP 1\n"));
reply[0]=0;
CHECK(command_steps(&cmd,"press 1 a 2"));
for (k=2;k<5;k++)
{
if (((input_driver_command_held(0,axes) >> RETRO_DEVICE_ID_JOYPAD_A) & 1) != want[k])
ok = false;
runloop_iterate();
}
CHECK(ok && !strcmp(reply,"DONE 3\nSTEP 1\n"));
}
/* that frame let go counts against the frames a request runs */
reply[0]=0;
CHECK(!command_steps(&cmd,"press 1 a 270; press 1 a 270") && strstr(reply,"step 2"));
if (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES)
command_run_frames_cancel("tidying up");
{
unsigned n=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"press 1 a 270; press 1 a 269"));
while (!reply[0] && n < 700) { runloop_iterate(); n++; }
CHECK(n == 540 && !strcmp(reply,"DONE 540\nSTEP 2\n"));
}
reply[0]=0;
{
char longarg[400];
memset(longarg,'0',sizeof(longarg));
memcpy(longarg,"1 a ",4);
longarg[sizeof(longarg)-2]='5'; longarg[sizeof(longarg)-1]='\0';
CHECK(!command_press(&cmd,longarg) && strstr(reply,"at most 255 characters"));
}
reply[0]=0;
CHECK(!command_steps(&cmd,"jump 3") && strstr(reply,"run, press, until, checkpoint or stop"));
reply[0]=0;
CHECK(!command_steps(&cmd,"") && strstr(reply,"STEPS ERROR"));
reply[0]=0;
CHECK(!command_steps(&cmd,"stop 1007 == 1; stop 1007 == 2; stop 1007 == 3;"
" stop 1007 == 4; stop 1007 == 5; run 1") && strstr(reply,"at most 4 stops"));
reply[0]=0;
CHECK(!command_steps(&cmd,"press 1 left_x 3") && strstr(reply,"axis"));
reply[0]=0;
for (i=0, req[0]=0; i<33; i++)
strlcat(req,"run 1;",sizeof(req));
CHECK(!command_steps(&cmd,req) && strstr(reply,"at most 32 steps"));
reply[0]=0;
CHECK(!command_steps(&cmd,"until 99000 == 0") && strstr(reply,"STEPS ERROR"));
cmd.error=false;
/* a checkpoint as a step: where the list got to, not where it
* started */
now=ram7(&cmd); reply[0]=0;
CHECK(command_steps(&cmd,"run 2; checkpoint; run 3"));
for (i=0;i<5;i++) runloop_iterate();
CHECK(!strcmp(reply,"DONE 5\nSTEP 3\n"));
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(ram7(&cmd) == ((now + 3) & 0xff));
/* first in the list, it is kept before the first frame runs */
now=ram7(&cmd); reply[0]=0;
CHECK(command_steps(&cmd,"checkpoint; run 2") && !reply[0]);
runloop_iterate(); runloop_iterate();
CHECK(!strcmp(reply,"DONE 2\nSTEP 2\n"));
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(ram7(&cmd) == ((now + 1) & 0xff));
reply[0]=0;
CHECK(!command_steps(&cmd,"checkpoint") && strstr(reply,"a step at least"));
cmd.error=false;
/* queued: answered at once, played on through what is added, and
* paused once it has all run, answering no one */
now=ram7(&cmd); reply[0]=0;
CHECK(command_steps(&cmd,"run 3|on") && !strcmp(reply,"QUEUED 1\n")
&& (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"run 2 |on") && !strcmp(reply,"QUEUED 2\n"));
reply[0]=0;
for (i=0;i<4;i++) runloop_iterate();
CHECK(!reply[0] && !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES)
&& ram7(&cmd) == ((now + 5) & 0xff));
runloop_iterate();
CHECK(ram7(&cmd) == ((now + 5) & 0xff));
/* run dry, it says how long it has sat paused waiting for more,
* and the steps that start it again say so too */
retro_sleep(30);
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strncmp(reply,"QUEUED 0\nIDLE ",14)
&& atol(reply + 14) >= 20);
reply[0]=0;
CHECK(command_steps(&cmd,"run 1|on") && !strncmp(reply,"QUEUED 1\nIDLE ",14)
&& atol(reply + 14) >= 20);
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 1\n"));
runloop_iterate();
/* a stop is kept for the next queued STEPS, whose steps are not
* played on from it */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 10; stop 1007 == %u|on",(now + 2) & 0xff);
CHECK(command_steps(&cmd,req) && !strncmp(reply,"QUEUED 1\nIDLE ",14));
reply[0]=0;
runloop_iterate(); runloop_iterate();
CHECK(!reply[0] && !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
CHECK(command_steps(&cmd,"run 1|on")
&& !strcmp(reply,"DISCARDED 1\nPREVIOUS STOPPED 2 STOP 1 STEP 1\n")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* each queued list runs with its own stops, none where it has
* none, and counts its own frames */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 2; stop 1007 == %u|on",(now + 4) & 0xff);
CHECK(command_steps(&cmd,req));
CHECK(command_steps(&cmd,"run 5|on"));
for (i=0;i<7;i++) runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strncmp(reply,"QUEUED 0\nIDLE ",14)
&& ram7(&cmd) == ((now + 7) & 0xff));
now=ram7(&cmd); reply[0]=0;
CHECK(command_steps(&cmd,"run 3|on"));
snprintf(req,sizeof(req),"run 9; stop 1007 == %u|on",(now + 5) & 0xff);
CHECK(command_steps(&cmd,req));
for (i=0;i<5;i++) runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"|on")
&& !strcmp(reply,"QUEUED 0\nPREVIOUS STOPPED 2 STOP 1 STEP 1\n"));
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 2; stop 1007 == %u|on",(now + 4) & 0xff);
CHECK(command_steps(&cmd,req));
CHECK(command_steps(&cmd,"run 5; stop 1006 == 7|on"));
for (i=0;i<7;i++) runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strncmp(reply,"QUEUED 0\nIDLE ",14)
&& ram7(&cmd) == ((now + 7) & 0xff));
/* a rollback drops a stop no one has heard about */
now=ram7(&cmd); reply[0]=0;
snprintf(req,sizeof(req),"run 5; stop 1007 == %u|on",(now + 1) & 0xff);
CHECK(command_steps(&cmd,req));
runloop_iterate();
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* and ends a queue planned from the state it left */
reply[0]=0;
CHECK(command_steps(&cmd,"run 10|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(!(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* so do a state loaded from a slot and a reset */
reply[0]=0;
CHECK(command_state(&cmd,"save 1"));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strcmp(reply,"STATE save 1"));
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_state(&cmd,"load 1"));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strcmp(reply,"STATE load 1, queue ended")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
remove("command_frames_test.state1");
remove("command_frames_test.state1.png");
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_reset(&cmd,""));
for (i=0;i<8 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strncmp(reply,"RESET",5)
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* a request that waits replaces the queue */
reply[0]=0;
CHECK(command_steps(&cmd,"run 10|on"));
reply[0]=0;
CHECK(command_run_frames(&cmd,"1") && !reply[0]);
runloop_iterate();
CHECK(!strcmp(reply,"RUN_FRAMES 1\nPREVIOUS REPLACED 0 STEP 1\n")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
/* and is not queued behind */
reply[0]=0;
CHECK(command_run_frames(&cmd,"5"));
CHECK(!command_steps(&cmd,"run 1|on") && strstr(reply,"a request waits"));
command_run_frames_cancel("tidying up");
cmd.error=false;
/* 64 queued and running at most */
for (i=0, req[0]=0; i<32; i++)
strlcat(req,"run 1;",sizeof(req));
strlcat(req,"|on",sizeof(req));
reply[0]=0;
CHECK(command_steps(&cmd,req) && !strcmp(reply,"QUEUED 32\n"));
reply[0]=0;
CHECK(command_steps(&cmd,req) && !strcmp(reply,"QUEUED 64\n"));
reply[0]=0;
CHECK(!command_steps(&cmd,"run 1|on") && strstr(reply,"the queue is full"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"run 1|on") && !strcmp(reply,"QUEUED 64\n"));
command_run_frames_cancel("tidying up");
/* what the tidying above left to report */
command_run_frames_forget();
/* a queued wait that never ends gives up as a request's does */
{
unsigned n=0;
reply[0]=0;
CHECK(command_steps(&cmd,"until 1007 > 255|on"));
while (n < 700 && (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES))
{ runloop_iterate(); n++; }
reply[0]=0;
CHECK(n == 540 && command_steps(&cmd,"|on")
&& !strcmp(reply,"QUEUED 0\nPREVIOUS WAITING 540 STEP 1\n"));
}
/* a reset, by hotkey as much as by command, ends the queue */
reply[0]=0;
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate();
command_event(CMD_EVENT_RESET,NULL);
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
/* idle is time the content sat paused: a frame it runs ends it */
reply[0]=0;
CHECK(command_steps(&cmd,"run 1|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strncmp(reply,"QUEUED 0\nIDLE ",14));
command_event(CMD_EVENT_UNPAUSE,NULL);
runloop_iterate();
command_event(CMD_EVENT_PAUSE,NULL);
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* and a press after such a frame is a tap with no gap before it */
reply[0]=0;
CHECK(command_press(&cmd,"1 b 1"));
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 b 1\n"));
command_event(CMD_EVENT_UNPAUSE,NULL);
runloop_iterate();
command_event(CMD_EVENT_PAUSE,NULL);
reply[0]=0;
CHECK(command_press(&cmd,"1 b 1"));
runloop_iterate();
CHECK(!strcmp(reply,"PRESS 1 b 1\n"));
reply[0]=0;
CHECK(!command_steps(&cmd,"run 1|maybe") && strstr(reply,"on or off"));
cmd.error=false;
/* how a queued list ended reaches whichever call comes next,
* labelled PREVIOUS, with whether that call's own steps ran */
command_run_frames_forget();
/* a: stopped, then a call that is not queued: its steps run */
CHECK(command_steps(&cmd,"run 5; stop 1006 == 6|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"run 1"));
runloop_iterate();
CHECK(!strcmp(reply,"DONE 1\nPREVIOUS STOPPED 1 STOP 1 STEP 1\nSTEP 1\n"));
/* b: stopped, then a queued call: its steps are dropped, and it
* says so */
CHECK(command_steps(&cmd,"run 5; stop 1006 == 6|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"run 2; run 3|on")
&& !strcmp(reply,"DISCARDED 2\nPREVIOUS STOPPED 1 STOP 1 STEP 1\n")
&& !(loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* c: a queued wait gave up, then a call that is not queued */
{
unsigned n=0;
CHECK(command_steps(&cmd,"until 1007 > 255|on"));
while (n < 700 && (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES))
{ runloop_iterate(); n++; }
reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
runloop_iterate();
CHECK(n == 540 && !strcmp(reply,"RUN_FRAMES 1\nPREVIOUS WAITING 540 STEP 1\n"));
}
/* d: a queue still running, replaced by a call that is not */
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate(); runloop_iterate(); runloop_iterate();
reply[0]=0;
CHECK(command_steps(&cmd,"run 1"));
runloop_iterate();
CHECK(!strcmp(reply,"DONE 1\nPREVIOUS REPLACED 3 STEP 1\nSTEP 1\n"));
/* and with nothing before it, none */
reply[0]=0;
CHECK(command_steps(&cmd,"run 1"));
runloop_iterate();
CHECK(!strcmp(reply,"DONE 1\nSTEP 1\n"));
/* values in hexadecimal, as masks are; ram[6] holds 6 */
reply[0]=0;
CHECK(command_run_until(&cmd,"1006 == 0x6") && !strcmp(reply,"MET 0\nVALUE 6\n"));
reply[0]=0;
CHECK(command_run_until(&cmd,"1006 & 0xF0 == 0x0") && !strcmp(reply,"MET 0\nVALUE 0\n"));
reply[0]=0;
CHECK(command_steps(&cmd,"until 1006 == 0x06") && !strcmp(reply,"DONE 0\nSTEP 1\n"));
reply[0]=0;
CHECK(!command_run_until(&cmd,"1006 == 0x1ff") && strstr(reply,"0 to 255"));
cmd.error=false; reply[0]=0;
CHECK(!command_run_until(&cmd,"1006 == 0x") && strstr(reply,"0 to 255"));
cmd.error=false;
/* a read runs on into the region that follows; where nothing
* maps the rest, it says how much it read */
reply[0]=0;
CHECK(command_read_memory(&cmd,"100e 4")
&& !strcmp(reply,"READ_CORE_MEMORY 100e 0E 0F 10 11\n"));
reply[0]=0;
CHECK(command_read_memory(&cmd,"101e 4")
&& !strcmp(reply,"READ_CORE_MEMORY 101e 1E 1F\nTRUNCATED 2\n"));
/* over MCP, one range: the one it takes */
cmd.structured=true;
reply[0]=0;
CHECK(!command_run_frames(&cmd,"-5") && strstr(reply,"a frame count from 1 to 540"));
reply[0]=0;
CHECK(!command_run_frames(&cmd,"541") && strstr(reply,"a frame count from 1 to 540"));
cmd.structured=false;
cmd.error=false;
/* a rollback or a reset that ends a queue says so */
reply[0]=0;
CHECK(command_state(&cmd,"checkpoint"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_state(&cmd,"rollback"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(!strcmp(reply,"STATE rollback, queue ended"));
CHECK(command_steps(&cmd,"run 100|on"));
runloop_iterate();
reply[0]=0;
CHECK(command_reset(&cmd,""));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(!strcmp(reply,"RESET, queue ended"));
/* and with no queue, nothing to say */
reply[0]=0;
CHECK(command_reset(&cmd,""));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
CHECK(!strcmp(reply,"RESET"));
/* RESET paused runs no frame after the reset */
before=calls; reply[0]=0;
CHECK(command_reset(&cmd,"on"));
for (i=0;i<8 && !reply[0];i++) task_queue_check();
runloop_iterate(); runloop_iterate();
CHECK(!strcmp(reply,"RESET") && calls == before
&& (loop->flags & RUNLOOP_FLAG_PAUSED));
cmd.error=false;
}
before=calls; reply[0]=0;
CHECK(command_run_frames(&cmd,"3"));
runloop_iterate();
blocked=true;
for(i=0;i<3;i++) runloop_iterate();
CHECK(calls-before == 1 && !reply[0] && (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
blocked=false;
runloop_iterate(); runloop_iterate();
CHECK(calls-before == 3 && !strcmp(reply,"RUN_FRAMES 3\n"));
before=calls; reply[0]=0;
CHECK(command_run_frames(&cmd,"1"));
loop->current_core.retro_run=NULL;
runloop_iterate();
CHECK(calls==before && !reply[0] && (loop->frame_work & RUNLOOP_WORK_RUN_FRAMES));
loop->current_core.retro_run=counted_run;
runloop_iterate();
CHECK(calls-before==1 && !strcmp(reply,"RUN_FRAMES 1\n"));
/* --command: a STEPS list's "; " is not where the host starts */
{
char out[256];
CHECK(command_network_send("STEPS run 2; run 1|on;127.0.0.1;55437"));
udp_line("STEPS |on", out, sizeof(out));
CHECK(!strncmp(out, "QUEUED 2\n", 9));
command_run_frames_cancel("tidying up");
/* ";;port": the empty host is the default one */
udp_line("STEPS |on", out, sizeof(out));
CHECK(command_network_send("STEPS run 2|on;;55437"));
udp_line("STEPS |on", out, sizeof(out));
CHECK(!strncmp(out, "QUEUED 1\n", 9));
command_run_frames_cancel("tidying up");
}
/* closing the content drops the watches: the next content's
* addresses mean something else */
CHECK(command_watch(&cmd,"two 1002"));
CHECK(command_set_input(&cmd,"1 start 1"));
{
char req[64];
unsigned now=ram7(&cmd);
snprintf(req,sizeof(req),"run 5; stop 1007 == %u|on",(now + 1) & 0xff);
CHECK(command_steps(&cmd,req));
runloop_iterate();
}
command_event(CMD_EVENT_UNLOAD_CORE,NULL);
for (i=0;i<8;i++) { runloop_iterate(); task_queue_check(); }
reply[0]=0;
CHECK(command_watch(&cmd,"") && !strcmp(reply,"WATCH none\n"));
/* and what SET_INPUT held: the next content starts with nothing held */
CHECK(input_driver_command_held(0,axes) == 0);
/* and a stop no one heard about */
reply[0]=0;
CHECK(command_steps(&cmd,"|on") && !strcmp(reply,"QUEUED 0\n"));
/* LOAD_CONTENT paused from no running content: how many frames the
* core has run once it answers - 1007 counts them */
{
char line[1024];
FILE *f = fopen("command_frames_test.bin", "wb");
if (f) { fputs("x", f); fclose(f); }
snprintf(line, sizeof(line), "%s|command_frames_test.bin|on", argv[1]);
cmd.structured=true;
reply[0]=0;
CHECK(command_load_content(&cmd,line));
for (i=0;i<60 && !reply[0];i++) { runloop_iterate(); task_queue_check(); }
CHECK(!strncmp(reply,"LOAD_CONTENT ",13));
reply[0]=0;
CHECK(command_read_memory(&cmd,"1007 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1007 01\n"));
for (i=0;i<5;i++) runloop_iterate();
reply[0]=0;
/* and one more, the frame the pause waits for, as a core is
* only marked running once it has run one */
CHECK(command_read_memory(&cmd,"1007 1")
&& !strcmp(reply,"READ_CORE_MEMORY 1007 02\n"));
CHECK(loop->flags & RUNLOOP_FLAG_PAUSED);
cmd.structured=false;
remove("command_frames_test.bin");
}
main_exit(NULL); remove(cfg_path);
printf("command_frames: %s (%u failures)\n",failures?"FAIL":"PASS",failures);
return failures ? 1 : 0;
}