diff --git a/Makefile b/Makefile index e8d4009..b965aaa 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ OBJ_NAMES := src/os/main.o src/os/test.o os_entry.o src/lib/video/VGA_text.o \ src/lib/device/serial.o src/lib/device/ps2.o src/lib/device/keyboard.o \ src/lib/container/ring_buffer.o \ src/lib/stdlib/stdio.o src/lib/stdlib/stdlib.o src/lib/stdlib/string.o \ - src/lib/pit/pit.o + src/lib/pit/pit.o src/lib/device/key_handlers.o .PHONY: clean qemu test diff --git a/src/lib/device/key_handlers.c b/src/lib/device/key_handlers.c new file mode 100644 index 0000000..9c3bfdc --- /dev/null +++ b/src/lib/device/key_handlers.c @@ -0,0 +1,116 @@ +#include "key_handlers.h" + +#include "video/VGA_text.h" + +void specialHandler(KeyPress out) { + if (!(out.modifiers & KEY_MOD_SHIFT)) { + switch (out.code) { + case Key_backspace: + if (getCursor()->highlight_offset) { + highlightDeletePrev(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + deletePrevChar(); + break; + case Key_delete: + if (getCursor()->highlight_offset) { + highlightDeleteCurrent(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + deleteCurrentChar(); + break; + case Key_left: + if (getCursor()->highlight_offset) { + cursorHighlightLeft(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + cursorLeft(); + break; + case Key_down: + if (getCursor()->highlight_offset) { + cursorHighlightDown(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + cursorDown(); + break; + case Key_up: + if (getCursor()->highlight_offset) { + cursorHighlightUp(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + cursorUp(); + break; + case Key_right: + if (getCursor()->highlight_offset) { + cursorHighlightRight(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } else + cursorRight(); + break; + default: + break; + } + } else { + if (((out.code == Key_up || out.code == Key_left) && + cursorIsAtStart()) || + ((out.code == Key_down || out.code == Key_right) && + cursorIsAtEnd())) + return; + if ((out.code == Key_up || out.code == Key_left || + out.code == Key_down || out.code == Key_right) && + !getCursor()->highlight_offset) + highlightCurrentChar(); + switch (out.code) { + case Key_up: + for (int i = 0; i < VGA_WIDTH && !cursorIsAtStart(); i++) { + if (getCursor()->highlight_offset > 0) { + highlightCurrentChar(); + cursorLeft(); + } else { + cursorLeft(); + highlightCurrentChar(); + } + getCursor()->highlight_offset--; + } + break; + case Key_down: + for (int i = 0; i < VGA_WIDTH && !cursorIsAtEnd(); i++) { + if (getCursor()->highlight_offset < 0) { + highlightCurrentChar(); + cursorRight(); + } else { + cursorRight(); + highlightCurrentChar(); + } + getCursor()->highlight_offset++; + } + break; + case Key_left: + if (getCursor()->highlight_offset > 0) { + highlightCurrentChar(); + cursorLeft(); + } else { + cursorLeft(); + highlightCurrentChar(); + } + getCursor()->highlight_offset--; + break; + case Key_right: + if (getCursor()->highlight_offset < 0) { + highlightCurrentChar(); + cursorRight(); + } else { + cursorRight(); + highlightCurrentChar(); + } + getCursor()->highlight_offset++; + break; + default: + break; + } + if ((out.code == Key_up || out.code == Key_left || + out.code == Key_down || out.code == Key_right) && + !getCursor()->highlight_offset) + highlightCurrentChar(); + } +} \ No newline at end of file diff --git a/src/lib/device/key_handlers.h b/src/lib/device/key_handlers.h new file mode 100644 index 0000000..c93ff22 --- /dev/null +++ b/src/lib/device/key_handlers.h @@ -0,0 +1,8 @@ +#ifndef KEY_HANDLERS_H +#define KEY_HANDLERS_H + +#include "keyboard.h" + +void specialHandler(KeyPress out); + +#endif \ No newline at end of file diff --git a/src/lib/device/ps2.c b/src/lib/device/ps2.c index 603b7e4..0a3f763 100644 --- a/src/lib/device/ps2.c +++ b/src/lib/device/ps2.c @@ -3,6 +3,7 @@ #include "../../os/hard/idt.h" #include "../../os/hard/port_io.h" #include "container/ring_buffer.h" +#include "key_handlers.h" #define PS2_BUF_SIZE 64 #define PS2_TIMEOUT 100000 @@ -72,6 +73,29 @@ const struct PS2Device *getPortType(int portnum) { // temporary include for #7 #include "video/VGA_text.h" +void vgaEditor(struct PS2Buf_t out) { + switch (out.keyEvent.code) { + case Key_backspace: + case Key_delete: + case Key_left: + case Key_down: + case Key_up: + case Key_right: + specialHandler(out.keyEvent); + break; + default: + char buf[2] = " "; + buf[0] = keyPressToASCII(out.keyEvent); + if (buf[0] != 0) { + if (getCursor()->highlight_offset) { + highlightDeletePrev(getCursor()->highlight_offset); + getCursor()->highlight_offset = 0; + } + print(buf, white); + } + } +} + void ps2HandlerPort1(isr_registers_t *regs) { uint8_t b = inb(PS2_DATA); @@ -86,9 +110,7 @@ void ps2HandlerPort1(isr_registers_t *regs) { // temporary to satisfy exactly what issue #7 says if (out.keyEvent.code != Key_none && out.keyEvent.event == KeyPressed) { - char buf[2] = " "; - buf[0] = keyPressToASCII(out.keyEvent); - print(buf, white); + vgaEditor(out); } } } diff --git a/src/lib/device/ps2.h b/src/lib/device/ps2.h index fd3ea1a..333088a 100644 --- a/src/lib/device/ps2.h +++ b/src/lib/device/ps2.h @@ -69,6 +69,8 @@ struct PS2Buf_t { int ps2Init(); const struct PS2Device *getPortType(int portnum); +void vgaEditor(struct PS2Buf_t out); + bool ps2Present(void); bool ps2Port1Present(void); bool ps2Port2Present(void); @@ -84,4 +86,4 @@ struct PS2Buf_t popDev1(void); struct PS2Buf_t peekDev2(void); struct PS2Buf_t popDev2(void); -#endif \ No newline at end of file +#endif diff --git a/src/lib/stdlib/stdio.c b/src/lib/stdlib/stdio.c index 16e634b..e85559f 100644 --- a/src/lib/stdlib/stdio.c +++ b/src/lib/stdlib/stdio.c @@ -5,11 +5,12 @@ #define MAX_SNPRINTF_STRING 100 -int snprintf(char *restrict buffer, size_t bufsz, char *format, ...); -int vsnprintf(char *restrict buffer, size_t bufsz, char *format, va_list ap); +int snprintf(char *restrict buffer, size_t bufsz, const char *format, ...); +int vsnprintf(char *restrict buffer, size_t bufsz, const char *format, + va_list ap); // Inspired by chapter 7.3 of The C Programming Language -int snprintf(char *restrict buffer, size_t bufsz, char *format, ...) { +int snprintf(char *restrict buffer, size_t bufsz, const char *format, ...) { va_list ap; int retval; va_start(ap, format); @@ -19,8 +20,9 @@ int snprintf(char *restrict buffer, size_t bufsz, char *format, ...) { return retval; } -int vsnprintf(char *restrict buffer, size_t bufsz, char *format, va_list ap) { - char *p; +int vsnprintf(char *restrict buffer, size_t bufsz, const char *format, + va_list ap) { + const char *p; // valid types unsigned char c; // printf("%c\n", 'c'); @@ -54,6 +56,7 @@ int vsnprintf(char *restrict buffer, size_t bufsz, char *format, va_list ap) { if (len + n + 1 < bufsz) { memcpy(buffer, s, len); buffer += len; + n += len - 1; } else { return n; } @@ -75,19 +78,22 @@ int vsnprintf(char *restrict buffer, size_t bufsz, char *format, va_list ap) { case 'i': i = va_arg(ap, int); int temp = i; - int bufSize = 0; + len = 0; if (i < 0) { - bufSize++; + len++; temp *= -1; } - for (; temp >= 1; temp = temp / 10, bufSize++) + for (; temp >= 1; temp = temp / 10, len++) ; + if (i == 0) + len++; // checks if there is space for the entire string // plus the null-terminating byte - if (bufSize + n + 1 < bufsz) { + if (len + n + 1 < bufsz) { itoa_s(i, buffer, MAX_SNPRINTF_STRING - n); - buffer += bufSize; + buffer += len; + n += len - 1; } else { return n; } diff --git a/src/lib/stdlib/stdio.h b/src/lib/stdlib/stdio.h index 01322fc..8d5085d 100644 --- a/src/lib/stdlib/stdio.h +++ b/src/lib/stdlib/stdio.h @@ -3,6 +3,7 @@ #include #include -int snprintf(char *restrict buffer, size_t bufsz, char *format, ...); +int snprintf(char *restrict buffer, size_t bufsz, const char *format, ...); -int vsnprintf(char *restrict buffer, size_t bufsz, char *format, va_list ap); +int vsnprintf(char *restrict buffer, size_t bufsz, const char *format, + va_list ap); diff --git a/src/lib/video/VGA_text.c b/src/lib/video/VGA_text.c index 23563ec..d925734 100644 --- a/src/lib/video/VGA_text.c +++ b/src/lib/video/VGA_text.c @@ -2,7 +2,11 @@ #include "../../os/hard/port_io.h" -VGA_Char *cursor = VGA_MEMORY; +VGA_Cursor cursor = {VGA_MEMORY, 0}; + +VGA_Cursor *getCursor(void) { + return &cursor; +} VGA_Char getVGAchar(unsigned char chr, VGA_Color foreground, VGA_Color background) { @@ -27,10 +31,22 @@ void writeText(const char *str, int x, int y, VGA_Color color) { } } +inline bool cursorIsAtStart(void) { + return cursor.pos == VGA_MEMORY; +} + +inline bool cursorIsAtEnd(void) { + return cursor.pos == VGA_END - 1; +} + +inline VGA_Color invert(VGA_Color color) { + return 15 - (unsigned)color; +} + void updateCursorPos(void) { // evil (but useful) pointer arithmetic - uint16_t pos = (cursor - VGA_MEMORY); + uint16_t pos = (cursor.pos - VGA_MEMORY); // low (mostly x but some y too) outb(VGA_ADDR_PORT, 0x0F); @@ -43,22 +59,161 @@ void updateCursorPos(void) { // checks and fixes the cursor, may do nothing void adjustCursor(void) { - if (cursor < VGA_MEMORY) { - cursor = VGA_MEMORY; - } else if (cursor >= VGA_END) { + if (cursor.pos < VGA_MEMORY) { + cursor.pos = VGA_MEMORY; + } else if (cursor.pos >= VGA_END) { scroll(); // set cursor to start of last line - cursor = VGA_MEMORY + VGA_SIZE - VGA_WIDTH; + cursor.pos = VGA_MEMORY + VGA_SIZE - VGA_WIDTH; } updateCursorPos(); } +void highlightCurrentChar(void) { + *cursor.pos = getVGAchar(cursor.pos->chr, invert(cursor.pos->color & 0xf), + invert((cursor.pos->color >> 4) & 0xf)); +} + +#define CLEAR_CHAR(ptr) (getVGAchar(' ', white, (ptr)->color >> 4)) + +#define SIGNUM(x) (((x) > 0) - ((x) < 0)) + +void highlightDeletePrev(int offset) { + if (offset < 0) + cursor.pos -= offset; + int abs_offset = SIGNUM(offset) * offset; + for (int i = abs_offset; i >= 0; i--) { + highlightCurrentChar(); + *cursor.pos = CLEAR_CHAR(cursor.pos); + cursor.pos--; + } + cursor.pos++; + adjustCursor(); +} + +void deletePrevChar(void) { + if (!cursorIsAtStart()) { + // preserve background + VGA_Char clearChar = CLEAR_CHAR(cursor.pos - 1); + *--cursor.pos = clearChar; + } + adjustCursor(); +} + +void highlightDeleteCurrent(int offset) { + if (offset > 0) + cursor.pos -= offset; + int abs_offset = SIGNUM(offset) * offset; + for (int i = abs_offset; i >= 0; i--) { + highlightCurrentChar(); + *cursor.pos = CLEAR_CHAR(cursor.pos); + cursor.pos++; + } + cursor.pos--; + adjustCursor(); +} + +void deleteCurrentChar(void) { + if (!cursorIsAtEnd()) { + // preserve background + VGA_Char clearChar = CLEAR_CHAR(cursor.pos); + *cursor.pos++ = clearChar; + } + adjustCursor(); +} + +void cursorHighlightDown(int offset) { + cursor.pos -= offset; + int sign = SIGNUM(offset); + int abs_offset = sign * offset; + for (int i = abs_offset; i >= 0; i--) { + *cursor.pos = + getVGAchar(cursor.pos->chr, invert(cursor.pos->color & 0xf), + invert((cursor.pos->color >> 4) & 0xf)); + cursor.pos += sign; + } + cursor.pos -= sign; + cursorDown(); + adjustCursor(); +} + +void cursorDown(void) { + if (cursor.pos < VGA_END - VGA_WIDTH) + cursor.pos += VGA_WIDTH; + else + cursor.pos = VGA_END - 1; + adjustCursor(); +} + +void cursorHighlightUp(int offset) { + cursor.pos -= offset; + int sign = SIGNUM(offset); + int abs_offset = sign * offset; + for (int i = abs_offset; i >= 0; i--) { + *cursor.pos = + getVGAchar(cursor.pos->chr, invert(cursor.pos->color & 0xf), + invert((cursor.pos->color >> 4) & 0xf)); + cursor.pos += sign; + } + cursor.pos -= sign; + cursorUp(); + adjustCursor(); +} + +void cursorUp(void) { + if (cursor.pos > VGA_MEMORY + VGA_WIDTH) + cursor.pos -= VGA_WIDTH; + else + cursor.pos = VGA_MEMORY; + adjustCursor(); +} + +void cursorHighlightLeft(int offset) { + if (offset < 0) + cursor.pos -= offset; + int abs_offset = SIGNUM(offset) * offset; + for (int i = abs_offset; i >= 0; i--) { + *cursor.pos = + getVGAchar(cursor.pos->chr, invert(cursor.pos->color & 0xf), + invert((cursor.pos->color >> 4) & 0xf)); + cursor.pos--; + } + cursor.pos++; + adjustCursor(); +} + +void cursorLeft(void) { + if (!cursorIsAtStart()) + cursor.pos--; + adjustCursor(); +} + +void cursorHighlightRight(int offset) { + if (offset > 0) + cursor.pos -= offset; + int abs_offset = SIGNUM(offset) * offset; + for (int i = abs_offset; i >= 0; i--) { + *cursor.pos = + getVGAchar(cursor.pos->chr, invert(cursor.pos->color & 0xf), + invert((cursor.pos->color >> 4) & 0xf)); + cursor.pos++; + } + cursor.pos--; + adjustCursor(); +} + +void cursorRight(void) { + if (!cursorIsAtEnd()) + cursor.pos++; + adjustCursor(); +} + void print(const char *str, VGA_Color color) { while (*str != 0) { // preserve background - VGA_Char chr = {*str++, color ^ (cursor->color & 0xf0)}; - *cursor++ = chr; + VGA_Char chr = {*str++, color ^ (cursor.pos->color & 0xf0)}; + *cursor.pos++ = chr; // prevent writing out of bounds! adjustCursor(); @@ -69,16 +224,16 @@ void println(const char *str, VGA_Color color) { print(str, color); // update cursor position to next line - cursor += VGA_WIDTH; - int remain = (int)(cursor - VGA_MEMORY) % (VGA_WIDTH); - cursor -= remain; + cursor.pos += VGA_WIDTH; + int remain = (int)(cursor.pos - VGA_MEMORY) % (VGA_WIDTH); + cursor.pos -= remain; adjustCursor(); // bounds check } void scroll() { - cursor -= VGA_WIDTH; // move cursor up - adjustCursor(); // bounds check + cursor.pos -= VGA_WIDTH; // move cursor up + adjustCursor(); // bounds check VGA_Char *current = VGA_MEMORY; VGA_Char *next = VGA_MEMORY + VGA_WIDTH; @@ -105,6 +260,6 @@ void clearScreenC(VGA_Char character) { for (unsigned short i = 0; i < VGA_SIZE; ++i) { VGA_MEMORY[i] = character; } - cursor = VGA_MEMORY; // reset cursor + cursor.pos = VGA_MEMORY; // reset cursor adjustCursor(); } \ No newline at end of file diff --git a/src/lib/video/VGA_text.h b/src/lib/video/VGA_text.h index 5f5484a..595a363 100644 --- a/src/lib/video/VGA_text.h +++ b/src/lib/video/VGA_text.h @@ -1,6 +1,8 @@ #ifndef VGA_TEXT_H #define VGA_TEXT_H +#include + // note VGA's width is equal to it's stride in text mode #define VGA_MEMORY ((VGA_Char *)(0xB8000)) @@ -41,6 +43,13 @@ typedef struct { unsigned char color; } VGA_Char; +typedef struct { + VGA_Char *pos; + int highlight_offset; +} VGA_Cursor; + +VGA_Cursor *getCursor(void); + // returns a VGA_Char with the supplied attributes VGA_Char getVGAchar(unsigned char chr, VGA_Color foreground, VGA_Color background); @@ -48,8 +57,36 @@ VGA_Char getVGAchar(unsigned char chr, VGA_Color foreground, // prints text at location, will NOT wrap void writeText(const char *str, int x, int y, VGA_Color color); +bool cursorIsAtStart(void); +bool cursorIsAtEnd(void); +VGA_Color invert(VGA_Color color); + +void highlightCurrentChar(void); + // affect cursor +// deletion + +void highlightDeletePrev(int offset); +void deletePrevChar(void); + +void highlightDeleteCurrent(int offset); +void deleteCurrentChar(void); + +// movement + +void cursorHighlightDown(int offset); +void cursorDown(void); + +void cursorHighlightUp(int offset); +void cursorUp(void); + +void cursorHighlightLeft(int offset); +void cursorLeft(void); + +void cursorHighlightRight(int offset); +void cursorRight(void); + // prints with wrapping, println does the same but adds a new line. void print(const char *str, VGA_Color color); void println(const char *str, VGA_Color color); diff --git a/tests/Makefile b/tests/Makefile index b881c4d..2395e9b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -4,9 +4,16 @@ OBJ_NAMES := $(C_FILES:%.c=%.o) MOS_ELF := ./../mOS.elf +VENV = .venv +VENV_PY = $(VENV)/bin/python + .PHONY: clean test -all: $(MOS_ELF) $(BIN_NAMES) +all: $(MOS_ELF) $(BIN_NAMES) venv_setup + +venv_setup: + python3 -m venv $(VENV) + $(VENV_PY) -m pip install -r requirements.txt %.bin: %.o test_entry.o $(LD) -o $@.elf $(LFLAGS) -T link.ld $^ --just-symbols=$(MOS_ELF) @@ -14,7 +21,7 @@ all: $(MOS_ELF) $(BIN_NAMES) rm $@.elf %.o: %.c - $(CC) -c $^ -o $@ $(CFLAGS) -I./../src/lib/ -I./../src/lib/stdlib/ + $(CC) -c $^ -o $@ $(CFLAGS) -I./src -I./../src/lib/ -I./../src/lib/stdlib/ test_entry.o: test_entry.asm nasm $^ -f elf32 -o $@ @@ -24,9 +31,9 @@ $(MOS_ELF): test: all ifdef TESTS - python3 test.py $(TESTS) + $(VENV_PY) test.py $(TESTS) else - python3 test.py + $(VENV_PY) test.py endif clean: diff --git a/tests/README.md b/tests/README.md index 4e70007..4a0915b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -36,3 +36,4 @@ The `.expect` file consists of all the output the `.c` file will produce when ev Testing requires the same dependencies as normal operation along with the following: * Python 3.8 + * Python venv diff --git a/tests/expected/lib/stdlib/test_keyboard.expect b/tests/expected/lib/stdlib/test_keyboard.expect new file mode 100644 index 0000000..f68e0f9 --- /dev/null +++ b/tests/expected/lib/stdlib/test_keyboard.expect @@ -0,0 +1 @@ +test_keyboard done diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..9cc8ab2 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,2 @@ +termcolor==2.5.0 +colorama==0.4.6 \ No newline at end of file diff --git a/tests/src/lib/container/ring_buffer.c b/tests/src/lib/container/ring_buffer.c index 2f1d156..c67dff8 100644 --- a/tests/src/lib/container/ring_buffer.c +++ b/tests/src/lib/container/ring_buffer.c @@ -1,7 +1,7 @@ #include "container/ring_buffer.h" -#include "../../test_helper.h" #include "device/serial.h" +#include "test_helper.h" #define RB_SIZE 32 typedef ring_buffer(RB_SIZE) ring_buffer_byte_t; diff --git a/tests/src/lib/stdlib/test_keyboard.c b/tests/src/lib/stdlib/test_keyboard.c new file mode 100644 index 0000000..c3dfa99 --- /dev/null +++ b/tests/src/lib/stdlib/test_keyboard.c @@ -0,0 +1,183 @@ +#include "device/keyboard.h" +#include "device/ps2.h" +#include "stdio.h" +#include "string.h" +#include "test_helper.h" +#include "video/VGA_text.h" + +#include + +// in case changes are made to the width of the screen text +// we still have a portable way of selecting the specific positions +#define END_COL (VGA_WIDTH - 1) +#define END_LINE (VGA_HEIGHT - 1) + +// enum representing the kinds of commands available +enum CMD { keyPress, checkOffset, checkPosition, setPosition, end }; + +struct TestCMD { + enum CMD cmd; + union { + struct PS2Buf_t kb; + int offset; + } data; +}; + +// creates a simulated key press command +struct TestCMD kbCMD(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 TestCMD){keyPress, {.kb = b}}; +} + +// creates an offset check command +struct TestCMD chkOffCMD(int expected) { + return (struct TestCMD){checkOffset, {.offset = expected}}; +} + +// creates a position check command +// expected position is relative to the beginning of vga memory +struct TestCMD chkPosCMD(unsigned line, unsigned col) { + return (struct TestCMD){checkPosition, + {.offset = (line * VGA_WIDTH) + col}}; +} + +// creates a set position command +// position is relative to the beginning of VGA memory +struct TestCMD setPosCMD(unsigned line, unsigned col) { + return (struct TestCMD){setPosition, {.offset = (line * VGA_WIDTH) + col}}; +} + +// creates an end command +struct TestCMD endCMD() { + return (struct TestCMD){end, {}}; +} + +// execute the supplied command based on its `cmd` field +int execCMD(struct TestCMD cmd, int idx) { + switch (cmd.cmd) { + case checkOffset: // asserts that the current highlight offset matches the + // supplied offset + ASSERT_M(getCursor()->highlight_offset == cmd.data.offset, + "CMD %i: Highlight difference | Expected: %i, Actual: %i", idx, + cmd.data.offset, getCursor()->highlight_offset); + break; + case checkPosition: // asserts that the relative position from the start of + // vga memory matches the supplied position + ASSERT_M(getCursor()->pos - VGA_MEMORY == cmd.data.offset, + "CMD %i: Position difference | Expected: %i, Actual: %i", idx, + cmd.data.offset, getCursor()->pos - VGA_MEMORY); + break; + case setPosition: // sets the cursor position, checks that the position is + // in bounds + if (cmd.data.offset < 0 || cmd.data.offset >= VGA_SIZE) { + FAIL_M( + "CMD %i: Offset of %i is out of bounds for VGA of length %i.", + idx, cmd.data.offset, VGA_SIZE); + return 1; + } + getCursor()->pos = VGA_MEMORY + cmd.data.offset; + break; + case keyPress: // simulates a keypress + vgaEditor(cmd.data.kb); + break; + case end: // end command has no effect on state, it is only used as a + // terminator + break; + default: + FAIL_M("CMD %i: Unexpected command type: %i", idx, cmd.cmd); + return 1; + } + return 0; +} + +void test_main() { + struct TestCMD b[] = { + // set cursor position to a known position to prevent breaking if the + // boot text changes + setPosCMD(2, 0), + // make sure that the position is set properly + chkPosCMD(2, 0), + + kbCMD(Key_a, KeyPressed, 0), + chkOffCMD(0), + chkPosCMD(2, 1), + + kbCMD(Key_left, KeyPressed, KEY_MOD_SHIFT), + chkOffCMD(-1), + chkPosCMD(2, 0), + + // make sure keys that do not produce a character do not affect the + // highlight offset + kbCMD(Key_Lctrl, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_Rctrl, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_LShift, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_RShift, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_home, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_end, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_pgUp, KeyPressed, 0), + chkOffCMD(-1), + kbCMD(Key_pgDown, KeyPressed, 0), + chkOffCMD(-1), + + // check that highlighting works properly + kbCMD(Key_up, KeyPressed, KEY_MOD_SHIFT), + chkOffCMD(-VGA_WIDTH - 1), + chkPosCMD(1, 0), + kbCMD(Key_up, KeyPressed, KEY_MOD_SHIFT), + chkOffCMD(-(2 * VGA_WIDTH) - 1), + chkPosCMD(0, 0), + + // check that key press removes highlight and sets cursor position to + // after typed character + kbCMD(Key_b, KeyPressed, 0), + chkOffCMD(0), + chkPosCMD(0, 1), + + // check that vertical keypresses move cursor properly while + // highlighting + kbCMD(Key_down, KeyPressed, KEY_MOD_SHIFT), + chkOffCMD(VGA_WIDTH), + chkPosCMD(1, 1), + kbCMD(Key_up, KeyPressed, 0), + chkOffCMD(0), + chkPosCMD(0, 1), + + // check wrapping + setPosCMD(0, END_COL), + kbCMD(Key_right, KeyPressed, 0), + chkPosCMD(1, 0), + kbCMD(Key_left, KeyPressed, 0), + chkPosCMD(0, END_COL), + + // check bottom bounds check + setPosCMD(END_LINE, 0), + kbCMD(Key_down, KeyPressed, 0), + chkPosCMD(END_LINE, END_COL), + kbCMD(Key_up, KeyPressed, KEY_MOD_SHIFT), + kbCMD(Key_down, KeyPressed, 0), + chkPosCMD(END_LINE, END_COL), + + endCMD(), + }; + for (int i = 0; b[i].cmd != end && i < sizeof(b) / sizeof(struct TestCMD); + i++) { + if (execCMD(b[i], i)) { + char err[] = "test_keyboard errored at command "; + serialWrite(COM1, (uint8_t *)err, sizeof(err) - 1); + char buf[4]; + int len = snprintf(buf, 4, "%i", i); + serialWrite(COM1, (uint8_t *)buf, len); + return; + } + } + char done[] = "test_keyboard done\n"; + serialWrite(COM1, (uint8_t *)(done), sizeof(done) - 1); +} diff --git a/tests/src/lib/stdlib/test_sort.c b/tests/src/lib/stdlib/test_sort.c index 9e5c3d0..dbc2b46 100644 --- a/tests/src/lib/stdlib/test_sort.c +++ b/tests/src/lib/stdlib/test_sort.c @@ -1,6 +1,6 @@ -#include "../../test_helper.h" #include "stdlib.h" #include "string.h" +#include "test_helper.h" #include #include diff --git a/tests/src/lib/stdlib/test_stdio.c b/tests/src/lib/stdlib/test_stdio.c index dada4b3..96cba99 100644 --- a/tests/src/lib/stdlib/test_stdio.c +++ b/tests/src/lib/stdlib/test_stdio.c @@ -1,6 +1,6 @@ -#include "../../test_helper.h" #include "stdio.h" #include "string.h" +#include "test_helper.h" #include @@ -9,7 +9,7 @@ // "expected" is the first argument because variable args need to be the last // argument void test_vsnprintf(char *expected, char *buffer, size_t bufsz, char *fmt, - int argn, ...) { + int num, int argn, ...) { int n; memset(buffer, '#', BUFSZ); @@ -21,6 +21,7 @@ void test_vsnprintf(char *expected, char *buffer, size_t bufsz, char *fmt, // serialWrite(COM1, (uint8_t*)(buffer), BUFSZ); ASSERT(n <= bufsz); + ASSERT(n == num); ASSERT(strncmp(buffer, expected, 2) == 0); ASSERT(buffer[bufsz] == '\0'); } @@ -29,15 +30,16 @@ void test_main() { char buffer[BUFSZ + 1]; buffer[BUFSZ] = '\0'; - test_vsnprintf("Hello!\n", buffer, BUFSZ, "%s\n", 1, "Hello!"); - test_vsnprintf("i=5\n", buffer, BUFSZ, "i=%i\n", 1, 5); - test_vsnprintf("i=42\n", buffer, BUFSZ, "i=%i\n", 1, 42); - test_vsnprintf("i=-42\n", buffer, BUFSZ, "i=%i\n", 1, -42); + test_vsnprintf("Hello!\n", buffer, BUFSZ, "%s\n", 7, 1, "Hello!"); + test_vsnprintf("i=5\n", buffer, BUFSZ, "i=%i\n", 4, 1, 5); + test_vsnprintf("i=42\n", buffer, BUFSZ, "i=%i\n", 5, 1, 42); + test_vsnprintf("i=-42\n", buffer, BUFSZ, "i=%i\n", 6, 1, -42); test_vsnprintf("just a regular string\n", buffer, BUFSZ, - "just a regular string\n", 0); - test_vsnprintf("i=-42%\n", buffer, BUFSZ, "i=%i%%\n", 1, 42); - test_vsnprintf("1, 2, 3, 4, 5\n", buffer, BUFSZ, "%i, %i, %i, %i, %i\n", 5, - 1, 2, 3, 4, 5); + "just a regular string\n", 22, 0); + test_vsnprintf("i=-42%\n", buffer, BUFSZ, "i=%i%%\n", 6, 1, 42); + test_vsnprintf("1, 2, 3, 4, 5\n", buffer, BUFSZ, "%i, %i, %i, %i, %i\n", 14, + 5, 1, 2, 3, 4, 5); + test_vsnprintf("i=0\n", buffer, BUFSZ, "i=%i\n", 4, 1, 0); char done[] = "test_stdio done\n"; serialWrite(COM1, (uint8_t *)(done), sizeof(done) - 1); diff --git a/tests/src/lib/stdlib/test_stdlib.c b/tests/src/lib/stdlib/test_stdlib.c index ef8ff7c..abe3a71 100644 --- a/tests/src/lib/stdlib/test_stdlib.c +++ b/tests/src/lib/stdlib/test_stdlib.c @@ -1,6 +1,6 @@ -#include "../../test_helper.h" #include "stdlib.h" #include "string.h" +#include "test_helper.h" #define BUFSZ 100 diff --git a/tests/src/lib/stdlib/test_string.c b/tests/src/lib/stdlib/test_string.c index 013859d..e856e3c 100644 --- a/tests/src/lib/stdlib/test_string.c +++ b/tests/src/lib/stdlib/test_string.c @@ -1,5 +1,5 @@ -#include "../../test_helper.h" #include "string.h" +#include "test_helper.h" #define BUFSZ \ 100 // the size of string compare buffers (to detect buffer overflow) diff --git a/tests/src/test_helper.h b/tests/src/test_helper.h index 24fe1bd..efd9bdb 100644 --- a/tests/src/test_helper.h +++ b/tests/src/test_helper.h @@ -2,6 +2,33 @@ #define TEST_HELPER_H #include "device/serial.h" +#include "stdio.h" +#include "string.h" + +#include + +void _print_assert(const char *fmt, const char *msg, ...) { + va_list args; + va_start(args, msg); + + while (!serialWriteReady(COM1)) + ; + serialWrite(COM1, (uint8_t *)msg, strnlen_s(msg, 256)); + + // avoid an empty line + // must be index 1 since a newline is appended by the macro + if (fmt[1] != 0) { + // buffer to write the message to + char buff[256]; + int len = vsnprintf(buff, sizeof(buff), fmt, args); + + while (!serialWriteReady(COM1)) + ; + serialWrite(COM1, (uint8_t *)buff, len); + } + + va_end(args); +} #define AS_STR(x) #x @@ -9,15 +36,17 @@ #define _STR_LINE(line) AS_STR(line) #define STR_LINE _STR_LINE(__LINE__) -#define ASSERT_M(condition, msg) \ +#define ASSERT_M(condition, fmt, ...) \ if (!(condition)) { \ - while (!serialWriteReady(COM1)) \ - ; \ - char errmsg[] = \ - msg AS_STR(condition) " failed in " __FILE__ ":" STR_LINE "\n"; \ - serialWrite(COM1, (uint8_t *)(errmsg), sizeof(errmsg) - 1); \ + _print_assert(fmt "\n", "Line " STR_LINE ": Assertion '" #condition \ + "' failed.\n" __VA_OPT__(, ) __VA_ARGS__); \ } +#define FAIL_M(fmt, ...) \ + _print_assert(fmt "\n", \ + "Line " STR_LINE \ + ": Fail assertion reached.\n" __VA_OPT__(, ) __VA_ARGS__) + #define ASSERT(condition) ASSERT_M(condition, "") #endif \ No newline at end of file diff --git a/tests/test.py b/tests/test.py index c7d6769..d19a702 100644 --- a/tests/test.py +++ b/tests/test.py @@ -7,6 +7,10 @@ import shutil import sys +from termcolor import cprint +from colorama import just_fix_windows_console + +just_fix_windows_console() BASE_PORT = 1111 MAX_PORT = 1234 @@ -112,6 +116,7 @@ def beginQemu(self): self._qemu = subprocess.Popen(command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) time.sleep(1) #give qemu some time to open self._ready = True + cprint(self.bin_path.name + " has started.", "green") def beginTest(self): self.test = Thread(target=test, args=[self]) @@ -131,15 +136,18 @@ def endQemu(self): if (self._qemu != None and self._qemu.poll() == None): try: # send the exit command to QEMU - self._qemu.communicate(b"\x01x", 5) + self._qemu.terminate() self._qemu.wait(10) except: # force qemu to quit since it refuses to exit normally + cprint(self.bin_path.name + " was forcefully closed.", "red", attrs=["bold"]) self._qemu.kill() + self._qemu.wait(5) self._qemu = None self._ready = False + cprint(self.bin_path.name + " has exited.", "cyan") os.remove(self._qemu_file) def end(self): @@ -269,7 +277,7 @@ def test(instance: TestInstance): return test_end_stub(instance, passed) except socket.timeout: - print("test timed out") + cprint(instance.bin_path.name + " | test timed out", "red") except socket.error as e: @@ -288,8 +296,12 @@ def test(instance: TestInstance): def create_instance(bin_path, expected_path): - while (active_instances > MAX_INSTANCES): - time.sleep(0.5) + while (True): + with instance_mutex: + if(active_instances >= MAX_INSTANCES): + time.sleep(0.5) + else: + break instance = TestInstance(get_port(), bin_path, expected_path) instance.start() @@ -348,7 +360,7 @@ def do_tests(): for instance in instances: instance.end() - print("All tests completed in " + str(time.time() - start) + "seconds") + print("All tests completed in " + str(time.time() - start) + " seconds") total_fail = 0 total_pass = 0 @@ -356,10 +368,10 @@ def do_tests(): result = instance.result if (result): total_pass += 1 - print(instance.bin_path.name + " PASSED") + cprint(instance.bin_path.name + " PASSED", "light_green") else: total_fail += 1 - print(instance.bin_path.name + " FAILED") + cprint(instance.bin_path.name + " FAILED", "red", attrs=["bold"]) print("Summary: PASSED-{}, FAILED-{}, TOTAL-{}" .format(total_pass, total_fail, len(instances)))