From 6372eaddca0196876de3ed41a27ffb1613f0f184 Mon Sep 17 00:00:00 2001 From: "R. Eric Wheeler" Date: Wed, 24 Feb 2021 20:46:16 -0800 Subject: [PATCH] Now with tests --- CMakeLists.txt | 11 +- example.c | 6 +- getch.c | 71 ++---- tests/ctest.h | 590 +++++++++++++++++++++++++++++++++++++++++++++ tests/getchTests.c | 147 +++++++++++ tests/test.c | 13 + 6 files changed, 788 insertions(+), 50 deletions(-) create mode 100644 tests/ctest.h create mode 100644 tests/getchTests.c create mode 100644 tests/test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index df56bc0..d545791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,17 @@ cmake_minimum_required(VERSION 3.16) project(getch C) set(CMAKE_C_STANDARD 11) +set(CMAKE_COLOR_MAKEFILE ON) add_library(getch SHARED getch.c getch.h) add_executable(example example.c ) +add_executable(getchTest tests/test.c tests/getchTests.c) +add_test(NAME getchTests + COMMAND getchTest + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(getchTest -L./build getch) +target_link_libraries(example -L./build getch) +if (ENABLE_TESTS EQUAL 1) + enable_testing() +endif () -target_link_libraries(example -L./build getch) \ No newline at end of file diff --git a/example.c b/example.c index 6de058d..b10bd02 100644 --- a/example.c +++ b/example.c @@ -4,13 +4,11 @@ int main() { int key = _getch(); - - printf("Key %d\n", key); + + printf("Key %d\n", key); if (key == 0 || key == 224) { key = _getch(); printf("Special Key : %d\n", key); } - - return 0; } \ No newline at end of file diff --git a/getch.c b/getch.c index f4e3f24..a8379fe 100644 --- a/getch.c +++ b/getch.c @@ -21,27 +21,24 @@ static struct termios oldTermAttributes; -static void setRawMode(void); -static void setNormalMode(void); - -inline static int discardRead(unsigned int length) +static int discardRead(unsigned int length) { char buffer[length]; - ssize_t bytes_read; + ssize_t bytesRead; int flags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK); - if( (bytes_read = fread(buffer, sizeof(char), length, stdin)) == -1) { + if( (bytesRead = fread(buffer, sizeof(char), length, stdin)) == -1) { perror("discardRead"); } fcntl(STDIN_FILENO, F_SETFL, flags); - return (int)bytes_read; + return (int)bytesRead; } -int getEventDevice(const char * device) +static int getEventDevice(const char * device) { glob_t search; @@ -77,28 +74,33 @@ int getEventDevice(const char * device) return -1; } -unsigned short getScanCode() +static unsigned short getScanCode() { - struct input_event inputEvent[5]; - int fd; + struct input_event inputEvent[3]; + int eventDevice; const char device[FILENAME_MAX]; - if(-1 == getEventDevice(device)) { + if(getEventDevice(device) == -1) { + perror("getEventDevice"); return KEY_RESERVED; } - if( (fd = open(device, O_RDONLY)) == -1 ) { + if( ( eventDevice = open(device, O_RDONLY)) == -1 ) { + perror("open"); return KEY_RESERVED; }; - if( read(fd, &inputEvent, sizeof (inputEvent)) == -1 ) { + + + if( read(eventDevice, &inputEvent, sizeof(inputEvent)) == -1) { + close(eventDevice); return KEY_RESERVED; } - close(fd); + close(eventDevice); - for(int i = 0; i<=5; i++) { - if(inputEvent[i].type == EV_KEY) { + for(int i = 0;i<3;i++) { + if(inputEvent[i].type == EV_KEY && inputEvent[i].code != KEY_ENTER) { return inputEvent[i].code; } } @@ -106,37 +108,16 @@ unsigned short getScanCode() return KEY_RESERVED; } -#pragma clang diagnostic push -#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" -inline static char *reverseString(char *str) -{ - char *p1, *p2; - - if (! str || ! *str) - return str; - for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) - { - *p1 ^= *p2; - *p2 ^= *p1; - *p1 ^= *p2; - } - return str; -} -#pragma clang diagnostic pop - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "bugprone-reserved-identifier" inline static void pushStdin(int c) { ungetc(c, stdin); } -#pragma clang diagnostic pop -inline static void setNormalMode(void) +static void setNormalMode(void) { tcsetattr(STDIN_FILENO, TCSANOW, &oldTermAttributes); } -inline static void setRawMode(void) +static void setRawMode(void) { tcgetattr(STDIN_FILENO, &oldTermAttributes); @@ -147,7 +128,7 @@ inline static void setRawMode(void) } -unsigned short resolveScanCode(unsigned short key) +static unsigned short resolveScanCode(unsigned short key) { switch(key) { case KEY_DOWN: return KEY_KP2; @@ -164,16 +145,15 @@ unsigned short resolveScanCode(unsigned short key) }; } -int readKey(void) { +static int readKey(void) { - unsigned short scanCode; int key = getchar(); if (key == 27) { - scanCode = getScanCode(); + unsigned short scanCode = getScanCode(); - if (scanCode == KEY_ESC) { + if (scanCode == KEY_ESC || scanCode == KEY_RESERVED) { return 27; } @@ -226,6 +206,7 @@ int readKey(void) { } pushStdin(scanCode); + return returnResult; } diff --git a/tests/ctest.h b/tests/ctest.h new file mode 100644 index 0000000..b1b52fa --- /dev/null +++ b/tests/ctest.h @@ -0,0 +1,590 @@ +/* Copyright 2011-2021 Bas van den Berg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CTEST_H +#define CTEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __GNUC__ +#define CTEST_IMPL_FORMAT_PRINTF(a, b) __attribute__ ((format(printf, a, b))) +#else +#define CTEST_IMPL_FORMAT_PRINTF(a, b) +#endif + +#include /* intmax_t, uintmax_t, PRI* */ +#include /* size_t */ + +typedef void (*ctest_nullary_run_func)(void); +typedef void (*ctest_unary_run_func)(void*); +typedef void (*ctest_setup_func)(void*); +typedef void (*ctest_teardown_func)(void*); + +union ctest_run_func_union { + ctest_nullary_run_func nullary; + ctest_unary_run_func unary; +}; + +#define CTEST_IMPL_PRAGMA(x) _Pragma (#x) + +#if defined(__GNUC__) +#if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +/* the GCC argument will work for both gcc and clang */ +#define CTEST_IMPL_DIAG_PUSH_IGNORED(w) \ + CTEST_IMPL_PRAGMA(GCC diagnostic push) \ + CTEST_IMPL_PRAGMA(GCC diagnostic ignored "-W" #w) +#define CTEST_IMPL_DIAG_POP() \ + CTEST_IMPL_PRAGMA(GCC diagnostic pop) +#else +/* the push/pop functionality wasn't in gcc until 4.6, fallback to "ignored" */ +#define CTEST_IMPL_DIAG_PUSH_IGNORED(w) \ + CTEST_IMPL_PRAGMA(GCC diagnostic ignored "-W" #w) +#define CTEST_IMPL_DIAG_POP() +#endif +#else +/* leave them out entirely for non-GNUC compilers */ +#define CTEST_IMPL_DIAG_PUSH_IGNORED(w) +#define CTEST_IMPL_DIAG_POP() +#endif + +struct ctest { + const char* ssname; // suite name + const char* ttname; // test name + union ctest_run_func_union run; + + void* data; + ctest_setup_func* setup; + ctest_teardown_func* teardown; + + int skip; + + unsigned int magic; +}; + +#define CTEST_IMPL_NAME(name) ctest_##name +#define CTEST_IMPL_FNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_run) +#define CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname) +#define CTEST_IMPL_DATA_SNAME(sname) CTEST_IMPL_NAME(sname##_data) +#define CTEST_IMPL_DATA_TNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_data) +#define CTEST_IMPL_SETUP_FNAME(sname) CTEST_IMPL_NAME(sname##_setup) +#define CTEST_IMPL_SETUP_FPNAME(sname) CTEST_IMPL_NAME(sname##_setup_ptr) +#define CTEST_IMPL_SETUP_TPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_setup_ptr) +#define CTEST_IMPL_TEARDOWN_FNAME(sname) CTEST_IMPL_NAME(sname##_teardown) +#define CTEST_IMPL_TEARDOWN_FPNAME(sname) CTEST_IMPL_NAME(sname##_teardown_ptr) +#define CTEST_IMPL_TEARDOWN_TPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_teardown_ptr) + +#define CTEST_IMPL_MAGIC (0xdeadbeef) +#ifdef __APPLE__ +#define CTEST_IMPL_SECTION __attribute__ ((used, section ("__DATA, .ctest"), aligned(1))) +#else +#define CTEST_IMPL_SECTION __attribute__ ((used, section (".ctest"), aligned(1))) +#endif + +#define CTEST_IMPL_STRUCT(sname, tname, tskip, tdata, tsetup, tteardown) \ + static struct ctest CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_SECTION = { \ + #sname, \ + #tname, \ + { (ctest_nullary_run_func) CTEST_IMPL_FNAME(sname, tname) }, \ + tdata, \ + (ctest_setup_func*) tsetup, \ + (ctest_teardown_func*) tteardown, \ + tskip, \ + CTEST_IMPL_MAGIC, \ + } + +#ifdef __cplusplus + +#define CTEST_SETUP(sname) \ + template <> void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#define CTEST_TEARDOWN(sname) \ + template <> void CTEST_IMPL_TEARDOWN_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#define CTEST_DATA(sname) \ + template void CTEST_IMPL_SETUP_FNAME(sname)(T* data) { } \ + template void CTEST_IMPL_TEARDOWN_FNAME(sname)(T* data) { } \ + struct CTEST_IMPL_DATA_SNAME(sname) + +#define CTEST_IMPL_CTEST(sname, tname, tskip) \ + static void CTEST_IMPL_FNAME(sname, tname)(void); \ + CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \ + static void CTEST_IMPL_FNAME(sname, tname)(void) + +#define CTEST_IMPL_CTEST2(sname, tname, tskip) \ + static struct CTEST_IMPL_DATA_SNAME(sname) CTEST_IMPL_DATA_TNAME(sname, tname); \ + static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \ + static void (*CTEST_IMPL_SETUP_TPNAME(sname, tname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_SETUP_FNAME(sname); \ + static void (*CTEST_IMPL_TEARDOWN_TPNAME(sname, tname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_TEARDOWN_FNAME(sname); \ + CTEST_IMPL_STRUCT(sname, tname, tskip, &CTEST_IMPL_DATA_TNAME(sname, tname), &CTEST_IMPL_SETUP_TPNAME(sname, tname), &CTEST_IMPL_TEARDOWN_TPNAME(sname, tname)); \ + static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#else + +#define CTEST_SETUP(sname) \ + static void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \ + static void (*CTEST_IMPL_SETUP_FPNAME(sname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_SETUP_FNAME(sname); \ + static void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#define CTEST_TEARDOWN(sname) \ + static void CTEST_IMPL_TEARDOWN_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \ + static void (*CTEST_IMPL_TEARDOWN_FPNAME(sname))(struct CTEST_IMPL_DATA_SNAME(sname)*) = &CTEST_IMPL_TEARDOWN_FNAME(sname); \ + static void CTEST_IMPL_TEARDOWN_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#define CTEST_DATA(sname) \ + struct CTEST_IMPL_DATA_SNAME(sname); \ + static void (*CTEST_IMPL_SETUP_FPNAME(sname))(struct CTEST_IMPL_DATA_SNAME(sname)*); \ + static void (*CTEST_IMPL_TEARDOWN_FPNAME(sname))(struct CTEST_IMPL_DATA_SNAME(sname)*); \ + struct CTEST_IMPL_DATA_SNAME(sname) + +#define CTEST_IMPL_CTEST(sname, tname, tskip) \ + static void CTEST_IMPL_FNAME(sname, tname)(void); \ + CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \ + static void CTEST_IMPL_FNAME(sname, tname)(void) + +#define CTEST_IMPL_CTEST2(sname, tname, tskip) \ + static struct CTEST_IMPL_DATA_SNAME(sname) CTEST_IMPL_DATA_TNAME(sname, tname); \ + static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \ + CTEST_IMPL_STRUCT(sname, tname, tskip, &CTEST_IMPL_DATA_TNAME(sname, tname), &CTEST_IMPL_SETUP_FPNAME(sname), &CTEST_IMPL_TEARDOWN_FPNAME(sname)); \ + static void CTEST_IMPL_FNAME(sname, tname)(struct CTEST_IMPL_DATA_SNAME(sname)* data) + +#endif + +void CTEST_LOG(const char* fmt, ...) CTEST_IMPL_FORMAT_PRINTF(1, 2); +void CTEST_ERR(const char* fmt, ...) CTEST_IMPL_FORMAT_PRINTF(1, 2); // doesn't return + +#define CTEST(sname, tname) CTEST_IMPL_CTEST(sname, tname, 0) +#define CTEST_SKIP(sname, tname) CTEST_IMPL_CTEST(sname, tname, 1) + +#define CTEST2(sname, tname) CTEST_IMPL_CTEST2(sname, tname, 0) +#define CTEST2_SKIP(sname, tname) CTEST_IMPL_CTEST2(sname, tname, 1) + + +void assert_str(const char* exp, const char* real, const char* caller, int line); +#define ASSERT_STR(exp, real) assert_str(exp, real, __FILE__, __LINE__) + +void assert_wstr(const wchar_t *exp, const wchar_t *real, const char* caller, int line); +#define ASSERT_WSTR(exp, real) assert_wstr(exp, real, __FILE__, __LINE__) + +void assert_data(const unsigned char* exp, size_t expsize, + const unsigned char* real, size_t realsize, + const char* caller, int line); +#define ASSERT_DATA(exp, expsize, real, realsize) \ + assert_data(exp, expsize, real, realsize, __FILE__, __LINE__) + +void assert_equal(intmax_t exp, intmax_t real, const char* caller, int line); +#define ASSERT_EQUAL(exp, real) assert_equal(exp, real, __FILE__, __LINE__) + +void assert_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line); +#define ASSERT_EQUAL_U(exp, real) assert_equal_u(exp, real, __FILE__, __LINE__) + +void assert_not_equal(intmax_t exp, intmax_t real, const char* caller, int line); +#define ASSERT_NOT_EQUAL(exp, real) assert_not_equal(exp, real, __FILE__, __LINE__) + +void assert_not_equal_u(uintmax_t exp, uintmax_t real, const char* caller, int line); +#define ASSERT_NOT_EQUAL_U(exp, real) assert_not_equal_u(exp, real, __FILE__, __LINE__) + +void assert_interval(intmax_t exp1, intmax_t exp2, intmax_t real, const char* caller, int line); +#define ASSERT_INTERVAL(exp1, exp2, real) assert_interval(exp1, exp2, real, __FILE__, __LINE__) + +void assert_null(void* real, const char* caller, int line); +#define ASSERT_NULL(real) assert_null((void*)real, __FILE__, __LINE__) + +void assert_not_null(const void* real, const char* caller, int line); +#define ASSERT_NOT_NULL(real) assert_not_null(real, __FILE__, __LINE__) + +void assert_true(int real, const char* caller, int line); +#define ASSERT_TRUE(real) assert_true(real, __FILE__, __LINE__) + +void assert_false(int real, const char* caller, int line); +#define ASSERT_FALSE(real) assert_false(real, __FILE__, __LINE__) + +void assert_fail(const char* caller, int line); +#define ASSERT_FAIL() assert_fail(__FILE__, __LINE__) + +void assert_dbl_near(double exp, double real, double tol, const char* caller, int line); +#define ASSERT_DBL_NEAR(exp, real) assert_dbl_near(exp, real, 1e-4, __FILE__, __LINE__) +#define ASSERT_DBL_NEAR_TOL(exp, real, tol) assert_dbl_near(exp, real, tol, __FILE__, __LINE__) + +void assert_dbl_far(double exp, double real, double tol, const char* caller, int line); +#define ASSERT_DBL_FAR(exp, real) assert_dbl_far(exp, real, 1e-4, __FILE__, __LINE__) +#define ASSERT_DBL_FAR_TOL(exp, real, tol) assert_dbl_far(exp, real, tol, __FILE__, __LINE__) + +#ifdef CTEST_MAIN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static size_t ctest_errorsize; +static char* ctest_errormsg; +#define MSG_SIZE 4096 +static char ctest_errorbuffer[MSG_SIZE]; +static jmp_buf ctest_err; +static int color_output = 1; +static const char* suite_name; + +typedef int (*ctest_filter_func)(struct ctest*); + +#define ANSI_BLACK "\033[0;30m" +#define ANSI_RED "\033[0;31m" +#define ANSI_GREEN "\033[0;32m" +#define ANSI_YELLOW "\033[0;33m" +#define ANSI_BLUE "\033[0;34m" +#define ANSI_MAGENTA "\033[0;35m" +#define ANSI_CYAN "\033[0;36m" +#define ANSI_GREY "\033[0;37m" +#define ANSI_DARKGREY "\033[01;30m" +#define ANSI_BRED "\033[01;31m" +#define ANSI_BGREEN "\033[01;32m" +#define ANSI_BYELLOW "\033[01;33m" +#define ANSI_BBLUE "\033[01;34m" +#define ANSI_BMAGENTA "\033[01;35m" +#define ANSI_BCYAN "\033[01;36m" +#define ANSI_WHITE "\033[01;37m" +#define ANSI_NORMAL "\033[0m" + +CTEST(suite, test) { } + +static void vprint_errormsg(const char* const fmt, va_list ap) CTEST_IMPL_FORMAT_PRINTF(1, 0); +static void print_errormsg(const char* const fmt, ...) CTEST_IMPL_FORMAT_PRINTF(1, 2); + +static void vprint_errormsg(const char* const fmt, va_list ap) { + // (v)snprintf returns the number that would have been written + const int ret = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, ap); + if (ret < 0) { + ctest_errormsg[0] = 0x00; + } else { + const size_t size = (size_t) ret; + const size_t s = (ctest_errorsize <= size ? size -ctest_errorsize : size); + // ctest_errorsize may overflow at this point + ctest_errorsize -= s; + ctest_errormsg += s; + } +} + +static void print_errormsg(const char* const fmt, ...) { + va_list argp; + va_start(argp, fmt); + vprint_errormsg(fmt, argp); + va_end(argp); +} + +static void msg_start(const char* color, const char* title) { + if (color_output) { + print_errormsg("%s", color); + } + print_errormsg(" %s: ", title); +} + +static void msg_end(void) { + if (color_output) { + print_errormsg(ANSI_NORMAL); + } + print_errormsg("\n"); +} + +void CTEST_LOG(const char* fmt, ...) +{ + va_list argp; + msg_start(ANSI_BLUE, "LOG"); + + va_start(argp, fmt); + vprint_errormsg(fmt, argp); + va_end(argp); + + msg_end(); +} + +CTEST_IMPL_DIAG_PUSH_IGNORED(missing-noreturn) + +void CTEST_ERR(const char* fmt, ...) +{ + va_list argp; + msg_start(ANSI_YELLOW, "ERR"); + + va_start(argp, fmt); + vprint_errormsg(fmt, argp); + va_end(argp); + + msg_end(); + longjmp(ctest_err, 1); +} + +CTEST_IMPL_DIAG_POP() + +void assert_str(const char* exp, const char* real, const char* caller, int line) { + if ((exp == NULL && real != NULL) || + (exp != NULL && real == NULL) || + (exp && real && strcmp(exp, real) != 0)) { + CTEST_ERR("%s:%d expected '%s', got '%s'", caller, line, exp, real); + } +} + +void assert_wstr(const wchar_t *exp, const wchar_t *real, const char* caller, int line) { + if ((exp == NULL && real != NULL) || + (exp != NULL && real == NULL) || + (exp && real && wcscmp(exp, real) != 0)) { + CTEST_ERR("%s:%d expected '%ls', got '%ls'", caller, line, exp, real); + } +} + +void assert_data(const unsigned char* exp, size_t expsize, + const unsigned char* real, size_t realsize, + const char* caller, int line) { + size_t i; + if (expsize != realsize) { + CTEST_ERR("%s:%d expected %" PRIuMAX " bytes, got %" PRIuMAX, caller, line, (uintmax_t) expsize, (uintmax_t) realsize); + } + for (i=0; i exp2) { + CTEST_ERR("%s:%d expected %" PRIdMAX "-%" PRIdMAX ", got %" PRIdMAX, caller, line, exp1, exp2, real); + } +} + +void assert_dbl_near(double exp, double real, double tol, const char* caller, int line) { + double diff = exp - real; + double absdiff = diff; + /* avoid using fabs and linking with a math lib */ + if(diff < 0) { + absdiff *= -1; + } + if (absdiff > tol) { + CTEST_ERR("%s:%d expected %0.3e, got %0.3e (diff %0.3e, tol %0.3e)", caller, line, exp, real, diff, tol); + } +} + +void assert_dbl_far(double exp, double real, double tol, const char* caller, int line) { + double diff = exp - real; + double absdiff = diff; + /* avoid using fabs and linking with a math lib */ + if(diff < 0) { + absdiff *= -1; + } + if (absdiff <= tol) { + CTEST_ERR("%s:%d expected %0.3e, got %0.3e (diff %0.3e, tol %0.3e)", caller, line, exp, real, diff, tol); + } +} + +void assert_null(void* real, const char* caller, int line) { + if ((real) != NULL) { + CTEST_ERR("%s:%d should be NULL", caller, line); + } +} + +void assert_not_null(const void* real, const char* caller, int line) { + if (real == NULL) { + CTEST_ERR("%s:%d should not be NULL", caller, line); + } +} + +void assert_true(int real, const char* caller, int line) { + if ((real) == 0) { + CTEST_ERR("%s:%d should be true", caller, line); + } +} + +void assert_false(int real, const char* caller, int line) { + if ((real) != 0) { + CTEST_ERR("%s:%d should be false", caller, line); + } +} + +void assert_fail(const char* caller, int line) { + CTEST_ERR("%s:%d shouldn't come here", caller, line); +} + + +static int suite_all(struct ctest* t) { + (void) t; // fix unused parameter warning + return 1; +} + +static int suite_filter(struct ctest* t) { + return strncmp(suite_name, t->ssname, strlen(suite_name)) == 0; +} + +static uint64_t getCurrentTime(void) { + struct timeval now; + gettimeofday(&now, NULL); + uint64_t now64 = (uint64_t) now.tv_sec; + now64 *= 1000000; + now64 += ((uint64_t) now.tv_usec); + return now64; +} + +static void color_print(const char* color, const char* text) { + if (color_output) + printf("%s%s" ANSI_NORMAL "\n", color, text); + else + printf("%s\n", text); +} + +#ifdef CTEST_SEGFAULT +#include +static void sighandler(int signum) +{ + const char msg_color[] = ANSI_BRED "[SIGSEGV: Segmentation fault]" ANSI_NORMAL "\n"; + const char msg_nocolor[] = "[SIGSEGV: Segmentation fault]\n"; + + const char* msg = color_output ? msg_color : msg_nocolor; + write(STDOUT_FILENO, msg, strlen(msg)); + + /* "Unregister" the signal handler and send the signal back to the process + * so it can terminate as expected */ + signal(signum, SIG_DFL); + kill(getpid(), signum); +} +#endif + +int ctest_main(int argc, const char *argv[]); + +__attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[]) +{ + static int total = 0; + static int num_ok = 0; + static int num_fail = 0; + static int num_skip = 0; + static int idx = 1; + static ctest_filter_func filter = suite_all; + +#ifdef CTEST_SEGFAULT + signal(SIGSEGV, sighandler); +#endif + + if (argc == 2) { + suite_name = argv[1]; + filter = suite_filter; + } +#ifdef CTEST_NO_COLORS + color_output = 0; +#else + color_output = isatty(1); +#endif + uint64_t t1 = getCurrentTime(); + + struct ctest* ctest_begin = &CTEST_IMPL_TNAME(suite, test); + struct ctest* ctest_end = &CTEST_IMPL_TNAME(suite, test); + // find begin and end of section by comparing magics + while (1) { + struct ctest* t = ctest_begin-1; + if (t->magic != CTEST_IMPL_MAGIC) break; + ctest_begin--; + } + while (1) { + struct ctest* t = ctest_end+1; + if (t->magic != CTEST_IMPL_MAGIC) break; + ctest_end++; + } + ctest_end++; // end after last one + + static struct ctest* test; + for (test = ctest_begin; test != ctest_end; test++) { + if (test == &CTEST_IMPL_TNAME(suite, test)) continue; + if (filter(test)) total++; + } + + for (test = ctest_begin; test != ctest_end; test++) { + if (test == &CTEST_IMPL_TNAME(suite, test)) continue; + if (filter(test)) { + ctest_errorbuffer[0] = 0; + ctest_errorsize = MSG_SIZE-1; + ctest_errormsg = ctest_errorbuffer; + printf("TEST %d/%d %s:%s ", idx, total, test->ssname, test->ttname); + fflush(stdout); + if (test->skip) { + color_print(ANSI_BYELLOW, "[SKIPPED]"); + num_skip++; + } else { + int result = setjmp(ctest_err); + if (result == 0) { + if (test->setup && *test->setup) (*test->setup)(test->data); + if (test->data) + test->run.unary(test->data); + else + test->run.nullary(); + if (test->teardown && *test->teardown) (*test->teardown)(test->data); + // if we got here it's ok +#ifdef CTEST_COLOR_OK + color_print(ANSI_BGREEN, "[OK]"); +#else + printf("[OK]\n"); +#endif + num_ok++; + } else { + color_print(ANSI_BRED, "[FAIL]"); + num_fail++; + } + if (ctest_errorsize != MSG_SIZE-1) printf("%s", ctest_errorbuffer); + } + idx++; + } + } + uint64_t t2 = getCurrentTime(); + + const char* color = (num_fail) ? ANSI_BRED : ANSI_GREEN; + char results[80]; + snprintf(results, sizeof(results), "RESULTS: %d tests (%d ok, %d failed, %d skipped) ran in %" PRIu64 " ms", total, num_ok, num_fail, num_skip, (t2 - t1)/1000); + color_print(color, results); + return num_fail; +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/tests/getchTests.c b/tests/getchTests.c new file mode 100644 index 0000000..008e966 --- /dev/null +++ b/tests/getchTests.c @@ -0,0 +1,147 @@ +#include "ctest.h" +#include +#include +#include +#include +#include +#include +#include + +#include "../getch.h" + +#define EV_PRESSED 1 +#define EV_RELEASED 0 + +inline static void writeToEventDevice(__u16 code) +{ + glob_t globResult; + glob("/dev/input/by-path/*-event-kbd", GLOB_NOSORT, NULL, &globResult); + + if(globResult.gl_pathc == 0) { + fprintf(stderr, "Failed to find input device."); + globfree(&globResult); + exit(EXIT_FAILURE); + } + + char device[strlen(globResult.gl_pathv[0])]; + strncpy(device, globResult.gl_pathv[0], strlen(globResult.gl_pathv[0]) + 1); + globfree(&globResult); + + struct input_event inputEvent[2]; + int eventDevice = open(device, O_WRONLY); + + if(eventDevice == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + for(int i=0; i<2;i++) { + gettimeofday(&inputEvent[i].time, NULL); + inputEvent[i].type = EV_KEY; + inputEvent[i].code = code; + inputEvent[i].value = i ? EV_RELEASED : EV_PRESSED; + } + + write(eventDevice, &inputEvent, sizeof(inputEvent)); + + + close(eventDevice); + + sync(); + + fflush(NULL); + +} + +inline static void reverseString(char * str) +{ + if (str) + { + char * end = str + strlen(str) - 1; + + // swap the values in the two given variables + // XXX: fails when a and b refer to same memory location +# define XOR_SWAP(a,b) do\ + {\ + (a) ^= (b);\ + (b) ^= (a);\ + (a) ^= (b);\ + } while (0) + + // walk inwards from both ends of the string, + // swapping until we get to the middle + while (str < end) + { + XOR_SWAP(*str, *end); + str++; + end--; + } +# undef XOR_SWAP + } +} + +inline static void writeStringToStdin(char * string) +{ + size_t stringLength = strlen(string); + char reversed[stringLength]; + strncpy(reversed, string, stringLength); + + reverseString(reversed); + + for(int i = 0; i