Explorar el Código

ReadLine and tests

Kajetan Johannes Hammerle hace 9 meses
padre
commit
d5982c41c8
Se han modificado 11 ficheros con 412 adiciones y 1 borrados
  1. 3 0
      CMakeLists.txt
  2. 10 0
      include/core/ReadLine.h
  3. 2 0
      open
  4. BIN
      readLineTest
  5. 4 0
      src/ErrorSimulator.c
  6. 12 0
      src/ErrorSimulator.h
  7. 297 0
      src/ReadLine.c
  8. 1 1
      tasks
  9. 1 0
      test/Main.c
  10. 1 0
      test/Tests.h
  11. 81 0
      test/modules/ReadLineTests.c

+ 3 - 0
CMakeLists.txt

@@ -17,6 +17,7 @@ set(SRC
     "src/Plane.c"
     "src/Quaternion.c"
     "src/Random.c"
+    "src/ReadLine.c"
     "src/RingBuffer.c"
     "src/SpinLock.c"
     "src/Utility.c"
@@ -39,6 +40,7 @@ set(SRC_TESTS
     "test/modules/PlaneTests.c"
     "test/modules/QuaternionTests.c"
     "test/modules/RandomTests.c"
+    "test/modules/ReadLineTests.c"
     "test/modules/RingBufferTests.c"
     "test/modules/SpinLockTests.c"
     "test/modules/UtilityTests.c"
@@ -113,6 +115,7 @@ target_sources(core PUBLIC
         ./include/core/Plane.h
         ./include/core/Quaternion.h
         ./include/core/Random.h
+        ./include/core/ReadLine.h
         ./include/core/RingBuffer.h
         ./include/core/SpinLock.h
         ./include/core/Types.h

+ 10 - 0
include/core/ReadLine.h

@@ -0,0 +1,10 @@
+#ifndef CORE_READ_LINE_H
+#define CORE_READ_LINE_H
+
+#include <stddef.h>
+
+bool coreStartReadLine(void);
+bool coreReadLine(char* buffer, size_t n);
+void coreStopReadLine(void);
+
+#endif

+ 2 - 0
open

@@ -0,0 +1,2 @@
+#!/bin/sh
+vim -p $(find -name *$1*.[hc])

BIN
readLineTest


+ 4 - 0
src/ErrorSimulator.c

@@ -4,5 +4,9 @@
 
 bool coreFailFileClose = false;
 bool coreFailTimeGet = false;
+bool coreFailThreadInit = false;
+bool coreFailMutexInit = false;
+bool coreFailMutexLock = false;
+bool coreFailMutexUnlock = false;
 
 #endif

+ 12 - 0
src/ErrorSimulator.h

@@ -4,11 +4,23 @@
 #ifdef ERROR_SIMULATOR
 extern bool coreFailFileClose;
 extern bool coreFailTimeGet;
+extern bool coreFailThreadInit;
+extern bool coreFailMutexInit;
+extern bool coreFailMutexLock;
+extern bool coreFailMutexUnlock;
 #define CORE_FILE_CLOSE_FAIL coreFailFile
 #define CORE_TIME_GET_FAIL coreFailTimeGet
+#define CORE_THREAD_INIT_FAIL coreFailThreadInit
+#define CORE_MUTEX_INIT_FAIL coreFailMutexInit
+#define CORE_MUTEX_LOCK_FAIL coreFailMutexLock
+#define CORE_MUTEX_UNLOCK_FAIL coreFailMutexUnlock
 #else
 #define CORE_FILE_CLOSE_FAIL false
 #define CORE_TIME_GET_FAIL false
+#define CORE_THREAD_INIT_FAIL false
+#define CORE_MUTEX_INIT_FAIL false
+#define CORE_MUTEX_LOCK_FAIL false
+#define CORE_MUTEX_UNLOCK_FAIL false
 #endif
 
 #endif

+ 297 - 0
src/ReadLine.c

@@ -0,0 +1,297 @@
+#include "core/ReadLine.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <threads.h>
+#include <unistd.h>
+
+#include "ErrorSimulator.h"
+#include "core/Logger.h"
+#include "core/RingBuffer.h"
+
+#define HISTORY_LENGTH 10
+#define CONSOLE_BUFFER_SIZE 256
+
+typedef struct ConsoleLine {
+    char data[CONSOLE_BUFFER_SIZE];
+    int length;
+} ConsoleLine;
+
+static atomic_bool running = true;
+static thrd_t readThread = {0};
+
+static struct termios original;
+static CoreRingBuffer buffer = {0};
+static ConsoleLine currentBuffer = {0};
+static int move = 0;
+static int cursorMove = 0;
+static mtx_t bufferMutex = {0};
+
+static ConsoleLine history[HISTORY_LENGTH];
+static int historyOffset = 0;
+static int historyIndex = 0;
+static int historyLength = 0;
+
+static void getAttributes(struct termios* t) {
+    if(tcgetattr(STDIN_FILENO, t)) {
+        CORE_LOG_WARNING("cannot get terminal attributes");
+    }
+}
+
+static void restoreAttributes() {
+    if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)) {
+        CORE_LOG_WARNING("cannot restore terminal attributes: %s",
+                         strerror(errno));
+    }
+}
+
+static char readChar() {
+    char c = 0;
+    ssize_t bytes = read(STDIN_FILENO, &c, 1);
+    return bytes <= 0 ? '\0' : c;
+}
+
+static void addChar(char c) {
+    if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) {
+        return;
+    }
+    for(int i = 0; i < move; i++) {
+        currentBuffer.data[currentBuffer.length - i] =
+            currentBuffer.data[currentBuffer.length - i - 1];
+    }
+    currentBuffer.length++;
+    currentBuffer.data[currentBuffer.length - move - 1] = c;
+    currentBuffer.data[currentBuffer.length] = '\0';
+}
+
+static void print(const char* s) {
+    fputs(s, stdout);
+}
+
+static void refreshLine(const char* prefix) {
+    print(prefix);
+    print(currentBuffer.data);
+    if(cursorMove > 0) {
+        printf("\33[%dD", cursorMove);
+    }
+    fflush(stdout);
+    print("\33[2K\r");
+}
+
+static bool clear() {
+    move = 0;
+    cursorMove = 0;
+    currentBuffer.length = 0;
+    currentBuffer.data[0] = '\0';
+    historyOffset = 0;
+    return false;
+}
+
+static void addToHistory() {
+    if(historyLength < HISTORY_LENGTH) {
+        historyLength++;
+    }
+    history[historyIndex] = currentBuffer;
+    historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
+}
+
+static void lock() {
+    if(mtx_lock(&bufferMutex) != thrd_success || CORE_MUTEX_LOCK_FAIL) {
+        CORE_LOG_WARNING("could not lock buffer mutex");
+    }
+}
+
+static void unlock() {
+    if(mtx_unlock(&bufferMutex) != thrd_success || CORE_MUTEX_UNLOCK_FAIL) {
+        CORE_LOG_WARNING("could not unlock buffer mutex");
+    }
+}
+
+static void addLine() {
+    addToHistory();
+    lock();
+    coreRingBufferAddPointer(&buffer, &currentBuffer);
+    unlock();
+    clear();
+}
+
+static char isUTF8Part(char c) {
+    return ((uint8_t)c & 0xC0) == 0x80;
+}
+
+static bool removeChar() {
+    int pos = currentBuffer.length - move;
+    if(pos > 0) {
+        int l = 1;
+        while(pos - l >= 0) {
+            if(!isUTF8Part(currentBuffer.data[pos - l])) {
+                break;
+            }
+            l++;
+        }
+        currentBuffer.length -= l;
+        for(int i = pos - l; i <= currentBuffer.length; i++) {
+            currentBuffer.data[i] = currentBuffer.data[i + l];
+        }
+    }
+    return false;
+}
+
+static bool handleControlKey(char c) {
+    switch(c) {
+        case 3: return clear();
+        case 10:
+        case 13: return true;
+        case 127: return removeChar();
+    }
+    return false;
+}
+
+static void copyHistory() {
+    currentBuffer = history[(historyIndex - historyOffset + HISTORY_LENGTH) %
+                            HISTORY_LENGTH];
+    move = 0;
+    cursorMove = 0;
+}
+
+static void handleUpArrow() {
+    if(historyOffset >= historyLength) {
+        return;
+    }
+    historyOffset++;
+    copyHistory();
+}
+
+static void handleDownArrow() {
+    if(historyOffset <= 1) {
+        return;
+    }
+    historyOffset--;
+    copyHistory();
+}
+
+static char getMoved() {
+    return currentBuffer.data[currentBuffer.length - move];
+}
+
+static void handleLeftArrow() {
+    if(move < currentBuffer.length) {
+        move++;
+        while(move < currentBuffer.length && isUTF8Part(getMoved())) {
+            move++;
+        }
+        cursorMove++;
+    }
+}
+
+static void handleRightArrow() {
+    if(move > 0) {
+        move--;
+        while(move > 0 && isUTF8Part(getMoved())) {
+            move--;
+        }
+        cursorMove--;
+    }
+}
+
+static void readEscapeSequence() {
+    if(readChar() != '[') {
+        return;
+    }
+    switch(readChar()) {
+        case 'A': handleUpArrow(); break;
+        case 'B': handleDownArrow(); break;
+        case 'C': handleRightArrow(); break;
+        case 'D': handleLeftArrow(); break;
+        case '3':
+            if(readChar() == '~' && move > 0) {
+                handleRightArrow();
+                removeChar();
+            }
+            break;
+    }
+}
+
+static void handleChars() {
+    while(true) {
+        char c = readChar();
+        if(c == 0) {
+            break;
+        } else if(c == 27) {
+            readEscapeSequence();
+        } else if(iscntrl(c)) {
+            if(handleControlKey(c)) {
+                addLine();
+                return;
+            }
+        } else {
+            addChar(c);
+        }
+    }
+}
+
+static int loop(void* data) {
+    (void)data;
+    while(running) {
+        handleChars();
+        refreshLine("wusi> ");
+        struct timespec t = {.tv_nsec = 1000000};
+        thrd_sleep(&t, nullptr);
+    }
+    return 0;
+}
+
+bool coreStartReadLine(void) {
+    // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
+    getAttributes(&original);
+    struct termios raw = original;
+    raw.c_iflag &= ~(tcflag_t)(ICRNL | IXON);
+    raw.c_lflag &= ~(tcflag_t)(ECHO | ICANON | IEXTEN | ISIG);
+    raw.c_cc[VMIN] = 0;
+    raw.c_cc[VTIME] = 0;
+
+    if(tcsetattr(STDIN_FILENO, TCSANOW, &raw)) {
+        CORE_LOG_WARNING("cannot set terminal attributes");
+    }
+
+    buffer = CORE_RING_BUFFER(10, sizeof(ConsoleLine));
+    atomic_store(&running, true);
+    if(CORE_MUTEX_INIT_FAIL ||
+       mtx_init(&bufferMutex, mtx_plain) != thrd_success) {
+        CORE_LOG_ERROR("cannot init buffer mutex");
+        coreStopReadLine();
+        return true;
+    } else if(CORE_THREAD_INIT_FAIL ||
+              thrd_create(&readThread, loop, nullptr) != thrd_success) {
+        CORE_LOG_ERROR("cannot start read thread");
+        coreStopReadLine();
+        return true;
+    }
+    return false;
+}
+
+bool coreReadLine(char* buffer_, size_t n) {
+    if(buffer.length == 0) {
+        return false;
+    }
+    lock();
+    ConsoleLine* line = coreRingBufferGetVoidPointer(&buffer, 0);
+    snprintf(buffer_, n, "%s", line->data);
+    coreRingBufferRemove(&buffer);
+    unlock();
+    return true;
+}
+
+void coreStopReadLine() {
+    atomic_store(&running, false);
+    thrd_join(readThread, nullptr);
+    restoreAttributes();
+    coreDestroyRingBuffer(&buffer);
+    mtx_destroy(&bufferMutex);
+}

