Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions tests/expected/test_kb_cmd.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
loop test starting
loop test completed
nesting test starting
nesting test completed
char table test starting
char table test completed
word test starting
word test completed
complex word test starting
complex word test completed
test_kb_cmd done
231 changes: 231 additions & 0 deletions tests/src/kb_cmd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#ifndef KB_CMD_H
#define KB_CMD_H

#include "device/keyboard.h"
#include "device/ps2.h"
#include "test_helper.h"

// Returns 0 if executed successfully, non-zero otherwise
typedef int (*CmdFunc)(void);

struct LoopNode {
int idx;
int remaining;
};

// if you have more than 8 nested loops then what the fuck
static struct LoopNode loopStack[8];
static int current = -1;

typedef enum {
// A singular key press
CMD_KEY_PRESS,

// Types the entire provided word
CMD_TYPE_WORD,

// Signifies the start of a command loop
CMD_LOOP_START,

// Signifies the end of the most recent loop command
CMD_LOOP_END,

// Runs a function
CMD_FUNC,

// End of command list
CMD_END
} CMD;

struct KbCmd {
CMD cmd;
union {
// key to press
struct PS2Buf_t kb;

// null terminated string
char *w;

// loop count
int loops;

// function to execute
CmdFunc func;
} data;
};

struct KeyData {
enum KeyCode c;
uint8_t mods;
};

#define keyDataStruct(code, modifiers) \
(struct KeyData) { \
code, modifiers \
}

#define keyAndShifted(unshiftedChar, shiftedChar, key) \
[unshiftedChar] = keyDataStruct(key, 0), [shiftedChar] = keyDataStruct( \
key, KEY_MOD_SHIFT)

// clang-format off
static const struct KeyData charToPressCMD[] = {
// Row 1
keyAndShifted('`', '~', Key_grave),
keyAndShifted('1', '!', Key_1),
keyAndShifted('2', '@', Key_2),
keyAndShifted('3', '#', Key_3),
keyAndShifted('4', '$', Key_4),
keyAndShifted('5', '%', Key_5),
keyAndShifted('6', '^', Key_6),
keyAndShifted('7', '&', Key_7),
keyAndShifted('8', '*', Key_8),
keyAndShifted('9', '(', Key_9),
keyAndShifted('0', ')', Key_0),
keyAndShifted('-', '_', Key_minus),
keyAndShifted('=', '+', Key_equal),

// Row 2
keyAndShifted('q', 'Q', Key_q),
keyAndShifted('w', 'W', Key_w),
keyAndShifted('e', 'E', Key_e),
keyAndShifted('r', 'R', Key_r),
keyAndShifted('t', 'T', Key_t),
keyAndShifted('y', 'Y', Key_y),
keyAndShifted('u', 'U', Key_u),
keyAndShifted('i', 'I', Key_i),
keyAndShifted('o', 'O', Key_o),
keyAndShifted('p', 'P', Key_p),
keyAndShifted('[', '{', Key_openSquare),
keyAndShifted(']', '}', Key_closeSquare),
keyAndShifted('\\', '|', Key_backSlash),

// Row 3
keyAndShifted('a', 'A', Key_a),
keyAndShifted('s', 'S', Key_s),
keyAndShifted('d', 'D', Key_d),
keyAndShifted('f', 'F', Key_f),
keyAndShifted('g', 'G', Key_g),
keyAndShifted('h', 'H', Key_h),
keyAndShifted('j', 'J', Key_j),
keyAndShifted('k', 'K', Key_k),
keyAndShifted('l', 'L', Key_l),
keyAndShifted(';', ':', Key_semicolon),
keyAndShifted('\'', '"', Key_apostrophe),

// Row 4
keyAndShifted('z', 'Z', Key_z),
keyAndShifted('x', 'X', Key_x),
keyAndShifted('c', 'C', Key_c),
keyAndShifted('v', 'V', Key_v),
keyAndShifted('b', 'B', Key_b),
keyAndShifted('n', 'N', Key_n),
keyAndShifted('m', 'M', Key_m),
keyAndShifted(',', '<', Key_comma),
keyAndShifted('.', '>', Key_period),
keyAndShifted('/', '?', Key_slash),

// Row 5
[' '] = keyDataStruct(Key_space, 0)
};
// clang-format on
// clang-format can you please not butcher the readability of the code?

#undef keyPressStruct
#undef keyAndShifted
#undef keyAndCapital

static struct KbCmd keyPressCMD(enum KeyCode code, enum KeyState event,
uint8_t modifiers) {
KeyPress kp = (KeyPress){0, code, event, modifiers};
struct PS2Buf_t b = (struct PS2Buf_t){PS2_KEY_EVENT, {.keyEvent = kp}};
return (struct KbCmd){CMD_KEY_PRESS, {.kb = b}};
}

static struct KbCmd keyPressCMDFromData(struct KeyData data) {
return (keyPressCMD(data.c, KeyPressed, data.mods));
}

static struct KbCmd typeWordCMD(char *word) {
return (struct KbCmd){CMD_TYPE_WORD, {.w = word}};
}

// `loops` must be greater than 0
static struct KbCmd loopStartCMD(int loops) {
return (struct KbCmd){CMD_LOOP_START, {.loops = loops}};
}

static struct KbCmd loopEndCMD() {
return (struct KbCmd){CMD_LOOP_END, {}};
}

static struct KbCmd funcCMD(CmdFunc func) {
return (struct KbCmd){CMD_FUNC, {.func = func}};
}

static struct KbCmd endCMD() {
return (struct KbCmd){CMD_END, {}};
}

