From f4d44db10452dbb896987724f5049f9557e08145 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 27 Oct 2023 22:30:46 -0500 Subject: [PATCH] TODO test execution starter --- CMakeLists.txt | 29 +++++++++- helpers/serialWrapper.cpp | 80 ++++++++++++++++++++++++++ helpers/serialWrapper.h | 19 ++++++ testRunner.cpp | 11 ++++ tests/README.md | 58 +++++++++++++++++++ tests/serialTest.cpp | 20 +++++++ tests/test.h | 118 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 helpers/serialWrapper.cpp create mode 100644 helpers/serialWrapper.h create mode 100644 tests/README.md create mode 100644 tests/serialTest.cpp create mode 100644 tests/test.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4133f33..6c38645 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,21 @@ execute_process(COMMAND ./parseConfigs.py set(MBED_PATH ${CMAKE_CURRENT_SOURCE_DIR}/mbed-os CACHE INTERNAL "") set(MBED_CONFIG_PATH ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "") set(APP_TARGET embedded-mbed) +set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) +set(HELPERS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/helpers) +set(TEST_SOURCES + ${TESTS_DIR}/serialTest.cpp +) +set(TEST_HEADERS + ${TESTS_DIR}/test.h +) +set(HELPER_SOURCES + ${HELPERS_DIR}/serialWrapper.cpp +) +set(HELPER_HEADERS + ${HELPERS_DIR}/serialWrapper.h +) + include(${MBED_PATH}/tools/cmake/app.cmake) @@ -14,7 +29,13 @@ project(${APP_TARGET}) add_subdirectory(${MBED_PATH}) -add_executable(${APP_TARGET} testRunner.cpp) +add_executable(${APP_TARGET} + testRunner.cpp + ${TEST_SOURCES} + ${TEST_HEADERS} + ${HELPER_SOURCES} + ${HELPER_HEADERS} +) # Precompile the definitions header. The generated header will be force included in all source files, so they don't need `#include "definitions.h"` target_precompile_headers(${APP_TARGET} @@ -22,6 +43,12 @@ target_precompile_headers(${APP_TARGET} definitions.h ) +target_include_directories(${APP_TARGET} + PRIVATE + ${TESTS_DIR} + ${HELPERS_DIR} +) + target_sources(${APP_TARGET} PRIVATE testRunner.cpp diff --git a/helpers/serialWrapper.cpp b/helpers/serialWrapper.cpp new file mode 100644 index 0000000..812fe8d --- /dev/null +++ b/helpers/serialWrapper.cpp @@ -0,0 +1,80 @@ +#include "serialWrapper.h" + + +/** +* Reads in a line of text, excluding newline +* Writes a null-terminated string into the provided destination buffer +* +* Parameters: +* dest: Pointer to destination buffer +* buffSize: Size of destination buffer +* * NOTE: Although the newline is not included in the final buffer +* contents, you must ensure that buffSize accounts for both +* the newline and null-terminator at the end of the buffer +* timeout: Time (in milliseconds) to wait for a line to be read +* * If timeout is negative, this will not time out +* * Default timeout is 10000ms (10s) +* +* Returns: +* size_t: Length of string written to buffer or -1 on error or timeout +**/ +size_t SerialWrapper::readLine(char* dest, size_t buffSize, int timeout) { + // Length of string stored in character buffer + size_t strlen = 0; + // Stores value returned by BufferedSerial.read() + int err = 0; + // Calling printf within loop is too slow + bool overflow = false; + // Whether or not there is a timeout + bool canTimeout = timeout >= 0; + + while(1) { + // Check for buffer overflow before read + /*TODO if(strlen >= BUFFER_SIZE) { + overflow = true; + break; + }*/ + + // Read in from serial + if((err = this->read(dest + strlen, buffSize)) > 0) { + strlen += err; + // TODO Check for buffer overflow after read + if(strlen >= buffSize) { + overflow = true; + break; + } + // Convert to string + dest[strlen] = '\0'; + + // Check for newline + char* newline; // Location of newline + char n[2] {'\n', '\0'}; // Newline character string TODO Any particular reason 0xd was used instead of '\n'? + if((newline = strstr(dest, n)) != NULL) { + // Change \n to \0 + *newline = '\0'; + strlen = newline - dest; + break; + } + } else if(err == 0) { + // Nothing read + if(canTimeout) { + if(timeout <= 0) { + // Time out + return -1; + } + ThisThread::sleep_for(100ms); + timeout -= 100; + } + } else { + // Error reading from buffer + return -1; + } + } + + if(overflow) { + // Buffer overflow + return -1; + } + + return strlen; +} \ No newline at end of file diff --git a/helpers/serialWrapper.h b/helpers/serialWrapper.h new file mode 100644 index 0000000..dbba36e --- /dev/null +++ b/helpers/serialWrapper.h @@ -0,0 +1,19 @@ +#ifndef __SERIAL_WRAPPER_H__ +#define __SERIAL_WRAPPER_H__ + +#include "mbed.h" +#include + +// TODO REMOVE: #define BUFFER_SIZE 16 + +class SerialWrapper : public BufferedSerial { + public: + SerialWrapper(PinName tx, PinName rx, int baud=MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE): BufferedSerial(tx, rx, baud) {}; + size_t readLine(char* dest, size_t buffSize, int timeout=10000); + + // TODO private: + // BufferedSerial *serial; + +}; + +#endif // __SERIAL_WRAPPER_H__ \ No newline at end of file diff --git a/testRunner.cpp b/testRunner.cpp index ab4dd55..8cc6445 100644 --- a/testRunner.cpp +++ b/testRunner.cpp @@ -1,8 +1,19 @@ #include "mbed.h" +#include "serialWrapper.h" +#include "serialTest.cpp" + +// Serial interface for BTF communication +SerialWrapper pc(USBTX, USBRX); int main() { + // Test BTF communication before running other tests + SerialTest serialTest = SerialTest("serial_basic"); + if(serialTest.execute()) { + // Run tests + } + while(1) { wait_us(1000000); } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..8a73b7b --- /dev/null +++ b/tests/README.md @@ -0,0 +1,58 @@ +# Tests +This is where all test classes will be defined. + +## Creating a New Test Class +All custom test classes must inherit the base `Test` class, as it defines a standard test structure that is required for communicating with the [BTF](https://github.com/badgerloop-software/BTF) test runner. Additionally, `test.h` provides a declaration of `pc`, a `SerialWrapper` object for communicating with the BTF test runner. The following is an example custom test class with implementation notes: +```cpp +#ifndef __CUSTOM_TEST__ +#define __CUSTOM_TEST__ + +#include "test.h" +#include + +using namespace std; + + +class CustomTest : public Test { + public: + // REQUIRED: Pass the name of the test to the base class' constructor + // The test name should be formatted as shown below, using the keys from tests.yml + // NOTE: If multiple tests have the same setup() and teardown(), you could use + // `CustomTest(const string name): Test(name) {` to run different tests in runTest() + // based on the name and save on redefining setup() and teardown() + CustomTest(): Test("_") { + // Add initialization here + }; + private: + // OPTIONAL: Test setup. If not defined, there is no setup + // This is called before starting the test in the BTF + void setup() { + // Add custom setup here + } + + // REQUIRED: Main test content + // Returns true if test passes, false otherwise + bool runTest() { + char buf[5]; + // You can use printf() and the pc object to communicate with the BTF + printf("foo\n"); // pc.write() also works + pc.readLine(buff, sizeof(buff)); + if(strcmp(buff, "bar") == 0) { + // TODO REMOVE Be sure to let the BTF know if the test passes or fails! + // printf("PASS\n"); + return true; + } + // TODO REMOVE Be sure to let the BTF know if the test passes or fails! + // printf("FAIL\n"); + return false; + } + + // OPTIONAL: Test teardown. If not defined, there is no teardown + // This is called after ending the test in the BTF + void teardown() { + // Add custom teardown here + } +}; + +#endif // __CUSTOM_TEST__ +``` \ No newline at end of file diff --git a/tests/serialTest.cpp b/tests/serialTest.cpp new file mode 100644 index 0000000..abcb501 --- /dev/null +++ b/tests/serialTest.cpp @@ -0,0 +1,20 @@ +#ifndef __SERIAL_TEST__ +#define __SERIAL_TEST__ + +#include "test.h" + +// TODO using namespace std; + + +class SerialTest : public Test { + public: + SerialTest(const string name): Test(name) {}; + private: + bool runTest() { + printf("%s\n", this->testName.c_str()); // TODO + // Simply pass, as signalStart() and signalEnd() will indicate success or failure + return true; + } +}; + +#endif // __SERIAL_TEST__ diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..02876f0 --- /dev/null +++ b/tests/test.h @@ -0,0 +1,118 @@ +#ifndef __TEST_H__ +#define __TEST_H__ + +#include "mbed.h" +#include "serialWrapper.h" +#include + +using namespace std; + +// Serial interface for BTF communication +extern SerialWrapper pc; + + +class Test { + public: + Test(const string name) { + this->testName = name; + } + + // Execute the test, including any setup and teardown + bool execute() { + bool ret; + setup(); + if(signalStart()) { + ret = runTest(); + // Even if the test passed, modify the return value to indicate serial + // communication status. This is particularly useful in verifying the + // success of the serial test + ret = signalEnd(ret); + } else { + ret = false; + } + teardown(); + + return ret; + } + protected: + // Name of the test, as defined in tests.yml + string testName; + private: + // Setup that can be redefined for each test (do nothing by default) + virtual void setup() {}; + // Main test content + virtual bool runTest() = 0; + // Teardown that can be redefined for each test (do nothing by default) + virtual void teardown() {}; + + // Signal to the BTF test runner to start the test + bool signalStart() { + // REMOVE bool started = false; + // REMOVE int attempts = 0; + char buff[7]; + + // Send start message to BTF test runner + printf("START TEST %s\n", testName.c_str()); + + // Read response from BTF test runner + // REMOVE Need this? memset(buff, '\0', sizeof(buff)); + size_t nread = pc.readLine(buff, sizeof(buff)); + + // The response should be "READY" + return (nread == 5) && (strcmp(buff, "READY") == 0); + /* REMOVE + do { + + // TODO Change to readLine with timeout + + if(pc.readable()) { + pc.read(buff, sizeof(buff)); + started = strncmp(buff, "READY", 5) == 0; + } else { + ThisThread::sleep_for(100); + attempts++; + } + } while(!started && (attempts < 100)); + + return started;*/ + } + + // Signal to the BTF test runner to end the test + bool signalEnd(bool result) { + // REMOVE bool ended = false; + // REMOVE int attempts = 0; + char buff[6]; + + // Send end message to BTF test runner + if(result) { + printf("PASS\n"); + } else { + printf("FAIL\n"); + } + + // Read response from BTF test runner + // REMOVE Need this? memset(buff, '\0', sizeof(buff)); + size_t nread = pc.readLine(buff, sizeof(buff)); + + // The response should be 'DONE'. If so, preserve the test result + return result && (nread == 4) && (strcmp(buff, "DONE") == 0); + + /* REMOVE + do { + + // TODO Change to readLine with timeout + + if(pc.readable()) { + pc.read(buff, sizeof(buff)); + ended = strncmp(buff, "DONE", 4) == 0; + } else { + ThisThread::sleep_for(100); + attempts++; + } + } while(!ended && (attempts < 100)); + + return ended;*/ + } +}; + +#endif // __TEST_H__ \ No newline at end of file