Skip to content

Commit

Permalink
Heavy refactoring of utils::*
Browse files Browse the repository at this point in the history
Doxygen for utils::trim() functions and variants.
Removes utils::fillDevInfoFromFirmwareImage() and utils::parseStringVersion()
Adds utils::getHardwareAsString() to return the hardware type+version from dev_info_t.
Adds utils::getFirmwareAsString() to return the firmware version and type from dev_info_t.
Adds utils::getBuildAsString() to return build date, time, commit, etc from dev_info_t.
Refactored utils::devInfoToString() to leverage new function and provide consistent formatting.
Refactors utils::devInfoFromString() now using regex patterns and breaking the "dev info" string into components is much more robust, and flexible - can now parse arbitrary strings of various dev info components intelligently, including from filenames, etc.
Parsing/Formatting devInfo now uses DV_BIT_* flags to indicate which components were parsed, or which components should be printed.
Adds new utils::split_from_string() which handles parsing a delimited string of types, using templates and a lambda to interpret values of type (used to parse versions, IP address, etc).
Adds unit-tests for test_utils.cpp for testing utils::* functions.
  • Loading branch information
kylemallory committed Jan 4, 2025
1 parent b179afd commit 894d638
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 229 deletions.
424 changes: 256 additions & 168 deletions src/util/util.cpp

Large diffs are not rendered by default.

71 changes: 59 additions & 12 deletions src/util/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
* @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved.
*/

#ifndef INERTIALSENSE_SDK__UTIL_H
#define INERTIALSENSE_SDK__UTIL_H
#ifndef IS_SDK__UTIL_H
#define IS_SDK__UTIL_H

#include <string>

Check failure on line 12 in src/util/util.h

View workflow job for this annotation

GitHub Actions / cpp-linter

src/util/util.h:12:10 [clang-diagnostic-error]

'string' file not found
#include <vector>
#include <memory>
#include <stdexcept>
#include <sstream>
#include <functional>

#include "ISComm.h"

Expand All @@ -27,7 +28,7 @@ namespace utils {
* @param t a set of characters, which will be removed if they exists
* @return a reference to the input string.
*/
std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; };
std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v");

/**
* @brief Trims right-side/trailing characters (whitespace by default) from the passed string; this modifies the string, in place.
Expand All @@ -38,7 +39,7 @@ namespace utils {
* @param t a set of characters, which will be removed if they exists
* @return a reference to the input string.
*/
std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; };
std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v");

/**
* @brief Trims left-size/leading and right-side/trailing characters (whitespace by default) from the passed string; this modifies
Expand All @@ -48,7 +49,7 @@ namespace utils {
* @param t a set of characters, which will be removed if they exists
* @return a reference to the input string.
*/
std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); };
std::string& trim(std::string& s, const char* t = " \t\n\r\f\v");


/**
Expand All @@ -57,23 +58,23 @@ namespace utils {
* @param t a set of characters, which will be removed if they exists
* @return the modified/trimmed copy of the original input string.
*/
std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v") { return ltrim(s, t); };
std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v");

/**
* rtrim() equivalent which makes a new copy, and does not modify the original
* @param s the string to be trimmed
* @param t a set of characters, which will be removed if they exists
* @return the modified/trimmed copy of the original input string.
*/
std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v") { return rtrim(s, t); };
std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v");

/**
* trim() equivalent which makes a new copy, and does not modify the original
* @param s the string to be trimmed
* @param t a set of characters, which will be removed if they exists
* @return the modified/trimmed copy of the original input string.
*/
std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v") { return trim(s, t); };
std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v");

/**
* Performs sprintf-type formatting, using a std:string for the format string, and outputting a std::string
Expand Down Expand Up @@ -132,6 +133,37 @@ namespace utils {
return s.str();
}

/**
* Parses a string of delimited values (ie, x.x.x.x) and populate the values into a passed
* std::array of type T and size N. If the string contains fewer than N numbers elements,
* the remaining elements are not assigned (you should initialize vOut before calling this
* function). At most N elements will be parsed. The lamba is used to convert the parsed
* substring into the value of type T.
*
* This is primarily used to parse versions and ip address, and the template provides
* default values that support this usage (T = uint8_t, N = 4). If you wish to use it for
* other types, remember to set the template parameters.
*
* @param s the string to split
* @param vOut a std::array<T,n> each element containing a parsed value.
* @param d the delimiters to use when splitting (if multiple, will be separated on ANY)
* (defaults to '.')
* @param lambda a lambda used to convert the parsed substring to a value of type T (defaults
* to stoi(), returning a decimal
* @return returns the number of elements parsed.
*/
template <typename T=uint8_t, int N=4>
int split_from_string(const std::string& s, T vOut[N], const char* d = ".", std::function<T(const std::string&)> lambda = [](const std::string& ss) -> T { return stoi(ss); } ) {
long unsigned int start = 0, n = 0, end = 0;
while ( (end = s.find_first_of(d, start)) != std::string::npos ) {
vOut[n++] = lambda(s.substr(start, end - start));
start = end + 1;
}
vOut[n++] = lambda(s.substr(start));
return n;
}


