joeylib2/tests/host/inputHost.c

211 lines
5.6 KiB
C

// inputHost.c - Host-cc harness for the typed-character queue
// (jlInputGetChar / jlInputCharPush in src/core/input.c). Exercises
// FIFO ordering, the character filter, drop-newest overflow, reset
// semantics, and the jlInputPoll refill path via a mocked
// jlpInputPoll.
//
// Built by scripts/check-input.sh with -DJOEYLIB_PLATFORM_BLANK so
// port.h routes jlpInputPoll/jlpJoystickReset to the mocks below.
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "joey/input.h"
#include "inputInternal.h"
#include "port.h"
// Characters the mocked jlpInputPoll delivers on its next call
// (one-shot; cleared after delivery).
static const uint8_t *gMockChars = NULL;
static int gMockCount = 0;
// Prototypes (harness-local, alphabetical).
static bool checkEmpty(const char *name);
static bool popExpect(const char *name, const uint8_t *expect, int count);
static void pushAll(const uint8_t *chars, int count);
static bool runFilter(void);
static bool runInterleave(void);
static bool runOrder(void);
static bool runOverflow(void);
static bool runPollRefill(void);
static bool runReset(void);
void jlpInputPoll(void) {
int i;
for (i = 0; i < gMockCount; i++) {
jlInputCharPush(gMockChars[i]);
}
gMockCount = 0;
}
void jlpJoystickReset(jlJoystickE js) {
(void)js;
}
static bool checkEmpty(const char *name) {
int got;
got = jlInputGetChar();
if (got != -1) {
printf(" %s: expected empty (-1), got %d\n", name, got);
return false;
}
return true;
}
static bool popExpect(const char *name, const uint8_t *expect, int count) {
int got;
int i;
for (i = 0; i < count; i++) {
got = jlInputGetChar();
if (got != (int)expect[i]) {
printf(" %s: index %d: expected 0x%02X, got %d\n",
name, i, expect[i], got);
return false;
}
}
return checkEmpty(name);
}
static void pushAll(const uint8_t *chars, int count) {
int i;
for (i = 0; i < count; i++) {
jlInputCharPush(chars[i]);
}
}
// Control characters other than BS/TAB/CR/ESC, plus DEL and 8-bit
// values, must never surface; the documented four and the printable
// range must pass through unchanged.
static bool runFilter(void) {
static const uint8_t rejected[] = { 0x00, 0x01, 0x07, 0x0A, 0x0C, 0x1F, 0x7F };
static const uint8_t accepted[] = {
JL_CHAR_BACKSPACE, JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE,
0x20, '0', 'Z', 'z', '~', 0x7E
};
bool ok;
jlInputCharReset();
pushAll(rejected, (int)sizeof(rejected));
ok = checkEmpty("filter-rejects");
pushAll(accepted, (int)sizeof(accepted));
ok &= popExpect("filter-accepts", accepted, (int)sizeof(accepted));
printf(" filter: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
// Pops interleaved with pushes keep FIFO order across the wrap point.
static bool runInterleave(void) {
static const uint8_t first[] = { 'a', 'b', 'c' };
static const uint8_t second[] = { 'd', 'e' };
static const uint8_t rest[] = { 'b', 'c', 'd', 'e' };
bool ok;
int got;
jlInputCharReset();
pushAll(first, (int)sizeof(first));
got = jlInputGetChar();
ok = (got == 'a');
if (!ok) {
printf(" interleave: expected 'a', got %d\n", got);
}
pushAll(second, (int)sizeof(second));
ok &= popExpect("interleave", rest, (int)sizeof(rest));
printf(" interleave: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
// The definition-of-done string from the RetroNet handoff must come
// back byte-for-byte.
static bool runOrder(void) {
static const char target[] = "192.168.1.10:6510";
bool ok;
jlInputCharReset();
pushAll((const uint8_t *)target, (int)(sizeof(target) - 1));
ok = popExpect("order", (const uint8_t *)target, (int)(sizeof(target) - 1));
printf(" order: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
// Overflow drops the NEWEST characters: exactly JL_CHAR_QUEUE_SIZE - 1
// survive, and they are the first ones pushed.
static bool runOverflow(void) {
uint8_t burst[JL_CHAR_QUEUE_SIZE + 8];
bool ok;
int i;
jlInputCharReset();
for (i = 0; i < (int)sizeof(burst); i++) {
burst[i] = (uint8_t)('!' + (i % 0x5E));
}
pushAll(burst, (int)sizeof(burst));
ok = popExpect("overflow", burst, JL_CHAR_QUEUE_SIZE - 1);
printf(" overflow: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
// The real app-facing path: jlInputPoll runs the port poll, which
// pushes translated characters; jlInputGetChar then pops them.
static bool runPollRefill(void) {
static const uint8_t typed[] = { 'H', 'i', '!', JL_CHAR_RETURN };
bool ok;
jlInputCharReset();
gMockChars = typed;
gMockCount = (int)sizeof(typed);
jlInputPoll();
ok = popExpect("poll-refill", typed, (int)sizeof(typed));
jlInputPoll();
ok &= checkEmpty("poll-refill-once");
printf(" poll-refill: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
// jlInputCharReset empties the queue (the jlInit path).
static bool runReset(void) {
static const uint8_t some[] = { 'x', 'y', 'z' };
bool ok;
jlInputCharReset();
pushAll(some, (int)sizeof(some));
jlInputCharReset();
ok = checkEmpty("reset");
printf(" reset: %s\n", ok ? "PASS" : "FAIL");
return ok;
}
int main(void) {
bool ok = true;
printf("inputHost: typed-character queue scenarios\n");
ok &= runOrder();
ok &= runFilter();
ok &= runOverflow();
ok &= runInterleave();
ok &= runReset();
ok &= runPollRefill();
if (!ok) {
printf("inputHost: FAIL\n");
return 1;
}
printf("inputHost: PASS\n");
return 0;
}