diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp index 0c9c97b5b9..bb47339f50 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 313c4c47a0..4f7d9f339a 100755 --- a/build.sh +++ b/build.sh @@ -14,6 +14,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 @@ -89,6 +90,27 @@ get_pio_envs_ending_with_string() { done } +set_build_env() { + # get git commit sha + COMMIT_HASH=$(git rev-parse --short HEAD) + + # set firmware build date + FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y') + + # get FIRMWARE_VERSION, which should be provided by the environment + if [ -z "$FIRMWARE_VERSION" ]; then + echo "FIRMWARE_VERSION must be set in environment" + exit 1 + fi + + # set firmware version string + # 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}\"'" +} + # get platform flag for a given environment # $1 should be the environment name get_platform_for_env() { @@ -120,29 +142,12 @@ build_firmware() { # get env platform for post build actions ENV_PLATFORM=($(get_platform_for_env $1)) - # get git commit sha - COMMIT_HASH=$(git rev-parse --short HEAD) - - # set firmware build date - FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y') - - # get FIRMWARE_VERSION, which should be provided by the environment - if [ -z "$FIRMWARE_VERSION" ]; then - echo "FIRMWARE_VERSION must be set in environment" - exit 1 - fi - - # set firmware version string - # e.g: v1.0.0-abcdef - FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION}-${COMMIT_HASH}" + set_build_env # 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}\"'" - # disable debug flags if requested disable_debug_flags @@ -245,6 +250,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 @@ -275,4 +292,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 864e5e1ffe..287b195e50 100644 --- a/platformio.ini +++ b/platformio.ini @@ -151,3 +151,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 ea546274da..b4f19e7231 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -105,8 +105,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) { @@ -140,4 +140,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/MeshCore.h b/src/MeshCore.h index 2db1d4c3ec..2a786f2228 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #define MAX_HASH_SIZE 8 diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 0e05620ec2..6774e2576b 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/ClientACL.cpp b/src/helpers/ClientACL.cpp index 1282382737..eba60526e7 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 b71afc72e2..e5e0622bab 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -125,7 +125,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 dc85d69cdd..e598777240 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 d0d7ee457e..021f38026a 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/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7b8399e260..5fbc3a1dc5 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -59,7 +59,7 @@ static const char* skip_hash(const char* name) { } 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) @@ -287,11 +287,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out.print(' '); } - if (parent->flags & REGION_DENY_FLOOD) { - out.printf("%s%s\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); - } else { - out.printf("%s%s F\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); - } + out.print(skip_hash(parent->name)); + out.print(parent->id == home_id ? "^" : ""); + out.println(parent->flags & REGION_DENY_FLOOD ? "" : " F"); for (int i = 0; i < num_regions; i++) { auto r = ®ions[i]; diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 89a174c228..acdbe08ea6 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -1,6 +1,10 @@ #pragma once +#ifdef NATIVE_PLATFORM +struct CayenneLPP; // Work around a clash between CayenneLPP & ArduinoNative +#else #include +#endif #include "sensors/LocationProvider.h" #define TELEM_PERM_BASE 0x01 // 'base' permission includes battery diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index a42e060a4c..50758e56ba 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 0000000000..404c747fe2 --- /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 0000000000..f985da5ebc --- /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 0000000000..76c3e17162 --- /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 0000000000..bd164ff2e4 --- /dev/null +++ b/test/test_common/mock_streams.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include + +MATCHER_P2(MemcmpAs, cb, len, "") { + bool result = true; + for (size_t i = 0; i < len; i++) { + if (cb[i] != arg[i]) { + *result_listener << "element #" << std::dec << i + << " differ: " << " 0x" << std::hex << static_cast(arg[i]) + << " vs " << " 0x" << std::hex << static_cast(cb[i]) + << "\n"; + result = false; + } else { + *result_listener << "element #" << std::dec << i + << " matches: " << " 0x" << std::hex << static_cast(arg[i]) << "\n"; + } + } + return result; +} + +class MockStream : public Stream { +public: + uint8_t *buffer; + size_t pos, cap; + bool own_buffer; + + // internal buffer; can expand + MockStream() + :pos(0),cap(0),own_buffer(true) + { + buffer = {0}; + } + + // external buffer; assumed infinite, can't expand + MockStream(uint8_t *b) + :buffer(b),pos(0),cap(SIZE_MAX),own_buffer(false) + { + buffer[0] = 0; + } + + // external buffer, known size, can't expand + MockStream(uint8_t *b, size_t sz) + :buffer(b),pos(0),cap(sz),own_buffer(false) + { + if (cap>0) buffer[0] = 0; + } + + virtual ~MockStream() { + if (own_buffer && buffer != nullptr) { + free(buffer); + } + } + + void clear() { + pos = 0; + if (cap>0) { + buffer[0] = 0; + } + } + + size_t write(uint8_t c) { + if (!expand(pos+1)) return 0; + buffer[pos++] = c; + if (cap>pos) buffer[pos] = 0; + return 1; + } + + size_t write(const uint8_t *src, size_t len) { + if (!expand(pos+len)) return 0; + memcpy(buffer+pos, src, len); + pos += len; + if (cap>pos) buffer[pos] = 0; + return len; + } + + MOCK_METHOD(int, available, (), (override)); + MOCK_METHOD(int, availableForWrite, (), (override)); + MOCK_METHOD(int, read, (), (override)); + MOCK_METHOD(int, peek, (), (override)); + +private: + bool expand(size_t newsize) { + if (newsize > cap) { + if (!own_buffer) return false; + newsize = (newsize+0x1f) & (~0x1f); // round up to next 32 + uint8_t *exp = (uint8_t *)realloc(buffer, newsize); + if (exp == nullptr) { + return false; + } + buffer = exp; + cap = newsize; + } + return true; + } +}; + +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 0000000000..f61b301af6 --- /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; + ASSERT_TRUE(id.writeTo(bs)); + ASSERT_EQ(memcmp(bs.buffer, pubhex, 32), 0); +} + +#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\xDA\x29" +#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); + + MockStream dump; + ASSERT_TRUE(id.writeTo(dump)); + // Correct serialization is pubkey || prvkey (for now) + ASSERT_EQ(dump.pos, PUB_KEY_SIZE + PRV_KEY_SIZE); + EXPECT_THAT(dump.buffer, MemcmpAs((uint8_t *)ZERO_PUB_KEY, PUB_KEY_SIZE)); + // ASSERT_EQ(memcmp(buffer, ZERO_PUB_KEY, PUB_KEY_SIZE), 0); + EXPECT_THAT(dump.buffer+PUB_KEY_SIZE, + MemcmpAs((uint8_t*)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 0000000000..7312e60449 --- /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_mocks.cpp b/test/test_common/test_mocks.cpp new file mode 100644 index 0000000000..70c6d0a82e --- /dev/null +++ b/test/test_common/test_mocks.cpp @@ -0,0 +1,44 @@ +#include + +#include "mock_streams.h" + +//using namespace testing; + +TEST(MockStreamTests, ExternalBuffer) +{ + uint8_t buf[21]; + MockStream s(buf); + s.write((uint8_t*)"0123456789", 10); + ASSERT_EQ(s.pos, 10); + ASSERT_STREQ((const char *)s.buffer, (const char*)"0123456789"); + s.write((uint8_t*)"ABCDEFGHIJ", 10); + ASSERT_EQ(s.pos, 20); + EXPECT_THAT(buf, MemcmpAs("0123456789ABCDEFGHIJ", 20)); + + MockStream s2(buf, 4); + ASSERT_EQ(s2.write((uint8_t *)"12345", 5), 0); + ASSERT_EQ(s2.pos, 0); + ASSERT_EQ(s2.cap, 4); + ASSERT_EQ(s2.write((uint8_t *)"1234", 4), 4); + ASSERT_EQ(s2.pos, 4); + EXPECT_THAT(buf, MemcmpAs("1234", 4)); +} + +TEST(MockStreamTests, InternalBuffer) +{ + MockStream s1; + uint8_t z[65]; + memset(z, 0, sizeof(z)); + s1.write(z, sizeof(z)); + ASSERT_EQ(s1.pos, sizeof(z)); + ASSERT_GE(s1.cap, sizeof(z)); + + MockStream s2; + for (int i = 0; i < 1024; i++) { + s2.write('A'); + } + ASSERT_EQ(s2.pos, 1024); + ASSERT_GE(s2.cap, 1024); + ASSERT_EQ(s2.buffer[1023], 'A'); +} + diff --git a/test/test_common/test_utils.cpp b/test/test_common/test_utils.cpp new file mode 100644 index 0000000000..763c839d07 --- /dev/null +++ b/test/test_common/test_utils.cpp @@ -0,0 +1,112 @@ +#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) +{ + MockStream s; + const uint8_t src[] = "\x00\x7f\xab\xff"; + mesh::Utils::printHex(s, src, 4); + EXPECT_STREQ((const char *)s.buffer, "007FABFF"); +} diff --git a/test/test_native/README.md b/test/test_native/README.md new file mode 100644 index 0000000000..68d886a4c2 --- /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 0000000000..250c7411a5 --- /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