/**
* Splits the passed string into a vector of strings, delimited by delimiter.
* @param str the string to be split
Expand All @@ -143,15 +175,30 @@ namespace utils {
std::string raw_hexdump(const char* raw_data, int bytesLen, int bytesPerLine);
std::string did_hexdump(const char *raw_data, const p_data_hdr_t& hdr, int bytesPerLine);

enum dev_info_fmt_e : uint16_t {
DV_BIT_SERIALNO = 0x001, //!< serial number
DV_BIT_FIRMWARE_VER = 0x002, //!< firmware version w/ optional release type
DV_BIT_HARDWARE_INFO = 0x004, //!< hdw type & version
DV_BIT_BUILD_KEY = 0x008, //!< build key and build number
DV_BIT_BUILD_DATE = 0x010, //!< build date
DV_BIT_BUILD_TIME = 0x020, //!< build time
DV_BIT_BUILD_COMMIT = 0x040, //!< repo hash & build status (dirty)
DV_BIT_ADDITIONAL_INFO = 0x100, //!< additional info
};

std::string getHardwareAsString(const dev_info_t& devInfo);
std::string getFirmwareAsString(const dev_info_t& devInfo);
std::string getBuildAsString(const dev_info_t& devInfo, uint16_t flags = -1);

std::string getCurrentTimestamp();
std::string devInfoToString(const dev_info_t& devInfo);
bool devInfoFromString(const std::string& str, dev_info_t& devInfo);
std::string devInfoToString(const dev_info_t& devInfo, uint16_t flags = -1);
uint16_t devInfoFromString(const std::string& str, dev_info_t& devInfo);
int parseStringVersion(const std::string& vIn, uint8_t vOut[4]);
uint64_t intDateTimeFromDevInfo(const dev_info_t& a, bool useMillis = false);
bool fillDevInfoFromFirmwareImage(std::string imgFilename, dev_info_t& devInfo);
bool devInfoFromFirmwareImage(std::string imgFilename, dev_info_t& devInfo);
bool isDevInfoCompatible(const dev_info_t& a, const dev_info_t& b);
bool compareFirmwareVersions(const dev_info_t& a, const dev_info_t& b);
};


#endif //INERTIALSENSE_SDK__UTIL_H
#endif //IS_SDK__UTIL_H
2 changes: 1 addition & 1 deletion tests/test_ISFirmwarePackage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

#include <gtest/gtest.h>

Check failure on line 12 in tests/test_ISFirmwarePackage.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

tests/test_ISFirmwarePackage.cpp:12:10 [clang-diagnostic-error]

'gtest/gtest.h' file not found
#include "gtest_helpers.h"
#include "test_utils.h"
#include "test_data_utils.h"

#include "ISFileManager.h"
#include "ISFirmwareUpdater.h"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ISFirmwareUpdate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <gtest/gtest.h>

Check failure on line 5 in tests/test_ISFirmwareUpdate.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

tests/test_ISFirmwareUpdate.cpp:5:10 [clang-diagnostic-error]

'gtest/gtest.h' file not found
#include "gtest_helpers.h"

#include <stdio.h>
#include <cstdio>
#include "../protocol/FirmwareUpdate.h"
#include "miniz.h"
#include "md5.h"
Expand Down
28 changes: 27 additions & 1 deletion tests/test_data_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,6 @@ uint32_t GenerateRawLogData(std::list<std::vector<uint8_t>*>& msgs, float logSiz
return runningSize;
}


bool AddDataToStream(uint8_t *buffer, int bufferSize, int &streamSize, uint8_t *data, int dataSize)
{
if (streamSize + dataSize < bufferSize)
Expand Down Expand Up @@ -715,6 +714,33 @@ int GenerateDataStream(uint8_t *buffer, int bufferSize, eTestGenDataOptions opti
return streamSize;
}

std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines)
{
std::vector<std::string> words= {"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat"};
int numSentences = RAND_RANGE(minSentences, maxSentences);
int numWords = RAND_RANGE(minWords, maxWords);

std::string sb;
for (int p = 0; p < numLines; p++)
{
for (int s = 0; s < numSentences; s++)
{
for (int w = 0; w < numWords; w++)
{
if (w > 0) { sb.append(" "); }
int word_idx = RAND_RANGE(0, words.size() - 1);
std::string word = words[ word_idx ];
if (w == 0) { word[0] = toupper(word[0]); }
sb.append(word);
}
sb.append(". ");
}
if (p < numLines-1) sb.append("\n");
}
return sb;
}


static int dummyIsbProtocolHandler(p_data_t* data, port_handle_t port) { return 0; }
static int dummyGenericProtocolHandler(const unsigned char* msg, int msgSize, port_handle_t port) { return 0; }

Expand Down
24 changes: 19 additions & 5 deletions tests/test_data_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
* @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved.
*/