+ 1 - 1
tasks

@@ -154,7 +154,7 @@ function runTests() {
     echo "--------------Post canary detection-------------"
     LLVM_PROFILE_FILE="post_canary.profraw" $valgrind ./test post_canary $valgrind || true
     echo "--------------Default run-----------------------"
-    LLVM_PROFILE_FILE="default.profraw" $valgrind ./test light $valgrind || true
+    LLVM_PROFILE_FILE="default.profraw" $valgrind ./test light $valgrind < ../readLineTest || true
 }
 
 if $test_debug; then

+ 1 - 0
test/Main.c

@@ -43,6 +43,7 @@ int main(int argAmount, const char** args) {
     coreTestPlane();
     coreTestQuaternion();
     coreTestRandom(light);
+    coreTestReadLine();
     coreTestRingBuffer();
     coreTestSpinLock();
     coreTestUtility(light);

+ 1 - 0
test/Tests.h

@@ -19,6 +19,7 @@ void coreTestPostCanary(void);
 void coreTestPreCanary(void);
 void coreTestQuaternion(void);
 void coreTestRandom(bool light);
+void coreTestReadLine(void);
 void coreTestRingBuffer(void);
 void coreTestSpinLock(void);
 void coreTestUtility(bool light);

+ 81 - 0
test/modules/ReadLineTests.c

@@ -0,0 +1,81 @@
+#include <stdio.h>
+#include <threads.h>
+
+#include "../Tests.h"
+#include "../src/ErrorSimulator.h"
+#include "core/ReadLine.h"
+
+static void sleepMillis(int millis) {
+    struct timespec t = {.tv_nsec = millis * 1000000};
+    thrd_sleep(&t, nullptr);
+}
+
+static void testString(int line, const char* s) {
+    char buffer[256];
+    for(int i = 0; i < 200; i++) {
+        if(coreReadLine(buffer, sizeof(buffer))) {
+            break;
+        }
+        sleepMillis(10);
+    }
+    if(!coreTestString(__FILE__, line, s, buffer)) {
+        const char* p = buffer;
+        printf("Invalid sequence: ");
+        while(*p != 0) {
+            printf("%u ", (unsigned int)*p & 0xFF);
+            p++;
+        }
+        putchar('\n');
+        p = s;
+        printf("Correct sequence: ");
+        while(*p != 0) {
+            printf("%u ", (unsigned int)*p & 0xFF);
+            p++;
+        }
+        putchar('\n');
+    }
+}
+
+void coreTestReadLine(void) {
+#ifdef ERROR_SIMULATOR
+    coreFailMutexInit = true;
+    if(!CORE_TEST_TRUE(coreStartReadLine())) {
+        return;
+    }
+    coreFailMutexInit = false;
+    coreFailThreadInit = true;
+    if(!CORE_TEST_TRUE(coreStartReadLine())) {
+        return;
+    }
+    coreFailThreadInit = false;
+    coreFailMutexLock = true;
+    coreFailMutexUnlock = true;
+#endif
+    if(!CORE_TEST_FALSE(coreStartReadLine())) {
+        return;
+    }
+    testString(__LINE__, "wusi");
+#ifdef ERROR_SIMULATOR
+    coreFailMutexLock = false;
+    coreFailMutexUnlock = false;
+#endif
+    testString(__LINE__, "gusi");
+    testString(__LINE__, "abc");
+    testString(__LINE__, "abc");
+    testString(__LINE__, "abd");
+    testString(__LINE__, "abö");
+    testString(__LINE__, "ghi");
+    testString(__LINE__, "abcghi");
+    testString(__LINE__, "abcghi");
+    testString(__LINE__, "abö");
+    testString(__LINE__, "abac");
+    testString(
+        __LINE__,
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbb");
+    testString(__LINE__, "abäo");
+    testString(__LINE__, "bäöo");
+    coreStopReadLine();
+}