diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp index 0c9c97b5b..bb47339f5 100644 --- a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp @@ -160,7 +160,8 @@ bool Adafruit_LittleFS::mkdir (char const *filepath) // make intermediate parent directory(ies) while ( NULL != (slash = strchr(slash, '/')) ) { - char parent[slash - filepath + 1] = { 0 }; + char parent[slash - filepath + 1]; + parent[0] = 0; memcpy(parent, filepath, slash - filepath); int rc = lfs_mkdir(&_lfs, parent); diff --git a/build.sh b/build.sh index f21279417..d7f8c6d58 100755 --- a/build.sh +++ b/build.sh @@ -13,6 +13,7 @@ Commands: build-companion-firmwares: Build all companion firmwares for all build targets. build-repeater-firmwares: Build all repeater firmwares for all build targets. build-room-server-firmwares: Build all chat room server firmwares for all build targets. + test: Run test on the given target (typically 'native') Examples: Build firmware for the "RAK_4631_repeater" device target @@ -68,9 +69,7 @@ get_pio_envs_ending_with_string() { done } -# build firmware for the provided pio env in $1 -build_firmware() { - +set_build_env() { # get git commit sha COMMIT_HASH=$(git rev-parse --short HEAD) @@ -87,13 +86,17 @@ build_firmware() { # e.g: v1.0.0-abcdef FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION}-${COMMIT_HASH}" + # add firmware version info to end of existing platformio build flags in environment vars + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" +} + +# build firmware for the provided pio env in $1 +build_firmware() { + # craft filename # e.g: RAK_4631_Repeater-v1.0.0-SHA FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" - # add firmware version info to end of existing platformio build flags in environment vars - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" - # build firmware target pio run -e $1 @@ -189,6 +192,18 @@ build_firmwares() { build_room_server_firmwares } +run_tests() { + envs=($(get_pio_envs_containing_string "$1")) + for env in "${envs[@]}"; do + run_test $env + done +} + +run_test() { + set_build_env + pio test -e $1 +} + # clean build dir rm -rf out mkdir -p out @@ -219,4 +234,6 @@ elif [[ $1 == "build-repeater-firmwares" ]]; then build_repeater_firmwares elif [[ $1 == "build-room-server-firmwares" ]]; then build_room_server_firmwares +elif [[ $1 == "test" ]] ; then + run_tests "$2" fi diff --git a/platformio.ini b/platformio.ini index 3907cf64b..0e940a2fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,3 +146,30 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit BME680 Library @ ^2.0.4 adafruit/Adafruit BMP085 Library @ ^1.2.4 + +; ----------------- Native (for tests) ----------------- +[env] +test_framework = googletest +test_speed = 115200 + +[env:native] +platform = native +lib_compat_mode = off +test_build_src = true +lib_deps = + ${arduino_base.lib_deps} + skaygin/ArduinoNative + file://arch/stm32/Adafruit_LittleFS_stm32 + file://test/mocks/Wire + file://test/mocks/SPI +build_flags = ${arduino_base.build_flags} + -D_USE_MATH_DEFINES -DNATIVE_PLATFORM -DINPUT_PULLDOWN=0x3 +build_src_filter = ${arduino_base.build_src_filter} + - + +[env:native-asan] +extends = env:native +platform = native +build_flags = ${env:native.build_flags} + -fsanitize=address + -fsanitize=bounds diff --git a/src/Identity.cpp b/src/Identity.cpp index 832989283..476eacf40 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -61,8 +61,8 @@ bool LocalIdentity::writeTo(Stream& s) const { } void LocalIdentity::printTo(Stream& s) const { - s.print("pub_key: "); Utils::printHex(s, pub_key, PUB_KEY_SIZE); s.println(); - s.print("prv_key: "); Utils::printHex(s, prv_key, PRV_KEY_SIZE); s.println(); + s.print("pub_key: "); Utils::printHex(s, pub_key, PUB_KEY_SIZE); s.println(""); + s.print("prv_key: "); Utils::printHex(s, prv_key, PRV_KEY_SIZE); s.println(""); } size_t LocalIdentity::writeTo(uint8_t* dest, size_t max_len) { @@ -96,4 +96,4 @@ void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_k ed25519_key_exchange(secret, other_pub_key, prv_key); } -} \ No newline at end of file +} diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 0e05620ec..6774e2576 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -1,4 +1,5 @@ #include +#include uint8_t AdvertDataBuilder::encodeTo(uint8_t app_data[]) { app_data[0] = _type; @@ -84,4 +85,4 @@ void AdvertTimeHelper::formatRelativeTimeDiff(char dest[], int32_t seconds_from_ } } } -} \ No newline at end of file +} diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 9b1eb1ce7..d461afa34 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -159,7 +159,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t timestamp; memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4] >> 2; // message attempt number, and other flags + uint8_t flags = data[4] >> 2; // message attempt number, and other flags // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 4ea19fd29..b49ad59d4 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -1,7 +1,7 @@ #include "ClientACL.h" static File openWrite(FILESYSTEM* _fs, const char* filename) { - #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(NATIVE_PLATFORM) _fs->remove(filename); return _fs->open(filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 88327aa89..efc8a0852 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -98,7 +98,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } void CommonCLI::savePrefs(FILESYSTEM* fs) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(NATIVE_PLATFORM) fs->remove("/com_prefs"); File file = fs->open("/com_prefs", FILE_O_WRITE); #elif defined(RP2040_PLATFORM) diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index dc85d69cd..e59877724 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -46,7 +46,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(NATIVE_PLATFORM) _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) @@ -68,7 +68,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(NATIVE_PLATFORM) _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) diff --git a/src/helpers/IdentityStore.h b/src/helpers/IdentityStore.h index d0d7ee457..021f38026 100644 --- a/src/helpers/IdentityStore.h +++ b/src/helpers/IdentityStore.h @@ -3,7 +3,7 @@ #if defined(ESP32) || defined(RP2040_PLATFORM) #include #define FILESYSTEM fs::FS -#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) +#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(NATIVE_PLATFORM) #include #define FILESYSTEM Adafruit_LittleFS diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 25cc53589..0af3c656c 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -2,6 +2,7 @@ #include #include +#include class RadioLibWrapper : public mesh::Radio { protected: diff --git a/test/mocks/SPI/SPI.h b/test/mocks/SPI/SPI.h new file mode 100644 index 000000000..404c747fe --- /dev/null +++ b/test/mocks/SPI/SPI.h @@ -0,0 +1,44 @@ +#ifndef SPI_H +#define SPI_H + +typedef int BitOrder; + + +typedef enum { + SPI_MODE0 = 0, + SPI_MODE1 = 1, + SPI_MODE2 = 2, + SPI_MODE3 = 3, +} SPIMode; + +class SPISettings { +public: + SPISettings(uint32_t clock, BitOrder bitOrder, SPIMode dataMode) {} + SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {} +}; + +class SPIClass +{ +public: + uint8_t transfer(uint8_t data) { return 0; } + uint16_t transfer16(uint16_t data) { return 0; } + void transfer(void *buf, size_t count) {} + + void transfer(const void *txbuf, void *rxbuf, size_t count) {} + + void usingInterrupt(int interruptNumber) {} + void notUsingInterrupt(int interruptNumber) {} + void beginTransaction(SPISettings settings) {} + void endTransaction(void) {} + + void attachInterrupt() {} + void detachInterrupt() {} + + void begin() {} + void end() {} +}; + +SPIClass SPI; + + +#endif diff --git a/test/mocks/Wire/Wire.h b/test/mocks/Wire/Wire.h new file mode 100644 index 000000000..f985da5eb --- /dev/null +++ b/test/mocks/Wire/Wire.h @@ -0,0 +1,39 @@ +#ifndef Wire_h +#define Wire_h + +#include "Stream.h" + +class TwoWire : public Stream +{ +public: + TwoWire(uint8_t bus_num){}; + ~TwoWire(){}; + bool setPins(int sda, int scl){}; + bool begin(){return true;} + bool begin(uint8_t addr){return true;} + void beginTransmission(uint16_t address){} + void beginTransmission(uint8_t address){} + void beginTransmission(int address){} + uint8_t endTransmission(bool sendStop) { return 0; } + uint8_t endTransmission(void) { return 0; } + size_t requestFrom(uint16_t address, size_t size, bool sendStop) { return 0; } + uint8_t requestFrom(uint16_t address, uint8_t size, bool sendStop) { return 0; } + uint8_t requestFrom(uint16_t address, uint8_t size, uint8_t sendStop) { return 0; } + size_t requestFrom(uint8_t address, size_t len, bool stopBit) { return 0; } + uint8_t requestFrom(uint16_t address, uint8_t size) { return 0; } + uint8_t requestFrom(uint8_t address, uint8_t size, uint8_t sendStop) { return 0; } + uint8_t requestFrom(uint8_t address, uint8_t size) { return 0; } + uint8_t requestFrom(int address, int size, int sendStop) { return 0; } + size_t write(uint8_t) { return 1; } + size_t write(const uint8_t *b, size_t n) { return n; } + int available(){ return 0; } + int read(void) { return 0; } + int peek(void) { return 0; } + bool end(){}; +}; + +extern TwoWire Wire; +extern TwoWire Wire1; + +#endif + diff --git a/test/test_common/README.md b/test/test_common/README.md new file mode 100644 index 000000000..76c3e1716 --- /dev/null +++ b/test/test_common/README.md @@ -0,0 +1,8 @@ +# Common tests + +This directory holds tests that are expected to pass on all platforms, +including native and on-device tests. + +Tests that exercise device-specific features should should not go here, +and should be capable of passing with hardware features mocked out +(e.g. SPI or Wire are present but return fake responses.) diff --git a/test/test_common/mock_streams.h b/test/test_common/mock_streams.h new file mode 100644 index 000000000..6368d12e0 --- /dev/null +++ b/test/test_common/mock_streams.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +class MockStream : public Stream { +public: + uint8_t *buffer; + size_t pos; + MockStream(uint8_t *b) + :buffer(b),pos(0) + { + buffer[0] = 0; + } + + void clear() { + pos = 0; + buffer[0] = 0; + } + + size_t write(uint8_t c) { + buffer[pos++] = c; + buffer[pos] = 0; + return 1; + } + + size_t write(const uint8_t *src, size_t size) override { + memcpy(buffer, src, size); + pos += size; + return size; + } + + MOCK_METHOD(int, available, (), (override)); + MOCK_METHOD(int, availableForWrite, (), (override)); + MOCK_METHOD(int, read, (), (override)); + MOCK_METHOD(int, peek, (), (override)); +}; + +class ConstantValueStream : public Stream { +public: + const uint8_t *buffer; + size_t pos, len; + + ConstantValueStream(const uint8_t *b, size_t l) + :buffer(b),pos(0),len(l) + {} + + int available() { + return (int)(len - pos); + } + MOCK_METHOD(size_t, write, (uint8_t c), (override)); + MOCK_METHOD(size_t, write, (const uint8_t *buffer, size_t size), (override)); + MOCK_METHOD(int, availableForWrite, (), (override)); + int read() { + if (pos >= len) { + return 0; + } + return (int)buffer[pos++]; + } + MOCK_METHOD(int, peek, (), (override)); +}; + diff --git a/test/test_common/test_identity.cpp b/test/test_common/test_identity.cpp new file mode 100644 index 000000000..7b77dc1ea --- /dev/null +++ b/test/test_common/test_identity.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#include +#include "mock_streams.h" +#include "Identity.h" +#include "Utils.h" + +using namespace mesh; + +TEST(IdentityTests, Identity) +{ + mesh::Identity id; + const uint8_t pubhex[] = + "87A47F423042DBEE25D1EA5CCC387FBA"; + + mesh::Identity fromPubkey(&pubhex[0]); + + ConstantValueStream cs(&pubhex[0], 64); + + ASSERT_TRUE(id.readFrom(cs)); + + uint8_t buffer[80]; + memset(buffer, 0, sizeof(buffer)); + MockStream bs(&buffer[0]); + ASSERT_TRUE(id.writeTo(bs)); + ASSERT_STREQ((const char *)bs.buffer, (const char *)pubhex); +} + +#define ZERO_PUB_KEY \ + "\x3B\x6A\x27\xBC\xCE\xB6\xA4\x2D\x62\xA3\xA8\xD0\x2A\x6F\x0D" \ + "\x73\x65\x32\x15\x77\x1D\xE2\x43\xA6\x3A\xC0\x48\xA1\x8B\x59" +#define ZERO_PRV_KEY \ + "\x50\x46\xAD\xC1\xDB\xA8\x38\x86\x7B\x2B\xBB\xFD\xD0\xC3\x42" \ + "\x3E\x58\xB5\x79\x70\xB5\x26\x7A\x90\xF5\x79\x60\x92\x4A\x87" \ + "\xF1\x56\x0A\x6A\x85\xEA\xA6\x42\xDA\xC8\x35\x42\x4B\x5D\x7C" \ + "\x8D\x63\x7C\x00\x40\x8C\x7A\x73\xDA\x67\x2B\x7F\x49\x85\x21" \ + "\x42\x0B\x6D\xD3" + +TEST(IdentityTests, LocalIdentity) +{ + // create a zero identity + uint8_t pub_key[PUB_KEY_SIZE], prv_key[PRV_KEY_SIZE], seed[SEED_SIZE]; + memset(seed, 0, SEED_SIZE); + ed25519_create_keypair(pub_key, prv_key, seed); + + // create a Stream containing that identity + uint8_t stored_key[PUB_KEY_SIZE+PRV_KEY_SIZE+SEED_SIZE]; + memcpy(stored_key, pub_key, PUB_KEY_SIZE); + memcpy(stored_key+PUB_KEY_SIZE, prv_key, PRV_KEY_SIZE); + // we're not saving seeds yet + memset(stored_key+PUB_KEY_SIZE+PRV_KEY_SIZE, 0, SEED_SIZE); + ConstantValueStream skf(stored_key, sizeof(stored_key)); + + mesh::LocalIdentity id; + ASSERT_TRUE(id.readFrom(skf)); + ASSERT_EQ(skf.pos, PUB_KEY_SIZE + PRV_KEY_SIZE); + + uint8_t buffer[1024]; + MockStream dump(&buffer[0]); + + ASSERT_TRUE(id.writeTo(dump)); + // Correct serialization is pubkey || prvkey (for now) + ASSERT_TRUE(memcmp(buffer, ZERO_PUB_KEY, PUB_KEY_SIZE)); + ASSERT_TRUE(memcmp(buffer+PUB_KEY_SIZE, ZERO_PRV_KEY, PRV_KEY_SIZE)); + // ... and for the moment, nothing else + ASSERT_EQ(dump.pos, PUB_KEY_SIZE + PRV_KEY_SIZE); + +} diff --git a/test/test_common/test_main.cpp b/test/test_common/test_main.cpp new file mode 100644 index 000000000..7312e6044 --- /dev/null +++ b/test/test_common/test_main.cpp @@ -0,0 +1,31 @@ +#include + +#if defined(ARDUINO) +#include + +void setup() +{ + Serial.begin(115200); + ::testing::InitGoogleTest(); +} + +void loop() +{ + if (RUN_ALL_TESTS()) + ; + delay(1000); +} + +#else + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + // or ::testing::InitGoogleMock(&argc, argv); + + if (RUN_ALL_TESTS()) + ; + return 0; +} + +#endif diff --git a/test/test_common/test_utils.cpp b/test/test_common/test_utils.cpp new file mode 100644 index 000000000..6e1815d3b --- /dev/null +++ b/test/test_common/test_utils.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +#include "mock_streams.h" +#include "Utils.h" + +using namespace mesh; + + +TEST(UtilTests, NopTest) +{ + EXPECT_EQ(1, 1); +} + +TEST(UtilTests, SHA256) +{ + uint8_t hash[257]; + memset(hash, 0, sizeof(hash)); + uint8_t msg[] = "foo"; + mesh::Utils::sha256(hash, (size_t)sizeof(hash), msg, 3); + EXPECT_STREQ((char*)hash, + (char*)"\x2c\x26\xb4\x6b\x68\xff\xc6\x8f\xf9\x9b\x45\x3c\x1d\x30\x41\x34\x13\x42\x2d\x70\x64\x83\xbf\xa0\xf9\x8a\x5e\x88\x62\x66\xe7\xae"); + + memset(hash, 0, sizeof(hash)); + mesh::Utils::sha256(hash, (size_t)sizeof(hash), msg, 1, msg+1, 2); + EXPECT_STREQ((char*)hash, + (char*)"\x2c\x26\xb4\x6b\x68\xff\xc6\x8f\xf9\x9b\x45\x3c\x1d\x30\x41\x34\x13\x42\x2d\x70\x64\x83\xbf\xa0\xf9\x8a\x5e\x88\x62\x66\xe7\xae"); +} + +TEST(UtilTests, toHex) +{ + char dst[20]; + uint8_t src[] = "\x01\x7f\x80\xff"; + mesh::Utils::toHex(&dst[0], src, 4); + EXPECT_STREQ(dst, (const char*)"017F80FF"); +} + +TEST(UtilTests, fromHex) +{ + uint8_t dst[20]; + memset(dst, 0, sizeof(dst)); + uint8_t want[] = "\x01\x7f\x80\xff"; + EXPECT_TRUE(mesh::Utils::fromHex(&dst[0], 4, "017F80FF")); + EXPECT_STREQ((const char *)dst, (const char *)want); +} + +TEST(UtilTests, fromHexWrongSize) +{ + uint8_t dst[20]; + EXPECT_FALSE(mesh::Utils::fromHex(&dst[0], 5, "017F80FF")); +} + +// this should pass but does not, because fromHex() doesn't +// actually validate string contents and silently produces +// zeroes +// TEST(UtilTests, fromHexMalformed) +// { +// uint8_t dst[20]; +// memset(dst, 0, sizeof(dst)); +// EXPECT_FALSE(mesh::Utils::fromHex(&dst[0], 4, "01FG80FF")); +// } + +TEST(UtilTests, isHexChar) +{ + EXPECT_TRUE(mesh::Utils::isHexChar('0')); + EXPECT_TRUE(mesh::Utils::isHexChar('1')); + EXPECT_TRUE(mesh::Utils::isHexChar('9')); + EXPECT_TRUE(mesh::Utils::isHexChar('A')); + EXPECT_TRUE(mesh::Utils::isHexChar('F')); + EXPECT_FALSE(mesh::Utils::isHexChar('G')); + EXPECT_FALSE(mesh::Utils::isHexChar('\xff')); + EXPECT_FALSE(mesh::Utils::isHexChar('\x0')); +} + +TEST(UtilTests, parseTextParts) +{ + char text[10]; + memset(text, 0, sizeof(text)); + const char *parts[10]; + ASSERT_EQ(mesh::Utils::parseTextParts("", &parts[0], 10, ','), 0); + + strcpy(text, "a"); + ASSERT_EQ(mesh::Utils::parseTextParts(text, &parts[0], 10, ','), 1); + ASSERT_STREQ(parts[0], "a"); + + strcpy(text, "b,c"); + ASSERT_EQ(mesh::Utils::parseTextParts(text, &parts[0], 10, ','), 2); + ASSERT_STREQ(parts[0], "b"); + ASSERT_STREQ(parts[1], "c"); + + strcpy(text, "d,,e"); + ASSERT_EQ(mesh::Utils::parseTextParts(text, &parts[0], 10, ','), 3); + ASSERT_STREQ(parts[0], "d"); + ASSERT_STREQ(parts[1], ""); + ASSERT_STREQ(parts[2], "e"); + + // This isn't normal string splitter behavior, but it's intentional + strcpy(text, "f,g,"); + ASSERT_EQ(mesh::Utils::parseTextParts(text, &parts[0], 10, ','), 2); + ASSERT_STREQ(parts[0], "f"); + ASSERT_STREQ(parts[1], "g"); +} + +TEST(UtilTests, printHex) +{ + uint8_t out[10]; + MockStream s(&out[0]); + + const uint8_t src[] = "\x00\x7f\xab\xff"; + mesh::Utils::printHex(s, src, 4); + EXPECT_STREQ((const char *)out, "007FABFF"); +} diff --git a/test/test_native/README.md b/test/test_native/README.md new file mode 100644 index 000000000..68d886a4c --- /dev/null +++ b/test/test_native/README.md @@ -0,0 +1,4 @@ +# Native-only tests + +This directory holds tests that are only relevant when built for the native +platform (e.g. running tests that cannot work on any device). diff --git a/test/test_native/trivial.cpp b/test/test_native/trivial.cpp new file mode 100644 index 000000000..250c7411a --- /dev/null +++ b/test/test_native/trivial.cpp @@ -0,0 +1,36 @@ +#include + +TEST(NopTest, ShouldPass) +{ + EXPECT_EQ(1, 1); +} + +#if defined(ARDUINO) +#include + +void setup() +{ + Serial.begin(115200); + ::testing::InitGoogleTest(); +} + +void loop() +{ + if (RUN_ALL_TESTS()) + ; + delay(1000); +} + +#else + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + // or ::testing::InitGoogleMock(&argc, argv); + + if (RUN_ALL_TESTS()) + ; + return 0; +} + +#endif