#ifndef IS_SDK_UNIT_TESTS_TEST_UTILS_H
#define IS_SDK_UNIT_TESTS_TEST_UTILS_H
#ifndef IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H
#define IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H

#include <list>

Check failure on line 12 in tests/test_data_utils.h

View workflow job for this annotation

GitHub Actions / cpp-linter

tests/test_data_utils.h:12:10 [clang-diagnostic-error]

'list' file not found

Expand All @@ -16,6 +16,11 @@
#include "ISLogger.h"
#include "time_conversion.h"

/**
* A macro to generate a random integer number between two values.
*/
#define RAND_RANGE(MIN, MAX) (MIN + rand() / (RAND_MAX / (MAX - MIN + 1) + 1))

typedef struct
{
protocol_type_t ptype; // Data start byte
Expand Down Expand Up @@ -45,8 +50,17 @@ int GenerateDataStream(uint8_t *buffer, int bufferSize, eTestGenDataOptions opti
uint32_t GenerateRawLogData(std::list<std::vector<uint8_t>*>& msgs, float logSizeMB=20, eTestGenDataOptions options=GEN_LOG_OPTIONS_NONE);


/**
* Generates a string of some arbitrary length, containing some number of random sentences/paragraphs of "Lorem Ipsum" text.
* This is primarily used to generate files of random content.
* @param minWords the minimum number of words per sentence to generate
* @param maxWords the maximum number of words per sentence to generate
* @param minSentences the minimum number of sentences per paragraph to generate
* @param maxSentences the maximum number of sentences per paragraph to generate
* @param numLines the actual number of lines (paragraphs?) to generate (this is NOT random).
* @return a std::string containing the generated content
*/
std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines);




#endif //IS_SDK_UNIT_TESTS_TEST_UTILS_H
#endif //IS_SDK_UNIT_TESTS_TEST_DATA_UTILS_H
134 changes: 110 additions & 24 deletions tests/test_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,123 @@
* @file test_utils.cpp
* @brief a collection of functions and classes that might be useful when writing/running unit tests
*
* Also includes a few unit tests for testing utils::*
*
* @author Kyle Mallory on 1/18/24.
* @copyright Copyright (c) 2024 Inertial Sense, Inc. All rights reserved.
*/

#include <string>
#include <vector>

#include <gtest/gtest.h>

Check failure on line 14 in tests/test_utils.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

tests/test_utils.cpp:14:10 [clang-diagnostic-error]

'gtest/gtest.h' file not found

#include "test_utils.h"
#include "util.h"

TEST(test_utils, Format_devInfo) {
dev_info_t devInfo = { };

std::string origStr = "SN102934: IMX-5.0 fw2.1.7-devel.128 E753C.83 deadbeaf 2024-09-18 15:35:43 (p12 cmp)"; // with a comma after the hardware info
uint16_t dvBits = utils::devInfoFromString(origStr, devInfo);

// Test the output of various alternative formatting
std::string devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_SERIALNO);
EXPECT_EQ(devInfoStr, "SN102934:");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_HARDWARE_INFO);
EXPECT_EQ(devInfoStr, "IMX-5.0");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_FIRMWARE_VER);
EXPECT_EQ(devInfoStr, "fw2.1.7-devel.128");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_KEY);
EXPECT_EQ(devInfoStr, "e753c.83");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_COMMIT);
EXPECT_EQ(devInfoStr, "deadbeaf");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_DATE);
EXPECT_EQ(devInfoStr, "2024-09-18");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_TIME);
EXPECT_EQ(devInfoStr, "15:35:43");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_ADDITIONAL_INFO);
EXPECT_EQ(devInfoStr, "(p12 cmp)");


devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_SERIALNO | utils::DV_BIT_HARDWARE_INFO | utils::DV_BIT_FIRMWARE_VER);
EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-devel.128");

devInfoStr = utils::devInfoToString(devInfo, utils::DV_BIT_BUILD_DATE | utils::DV_BIT_BUILD_TIME);
EXPECT_EQ(devInfoStr, "2024-09-18 15:35:43");

devInfoStr = utils::devInfoToString(devInfo);
EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-devel.128 deadbeaf e753c.83 2024-09-18 15:35:43 (p12 cmp)");
}


TEST(test_utils, FormatAndParse_devInfo) {
dev_info_t devInfo = { };

std::string origStr;
uint16_t dvBits = 0;

// test legacy formats
origStr = "SN102934: IMX-5.0, fw2.1.7 b83c 2024-09-18 15:35:43 (p12 cmp)"; // with a comma after the hardware info
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl;
std::string devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-rc b83 2024-09-18 15:35:43 (p12 cmp)");

origStr = "SN032014: uINS-3.2 fw1.0.9 b12 2022-11-03 00:31:18 (p0)"; // with-out the comma after the hardware info
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN032014: uINS-3.2 fw1.0.9 b12 2022-11-03 00:31:18 (p0)");

origStr = "SN23914: GPX-1.0 fw0.1.12 b0 2023-11-03 9:09:13 ()";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN023914: GPX-1.0 fw0.1.12 2023-11-03 09:09:13");

origStr = "SN505192: IMX-5.1.0 fw0.1.12.8 b123 2025-07-31 13:38:22";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 63) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN505192: IMX-5.1 fw0.1.12.8 b123 2025-07-31 13:38:22");

origStr = "SN98712178: GPX-1.0.4 fw2.18.5.3 b123c 2025-08-22 18:56:03";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 63) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN98712178: GPX-1.0.4 fw2.18.5-rc.3 b123 2025-08-22 18:56:03");


// newer 2.1.0 format
origStr = "SN102934: IMX-5.0 fw2.1.7-rc.83 9d849cba 1739C.5 2024-09-18 15:35:43 (p12 cmp)";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 383) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7-rc.83 9d849cba 1739c.5 2024-09-18 15:35:43 (p12 cmp)");

origStr = "SN102934: IMX-5.0 fw2.1.7 b83 2024-09-18 15:35:43 (p12 cmp)";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 319) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "SN102934: IMX-5.0 fw2.1.7 b83 2024-09-18 15:35:43 (p12 cmp)");
}

TEST(test_utils, parse_devInfo_from_filename) {
dev_info_t devInfo = { };

std::string origStr;
uint16_t dvBits = 0;

// test legacy formats
origStr = "IS_IMX-5_v2.0.6_b80_2024-05-30_145347.hex";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 62) << "Unable to parse '" << origStr << "'" << std::endl;
std::string devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "IMX-5.0 fw2.0.6 b80 2024-05-30 14:53:47");


std::string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numLines)
{
std::vector<std::string> words= {"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat"};
int numSentences = RAND_RANGE(minSentences, maxSentences);
int numWords = RAND_RANGE(minWords, maxWords);

std::string sb;
for (int p = 0; p < numLines; p++)
{
for (int s = 0; s < numSentences; s++)
{
for (int w = 0; w < numWords; w++)
{
if (w > 0) { sb.append(" "); }
int word_idx = RAND_RANGE(0, words.size() - 1);
std::string word = words[ word_idx ];
if (w == 0) { word[0] = toupper(word[0]); }
sb.append(word);
}
sb.append(". ");
}
if (p < numLines-1) sb.append("\n");
}
return sb;
origStr = "IS_GPX-1_v2.3.0-snap.153+2024-12-17_002837.encrypted.bin";
EXPECT_EQ(dvBits = utils::devInfoFromString(origStr, devInfo), 54) << "Unable to parse '" << origStr << "'" << std::endl;
devInfoStr = utils::devInfoToString(devInfo, dvBits);
EXPECT_EQ(devInfoStr, "GPX-1.0 fw2.3.0-snap.153 2024-12-17 00:28:37");
}
Loading

0 comments on commit 894d638

Please sign in to comment.