// Returns 0 when exiting normally, and anything else when things go wrong
typedef void (*KeyPressHandler)(struct PS2Buf_t);
typedef int (*ExecFunc)(struct KbCmd, int *, KeyPressHandler);

static void baseKeyHandler(struct PS2Buf_t kb) {
vgaEditor(kb);
}

static int baseExec(struct KbCmd cmd, int *idx, KeyPressHandler kp) {
switch (cmd.cmd) {
case CMD_KEY_PRESS:
kp(cmd.data.kb);
break;
case CMD_TYPE_WORD:
for (char *c = cmd.data.w; *c != 0; c++) {
kp(keyPressCMDFromData(charToPressCMD[(int)*c]).data.kb);
}
break;
case CMD_LOOP_START:
if (cmd.data.loops < 1) {
FAIL_M("Loop count must be greater than 0, got %i", cmd.data.loops);
return 1;
}
if (current == -1 || loopStack[current].idx != *idx) {
current++;
loopStack[current] = (struct LoopNode){*idx, cmd.data.loops};
}
loopStack[current].remaining--;
break;
case CMD_LOOP_END:
if (loopStack[current].remaining) {
// subtract 1 because idx is incremented after command completes
// this was a surprisingly annoying issue to track down
*idx = loopStack[current].idx - 1;
} else {
current--;
}
break;
case CMD_FUNC:
return cmd.data.func();
case CMD_END:
break;
default:
FAIL_M("Unexpected command type: %i", cmd.cmd);
return 1;
}
return 0;
}

static int execList(struct KbCmd *cmds, ExecFunc f, KeyPressHandler kp) {
for (int i = 0; cmds[i].cmd != CMD_END; i++) {
if (f(cmds[i], &i, kp)) {
char buff[32];
int len = snprintf(buff, sizeof(buff), "Command %i failed", i);
serialWrite(COM1, (uint8_t *)buff, len);
return 1;
}
}
return 0;
}

#endif
147 changes: 147 additions & 0 deletions tests/src/test_kb_cmd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "kb_cmd.h"
#include "string.h"

#define testStart(name, print) \
int name##Start() { \
char text[] = print " starting\n"; \
serialWrite(COM1, (uint8_t *)(text), sizeof(text) - 1); \
return 0; \
}

#define testEnd(name, print) \
int name##End() { \
char text[] = print " completed\n"; \
serialWrite(COM1, (uint8_t *)(text), sizeof(text) - 1); \
return 0; \
}

char buff[64];
int buffIdx = 0;

int resetBuff() {
memset(buff, 0, sizeof(buff));
buffIdx = 0;
return 0;
}

void testKeyHandler(struct PS2Buf_t buf) {
buff[buffIdx++] = keyPressToASCII(buf.keyEvent);
}

// clang-format off

// Loop prints
testStart(loopTest, "loop test")
testEnd(loopTest, "loop test")

// Nesting prints
testStart(nestingTest, "nesting test")
testEnd(nestingTest, "nesting test")

// Char table prints
testStart(charTableTest, "char table test")
testEnd(charTableTest, "char table test")

// Word prints
testStart(wordTest, "word test")
testEnd(wordTest, "word test")

// Complex word prints
testStart(complexWordTest, "complex word test")
testEnd(complexWordTest, "complex word test")

// Loop testing
int v = 0;

// clang-format on

int vReset() {
v = 0;
return 0;
}

int vInc() {
v++;
return 0;
}

int testSingleLoop() {
ASSERT_M(v == 4, "Expected to loop 4 times, looped %i time(s) instead", v);
return 0;
}

int testNestedLoop() {
ASSERT_M(v == 6, "Expected to loop 6 times, looped %i time(s) instead", v);
return 0;
}

// Char table testing
int capitalShiftTest() {
ASSERT(Key_a == charToPressCMD['a'].c);
ASSERT(0 == charToPressCMD['a'].mods);
ASSERT(Key_a == charToPressCMD['A'].c);
ASSERT(KEY_MOD_SHIFT == charToPressCMD['A'].mods)
return 0;
}

int shiftTest() {
ASSERT(Key_grave == charToPressCMD['`'].c)
ASSERT(0 == charToPressCMD['`'].mods)
ASSERT(Key_grave == charToPressCMD['~'].c)
ASSERT(KEY_MOD_SHIFT == charToPressCMD['~'].mods)
return 0;
}

// Word type testing
int wordTest() {
ASSERT(strncmp("test", buff, 5))
return 0;
}

int complexWordTest() {
ASSERT(strncmp("This is a very Loong word$%@^@\\", buff, 32))
return 0;
}

void test_main() {
struct KbCmd list[] = {
// Single loop test
funcCMD(loopTestStart),
loopStartCMD(4),
funcCMD(vInc),
loopEndCMD(),
funcCMD(testSingleLoop),
funcCMD(loopTestEnd),

funcCMD(vReset),
// Nested loop test
funcCMD(nestingTestStart),
loopStartCMD(2),
loopStartCMD(3),
funcCMD(vInc),
loopEndCMD(),
loopEndCMD(),
funcCMD(testNestedLoop),
funcCMD(nestingTestEnd),

// Char table values test
funcCMD(charTableTestStart),
funcCMD(capitalShiftTest),
funcCMD(shiftTest),
funcCMD(charTableTestEnd),

// Word type test
funcCMD(wordTestStart),
typeWordCMD("test"),
funcCMD(wordTestEnd),

funcCMD(complexWordTestStart),
typeWordCMD("This is a very Loong word$%@^@\\"),
funcCMD(complexWordTestEnd),

endCMD(),
};
execList(list, baseExec, testKeyHandler);
char done[] = "test_kb_cmd done\n";
serialWrite(COM1, (uint8_t *)(done), sizeof(done) - 1);
}