mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-03 01:50:24 +08:00
Adding doc and tests (#1492)
* added doc and tests for util2d.h * updated cmake-ros ci * Added util3d.h doc and tests * util3d_transforms.h: Added doc and tests * util3d_filtering.h: started doc and test * util3d_filtering.h: more tests and doc * Added more doc/tests * finished util3d_filtering doc and tests * added test for util2d::depthBleedingFiltering * Added util3d_registration tests * Added util3d_features.h doc/tests * added doc/tests for util3d_correspondences.h * added doc/gtest for util3d_mapping.h (missing hpp functions) * finished testing util3d_mapping.hpp * Added util3d_motion_estimation.h tests (2D->3D done) * finished util3d_motion_estimation.h tests * minimal util3d_surface.h * Added Transform and VisualWord tests * Added doc for CameraModel and StereoCameraModel * Added more logs in ros ci * Passing tests on fical * improved all devcontainer * added devcontainer kilted, fixed source setup.bash, removed ldconfig in ros-cmake workflow * cleanup * source ros * Added utilite tests * Added testing to appveyor, github actions cancellable on re-commit on same branch * appveyor testing without all targets * appveyor: specifying ALL_BUILD target * Fixed Util2dTest.NMSImageBoundsRespected test * Fixing PCL Indices error on old pcl * Added VWDictionary tests and doc. Fixed LSH not working (fix from https://github.com/flann-lib/flann/pull/472 * fixing some appveyor CI errors, added test to check dictionary serialization against all type * Added StereoDense, StereoBM and StereoSGBM doc and tests * Added Stereo tests * Added CameraModel and StereoCameraModel tests * Added doc and test for Statistics * Added doc/tests for Signature * Added doc/test for SensorEvent, added doc for SensorCaptureInfo * Added doc to SensorData * Added SensorData tests * Added SensorCapture and SensorCaptureThread doc and tests * fixed sensordata test * updated SSC test and doc * Added doc and tests for BayesFilter class * Enabled testing on mac, updated windows testing like on linux * added test_link * fixed unresolved on windows * fixed ThreadHandle error on macos ci * Added GPS and GeodeticCoords tests * Added tests for compression * Added Odometry tests (base class only) * Added DBDriver tests * Added coverage report * uniformized test names * fixing concurancy and coverage ci * dont built tools, examples and app for coverage build * fixed report tool rebuilt without qt compilation error * updated coverage option * updated coverage config * added doc CI job * fixing windows and mac ci errors * Added DBDriverSqlite3 tests * Added IMU tests * Added Graph tests * fixing flaky macos test * Added IMUThread and IMUFilter tests * Added Landmarks tests * Added LASWriter tests * fixing seed flaky test * fixing flaky macos timing tests * Added LocalGrid tests * Added LocalGridMaker tests * fixing ci errors * Added GlobalMap tests * Added doc for EnvSensor * Added Features2D tests * Added Registration tests * Added RegistrationVis tests * Added doc for Rtabmap and Memory classes * Added Memory and Rtabmap tests * making some tests less flaky * lcov 1.14 support * updated compatible tool arguments * Added integration tests (RGB-D, Stereo, Lidar2d, Lidar3d) * More octomap checks * Refactored how/when python interpretor is created to simplify library usage * Added python tests * fixed some flaky tests * suppressed some third party related warnings * fixed ceres tests * more flaky fixes * Fixing tests without libpointmatcher * Added RANSAC rejection filter to PCL ICP * fixing multi platform flakiness * Added test to detect regression * Fixing windows pcl link error * fixed some macos flakiness * bigger 2D2D registration error on opencv 4.6.0 * flakiness * fixing flaky tests on windows and mac * flaky thread test on slow mac VM * windows slow test * fixing more ci erros * fxing temp dir on windows * Added Optimizer tests and discovered some bugs (fixed) * fixing flaky tests in mac and windows * Added Optimizer doc * Added GTSAM BA, updated Ceres to use g2o ba parameters. Renamed g2o's ba related parameters to Optimizer group and used by both gtsam and ceres. * fixing build without gtsam * fixing home dir * fixing python ci isssues * Added multicam ba tests * Added Ceres multicam BA support * Aligned BundleAdjustment parameters with Optimizer/Strategy to avoid confusion in the code * Added BA integration test * Added robust graph optimization integration test * Added loop3it test * Added stereo20Hz test * Added smartfactor gtsam * Fixed bugged check and warn if python didn't return any descriptors * Fixing gtsam version build issues * fixing tilt on windows ci * loosing ceres integration test for ci * mac ci flakiness * updating missing param in gui * updating test bound for mac * added appearance-based tests, set min gftt quality to quality level * testing more stuff * improving features2d tests * ci flakiness * fixing flaky ci * ci fixes * flaky fixes * Added RegistrationIcp tests * Added icp integration test with real-worl corridor like env * intermediate nodes * fixing enum * Updated test to catch #1714 * Fixed 2d corridor failing on pcl * flaky pnp test * flaky brisk test * Set rtabmap_integration test as long * updating loop closure test * flaky ci tests * TEsting roundtrip g2o/toro save/load * loosing test bound * fixed cuda capable checks * flaky tests * Debugging test hanging * more debugging stuff * updating limit * windows: disabled cuda on ci to avoid incompatible driver issue. Fixing a bad test mem allocation * trying fixing cuda hanging issue * fixing ci flakyness * flaky tests * Updated BOW flaky tests by checking min precision/recall instead of recall@100precision. Fixed signature test * CameraModel::load() test initRectificationMap param * test dbdriver load dictionary idsOnly * Memory: test keepLinkedInDb param * added dummyDictionary tests * test intermediate nodes count * Added MarkerDetector tests * reverted breaking change of UMutex and USemaphore * Features2d: fixed compiltion warnings with clang about override * clang warnings * fixing test build with pcl 1.8 * g2o and gtsam build errors on android * opencv5 test fixes * disabled testing for ios and android builds * normalized endline characters for easier diff * added LF CRLF rule * bump 0.23.10. fixing doc version * Publish rtabmap website doc from ci * fixing MSCVC build error * macos icp flaky test * fixing ceres macos test bound * ficing more flaky tests * fixing opencv5 related test errors. Also fixed an actual bug in ENU_WGS84ToGeocentric_WGS84() * added comment about mrpt change * removed rosdoc2 (will add it for rtabmap_ros later) * fixing website style * updated download links * locally deployable website with api * sweep doxygen issues * improved/revised doxygen main pages * removed examples empty page * Updated doxygen style * more concise doxygen groups * added api link on main readme * fixing utilite test error * fixing CommonFilteringGroundNormalsUp test * updated precisionRecall test bounds for Freak and brief descriptors * fixing scale check in ba tests * disabled tests on windows cuda build (missing dlls amd runner cannot test cuda anyway) * ceres: missing suitesparse dep in windows ci * adjusting recall thr for fast/freak * ficing more flaky tests * fixing flaky tests * disabled coverage in ros ci * Enable integration tests for ros ci jobs * loosing up some threshold for failing tests * trigger cache * fixing test data in ros ci. Updated flaky test for mac * slaking some test limit * Fixed rtabmap-detectMoreLoopClosures inverted output value * loosing up sift recall on mac * optimizer re-ordered distribution for reproducible results (mac g2o) * macos dump test crash log * combining all tests to save time on shared library reload. Also fixed Logs with missing arguments. * Added ENABLE_FORMAT_ERRORS cmake option * do test only one time * fixed all format warnings * format security android build errors * less verbose tests * updated ImuUThread test * fixed a log * Fixed libpointmatcher 2d normals eigen issue * Fixing libpointmatcher conversion issues * fixing libpointmatcher test on windows ci * cleanup comments, relax some test thr * disabled sequoia-intel ci build (too flaky, would need extensive testing directly on that machine)
This commit is contained in:
@@ -8,3 +8,7 @@ ENDIF(UNIX AND NOT ANDROID)
|
||||
ADD_SUBDIRECTORY( src )
|
||||
|
||||
ADD_SUBDIRECTORY( resource_generator )
|
||||
|
||||
if(BUILD_TESTING)
|
||||
ADD_SUBDIRECTORY( test )
|
||||
endif()
|
||||
|
||||
@@ -93,27 +93,80 @@ std::string UTILITE_EXPORT uToUpperCase(const std::string & str);
|
||||
std::string UTILITE_EXPORT uToLowerCase(const std::string & str);
|
||||
|
||||
/**
|
||||
* Convert a number (unsigned int) to a string.
|
||||
* @param number the number to convert in a string
|
||||
* @return the string
|
||||
* Convert an unsigned integer to a string.
|
||||
*
|
||||
* This function converts an unsigned integer value to its string representation.
|
||||
* The conversion uses standard decimal notation.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string str = uNumber2Str(42u);
|
||||
* // str contains "42"
|
||||
* @endcode
|
||||
*
|
||||
* @param number the unsigned integer number to convert to a string
|
||||
* @return the string representation of the number
|
||||
*/
|
||||
std::string UTILITE_EXPORT uNumber2Str(unsigned int number);
|
||||
|
||||
/**
|
||||
* Convert a number (int) to a string.
|
||||
* @param number the number to convert in a string
|
||||
* @return the string
|
||||
* Convert a signed integer to a string.
|
||||
*
|
||||
* This function converts a signed integer value to its string representation.
|
||||
* Negative numbers will include a minus sign prefix.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string str = uNumber2Str(-42);
|
||||
* // str contains "-42"
|
||||
* @endcode
|
||||
*
|
||||
* @param number the integer number to convert to a string
|
||||
* @return the string representation of the number (includes minus sign for negative values)
|
||||
*/
|
||||
std::string UTILITE_EXPORT uNumber2Str(int number);
|
||||
|
||||
/**
|
||||
* Convert a number (float) to a string.
|
||||
* @param number the number to convert in a string
|
||||
* @return the string
|
||||
* Convert a float to a string.
|
||||
*
|
||||
* This function converts a floating-point value to its string representation
|
||||
* with configurable precision and format.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string str1 = uNumber2Str(3.14159f, 2, false);
|
||||
* // str1 contains "3.14" (scientific notation if needed)
|
||||
*
|
||||
* std::string str2 = uNumber2Str(3.14159f, 2, true);
|
||||
* // str2 contains "3.14" (fixed decimal notation)
|
||||
* @endcode
|
||||
*
|
||||
* @param number the float number to convert to a string
|
||||
* @param precision the number of decimal places to display (default: 6)
|
||||
* @param fixed if true, use fixed decimal notation; if false, use scientific notation when appropriate (default: false)
|
||||
* @return the string representation of the number
|
||||
*/
|
||||
std::string UTILITE_EXPORT uNumber2Str(float number, int precision=6, bool fixed = false);
|
||||
|
||||
/**
|
||||
* Convert a number (double) to a string.
|
||||
* @param number the number to convert in a string
|
||||
* @return the string
|
||||
* Convert a double to a string.
|
||||
*
|
||||
* This function converts a double-precision floating-point value to its string
|
||||
* representation with configurable precision and format.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string str1 = uNumber2Str(3.141592653589793, 4, false);
|
||||
* // str1 contains "3.1416" (scientific notation if needed)
|
||||
*
|
||||
* std::string str2 = uNumber2Str(3.141592653589793, 4, true);
|
||||
* // str2 contains "3.1416" (fixed decimal notation)
|
||||
* @endcode
|
||||
*
|
||||
* @param number the double number to convert to a string
|
||||
* @param precision the number of decimal places to display (default: 6)
|
||||
* @param fixed if true, use fixed decimal notation; if false, use scientific notation when appropriate (default: false)
|
||||
* @return the string representation of the number
|
||||
*/
|
||||
std::string UTILITE_EXPORT uNumber2Str(double number, int precision=6, bool fixed = false);
|
||||
|
||||
@@ -271,7 +324,12 @@ std::string UTILITE_EXPORT uFormatv (const char *fmt, va_list ap);
|
||||
/**
|
||||
* Format a string like printf, and return it as a std::string
|
||||
*/
|
||||
std::string UTILITE_EXPORT uFormat (const char *fmt, ...);
|
||||
std::string UTILITE_EXPORT uFormat (const char *fmt, ...)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// Compiler-checked format string (see the note on ULogger::write).
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
;
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
|
||||
@@ -318,7 +318,11 @@ public:
|
||||
|
||||
/**
|
||||
* Set is the logger buffers messages, default false. When true, the messages are
|
||||
* buffered until the application is closed or ULogger::flush() is called.
|
||||
* buffered until the application is closed or ULogger::flush() is called.
|
||||
*
|
||||
* Note that if logging to a file, flush doesn't mean that messages are written to file right
|
||||
* away. To write everything on disk, set logger type to kTypeNoLog.
|
||||
*
|
||||
* @see ULogger::flush()
|
||||
* @param buffered true to buffer messages, otherwise set to false.
|
||||
*/
|
||||
@@ -374,14 +378,6 @@ public:
|
||||
*/
|
||||
static void flush();
|
||||
|
||||
/**
|
||||
* Write a message directly to logger without level handling.
|
||||
* @param msg the message to write.
|
||||
* @param ... the variable arguments
|
||||
* @deprecated use UDEBUG(), UINFO(), UWARN(), UERROR() or UFATAL()
|
||||
*/
|
||||
static void write(const char* msg, ...);
|
||||
|
||||
/*
|
||||
* Write a message to logger: use UDEBUG(), UINFO(), UWARN(), UERROR() or UFATAL() instead.
|
||||
* @param level the log level of this message
|
||||
@@ -396,7 +392,19 @@ public:
|
||||
int line,
|
||||
const char *function,
|
||||
const char* msg,
|
||||
...);
|
||||
...)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// Let the compiler type-check every UDEBUG/UINFO/UWARN/UERROR
|
||||
// format string against its arguments. Without this, a mismatch is
|
||||
// invisible until the line actually executes -- and since
|
||||
// ULogger::write() returns early when the message is below the
|
||||
// current log level, a bad format can sit latent for years and then
|
||||
// segfault only for users running at a lower level. `msg` is the
|
||||
// 5th parameter (no implicit `this`: the function is static) and
|
||||
// the varargs start at 6.
|
||||
__attribute__((format(printf, 5, 6)))
|
||||
#endif
|
||||
;
|
||||
|
||||
/**
|
||||
* Get the time in the format "2008-7-13 12:23:44".
|
||||
|
||||
@@ -119,7 +119,7 @@ inline T uMax(const T * v, unsigned int size, unsigned int & index)
|
||||
template<class T>
|
||||
inline T uMax(const std::vector<T> & v, unsigned int & index)
|
||||
{
|
||||
return uMax(v.data(), v->size(), index);
|
||||
return uMax(v.data(), v.size(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
|
||||
/**
|
||||
* Lock the mutex.
|
||||
* @return 0 on success, an error code otherwise.
|
||||
*/
|
||||
int lock() const
|
||||
{
|
||||
@@ -93,6 +94,11 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Try locking the mutex.
|
||||
* @return 0 if the mutex has been locked by this call, EBUSY (or another
|
||||
* error code) otherwise.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#if(_WIN32_WINNT >= 0x0400)
|
||||
int lockTry() const
|
||||
@@ -109,6 +115,7 @@ public:
|
||||
|
||||
/**
|
||||
* Unlock the mutex.
|
||||
* @return 0 on success, an error code otherwise.
|
||||
*/
|
||||
int unlock() const
|
||||
{
|
||||
|
||||
@@ -23,17 +23,46 @@
|
||||
#include "rtabmap/utilite/utilite_export.h" // DLL export/import defines
|
||||
|
||||
/**
|
||||
* This class is used to get some informations
|
||||
* about the current process.
|
||||
* \file UProcessInfo.h
|
||||
* \brief UProcessInfo class for getting process information
|
||||
*
|
||||
* This class provides methods to retrieve information about the current process,
|
||||
* such as memory usage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \class UProcessInfo
|
||||
* \brief Class for getting information about the current process
|
||||
*
|
||||
* This class provides static methods to retrieve information about the current
|
||||
* process, such as memory usage. The information is retrieved from the operating
|
||||
* system.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* long int memory = UProcessInfo::getMemoryUsage();
|
||||
* std::cout << "Memory usage: " << memory << " bytes" << std::endl;
|
||||
* @endcode
|
||||
*/
|
||||
class UTILITE_EXPORT UProcessInfo {
|
||||
public:
|
||||
/**
|
||||
* \brief Constructor
|
||||
*/
|
||||
UProcessInfo();
|
||||
|
||||
/**
|
||||
* \brief Virtual destructor
|
||||
*/
|
||||
virtual ~UProcessInfo();
|
||||
|
||||
/**
|
||||
* Get the memory used by the current process.
|
||||
* @return the number of bytes used by the current process.
|
||||
* \brief Get the memory used by the current process
|
||||
*
|
||||
* This method returns the amount of memory currently used by the process.
|
||||
* The exact meaning of "memory used" may vary between operating systems.
|
||||
*
|
||||
* @return the number of bytes used by the current process, or -1 on error
|
||||
*/
|
||||
static long int getMemoryUsage();
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "rtabmap/utilite/Win32/UWin32.h"
|
||||
#include <atomic>
|
||||
#define SEM_VALUE_MAX ((int) ((~0u) >> 1))
|
||||
#else
|
||||
#include <pthread.h>
|
||||
@@ -59,6 +60,9 @@ public:
|
||||
* @param n number to initialize
|
||||
*/
|
||||
USemaphore( int initValue = 0 )
|
||||
#ifdef _WIN32
|
||||
: _count(initValue)
|
||||
#endif
|
||||
{
|
||||
#ifdef _WIN32
|
||||
S = CreateSemaphore(0,initValue,SEM_VALUE_MAX,0);
|
||||
@@ -94,6 +98,9 @@ public:
|
||||
while(n-- > 0 && rt==0)
|
||||
{
|
||||
rt = WaitForSingleObject((HANDLE)S, ms<=0?INFINITE:ms);
|
||||
if(rt == 0) {
|
||||
--_count;
|
||||
}
|
||||
}
|
||||
return rt == 0;
|
||||
}
|
||||
@@ -133,44 +140,67 @@ public:
|
||||
|
||||
/*
|
||||
* Try to acquire the semaphore, not a blocking call.
|
||||
* @return false if the semaphore can't be taken without waiting (value <= 0), true otherwise
|
||||
* @return 0 if the semaphore was acquired, EAGAIN if it couldn't be taken
|
||||
* without waiting.
|
||||
* @note The Windows overload takes no argument (it always acquires 1).
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
int acquireTry() const
|
||||
{
|
||||
return ((WaitForSingleObject((HANDLE)S,INFINITE)==WAIT_OBJECT_0)?0:EAGAIN);
|
||||
// Non-blocking try-acquire: timeout 0, not INFINITE. INFINITE here
|
||||
// turned this into a blocking acquire and hung tests when the count
|
||||
// reached 0.
|
||||
if(WaitForSingleObject((HANDLE)S, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
--_count;
|
||||
return 0;
|
||||
}
|
||||
return EAGAIN;
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* @param n the number to acquire.
|
||||
*/
|
||||
int acquireTry(int n)
|
||||
{
|
||||
pthread_mutex_lock(&_waitMutex);
|
||||
if(n > _available)
|
||||
{
|
||||
pthread_mutex_unlock(&_waitMutex);
|
||||
return false;
|
||||
return EAGAIN;
|
||||
}
|
||||
_available -= n;
|
||||
pthread_mutex_unlock(&_waitMutex);
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Release the semaphore, increasing its value by 1 and
|
||||
* Release the semaphore, increasing its value by n and
|
||||
* signaling waiting threads (which called acquire()).
|
||||
* @param n the number to release.
|
||||
* @return 0 on success, ERANGE if the release would push the semaphore over
|
||||
* SEM_VALUE_MAX (the count is then left unchanged). The Unix
|
||||
* implementation cannot fail and always returns 0.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
int release(int n = 1) const
|
||||
{
|
||||
return (ReleaseSemaphore((HANDLE)S,n,0)?0:ERANGE);
|
||||
if(ReleaseSemaphore((HANDLE)S, n, 0))
|
||||
{
|
||||
_count += n;
|
||||
return 0;
|
||||
}
|
||||
return ERANGE;
|
||||
}
|
||||
#else
|
||||
void release(int n = 1)
|
||||
int release(int n = 1)
|
||||
{
|
||||
pthread_mutex_lock(&_waitMutex);
|
||||
_available += n;
|
||||
pthread_cond_broadcast(&_cond);
|
||||
pthread_mutex_unlock(&_waitMutex);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -181,7 +211,11 @@ public:
|
||||
#ifdef _WIN32
|
||||
int value() const
|
||||
{
|
||||
LONG V = -1; ReleaseSemaphore((HANDLE)S,0,&V); return V;
|
||||
// ReleaseSemaphore(S, 0, &V) returns FALSE on Windows (release count
|
||||
// must be >= 1) and never writes V, so we mirror the count in an atomic
|
||||
// instead. The value is eventually-consistent under concurrent
|
||||
// acquire/release but accurate at quiescence.
|
||||
return (int)_count.load();
|
||||
}
|
||||
#else
|
||||
int value()
|
||||
@@ -204,6 +238,7 @@ public:
|
||||
{
|
||||
CloseHandle(S);
|
||||
S = CreateSemaphore(0,init,SEM_VALUE_MAX,0);
|
||||
_count = init;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -212,6 +247,7 @@ private:
|
||||
#ifdef _WIN32
|
||||
USemaphore(const USemaphore &S){}
|
||||
HANDLE S;
|
||||
mutable std::atomic<long> _count;
|
||||
#else
|
||||
USemaphore(const USemaphore &):_available(0){}
|
||||
pthread_mutex_t _waitMutex;
|
||||
|
||||
@@ -22,7 +22,12 @@
|
||||
|
||||
#include "rtabmap/utilite/UThread.h"
|
||||
|
||||
//For backward compatibility
|
||||
/**
|
||||
* \file UThreadNode.h
|
||||
* \brief Backward-compatible alias header for \ref UThread.
|
||||
*/
|
||||
|
||||
/** @brief Alias for @ref UThread, kept for backward compatibility. */
|
||||
typedef UThread UThreadNode;
|
||||
|
||||
|
||||
|
||||
@@ -75,14 +75,6 @@ public:
|
||||
double elapsed() {return getElapsedTime();}
|
||||
double getElapsedTime();
|
||||
|
||||
/**
|
||||
* This method is used to get the interval time
|
||||
* between stop() and the start().
|
||||
* @return double the interval in seconds.
|
||||
* @deprecated use elapsed() instead.
|
||||
*/
|
||||
UTILITE_DEPRECATED double getInterval();
|
||||
|
||||
/**
|
||||
* This method is used to get the interval of
|
||||
* the timer while it is running. It's automatically
|
||||
|
||||
@@ -25,100 +25,433 @@
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Experimental class...
|
||||
* \file UVariant.h
|
||||
* \brief UVariant class for storing and converting between different data types
|
||||
*
|
||||
* This class provides a type-safe way to store values of different types
|
||||
* and convert between them. It supports basic types (bool, char, int, float, etc.)
|
||||
* and arrays of these types.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* UVariant v(42);
|
||||
* if(v.isInt())
|
||||
* {
|
||||
* int value = v.toInt();
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
* \class UVariant
|
||||
* \brief A variant type that can hold values of different types
|
||||
*
|
||||
* UVariant is a type-safe container that can store values of various types
|
||||
* including booleans, integers, floats, strings, and arrays. It provides
|
||||
* methods to check the stored type and convert to other types.
|
||||
*/
|
||||
class UTILITE_EXPORT UVariant
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \enum Type
|
||||
* \brief Enumeration of supported data types
|
||||
*/
|
||||
enum Type{
|
||||
kBool,
|
||||
kChar,
|
||||
kUChar,
|
||||
kShort,
|
||||
kUShort,
|
||||
kInt,
|
||||
kUInt,
|
||||
kFloat,
|
||||
kDouble,
|
||||
kStr,
|
||||
kCharArray,
|
||||
kUCharArray,
|
||||
kShortArray,
|
||||
kUShortArray,
|
||||
kIntArray,
|
||||
kUIntArray,
|
||||
kFloatArray,
|
||||
kDoubleArray,
|
||||
kUndef
|
||||
kBool, /**< Boolean type */
|
||||
kChar, /**< Signed char type */
|
||||
kUChar, /**< Unsigned char type */
|
||||
kShort, /**< Short integer type */
|
||||
kUShort, /**< Unsigned short integer type */
|
||||
kInt, /**< Integer type */
|
||||
kUInt, /**< Unsigned integer type */
|
||||
kFloat, /**< Float type */
|
||||
kDouble, /**< Double type */
|
||||
kStr, /**< String type */
|
||||
kCharArray, /**< Array of signed char */
|
||||
kUCharArray, /**< Array of unsigned char */
|
||||
kShortArray, /**< Array of short */
|
||||
kUShortArray, /**< Array of unsigned short */
|
||||
kIntArray, /**< Array of int */
|
||||
kUIntArray, /**< Array of unsigned int */
|
||||
kFloatArray, /**< Array of float */
|
||||
kDoubleArray, /**< Array of double */
|
||||
kUndef /**< Undefined type (default) */
|
||||
};
|
||||
public:
|
||||
/**
|
||||
* \brief Default constructor
|
||||
* Creates an undefined variant
|
||||
*/
|
||||
UVariant();
|
||||
|
||||
/**
|
||||
* \brief Constructor from bool
|
||||
* @param value the boolean value
|
||||
*/
|
||||
UVariant(const bool & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from signed char
|
||||
* @param value the signed char value
|
||||
*/
|
||||
UVariant(const signed char & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from unsigned char
|
||||
* @param value the unsigned char value
|
||||
*/
|
||||
UVariant(const unsigned char & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from short
|
||||
* @param value the short value
|
||||
*/
|
||||
UVariant(const short & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from unsigned short
|
||||
* @param value the unsigned short value
|
||||
*/
|
||||
UVariant(const unsigned short & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from int
|
||||
* @param value the integer value
|
||||
*/
|
||||
UVariant(const int & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from unsigned int
|
||||
* @param value the unsigned integer value
|
||||
*/
|
||||
UVariant(const unsigned int & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from float
|
||||
* @param value the float value
|
||||
*/
|
||||
UVariant(const float & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from double
|
||||
* @param value the double value
|
||||
*/
|
||||
UVariant(const double & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from C string
|
||||
* @param value the C string value
|
||||
*/
|
||||
UVariant(const char * value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from std::string
|
||||
* @param value the string value
|
||||
*/
|
||||
UVariant(const std::string & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of signed char
|
||||
* @param value the array of signed char
|
||||
*/
|
||||
UVariant(const std::vector<signed char> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of unsigned char
|
||||
* @param value the array of unsigned char
|
||||
*/
|
||||
UVariant(const std::vector<unsigned char> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of short
|
||||
* @param value the array of short
|
||||
*/
|
||||
UVariant(const std::vector<short> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of unsigned short
|
||||
* @param value the array of unsigned short
|
||||
*/
|
||||
UVariant(const std::vector<unsigned short> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of int
|
||||
* @param value the array of int
|
||||
*/
|
||||
UVariant(const std::vector<int> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of unsigned int
|
||||
* @param value the array of unsigned int
|
||||
*/
|
||||
UVariant(const std::vector<unsigned int> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of float
|
||||
* @param value the array of float
|
||||
*/
|
||||
UVariant(const std::vector<float> & value);
|
||||
|
||||
/**
|
||||
* \brief Constructor from array of double
|
||||
* @param value the array of double
|
||||
*/
|
||||
UVariant(const std::vector<double> & value);
|
||||
|
||||
/**
|
||||
* \brief Get the type of the stored value
|
||||
* @return the Type enum value
|
||||
*/
|
||||
Type type() const {return type_;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is undefined
|
||||
* @return true if type is kUndef
|
||||
*/
|
||||
bool isUndef() const {return type_ == kUndef;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a bool
|
||||
* @return true if type is kBool
|
||||
*/
|
||||
bool isBool() const {return type_ == kBool;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a signed char
|
||||
* @return true if type is kChar
|
||||
*/
|
||||
bool isChar() const {return type_ == kChar;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an unsigned char
|
||||
* @return true if type is kUChar
|
||||
*/
|
||||
bool isUChar() const {return type_ == kUChar;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a short
|
||||
* @return true if type is kShort
|
||||
*/
|
||||
bool isShort() const {return type_ == kShort;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an unsigned short
|
||||
* @return true if type is kUShort
|
||||
*/
|
||||
bool isUShort() const {return type_ == kUShort;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an int
|
||||
* @return true if type is kInt
|
||||
*/
|
||||
bool isInt() const {return type_ == kInt;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an unsigned int
|
||||
* @return true if type is kUInt
|
||||
*/
|
||||
bool isUInt() const {return type_ == kUInt;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a float
|
||||
* @return true if type is kFloat
|
||||
*/
|
||||
bool isFloat() const {return type_ == kFloat;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a double
|
||||
* @return true if type is kDouble
|
||||
*/
|
||||
bool isDouble() const {return type_ == kDouble;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is a string
|
||||
* @return true if type is kStr
|
||||
*/
|
||||
bool isStr() const {return type_ == kStr;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of signed char
|
||||
* @return true if type is kCharArray
|
||||
*/
|
||||
bool isCharArray() const {return type_ == kCharArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of unsigned char
|
||||
* @return true if type is kUCharArray
|
||||
*/
|
||||
bool isUCharArray() const {return type_ == kUCharArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of short
|
||||
* @return true if type is kShortArray
|
||||
*/
|
||||
bool isShortArray() const {return type_ == kShortArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of unsigned short
|
||||
* @return true if type is kUShortArray
|
||||
*/
|
||||
bool isUShortArray() const {return type_ == kUShortArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of int
|
||||
* @return true if type is kIntArray
|
||||
*/
|
||||
bool isIntArray() const {return type_ == kIntArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of unsigned int
|
||||
* @return true if type is kUIntArray
|
||||
*/
|
||||
bool isUIntArray() const {return type_ == kUIntArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of float
|
||||
* @return true if type is kFloatArray
|
||||
*/
|
||||
bool isFloatArray() const {return type_ == kFloatArray;}
|
||||
|
||||
/**
|
||||
* \brief Check if the variant is an array of double
|
||||
* @return true if type is kDoubleArray
|
||||
*/
|
||||
bool isDoubleArray() const {return type_ == kDoubleArray;}
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to bool
|
||||
* @return the boolean value
|
||||
*/
|
||||
bool toBool() const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to signed char
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the signed char value
|
||||
*/
|
||||
signed char toChar(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to unsigned char
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the unsigned char value
|
||||
*/
|
||||
unsigned char toUChar(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to short
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the short value
|
||||
*/
|
||||
short toShort(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to unsigned short
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the unsigned short value
|
||||
*/
|
||||
unsigned short toUShort(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to int
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the integer value
|
||||
*/
|
||||
int toInt(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to unsigned int
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the unsigned integer value
|
||||
*/
|
||||
unsigned int toUInt(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to float
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the float value
|
||||
*/
|
||||
float toFloat(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to double
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the double value
|
||||
*/
|
||||
double toDouble(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to string
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the string value
|
||||
*/
|
||||
std::string toStr(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of signed char
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of signed char
|
||||
*/
|
||||
std::vector<signed char> toCharArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of unsigned char
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of unsigned char
|
||||
*/
|
||||
std::vector<unsigned char> toUCharArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of short
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of short
|
||||
*/
|
||||
std::vector<short> toShortArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of unsigned short
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of unsigned short
|
||||
*/
|
||||
std::vector<unsigned short> toUShortArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of int
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of int
|
||||
*/
|
||||
std::vector<int> toIntArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of unsigned int
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of unsigned int
|
||||
*/
|
||||
std::vector<unsigned int> toUIntArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of float
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of float
|
||||
*/
|
||||
std::vector<float> toFloatArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Convert the variant to array of double
|
||||
* @param ok pointer to bool to indicate success (optional)
|
||||
* @return the array of double
|
||||
*/
|
||||
std::vector<double> toDoubleArray(bool * ok = 0) const;
|
||||
|
||||
/**
|
||||
* \brief Virtual destructor
|
||||
*/
|
||||
virtual ~UVariant() {}
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
std::vector<unsigned char> data_;
|
||||
Type type_; /**< The type of the stored value */
|
||||
std::vector<unsigned char> data_; /**< The stored data */
|
||||
};
|
||||
|
||||
#endif /* UVARIANT_H */
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef UTILITE_H
|
||||
#define UTILITE_H
|
||||
|
||||
/** \mainpage UtiLite
|
||||
/** \defgroup UtiLite
|
||||
*
|
||||
* \section intro Introduction
|
||||
* <a href="http://utilite.googlecode.com">UtiLite</a> is a simple library to create small cross-platform
|
||||
@@ -29,7 +29,7 @@
|
||||
*
|
||||
* UtiLite provides a utility application called \ref uResourceGeneratorPage "uResourceGenerator" to generate resources to include in an executable. For example:
|
||||
* @code
|
||||
* $ ./uresourcegenerator DatabaseSchema.sql
|
||||
* $ rtabmap-res_tool DatabaseSchema.sql
|
||||
* @endcode
|
||||
* This will generate a HEX file "DatabaseSchema_sql.h" which can be included in source files.
|
||||
* Data of the file is global and can be accessed by the generated const char * DATABASESCHEMA_SQL.
|
||||
@@ -43,7 +43,6 @@
|
||||
* std::vector<char> bytes = uHex2Bytes(hex);
|
||||
* @endcode
|
||||
*
|
||||
* A generated \ref findUtilitePage "FindUtiLite.cmake" is also provided for easy linking with the library.
|
||||
*
|
||||
*
|
||||
* \section logger ULogger
|
||||
@@ -163,47 +162,12 @@
|
||||
* \section processinfo UProcessInfo
|
||||
* This class can be used to get the process memory usage: UProcessInfo::getMemoryUsage().
|
||||
*
|
||||
* \section qtLib Qt Widgets (libutilite_qt.so : OPTIONAL)
|
||||
* If Qt is found on the system, the UtiLite Qt library (libutilite_qt.so, libutilite_qt.dll) with
|
||||
* useful widgets is built. Use class UPlot to create a plot like MATLAB, and incrementally add
|
||||
* new values like a scope. USpectrogram is used to
|
||||
* show audio frequency frames.
|
||||
* - UPlot,
|
||||
* - USpectrogram,
|
||||
* - UImageView.
|
||||
* @image html UPlot.gif
|
||||
* @image html USpectrogram.png
|
||||
*
|
||||
* \section audioLib Audio stuff (libutilite_audio.so : OPTIONAL)
|
||||
* If FMOD is found on the system, the UtiLite audio
|
||||
* library is built (libutilite_audio.so, libutilite_audio.dll). It is a wrapper
|
||||
* of FMOD methods with a convenient interface to extract audio frames.
|
||||
* - UAudioCapture,
|
||||
* - UAudioCaptureFile,
|
||||
* - UAudioCaptureMic,
|
||||
* - UAudioCaptureFFT,
|
||||
* - UAudioPlayer,
|
||||
* - UAudioPlayerTone,
|
||||
* - UWav,
|
||||
* - UMp3Encoder (only if Lame is also found on the system).
|
||||
*
|
||||
* \section cvLib OpenCV stuff (libutilite_cv.so : OPTIONAL)
|
||||
* If OpenCV is found on the system, the UtiLite cv
|
||||
* library is built (libutilite_cv.so, libutilite_cv.dll). It provides
|
||||
* image capture classes used to read from a webcam, a video file
|
||||
* or a directory of images. If UtiLite is also built with Qt, a
|
||||
* convenient function uCvMat2QImage() can be used to convert a cv::Mat
|
||||
* image to a QImage.
|
||||
* - UVideoCapture,
|
||||
* - UImageFolderCapture,
|
||||
* - UColorTable,
|
||||
* - uCvMat2QImage() (only if Qt is also found on the system).
|
||||
*/
|
||||
|
||||
/*! \page uResourceGeneratorPage uResourceGenerator
|
||||
* UtiLite provides a utility application called \ref uResourceGeneratorPage "uResourceGenerator" to generate resources to include in an executable. For example:
|
||||
* @code
|
||||
* $ ./uresourcegenerator DatabaseSchema.sql
|
||||
* $ rtabmap-res_tool DatabaseSchema.sql
|
||||
* @endcode
|
||||
* This will generate a HEX file "DatabaseSchema_sql.h" which can be included in source files.
|
||||
* Data of the file is global and can be accessed by the generated const char * DATABASESCHEMA_SQL.
|
||||
@@ -221,7 +185,7 @@
|
||||
* @code
|
||||
* ADD_CUSTOM_COMMAND(
|
||||
* OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/DatabaseSchema_sql.h
|
||||
* COMMAND ${URESOURCEGENERATOR_EXEC} -n my_namespace -p ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/DatabaseSchema.sql
|
||||
* COMMAND ${RTABMAP_RES_TOOL} -n my_namespace -p ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/DatabaseSchema.sql
|
||||
* COMMENT "[Creating database resource]"
|
||||
* DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/DatabaseSchema.sql
|
||||
* )
|
||||
@@ -231,7 +195,9 @@
|
||||
* ADD_LIBRARY(mylib ${SRC_FILES} ${RESOURCES})
|
||||
* ADD_EXECUTABLE(myexecutable ${SRC_FILES} ${RESOURCES})
|
||||
* @endcode
|
||||
* The variable URESOURCEGENERATOR_EXEC is set when FIND_PACKAGE(UtiLite) is done, you would need to add \ref findUtilitePage "FindUtiLite.cmake".
|
||||
* The tool is installed as `rtabmap-res_tool`. It is not exported by FIND_PACKAGE(RTABMap),
|
||||
* so locate it with FIND_PROGRAM(RTABMAP_RES_TOOL rtabmap-res_tool). Inside RTAB-Map's own
|
||||
* build, the `res_tool` CMake target can be used directly as the COMMAND instead.
|
||||
*/
|
||||
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
|
||||
@@ -278,6 +278,11 @@ std::string UDirectory::getDir(const std::string & filePath)
|
||||
{
|
||||
if(dir[i] == '/' || dir[i] == '\\')
|
||||
{
|
||||
if(i == 0)
|
||||
{
|
||||
// leading /
|
||||
break;
|
||||
}
|
||||
//remove separators...
|
||||
dir[i] = 0;
|
||||
--i;
|
||||
|
||||
@@ -225,17 +225,18 @@ bool UEventsManager::dispatchEvent(UEvent * event, const UEventsSender * sender)
|
||||
if(std::find(handlers_.begin(), handlers_.end(), *it) != handlers_.end())
|
||||
{
|
||||
UEventsHandler * handler = *it;
|
||||
handlersMutex_.unlock();
|
||||
|
||||
// Don't process event if the handler is the same as the sender
|
||||
if(handler != sender)
|
||||
{
|
||||
// To be able to add/remove an handler in a handleEvent call (without a deadlock)
|
||||
// @see _addHandler(), _removeHandler()
|
||||
// Keep handlersMutex_ held across handleEvent() so a concurrent
|
||||
// removeHandler() in another thread cannot return while this
|
||||
// dispatch is in flight (which would let the handler be
|
||||
// destroyed under us). The mutex is recursive, so a handler
|
||||
// that calls addHandler()/removeHandler() from within
|
||||
// handleEvent() still works.
|
||||
handled = handler->handleEvent(event);
|
||||
}
|
||||
|
||||
handlersMutex_.lock();
|
||||
}
|
||||
}
|
||||
handlersMutex_.unlock();
|
||||
|
||||
@@ -87,7 +87,7 @@ std::string UFile::getName(const std::string & filePath)
|
||||
std::string UFile::getExtension(const std::string &filePath)
|
||||
{
|
||||
std::list<std::string> list = uSplit(filePath, '.');
|
||||
if(list.size())
|
||||
if(list.size()>1)
|
||||
{
|
||||
return list.back();
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@ std::map<std::string, unsigned long> ULogger::getRegisteredThreads()
|
||||
|
||||
void ULogger::reset()
|
||||
{
|
||||
UScopeMutex lock(loggerMutex_);
|
||||
flush();
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
append_ = true;
|
||||
printTime_ = true;
|
||||
@@ -266,9 +268,11 @@ void ULogger::reset()
|
||||
printWhere_ = true;
|
||||
printWhereFullPath_ = false;
|
||||
printThreadID_ = false;
|
||||
buffered_ = false;
|
||||
limitWhereLength_ = false;
|
||||
level_ = kInfo; // By default, we show all info msgs + upper level (Warning, Error)
|
||||
logFileName_ = ULogger::kDefaultLogFileName;
|
||||
registeredThreads_.clear();
|
||||
}
|
||||
|
||||
void ULogger::setBuffered(bool buffered)
|
||||
@@ -300,66 +304,6 @@ void ULogger::_flush()
|
||||
bufferedMsgs_.clear();
|
||||
}
|
||||
|
||||
void ULogger::write(const char* msg, ...)
|
||||
{
|
||||
loggerMutex_.lock();
|
||||
if(!instance_)
|
||||
{
|
||||
loggerMutex_.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string endline = "";
|
||||
if(printEndline_) {
|
||||
endline = "\r\n";
|
||||
}
|
||||
|
||||
std::string time = "";
|
||||
if(printTime_)
|
||||
{
|
||||
getTime(time);
|
||||
time.append(" - ");
|
||||
}
|
||||
|
||||
|
||||
if(printTime_)
|
||||
{
|
||||
if(buffered_)
|
||||
{
|
||||
bufferedMsgs_.append(time.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ULogger::getInstance()->_writeStr(time.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
if(buffered_)
|
||||
{
|
||||
bufferedMsgs_.append(uFormatv(msg, args));
|
||||
}
|
||||
else
|
||||
{
|
||||
ULogger::getInstance()->_write(msg, args);
|
||||
}
|
||||
va_end(args);
|
||||
if(printEndline_)
|
||||
{
|
||||
if(buffered_)
|
||||
{
|
||||
bufferedMsgs_.append(endline.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ULogger::getInstance()->_writeStr(endline.c_str());
|
||||
}
|
||||
}
|
||||
loggerMutex_.unlock();
|
||||
|
||||
}
|
||||
|
||||
void ULogger::write(ULogger::Level level,
|
||||
const char * file,
|
||||
int line,
|
||||
|
||||
@@ -69,7 +69,7 @@ void UThread::kill()
|
||||
}
|
||||
else
|
||||
{
|
||||
UERROR("thread (%d) is supposed to be running...", threadId_);
|
||||
UERROR("thread (%d) is supposed to be running...", (int)threadId_);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -65,17 +65,6 @@ double UTimer::getElapsedTime()
|
||||
QueryPerformanceCounter(&now);
|
||||
return double(now.QuadPart - startTimeRecorded_.QuadPart) / frequency_.QuadPart;
|
||||
}
|
||||
double UTimer::getInterval()
|
||||
{
|
||||
if(stopTimeRecorded_.QuadPart == startTimeRecorded_.QuadPart)
|
||||
{
|
||||
return getElapsedTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
return double(stopTimeRecorded_.QuadPart - startTimeRecorded_.QuadPart) / frequency_.QuadPart;
|
||||
}
|
||||
}
|
||||
#else
|
||||
double UTimer::now()
|
||||
{
|
||||
@@ -99,19 +88,6 @@ double UTimer::getElapsedTime()
|
||||
return UTimer::now() - (double(startTimeRecorded_.tv_sec) + double(startTimeRecorded_.tv_usec) / 1000000.0);
|
||||
|
||||
}
|
||||
double UTimer::getInterval()
|
||||
{
|
||||
if(startTimeRecorded_.tv_sec == stopTimeRecorded_.tv_sec && startTimeRecorded_.tv_usec == stopTimeRecorded_.tv_usec)
|
||||
{
|
||||
return getElapsedTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
double start = double(startTimeRecorded_.tv_sec) + double(startTimeRecorded_.tv_usec) / 1000000.0;
|
||||
double stop = double(stopTimeRecorded_.tv_sec) + double(stopTimeRecorded_.tv_usec) / 1000000.0;
|
||||
return stop - start;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
double UTimer::ticks() // Stop->start and return Interval
|
||||
|
||||
66
utilite/test/CMakeLists.txt
Normal file
66
utilite/test/CMakeLists.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
# UConversion.h
|
||||
add_executable(test_uconversion test_uconversion.cpp)
|
||||
target_link_libraries(test_uconversion gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_uconversion COMMAND test_uconversion)
|
||||
|
||||
# UTimer.h
|
||||
add_executable(test_utimer test_utimer.cpp)
|
||||
target_link_libraries(test_utimer gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_utimer COMMAND test_utimer)
|
||||
|
||||
# UVariant.h
|
||||
add_executable(test_uvariant test_uvariant.cpp)
|
||||
target_link_libraries(test_uvariant gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_uvariant COMMAND test_uvariant)
|
||||
|
||||
# UFile.h
|
||||
add_executable(test_ufile test_ufile.cpp)
|
||||
target_link_libraries(test_ufile gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_ufile COMMAND test_ufile)
|
||||
|
||||
# UDirectory.h
|
||||
add_executable(test_udirectory test_udirectory.cpp)
|
||||
target_link_libraries(test_udirectory gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_udirectory COMMAND test_udirectory)
|
||||
|
||||
# UProcessInfo.h
|
||||
add_executable(test_uprocessinfo test_uprocessinfo.cpp)
|
||||
target_link_libraries(test_uprocessinfo gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_uprocessinfo COMMAND test_uprocessinfo)
|
||||
|
||||
# UMath.h
|
||||
add_executable(test_umath test_umath.cpp)
|
||||
target_link_libraries(test_umath gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_umath COMMAND test_umath)
|
||||
|
||||
# UStl.h
|
||||
add_executable(test_ustl test_ustl.cpp)
|
||||
target_link_libraries(test_ustl gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_ustl COMMAND test_ustl)
|
||||
|
||||
# UEventsHandler.h, UEventsManager.h, UEventsSender.h
|
||||
add_executable(test_uevents test_uevents.cpp)
|
||||
target_link_libraries(test_uevents gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_uevents COMMAND test_uevents)
|
||||
|
||||
# UMutex.h
|
||||
add_executable(test_umutex test_umutex.cpp)
|
||||
target_link_libraries(test_umutex gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_umutex COMMAND test_umutex)
|
||||
|
||||
# UThread.h
|
||||
add_executable(test_uthread test_uthread.cpp)
|
||||
target_link_libraries(test_uthread gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_uthread COMMAND test_uthread)
|
||||
|
||||
# USemaphore.h
|
||||
add_executable(test_usemaphore test_usemaphore.cpp)
|
||||
target_link_libraries(test_usemaphore gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_usemaphore COMMAND test_usemaphore)
|
||||
|
||||
# ULogger.h
|
||||
add_executable(test_ulogger test_ulogger.cpp)
|
||||
target_link_libraries(test_ulogger gtest_main rtabmap::utilite)
|
||||
add_test(NAME test_ulogger COMMAND test_ulogger)
|
||||
set_tests_properties(test_ulogger PROPERTIES RUN_SERIAL TRUE)
|
||||
|
||||
203
utilite/test/test_uconversion.cpp
Normal file
203
utilite/test/test_uconversion.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UConversion.h"
|
||||
|
||||
TEST(UConversionTest, UReplaceCharChar)
|
||||
{
|
||||
std::string str = "Hello";
|
||||
std::string result = uReplaceChar(str, 'l', 'p');
|
||||
EXPECT_EQ(result, "Heppo");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UReplaceCharString)
|
||||
{
|
||||
std::string str = "Hello";
|
||||
std::string result = uReplaceChar(str, 'o', "oween");
|
||||
EXPECT_EQ(result, "Helloween");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UToUpperCase)
|
||||
{
|
||||
std::string str = "hello!";
|
||||
std::string result = uToUpperCase(str);
|
||||
EXPECT_EQ(result, "HELLO!");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UToLowerCase)
|
||||
{
|
||||
std::string str = "HELLO!";
|
||||
std::string result = uToLowerCase(str);
|
||||
EXPECT_EQ(result, "hello!");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UNumber2StrUnsignedInt)
|
||||
{
|
||||
unsigned int number = 42;
|
||||
std::string result = uNumber2Str(number);
|
||||
EXPECT_EQ(result, "42");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UNumber2StrInt)
|
||||
{
|
||||
int number = -42;
|
||||
std::string result = uNumber2Str(number);
|
||||
EXPECT_EQ(result, "-42");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UNumber2StrFloat)
|
||||
{
|
||||
float number = 0.14159f;
|
||||
std::string result = uNumber2Str(number, 2, false);
|
||||
EXPECT_STREQ(result.c_str(), "0.14");
|
||||
|
||||
number = 3.14159f;
|
||||
result = uNumber2Str(number, 2, false);
|
||||
EXPECT_STREQ(result.c_str(), "3.1");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UNumber2StrDouble)
|
||||
{
|
||||
double number = 0.14159;
|
||||
std::string result = uNumber2Str(number, 2, false);
|
||||
EXPECT_STREQ(result.c_str(), "0.14");
|
||||
|
||||
number = 3.14159;
|
||||
result = uNumber2Str(number, 2, false);
|
||||
EXPECT_STREQ(result.c_str(), "3.1");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2Int)
|
||||
{
|
||||
std::string str = "42";
|
||||
int result = uStr2Int(str);
|
||||
EXPECT_EQ(result, 42);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2IntNegative)
|
||||
{
|
||||
std::string str = "-42";
|
||||
int result = uStr2Int(str);
|
||||
EXPECT_EQ(result, -42);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2Float)
|
||||
{
|
||||
std::string str = "3.14";
|
||||
float result = uStr2Float(str);
|
||||
EXPECT_NEAR(result, 3.14f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2FloatComma)
|
||||
{
|
||||
std::string str = "3,14";
|
||||
float result = uStr2Float(str);
|
||||
EXPECT_NEAR(result, 3.14f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2Double)
|
||||
{
|
||||
std::string str = "3.14159";
|
||||
double result = uStr2Double(str);
|
||||
EXPECT_NEAR(result, 3.14159, 0.00001);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2DoubleComma)
|
||||
{
|
||||
std::string str = "3,14159";
|
||||
double result = uStr2Double(str);
|
||||
EXPECT_NEAR(result, 3.14159, 0.00001);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UBool2Str)
|
||||
{
|
||||
std::string resultTrue = uBool2Str(true);
|
||||
std::string resultFalse = uBool2Str(false);
|
||||
EXPECT_EQ(resultTrue, "true");
|
||||
EXPECT_EQ(resultFalse, "false");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2Bool)
|
||||
{
|
||||
EXPECT_TRUE(uStr2Bool("true"));
|
||||
EXPECT_TRUE(uStr2Bool("TRUE"));
|
||||
EXPECT_TRUE(uStr2Bool("True"));
|
||||
EXPECT_FALSE(uStr2Bool("false"));
|
||||
EXPECT_FALSE(uStr2Bool("FALSE"));
|
||||
EXPECT_FALSE(uStr2Bool("0"));
|
||||
EXPECT_TRUE(uStr2Bool("1"));
|
||||
EXPECT_TRUE(uStr2Bool("anything"));
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UStr2Bytes)
|
||||
{
|
||||
std::string str = "Hello";
|
||||
std::vector<unsigned char> bytes = uStr2Bytes(str);
|
||||
EXPECT_EQ(bytes.size(), 6); // "Hello" + null terminator
|
||||
EXPECT_EQ(bytes[0], 'H');
|
||||
EXPECT_EQ(bytes[4], 'o');
|
||||
EXPECT_EQ(bytes[5], '\0');
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UBytes2Str)
|
||||
{
|
||||
std::vector<unsigned char> bytes = {'H', 'e', 'l', 'l', 'o', '\0'};
|
||||
std::string result = uBytes2Str(bytes);
|
||||
EXPECT_EQ(result, "Hello");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UBytes2Hex)
|
||||
{
|
||||
char bytes[] = {0x3F, 0x1A};
|
||||
std::string result = uBytes2Hex(bytes, 2);
|
||||
EXPECT_EQ(result, "3F1A");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UHex2Bytes)
|
||||
{
|
||||
std::string hex = "1f3B";
|
||||
std::vector<char> bytes = uHex2Bytes(hex);
|
||||
EXPECT_EQ(bytes.size(), 2);
|
||||
EXPECT_EQ(bytes[0], 0x1F);
|
||||
EXPECT_EQ(bytes[1], 0x3B);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UHex2BytesWithLength)
|
||||
{
|
||||
std::string hex = "1f3B";
|
||||
std::vector<char> bytes = uHex2Bytes(hex.c_str(), 4);
|
||||
EXPECT_EQ(bytes.size(), 2);
|
||||
EXPECT_EQ(bytes[0], 0x1F);
|
||||
EXPECT_EQ(bytes[1], 0x3B);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UHex2Str)
|
||||
{
|
||||
std::string hex = "48656C6C6F21";
|
||||
std::string result = uHex2Str(hex);
|
||||
EXPECT_EQ(result, "Hello!");
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UHex2Ascii)
|
||||
{
|
||||
unsigned char c = 0xFA;
|
||||
unsigned char left = uHex2Ascii(c, false);
|
||||
unsigned char right = uHex2Ascii(c, true);
|
||||
EXPECT_EQ(left, 'F');
|
||||
EXPECT_EQ(right, 'A');
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UAscii2Hex)
|
||||
{
|
||||
unsigned char result = uAscii2Hex('F');
|
||||
EXPECT_EQ(result, 0x0F);
|
||||
result = uAscii2Hex('a');
|
||||
EXPECT_EQ(result, 0x0A);
|
||||
}
|
||||
|
||||
TEST(UConversionTest, UFormat)
|
||||
{
|
||||
std::string result = uFormat("Hello %s %d", "world", 42);
|
||||
EXPECT_NE(result.find("Hello"), std::string::npos);
|
||||
EXPECT_NE(result.find("world"), std::string::npos);
|
||||
EXPECT_NE(result.find("42"), std::string::npos);
|
||||
}
|
||||
|
||||
242
utilite/test/test_udirectory.cpp
Normal file
242
utilite/test/test_udirectory.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UDirectory.h"
|
||||
#include "rtabmap/utilite/UFile.h"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
|
||||
TEST(UDirectoryTest, Exists)
|
||||
{
|
||||
// Test with current directory (should exist)
|
||||
EXPECT_TRUE(UDirectory::exists("."));
|
||||
|
||||
// Test with nonexistent directory
|
||||
EXPECT_FALSE(UDirectory::exists("nonexistent_directory_xyz123"));
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, GetDir)
|
||||
{
|
||||
std::string filePath = "/path/to/file.txt";
|
||||
std::string dir = UDirectory::getDir(filePath);
|
||||
EXPECT_EQ(dir, "/path/to");
|
||||
|
||||
std::string filePath2 = "/path";
|
||||
std::string dir2 = UDirectory::getDir(filePath2);
|
||||
EXPECT_EQ(dir2, "/");
|
||||
|
||||
std::string filePath3 = "file.txt";
|
||||
std::string dir3 = UDirectory::getDir(filePath3);
|
||||
EXPECT_EQ(dir3, ".");
|
||||
|
||||
std::string filePath4 = "./file.txt";
|
||||
std::string dir4 = UDirectory::getDir(filePath4);
|
||||
EXPECT_EQ(dir4, ".");
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, CurrentDir)
|
||||
{
|
||||
std::string current = UDirectory::currentDir(false);
|
||||
EXPECT_GT(current.size(), 0);
|
||||
|
||||
std::string currentWithSep = UDirectory::currentDir(true);
|
||||
EXPECT_GT(currentWithSep.size(), 0);
|
||||
// Should end with separator if trailingSeparator is true
|
||||
std::string sep = UDirectory::separator();
|
||||
if(currentWithSep.size() > 0)
|
||||
{
|
||||
EXPECT_EQ(currentWithSep.back(), sep[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, MakeDir)
|
||||
{
|
||||
std::string testDir = "test_make_dir";
|
||||
// Remove if exists
|
||||
if(UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
bool result = UDirectory::makeDir(testDir);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_TRUE(UDirectory::exists(testDir));
|
||||
|
||||
// Cleanup
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, RemoveDir)
|
||||
{
|
||||
std::string testDir = "test_remove_dir";
|
||||
// Create if doesn't exist
|
||||
if(!UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::makeDir(testDir);
|
||||
}
|
||||
|
||||
bool result = UDirectory::removeDir(testDir);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_FALSE(UDirectory::exists(testDir));
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, HomeDir)
|
||||
{
|
||||
std::string home = UDirectory::homeDir();
|
||||
EXPECT_GT(home.size(), 0);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, Separator)
|
||||
{
|
||||
std::string sep = UDirectory::separator();
|
||||
EXPECT_EQ(sep.size(), 1);
|
||||
#ifdef _WIN32
|
||||
EXPECT_EQ(sep, "\\");
|
||||
#else
|
||||
EXPECT_EQ(sep, "/");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, Constructor)
|
||||
{
|
||||
std::string testDir = ".";
|
||||
UDirectory dir(testDir);
|
||||
EXPECT_TRUE(dir.isValid());
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, SetPath)
|
||||
{
|
||||
UDirectory dir;
|
||||
dir.setPath(".");
|
||||
EXPECT_TRUE(dir.isValid());
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, GetNextFileName)
|
||||
{
|
||||
// Create a test directory with files
|
||||
std::string testDir = "test_dir_files";
|
||||
if(!UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::makeDir(testDir);
|
||||
}
|
||||
|
||||
// Create some test files
|
||||
std::ofstream file1(testDir + UDirectory::separator() + "file1.txt");
|
||||
file1 << "content1";
|
||||
file1.close();
|
||||
|
||||
std::ofstream file2(testDir + UDirectory::separator() + "file2.txt");
|
||||
file2 << "content2";
|
||||
file2.close();
|
||||
|
||||
UDirectory dir(testDir);
|
||||
std::string name1 = dir.getNextFileName();
|
||||
std::string name2 = dir.getNextFileName();
|
||||
|
||||
EXPECT_STREQ(name1.c_str(), "file1.txt");
|
||||
EXPECT_STREQ(name2.c_str(), "file2.txt");
|
||||
|
||||
// Cleanup
|
||||
UFile::erase(testDir + UDirectory::separator() + "file1.txt");
|
||||
UFile::erase(testDir + UDirectory::separator() + "file2.txt");
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, GetNextFilePath)
|
||||
{
|
||||
std::string testDir = "test_dir_paths";
|
||||
if(!UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::makeDir(testDir);
|
||||
}
|
||||
|
||||
std::ofstream file(testDir + UDirectory::separator() + "test.txt");
|
||||
file << "content";
|
||||
file.close();
|
||||
|
||||
UDirectory dir(testDir);
|
||||
std::string path = dir.getNextFilePath();
|
||||
EXPECT_GT(path.size(), 0);
|
||||
EXPECT_NE(path.find("test.txt"), std::string::npos);
|
||||
|
||||
// Cleanup
|
||||
UFile::erase(testDir + UDirectory::separator() + "test.txt");
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, GetFileNames)
|
||||
{
|
||||
std::string testDir = "test_dir_list";
|
||||
if(!UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::makeDir(testDir);
|
||||
}
|
||||
|
||||
std::ofstream file1(testDir + UDirectory::separator() + "file1.txt");
|
||||
file1 << "content1";
|
||||
file1.close();
|
||||
|
||||
std::ofstream file2(testDir + UDirectory::separator() + "file2.txt");
|
||||
file2 << "content2";
|
||||
file2.close();
|
||||
|
||||
UDirectory dir(testDir);
|
||||
const std::list<std::string>& fileNames = dir.getFileNames();
|
||||
EXPECT_GE(fileNames.size(), 2);
|
||||
EXPECT_STREQ(fileNames.front().c_str(), "file1.txt");
|
||||
EXPECT_STREQ(fileNames.back().c_str(), "file2.txt");
|
||||
|
||||
// Cleanup
|
||||
UFile::erase(testDir + UDirectory::separator() + "file1.txt");
|
||||
UFile::erase(testDir + UDirectory::separator() + "file2.txt");
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, Rewind)
|
||||
{
|
||||
std::string testDir = "test_dir_rewind";
|
||||
if(UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
UDirectory::makeDir(testDir);
|
||||
std::ofstream file1(testDir + UDirectory::separator() + "file1.txt");
|
||||
file1 << "content1";
|
||||
file1.close();
|
||||
UDirectory dir(testDir);
|
||||
std::string name1 = dir.getNextFileName();
|
||||
EXPECT_FALSE(name1.empty());
|
||||
dir.rewind();
|
||||
std::string name2 = dir.getNextFileName();
|
||||
// Should get the same file name after rewind
|
||||
EXPECT_EQ(name1, name2);
|
||||
}
|
||||
|
||||
TEST(UDirectoryTest, ExtensionsFilter)
|
||||
{
|
||||
std::string testDir = "test_dir_ext";
|
||||
if(!UDirectory::exists(testDir))
|
||||
{
|
||||
UDirectory::makeDir(testDir);
|
||||
}
|
||||
|
||||
std::ofstream file1(testDir + UDirectory::separator() + "file1.txt");
|
||||
file1 << "content1";
|
||||
file1.close();
|
||||
|
||||
std::ofstream file2(testDir + UDirectory::separator() + "file2.jpg");
|
||||
file2 << "content2";
|
||||
file2.close();
|
||||
|
||||
UDirectory dir(testDir, "txt");
|
||||
const std::list<std::string>& fileNames = dir.getFileNames();
|
||||
// Should only get .txt files
|
||||
for(const std::string& name : fileNames)
|
||||
{
|
||||
EXPECT_NE(name.find(".txt"), std::string::npos);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
UFile::erase(testDir + UDirectory::separator() + "file1.txt");
|
||||
UFile::erase(testDir + UDirectory::separator() + "file2.jpg");
|
||||
UDirectory::removeDir(testDir);
|
||||
}
|
||||
|
||||
607
utilite/test/test_uevents.cpp
Normal file
607
utilite/test/test_uevents.cpp
Normal file
@@ -0,0 +1,607 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UEvent.h"
|
||||
#include "rtabmap/utilite/UEventsHandler.h"
|
||||
#include "rtabmap/utilite/UEventsManager.h"
|
||||
#include "rtabmap/utilite/UEventsSender.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
|
||||
// Test event classes
|
||||
class TestEvent : public UEvent
|
||||
{
|
||||
public:
|
||||
TestEvent(int code = 0) : UEvent(code) {}
|
||||
virtual ~TestEvent() {}
|
||||
virtual std::string getClassName() const {return "TestEvent";}
|
||||
};
|
||||
|
||||
class AnotherEvent : public UEvent
|
||||
{
|
||||
public:
|
||||
AnotherEvent() : UEvent(0) {}
|
||||
virtual ~AnotherEvent() {}
|
||||
virtual std::string getClassName() const {return "AnotherEvent";}
|
||||
};
|
||||
|
||||
// Test handler class
|
||||
class TestHandler : public UEventsHandler
|
||||
{
|
||||
public:
|
||||
TestHandler() : eventCount_(0), lastEventCode_(0){}
|
||||
|
||||
int getEventCount() const { return eventCount_; }
|
||||
int getLastEventCode() const { return lastEventCode_; }
|
||||
const std::string & getLastEventClassName() const { return lastEventClassName_; }
|
||||
void reset() { eventCount_ = 0; lastEventCode_ = 0; lastEventClassName_.clear(); }
|
||||
|
||||
protected:
|
||||
virtual bool handleEvent(UEvent * event)
|
||||
{
|
||||
eventCount_++;
|
||||
lastEventCode_ = event->getCode();
|
||||
lastEventClassName_ = event->getClassName();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int eventCount_;
|
||||
int lastEventCode_;
|
||||
std::string lastEventClassName_;
|
||||
bool ownershipTaken_;
|
||||
};
|
||||
|
||||
// Handler that takes ownership
|
||||
class OwnershipHandler : public UEventsHandler
|
||||
{
|
||||
public:
|
||||
OwnershipHandler() : event_(nullptr) {}
|
||||
~OwnershipHandler() {delete event_;}
|
||||
const UEvent * getEvent() const {return event_;}
|
||||
void reset() { delete event_; event_ = nullptr;}
|
||||
|
||||
protected:
|
||||
virtual bool handleEvent(UEvent * event)
|
||||
{
|
||||
event_ = event; // Take ownership
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
UEvent * event_;
|
||||
};
|
||||
|
||||
// Handler that filters events
|
||||
class FilteringHandler : public UEventsHandler
|
||||
{
|
||||
public:
|
||||
FilteringHandler() : testEventCount_(0), anotherEventCount_(0) {}
|
||||
int getTestEventCount() const { return testEventCount_; }
|
||||
int getAnotherEventCount() const { return anotherEventCount_; }
|
||||
void reset() { testEventCount_ = 0; anotherEventCount_ = 0; }
|
||||
|
||||
protected:
|
||||
virtual bool handleEvent(UEvent * event)
|
||||
{
|
||||
if(event->getClassName() == "TestEvent")
|
||||
{
|
||||
testEventCount_++;
|
||||
}
|
||||
else if(event->getClassName() == "AnotherEvent")
|
||||
{
|
||||
anotherEventCount_++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int testEventCount_;
|
||||
int anotherEventCount_;
|
||||
};
|
||||
|
||||
// Test sender class that can post events
|
||||
class TestSender : public UEventsSender
|
||||
{
|
||||
public:
|
||||
TestSender() {}
|
||||
|
||||
// Public method to test protected post() method
|
||||
void testPost(UEvent* event, bool async = true) const
|
||||
{
|
||||
post(event, async);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(UEventsTest, UEventGetClassName)
|
||||
{
|
||||
TestEvent event;
|
||||
EXPECT_EQ(event.getClassName(), "TestEvent");
|
||||
EXPECT_EQ(event.getCode(), 0);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventGetCode)
|
||||
{
|
||||
TestEvent event(42);
|
||||
EXPECT_EQ(event.getCode(), 42);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsHandlerRegisterUnregister)
|
||||
{
|
||||
TestHandler handler;
|
||||
|
||||
// Register handler
|
||||
handler.registerToEventsManager();
|
||||
|
||||
// Post an event
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
// Give time for event to be processed
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_GT(handler.getEventCount(), 0);
|
||||
|
||||
// Unregister handler
|
||||
handler.unregisterFromEventsManager();
|
||||
|
||||
// Post another event
|
||||
int countBefore = handler.getEventCount();
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// Should not receive event after unregister
|
||||
EXPECT_EQ(handler.getEventCount(), countBefore);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsHandlerDestructorUnregisters)
|
||||
{
|
||||
TestHandler* handler = new TestHandler();
|
||||
handler->registerToEventsManager();
|
||||
|
||||
// Post an event
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_EQ(handler->getEventCount(), 1);
|
||||
|
||||
// Delete handler (should unregister automatically)
|
||||
handler->unregisterFromEventsManager();
|
||||
|
||||
// Post another event
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_EQ(handler->getEventCount(), 1);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerAddRemoveHandler)
|
||||
{
|
||||
TestHandler handler1;
|
||||
TestHandler handler2;
|
||||
|
||||
UEventsManager::addHandler(&handler1);
|
||||
UEventsManager::addHandler(&handler2);
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_GT(handler1.getEventCount(), 0);
|
||||
EXPECT_GT(handler2.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler1);
|
||||
|
||||
int count1Before = handler1.getEventCount();
|
||||
int count2Before = handler2.getEventCount();
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_EQ(handler1.getEventCount(), count1Before);
|
||||
EXPECT_GT(handler2.getEventCount(), count2Before);
|
||||
|
||||
UEventsManager::removeHandler(&handler2);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerPostAsync)
|
||||
{
|
||||
TestHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
// Give time for async processing
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
EXPECT_GT(handler.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerPostSync)
|
||||
{
|
||||
TestHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
UEventsManager::post(new TestEvent(), false);
|
||||
|
||||
// Sync should be immediate
|
||||
EXPECT_GT(handler.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerMultipleHandlers)
|
||||
{
|
||||
TestHandler handler1;
|
||||
TestHandler handler2;
|
||||
TestHandler handler3;
|
||||
|
||||
UEventsManager::addHandler(&handler1);
|
||||
UEventsManager::addHandler(&handler2);
|
||||
UEventsManager::addHandler(&handler3);
|
||||
|
||||
handler1.reset();
|
||||
handler2.reset();
|
||||
handler3.reset();
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_GT(handler1.getEventCount(), 0);
|
||||
EXPECT_GT(handler2.getEventCount(), 0);
|
||||
EXPECT_GT(handler3.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler1);
|
||||
UEventsManager::removeHandler(&handler2);
|
||||
UEventsManager::removeHandler(&handler3);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerEventOrdering)
|
||||
{
|
||||
TestHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
// Post multiple events
|
||||
UEventsManager::post(new TestEvent(1), true);
|
||||
UEventsManager::post(new TestEvent(2), true);
|
||||
UEventsManager::post(new TestEvent(3), true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Should receive all events (order may vary in async mode)
|
||||
EXPECT_GE(handler.getEventCount(), 3);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsHandlerOwnership)
|
||||
{
|
||||
OwnershipHandler handler;
|
||||
TestHandler handler0;
|
||||
TestHandler handler2;
|
||||
UEventsManager::addHandler(&handler0);
|
||||
UEventsManager::addHandler(&handler);
|
||||
UEventsManager::addHandler(&handler2);
|
||||
|
||||
UEventsManager::post(new TestEvent(42), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_NE(handler.getEvent(), nullptr);
|
||||
EXPECT_EQ(handler.getEvent()->getCode(), 42);
|
||||
|
||||
// Should have received the event
|
||||
EXPECT_GT(handler0.getEventCount(), 0);
|
||||
|
||||
// Should not have received the event
|
||||
EXPECT_EQ(handler2.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler0);
|
||||
UEventsManager::removeHandler(&handler);
|
||||
UEventsManager::removeHandler(&handler2);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsHandlerFiltering)
|
||||
{
|
||||
FilteringHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
UEventsManager::post(new AnotherEvent(), true);
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
EXPECT_EQ(handler.getTestEventCount(), 2);
|
||||
EXPECT_EQ(handler.getAnotherEventCount(), 1);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsSenderPost)
|
||||
{
|
||||
TestSender sender;
|
||||
TestHandler handler;
|
||||
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
// Test protected post() method through TestSender
|
||||
sender.testPost(new TestEvent(), true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_GT(handler.getEventCount(), 0);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsSenderDestructorRemovesPipes)
|
||||
{
|
||||
TestSender* sender = new TestSender();
|
||||
TestHandler handler;
|
||||
|
||||
UEventsManager::addHandler(&handler);
|
||||
UEventsManager::createPipe(sender, &handler, "TestEvent");
|
||||
|
||||
// Delete sender - should remove pipes automatically
|
||||
delete sender;
|
||||
|
||||
// Should not crash and pipes should be removed
|
||||
SUCCEED();
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerCreateRemovePipe)
|
||||
{
|
||||
TestSender sender;
|
||||
TestHandler handler;
|
||||
TestHandler handler2;
|
||||
|
||||
UEventsManager::addHandler(&handler);
|
||||
UEventsManager::addHandler(&handler2);
|
||||
|
||||
// Create a pipe
|
||||
UEventsManager::createPipe(&sender, &handler, "TestEvent");
|
||||
|
||||
handler.reset();
|
||||
|
||||
// Post event with sender
|
||||
sender.testPost(new TestEvent());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_EQ(handler.getEventCount(), 1);
|
||||
EXPECT_EQ(handler2.getEventCount(), 0);
|
||||
|
||||
// Post event with sender using UEventsManager
|
||||
UEventsManager::post(new TestEvent(), true, &sender);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_EQ(handler.getEventCount(), 2);
|
||||
EXPECT_EQ(handler2.getEventCount(), 0);
|
||||
|
||||
// Post global event
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_EQ(handler.getEventCount(), 3);
|
||||
EXPECT_EQ(handler2.getEventCount(), 1);
|
||||
|
||||
// Remove pipe
|
||||
UEventsManager::removePipe(&sender, &handler, "TestEvent");
|
||||
|
||||
|
||||
// Post another event
|
||||
UEventsManager::post(new TestEvent(), true, &sender);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// Should still receive events, no pipe filtering
|
||||
EXPECT_EQ(handler.getEventCount(), 4);
|
||||
EXPECT_EQ(handler2.getEventCount(), 2);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
UEventsManager::removeHandler(&handler2);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, UEventsManagerRemoveAllPipes)
|
||||
{
|
||||
TestSender sender;
|
||||
TestHandler handler1;
|
||||
TestHandler handler2;
|
||||
|
||||
UEventsManager::addHandler(&handler1);
|
||||
UEventsManager::addHandler(&handler2);
|
||||
|
||||
UEventsManager::createPipe(&sender, &handler1, "TestEvent");
|
||||
UEventsManager::createPipe(&sender, &handler2, "AnotherEvent");
|
||||
|
||||
sender.testPost(new TestEvent());
|
||||
sender.testPost(new AnotherEvent());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_EQ(handler1.getEventCount(), 1);
|
||||
EXPECT_EQ(handler2.getEventCount(), 1);
|
||||
|
||||
UEventsManager::removeAllPipes(&sender);
|
||||
|
||||
sender.testPost(new TestEvent());
|
||||
sender.testPost(new AnotherEvent());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_EQ(handler1.getEventCount(), 3);
|
||||
EXPECT_EQ(handler2.getEventCount(), 3);
|
||||
|
||||
UEventsManager::removeHandler(&handler1);
|
||||
UEventsManager::removeHandler(&handler2);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, MultipleEventTypes)
|
||||
{
|
||||
FilteringHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
UEventsManager::post(new AnotherEvent(), true);
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
EXPECT_EQ(handler.getTestEventCount(), 2);
|
||||
EXPECT_EQ(handler.getAnotherEventCount(), 1);
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
TEST(UEventsTest, HandlerReceivesCorrectEvent)
|
||||
{
|
||||
TestHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
handler.reset();
|
||||
|
||||
TestEvent* event = new TestEvent(42);
|
||||
UEventsManager::post(event, true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
EXPECT_EQ(handler.getLastEventCode(), 42);
|
||||
EXPECT_STREQ(handler.getLastEventClassName().c_str(), "TestEvent");
|
||||
|
||||
UEventsManager::removeHandler(&handler);
|
||||
}
|
||||
|
||||
// Handler that blocks inside handleEvent until released, used to observe
|
||||
// whether removeHandler() waits for an in-flight dispatch to finish.
|
||||
class BlockingHandler : public UEventsHandler
|
||||
{
|
||||
public:
|
||||
BlockingHandler() : inHandler_(false), finishedHandler_(false), release_(false) {}
|
||||
|
||||
bool inHandler() const { return inHandler_.load(); }
|
||||
bool finishedHandler() const { return finishedHandler_.load(); }
|
||||
void release() { release_ = true; }
|
||||
|
||||
protected:
|
||||
virtual bool handleEvent(UEvent *)
|
||||
{
|
||||
inHandler_ = true;
|
||||
while(!release_.load())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
finishedHandler_ = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> inHandler_;
|
||||
std::atomic<bool> finishedHandler_;
|
||||
std::atomic<bool> release_;
|
||||
};
|
||||
|
||||
// Regression test: removeHandler() must not return while another thread is
|
||||
// inside handleEvent() for that handler. Otherwise the caller can destroy the
|
||||
// handler (or data it points at) while the dispatcher still uses it, causing
|
||||
// heap corruption.
|
||||
TEST(UEventsTest, RemoveHandlerBlocksUntilHandleEventCompletes)
|
||||
{
|
||||
BlockingHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
// Wait until the dispatcher thread is inside handleEvent.
|
||||
UTimer waitEnter;
|
||||
while(!handler.inHandler() && waitEnter.ticks() < 1.0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(handler.inHandler())
|
||||
<< "Dispatcher never entered handleEvent within 1s";
|
||||
|
||||
// Call removeHandler from a separate thread; it must block until the
|
||||
// dispatcher exits handleEvent.
|
||||
std::atomic<bool> removeReturned(false);
|
||||
std::thread remover([&]() {
|
||||
UEventsManager::removeHandler(&handler);
|
||||
removeReturned = true;
|
||||
});
|
||||
|
||||
// Give the remover thread time to call into removeHandler and (correctly)
|
||||
// get stuck waiting for the dispatcher.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_FALSE(removeReturned.load())
|
||||
<< "removeHandler returned while handleEvent was still running";
|
||||
EXPECT_FALSE(handler.finishedHandler());
|
||||
|
||||
// Let the dispatcher finish; removeHandler should now complete promptly.
|
||||
handler.release();
|
||||
|
||||
UTimer waitReturn;
|
||||
while(!removeReturned.load() && waitReturn.ticks() < 1.0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
EXPECT_TRUE(removeReturned.load())
|
||||
<< "removeHandler did not return after handleEvent completed";
|
||||
EXPECT_TRUE(handler.finishedHandler());
|
||||
remover.join();
|
||||
}
|
||||
|
||||
// Handler that removes itself from within handleEvent, exercising the
|
||||
// recursive-mutex behavior of handlersMutex_.
|
||||
class SelfRemovingHandler : public UEventsHandler
|
||||
{
|
||||
public:
|
||||
SelfRemovingHandler() : called_(false) {}
|
||||
bool called() const { return called_.load(); }
|
||||
|
||||
protected:
|
||||
virtual bool handleEvent(UEvent *)
|
||||
{
|
||||
called_ = true;
|
||||
// Must not deadlock: handlersMutex_ is recursive, so the dispatcher
|
||||
// thread can re-enter it via removeHandler() while still holding it
|
||||
// for the surrounding dispatch.
|
||||
UEventsManager::removeHandler(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> called_;
|
||||
};
|
||||
|
||||
TEST(UEventsTest, HandlerCanRemoveItselfFromWithinHandleEvent)
|
||||
{
|
||||
SelfRemovingHandler handler;
|
||||
UEventsManager::addHandler(&handler);
|
||||
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
|
||||
UTimer t;
|
||||
while(!handler.called() && t.ticks() < 1.0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
EXPECT_TRUE(handler.called())
|
||||
<< "Handler appears to be deadlocked inside handleEvent";
|
||||
|
||||
// After self-removal, further events must not reach the handler. Since
|
||||
// handleEvent flipped 'called_' once, post again and confirm no further
|
||||
// calls are observed (we can only verify via a second handler that the
|
||||
// dispatcher is still alive afterwards).
|
||||
TestHandler sentinel;
|
||||
UEventsManager::addHandler(&sentinel);
|
||||
UEventsManager::post(new TestEvent(), true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT_GT(sentinel.getEventCount(), 0)
|
||||
<< "Dispatcher is no longer delivering events after self-removal";
|
||||
UEventsManager::removeHandler(&sentinel);
|
||||
}
|
||||
|
||||
145
utilite/test/test_ufile.cpp
Normal file
145
utilite/test/test_ufile.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UFile.h"
|
||||
#include "rtabmap/utilite/UDirectory.h"
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
|
||||
TEST(UFileTest, Exists)
|
||||
{
|
||||
// Create a temporary file
|
||||
std::string testFile = "test_file_exists.txt";
|
||||
std::ofstream file(testFile);
|
||||
file << "test content";
|
||||
file.close();
|
||||
|
||||
EXPECT_TRUE(UFile::exists(testFile));
|
||||
EXPECT_FALSE(UFile::exists("nonexistent_file.txt"));
|
||||
|
||||
// Cleanup
|
||||
std::remove(testFile.c_str());
|
||||
}
|
||||
|
||||
TEST(UFileTest, Length)
|
||||
{
|
||||
std::string testFile = "test_file_length.txt";
|
||||
std::string content = "test content";
|
||||
std::ofstream file(testFile);
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
long length = UFile::length(testFile);
|
||||
EXPECT_EQ(length, static_cast<long>(content.size()));
|
||||
|
||||
long lengthNonexistent = UFile::length("nonexistent_file.txt");
|
||||
EXPECT_EQ(lengthNonexistent, 0);
|
||||
|
||||
// Cleanup
|
||||
std::remove(testFile.c_str());
|
||||
}
|
||||
|
||||
TEST(UFileTest, Erase)
|
||||
{
|
||||
std::string testFile = "test_file_erase.txt";
|
||||
std::ofstream file(testFile);
|
||||
file << "test content";
|
||||
file.close();
|
||||
|
||||
EXPECT_TRUE(UFile::exists(testFile));
|
||||
int result = UFile::erase(testFile);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_FALSE(UFile::exists(testFile));
|
||||
}
|
||||
|
||||
TEST(UFileTest, Rename)
|
||||
{
|
||||
std::string oldFile = "test_file_old.txt";
|
||||
std::string newFile = "test_file_new.txt";
|
||||
std::ofstream file(oldFile);
|
||||
file << "test content";
|
||||
file.close();
|
||||
|
||||
int result = UFile::rename(oldFile, newFile);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_FALSE(UFile::exists(oldFile));
|
||||
EXPECT_TRUE(UFile::exists(newFile));
|
||||
|
||||
// Cleanup
|
||||
std::remove(newFile.c_str());
|
||||
}
|
||||
|
||||
TEST(UFileTest, GetName)
|
||||
{
|
||||
std::string path = "/path/to/file.txt";
|
||||
std::string name = UFile::getName(path);
|
||||
EXPECT_EQ(name, "file.txt");
|
||||
|
||||
std::string path2 = "file.txt";
|
||||
std::string name2 = UFile::getName(path2);
|
||||
EXPECT_EQ(name2, "file.txt");
|
||||
}
|
||||
|
||||
TEST(UFileTest, GetExtension)
|
||||
{
|
||||
std::string path = "/path/to/file.txt";
|
||||
std::string ext = UFile::getExtension(path);
|
||||
EXPECT_EQ(ext, "txt");
|
||||
|
||||
std::string path2 = "file";
|
||||
std::string ext2 = UFile::getExtension(path2);
|
||||
EXPECT_STREQ(ext2.c_str(), "");
|
||||
}
|
||||
|
||||
TEST(UFileTest, Copy)
|
||||
{
|
||||
std::string sourceFile = "test_file_source.txt";
|
||||
std::string destFile = "test_file_dest.txt";
|
||||
std::string content = "test content";
|
||||
|
||||
std::ofstream file(sourceFile);
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
UFile::copy(sourceFile, destFile);
|
||||
EXPECT_TRUE(UFile::exists(destFile));
|
||||
EXPECT_EQ(UFile::length(destFile), static_cast<long>(content.size()));
|
||||
|
||||
// Cleanup
|
||||
std::remove(sourceFile.c_str());
|
||||
std::remove(destFile.c_str());
|
||||
}
|
||||
|
||||
TEST(UFileTest, InstanceMethods)
|
||||
{
|
||||
std::string testFile = "test_file_instance.txt";
|
||||
std::ofstream file(testFile);
|
||||
file << "test content";
|
||||
file.close();
|
||||
|
||||
UFile ufile(testFile);
|
||||
EXPECT_TRUE(ufile.isValid());
|
||||
EXPECT_TRUE(ufile.exists());
|
||||
EXPECT_GT(ufile.length(), 0);
|
||||
EXPECT_EQ(ufile.getName(), "test_file_instance.txt");
|
||||
EXPECT_EQ(ufile.getExtension(), "txt");
|
||||
|
||||
// Cleanup
|
||||
std::remove(testFile.c_str());
|
||||
}
|
||||
|
||||
TEST(UFileTest, InstanceRename)
|
||||
{
|
||||
std::string testFile = "test_file_rename_old.txt";
|
||||
std::ofstream file(testFile);
|
||||
file << "test content";
|
||||
file.close();
|
||||
|
||||
UFile ufile(testFile);
|
||||
int result = ufile.rename("test_file_rename_new");
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_FALSE(UFile::exists(testFile));
|
||||
EXPECT_TRUE(UFile::exists("test_file_rename_new.txt"));
|
||||
|
||||
// Cleanup
|
||||
std::remove("test_file_rename_new.txt");
|
||||
}
|
||||
|
||||
796
utilite/test/test_ulogger.cpp
Normal file
796
utilite/test/test_ulogger.cpp
Normal file
@@ -0,0 +1,796 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UFile.h"
|
||||
#include "rtabmap/utilite/UThread.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
TEST(ULoggerTest, DefaultState)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
EXPECT_EQ(ULogger::type(), ULogger::kTypeNoLog);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kInfo);
|
||||
EXPECT_TRUE(ULogger::isPrintTime());
|
||||
EXPECT_TRUE(ULogger::isPrintLevel());
|
||||
EXPECT_TRUE(ULogger::isPrintEndLine());
|
||||
EXPECT_TRUE(ULogger::isPrintColored());
|
||||
EXPECT_TRUE(ULogger::isPrintWhere());
|
||||
EXPECT_FALSE(ULogger::isPrintWhereFullPath());
|
||||
EXPECT_FALSE(ULogger::isPrintThreadId());
|
||||
EXPECT_FALSE(ULogger::isBuffered());
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetType)
|
||||
{
|
||||
ULogger::setType(ULogger::kTypeConsole);
|
||||
EXPECT_EQ(ULogger::type(), ULogger::kTypeConsole);
|
||||
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
EXPECT_EQ(ULogger::type(), ULogger::kTypeNoLog);
|
||||
|
||||
ULogger::setType(ULogger::kTypeFile, "test_log.txt", false);
|
||||
EXPECT_EQ(ULogger::type(), ULogger::kTypeFile);
|
||||
|
||||
// Cleanup
|
||||
UFile::erase("test_log.txt");
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetLevel)
|
||||
{
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kDebug);
|
||||
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kInfo);
|
||||
|
||||
ULogger::setLevel(ULogger::kWarning);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kWarning);
|
||||
|
||||
ULogger::setLevel(ULogger::kError);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kError);
|
||||
|
||||
ULogger::setLevel(ULogger::kFatal);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kFatal);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, LevelFiltering)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_level_filtering_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
|
||||
// Debug should not be logged
|
||||
UDEBUG("This debug message should not appear");
|
||||
|
||||
// Info and above should be logged
|
||||
UINFO("This info message should appear");
|
||||
UWARN("This warning message should appear");
|
||||
UERROR("This error message should appear");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string fileContent(
|
||||
(std::istreambuf_iterator<char>(file)), // Start iterator
|
||||
std::istreambuf_iterator<char>() // End iterator
|
||||
);
|
||||
file.close();
|
||||
|
||||
EXPECT_FALSE(uStrContains(fileContent, "DEBUG"));
|
||||
EXPECT_TRUE(uStrContains(fileContent, "INFO"));
|
||||
EXPECT_TRUE(uStrContains(fileContent, "WARN"));
|
||||
EXPECT_TRUE(uStrContains(fileContent, "ERROR"));
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintTime)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
std::string testFile = "logger_print_time_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintTime(false);
|
||||
EXPECT_FALSE(ULogger::isPrintTime());
|
||||
UINFO("This info message should appear without time");
|
||||
|
||||
ULogger::setPrintTime(true);
|
||||
EXPECT_TRUE(ULogger::isPrintTime());
|
||||
UINFO("This info message should appear with time");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have date
|
||||
std::getline(file, line);
|
||||
EXPECT_EQ(uSplit(line, '-').size(), 1);
|
||||
// Time is format YYYY-MM-DD HH:MM:SS
|
||||
std::getline(file, line);
|
||||
EXPECT_EQ(uSplit(line, '-').size(), 3);
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintLevel)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
std::string testFile = "logger_print_level_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintLevel(false);
|
||||
EXPECT_FALSE(ULogger::isPrintLevel());
|
||||
UINFO("This info message should appear without level");
|
||||
|
||||
ULogger::setPrintLevel(true);
|
||||
EXPECT_TRUE(ULogger::isPrintLevel());
|
||||
UINFO("This info message should appear with level");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have level
|
||||
std::getline(file, line);
|
||||
EXPECT_EQ(uSplit(line, ']').size(), 1);
|
||||
// Level format: [INFO] msg...
|
||||
std::getline(file, line);
|
||||
EXPECT_EQ(uSplit(line, ']').size(), 2);
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintEndline)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_print_endline_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintEndline(false);
|
||||
EXPECT_FALSE(ULogger::isPrintEndLine());
|
||||
UINFO("This info message should appear first. ");
|
||||
|
||||
ULogger::setPrintEndline(true);
|
||||
EXPECT_TRUE(ULogger::isPrintEndLine());
|
||||
UINFO("This info message should appear on same line than previous msg.");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have level
|
||||
std::getline(file, line);
|
||||
EXPECT_FALSE(line.empty());
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(line.empty());
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintColored)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
ULogger::setPrintColored(false);
|
||||
EXPECT_FALSE(ULogger::isPrintColored());
|
||||
|
||||
ULogger::setPrintColored(true);
|
||||
EXPECT_TRUE(ULogger::isPrintColored());
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintWhere)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_print_where_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintWhere(false);
|
||||
EXPECT_FALSE(ULogger::isPrintWhere());
|
||||
UINFO("This info message should appear without where");
|
||||
|
||||
ULogger::setPrintWhere(true);
|
||||
EXPECT_TRUE(ULogger::isPrintWhere());
|
||||
UINFO("This info message should appear with where");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have where (no test_ulogger.cpp)
|
||||
std::getline(file, line);
|
||||
EXPECT_FALSE(uStrContains(line, "test_ulogger.cpp"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "test_ulogger.cpp"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintWhereFullPath)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_print_where_full_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintWhereFullPath(false);
|
||||
EXPECT_FALSE(ULogger::isPrintWhereFullPath());
|
||||
UINFO("This info message should appear without full path");
|
||||
|
||||
ULogger::setPrintWhereFullPath(true);
|
||||
EXPECT_TRUE(ULogger::isPrintWhereFullPath());
|
||||
UINFO("This info message should appear with full path");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have full path (without / or \\)
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(!uStrContains(line, "/") && !uStrContains(line, "\\"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "/") || uStrContains(line, "\\"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetPrintThreadId)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_print_thread_id_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setPrintThreadId(false);
|
||||
EXPECT_FALSE(ULogger::isPrintThreadId());
|
||||
UINFO("This info message should appear without thread ID");
|
||||
|
||||
ULogger::setPrintThreadId(true);
|
||||
EXPECT_TRUE(ULogger::isPrintThreadId());
|
||||
UINFO("This info message should appear with thread ID");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
// First line should not have thread ID (without {})
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(!uStrContains(line, "{") && !uStrContains(line, "}"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "{") && uStrContains(line, "}"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetBuffered)
|
||||
{
|
||||
ULogger::reset();
|
||||
ULogger::setBuffered(true);
|
||||
EXPECT_TRUE(ULogger::isBuffered());
|
||||
|
||||
ULogger::setBuffered(false);
|
||||
EXPECT_FALSE(ULogger::isBuffered());
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, FileLogging)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "test_ulogger_file.txt";
|
||||
|
||||
// Remove file if exists
|
||||
if(UFile::exists(testFile))
|
||||
{
|
||||
UFile::erase(testFile);
|
||||
}
|
||||
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
|
||||
UINFO("Test message 1");
|
||||
UWARN("Test message 2");
|
||||
UERROR("Test message 3");
|
||||
|
||||
// Make sure everything is written to file
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
// Check file exists and has content
|
||||
EXPECT_TRUE(UFile::exists(testFile));
|
||||
EXPECT_GT(UFile::length(testFile), 0);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, FileLoggingAppend)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "test_ulogger_append.txt";
|
||||
|
||||
// Remove file if exists
|
||||
if(UFile::exists(testFile))
|
||||
{
|
||||
UFile::erase(testFile);
|
||||
}
|
||||
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
|
||||
UINFO("First message");
|
||||
// Make sure everything is written to file
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
long length1 = UFile::length(testFile);
|
||||
|
||||
// Append mode
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, true);
|
||||
UINFO("Second message");
|
||||
// Make sure everything is written to file
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
long length2 = UFile::length(testFile);
|
||||
EXPECT_GT(length2, length1);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, BufferedLogging)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "test_ulogger_buffered.txt";
|
||||
|
||||
// Remove file if exists
|
||||
if(UFile::exists(testFile))
|
||||
{
|
||||
UFile::erase(testFile);
|
||||
}
|
||||
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
ULogger::setBuffered(true);
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
|
||||
UINFO("Buffered message 1");
|
||||
UINFO("Buffered message 2");
|
||||
|
||||
// File should be empty before flush
|
||||
EXPECT_EQ(UFile::length(testFile), 0);
|
||||
|
||||
// Make sure everything is written to file
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
// File should have content after flush
|
||||
EXPECT_GT(UFile::length(testFile), 0);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, GetTime)
|
||||
{
|
||||
std::string timeStr;
|
||||
int result = ULogger::getTime(timeStr);
|
||||
|
||||
EXPECT_GT(result, 0);
|
||||
EXPECT_GT(timeStr.size(), 0u);
|
||||
|
||||
// Time string should contain date/time format
|
||||
// Format is typically "YYYY-MM-DD HH:MM:SS" or similar
|
||||
EXPECT_NE(timeStr.find("-"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, SetEventLevel)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
ULogger::setEventLevel(ULogger::kDebug);
|
||||
EXPECT_EQ(ULogger::eventLevel(), ULogger::kDebug);
|
||||
|
||||
ULogger::setEventLevel(ULogger::kInfo);
|
||||
EXPECT_EQ(ULogger::eventLevel(), ULogger::kInfo);
|
||||
|
||||
ULogger::setEventLevel(ULogger::kWarning);
|
||||
EXPECT_EQ(ULogger::eventLevel(), ULogger::kWarning);
|
||||
|
||||
ULogger::setEventLevel(ULogger::kError);
|
||||
EXPECT_EQ(ULogger::eventLevel(), ULogger::kError);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, RegisterCurrentThread)
|
||||
{
|
||||
ULogger::reset();
|
||||
ULogger::registerCurrentThread("TestThread");
|
||||
|
||||
std::map<std::string, unsigned long> threads = ULogger::getRegisteredThreads();
|
||||
EXPECT_GT(threads.size(), 0u);
|
||||
EXPECT_NE(threads.find("TestThread"), threads.end());
|
||||
|
||||
ULogger::unregisterCurrentThread();
|
||||
|
||||
threads = ULogger::getRegisteredThreads();
|
||||
EXPECT_EQ(threads.find("TestThread"), threads.end());
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, ThreadIdFilter)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
unsigned long currentId = UThread::currentThreadId();
|
||||
std::set<unsigned long> filter;
|
||||
filter.insert(currentId);
|
||||
|
||||
ULogger::setTreadIdFilter(filter);
|
||||
|
||||
const std::set<unsigned long>& retrieved = ULogger::getTreadIdFilter();
|
||||
EXPECT_EQ(retrieved.size(), 1u);
|
||||
EXPECT_NE(retrieved.find(currentId), retrieved.end());
|
||||
}
|
||||
|
||||
class OtherThread : public UThread
|
||||
{
|
||||
public:
|
||||
OtherThread() : logOnce_(false) {}
|
||||
std::string name() {return "OtherThread";}
|
||||
void logOnce() {logOnce_ = true;}
|
||||
protected:
|
||||
virtual void mainLoopBegin() {ULogger::registerCurrentThread(name());}
|
||||
virtual void mainLoopEnd() {ULogger::unregisterCurrentThread();}
|
||||
virtual void mainLoop() {
|
||||
uSleep(10);
|
||||
if(logOnce_)
|
||||
{
|
||||
UINFO("Logging from OtherThread");
|
||||
logOnce_ = false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
bool logOnce_;
|
||||
};
|
||||
|
||||
TEST(ULoggerTest, ThreadIdFilterVector)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
// Both threads are registering to logger
|
||||
ULogger::registerCurrentThread("FilterThread");
|
||||
OtherThread otherThread;
|
||||
otherThread.start();
|
||||
|
||||
// OtherThread registers itself from inside mainLoopBegin(), which runs
|
||||
// asynchronously after start(). setTreadIdFilter() resolves names against
|
||||
// the current registeredThreads_ map, so we have to wait for the other
|
||||
// thread to register before applying the filter -- otherwise only
|
||||
// "FilterThread" ends up in the filter set (intermittent failure on slow
|
||||
// Windows runners).
|
||||
for(int i = 0; i < 200; ++i)
|
||||
{
|
||||
const std::map<std::string, unsigned long> regs = ULogger::getRegisteredThreads();
|
||||
if(regs.find(otherThread.name()) != regs.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
uSleep(10);
|
||||
}
|
||||
|
||||
std::vector<std::string> threadNames;
|
||||
threadNames.push_back(otherThread.name());
|
||||
threadNames.push_back("FilterThread");
|
||||
|
||||
ULogger::setTreadIdFilter(threadNames);
|
||||
|
||||
const std::set<unsigned long>& retrieved = ULogger::getTreadIdFilter();
|
||||
EXPECT_EQ(retrieved.size(), 2u);
|
||||
|
||||
std::string testFile = "logger_thread_log_filtering_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
UINFO("This log should appear");
|
||||
|
||||
threadNames.pop_back();
|
||||
ULogger::setTreadIdFilter(threadNames);
|
||||
UINFO("This log should not appear");
|
||||
|
||||
otherThread.logOnce();
|
||||
otherThread.join(true);
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "This log should appear"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "Logging from OtherThread"));
|
||||
file.close();
|
||||
|
||||
ULogger::unregisterCurrentThread();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, WriteWithLevel)
|
||||
{
|
||||
ULogger::reset();
|
||||
|
||||
std::string testFile = "logger_write_level_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
|
||||
// Test write with different levels
|
||||
ULogger::write(ULogger::kDebug, __FILE__, __LINE__, __FUNCTION__, "Debug message %d", 42);
|
||||
ULogger::write(ULogger::kInfo, __FILE__, __LINE__, __FUNCTION__, "Info message %s", "test");
|
||||
ULogger::write(ULogger::kWarning, __FILE__, __LINE__, __FUNCTION__, "Warning message");
|
||||
ULogger::write(ULogger::kError, __FILE__, __LINE__, __FUNCTION__, "Error message");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[DEBUG]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Debug message 42"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ INFO]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Info message test"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ WARN]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Warning message"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ERROR]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Error message"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, Macros)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_macros_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
|
||||
UDEBUG("Debug macro message");
|
||||
UINFO("Info macro message");
|
||||
UWARN("Warning macro message");
|
||||
UERROR("Error macro message");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[DEBUG]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Debug macro message"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ INFO]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Info macro message"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ WARN]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Warning macro message"));
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "[ERROR]"));
|
||||
EXPECT_TRUE(uStrContains(line, "Error macro message"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, Reset)
|
||||
{
|
||||
ULogger::reset();
|
||||
// Change some settings
|
||||
ULogger::setType(ULogger::kTypeConsole);
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
ULogger::setPrintTime(false);
|
||||
ULogger::setPrintLevel(false);
|
||||
ULogger::setBuffered(true);
|
||||
|
||||
// Reset
|
||||
ULogger::reset();
|
||||
|
||||
// Should be back to defaults
|
||||
EXPECT_EQ(ULogger::type(), ULogger::kTypeNoLog);
|
||||
EXPECT_EQ(ULogger::level(), ULogger::kInfo);
|
||||
EXPECT_TRUE(ULogger::isPrintTime());
|
||||
EXPECT_TRUE(ULogger::isPrintLevel());
|
||||
EXPECT_FALSE(ULogger::isBuffered());
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, MultipleLogLevels)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_multiple_levels_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
// Test all levels
|
||||
ULogger::setLevel(ULogger::kDebug);
|
||||
UDEBUG("Debug");
|
||||
UINFO("Info");
|
||||
UWARN("Warning");
|
||||
UERROR("Error");
|
||||
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
UDEBUG("Debug - should not appear");
|
||||
UINFO("Info");
|
||||
UWARN("Warning");
|
||||
UERROR("Error");
|
||||
|
||||
ULogger::setLevel(ULogger::kWarning);
|
||||
UDEBUG("Debug - should not appear");
|
||||
UINFO("Info - should not appear");
|
||||
UWARN("Warning");
|
||||
UERROR("Error");
|
||||
|
||||
ULogger::setLevel(ULogger::kError);
|
||||
UDEBUG("Debug - should not appear");
|
||||
UINFO("Info - should not appear");
|
||||
UWARN("Warning - should not appear");
|
||||
UERROR("Error");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
int count = 0;
|
||||
std::getline(file, line);
|
||||
while(!line.empty())
|
||||
{
|
||||
EXPECT_TRUE(!uStrContains(line, "should not appear"));
|
||||
++count;
|
||||
std::getline(file, line);
|
||||
}
|
||||
EXPECT_EQ(count, 10);
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, FileContent)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "test_ulogger_content.txt";
|
||||
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
ULogger::setLevel(ULogger::kInfo);
|
||||
ULogger::setPrintTime(false);
|
||||
ULogger::setPrintLevel(true);
|
||||
ULogger::setPrintWhere(false);
|
||||
|
||||
UINFO("Test content message");
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
// Read file and check content
|
||||
std::ifstream file(testFile);
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
EXPECT_NE(content.find("Test content message"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, ThreadSafety)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_thread_safety_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
// Disable all printing to easyly test results below
|
||||
ULogger::setPrintTime(false);
|
||||
ULogger::setPrintLevel(false);
|
||||
ULogger::setPrintWhere(false);
|
||||
ULogger::setPrintWhereFullPath(false);
|
||||
ULogger::setPrintColored(false);
|
||||
ULogger::setPrintThreadId(false);
|
||||
|
||||
static constexpr int numThreads = 5;
|
||||
static constexpr int messagesPerThread = 9;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
threads.emplace_back([i]() {
|
||||
for(int j = 0; j < messagesPerThread; ++j)
|
||||
{
|
||||
UINFO("Thread %d message %d", i, j);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for(auto& t : threads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
int count = 0;
|
||||
std::getline(file, line);
|
||||
while(!line.empty())
|
||||
{
|
||||
// The logger writes "\r\n" endings, on platforms where the stream doesn't
|
||||
// translate them (non-Windows) the '\r' is left in the line by getline().
|
||||
if(line[line.size()-1] == '\r')
|
||||
{
|
||||
line.erase(line.size()-1);
|
||||
}
|
||||
// Expect all messages on single line, no interleaved, format: "Thread X message X"
|
||||
EXPECT_EQ(line.length(), 18);
|
||||
++count;
|
||||
std::getline(file, line);
|
||||
}
|
||||
EXPECT_EQ(count, 45);
|
||||
file.close();
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, DefaultLogFileName)
|
||||
{
|
||||
EXPECT_EQ(ULogger::kDefaultLogFileName, "./ULog.txt");
|
||||
}
|
||||
|
||||
TEST(ULoggerTest, AllPrintOptions)
|
||||
{
|
||||
ULogger::reset();
|
||||
std::string testFile = "logger_all_print_options_test.txt";
|
||||
ULogger::setType(ULogger::kTypeFile, testFile, false);
|
||||
|
||||
// Test with all print options enabled
|
||||
ULogger::setPrintTime(true);
|
||||
ULogger::setPrintLevel(true);
|
||||
ULogger::setPrintWhereFullPath(true);
|
||||
ULogger::setPrintColored(true);
|
||||
ULogger::setPrintThreadId(true);
|
||||
|
||||
UINFO("Message with all options");
|
||||
|
||||
// Test with all print options disabled
|
||||
ULogger::setPrintTime(false);
|
||||
ULogger::setPrintLevel(false);
|
||||
ULogger::setPrintWhere(false);
|
||||
ULogger::setPrintWhereFullPath(false);
|
||||
ULogger::setPrintColored(false);
|
||||
ULogger::setPrintThreadId(false);
|
||||
|
||||
UINFO("Message with no options");
|
||||
|
||||
// Make sure the log file is closed
|
||||
ULogger::setType(ULogger::kTypeNoLog);
|
||||
|
||||
std::ifstream file(testFile);
|
||||
std::string line;
|
||||
EXPECT_TRUE(file.is_open());
|
||||
|
||||
std::getline(file, line);
|
||||
EXPECT_TRUE(uStrContains(line, "["));
|
||||
EXPECT_TRUE(uStrContains(line, "{"));
|
||||
EXPECT_TRUE(uStrContains(line, "-"));
|
||||
EXPECT_TRUE(uStrContains(line, ".cpp"));
|
||||
EXPECT_TRUE(uStrContains(line, "/") || uStrContains(line, "\\"));
|
||||
|
||||
std::getline(file, line);
|
||||
EXPECT_FALSE(uStrContains(line, "["));
|
||||
EXPECT_FALSE(uStrContains(line, "{"));
|
||||
EXPECT_FALSE(uStrContains(line, "-"));
|
||||
EXPECT_FALSE(uStrContains(line, ".cpp"));
|
||||
EXPECT_FALSE(uStrContains(line, "\\"));
|
||||
EXPECT_FALSE(uStrContains(line, "/"));
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
284
utilite/test/test_umath.cpp
Normal file
284
utilite/test/test_umath.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UMath.h"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
TEST(UMathTest, UIsNan)
|
||||
{
|
||||
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||
float normalValue = 3.14f;
|
||||
|
||||
EXPECT_TRUE(uIsNan(nanValue));
|
||||
EXPECT_FALSE(uIsNan(normalValue));
|
||||
|
||||
double nanDouble = std::numeric_limits<double>::quiet_NaN();
|
||||
double normalDouble = 3.14;
|
||||
EXPECT_TRUE(uIsNan(nanDouble));
|
||||
EXPECT_FALSE(uIsNan(normalDouble));
|
||||
}
|
||||
|
||||
TEST(UMathTest, UIsFinite)
|
||||
{
|
||||
float infValue = std::numeric_limits<float>::infinity();
|
||||
float normalValue = 3.14f;
|
||||
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||
|
||||
EXPECT_FALSE(uIsFinite(infValue));
|
||||
EXPECT_TRUE(uIsFinite(normalValue));
|
||||
EXPECT_FALSE(uIsFinite(nanValue));
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMin3)
|
||||
{
|
||||
EXPECT_EQ(uMin3(3, 1, 2), 1);
|
||||
EXPECT_EQ(uMin3(1, 2, 3), 1);
|
||||
EXPECT_EQ(uMin3(2, 3, 1), 1);
|
||||
EXPECT_EQ(uMin3(1.5f, 2.5f, 0.5f), 0.5f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMax3)
|
||||
{
|
||||
EXPECT_EQ(uMax3(3, 1, 2), 3);
|
||||
EXPECT_EQ(uMax3(1, 2, 3), 3);
|
||||
EXPECT_EQ(uMax3(2, 3, 1), 3);
|
||||
EXPECT_EQ(uMax3(1.5f, 2.5f, 0.5f), 2.5f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMaxArray)
|
||||
{
|
||||
int arr[] = {1, 5, 3, 9, 2};
|
||||
unsigned int index;
|
||||
int max = uMax(arr, 5, index);
|
||||
EXPECT_EQ(max, 9);
|
||||
EXPECT_EQ(index, 3u);
|
||||
|
||||
int max2 = uMax(arr, 5);
|
||||
EXPECT_EQ(max2, 9);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMaxVector)
|
||||
{
|
||||
std::vector<int> v = {1, 5, 3, 9, 2};
|
||||
unsigned int index;
|
||||
int max = uMax(v, index);
|
||||
EXPECT_EQ(max, 9);
|
||||
EXPECT_EQ(index, 3u);
|
||||
|
||||
int max2 = uMax(v);
|
||||
EXPECT_EQ(max2, 9);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMinArray)
|
||||
{
|
||||
int arr[] = {5, 1, 3, 9, 2};
|
||||
unsigned int index;
|
||||
int min = uMin(arr, 5, index);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(index, 1u);
|
||||
|
||||
int min2 = uMin(arr, 5);
|
||||
EXPECT_EQ(min2, 1);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMinVector)
|
||||
{
|
||||
std::vector<int> v = {5, 1, 3, 9, 2};
|
||||
unsigned int index;
|
||||
int min = uMin(v, index);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(index, 1u);
|
||||
|
||||
int min2 = uMin(v);
|
||||
EXPECT_EQ(min2, 1);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMinMaxArray)
|
||||
{
|
||||
int arr[] = {5, 1, 3, 9, 2};
|
||||
int min, max;
|
||||
unsigned int indexMin, indexMax;
|
||||
uMinMax(arr, 5, min, max, indexMin, indexMax);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(max, 9);
|
||||
EXPECT_EQ(indexMin, 1u);
|
||||
EXPECT_EQ(indexMax, 3u);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMinMaxVector)
|
||||
{
|
||||
std::vector<int> v = {5, 1, 3, 9, 2};
|
||||
int min, max;
|
||||
unsigned int indexMin, indexMax;
|
||||
uMinMax(v, min, max, indexMin, indexMax);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(max, 9);
|
||||
EXPECT_EQ(indexMin, 1u);
|
||||
EXPECT_EQ(indexMax, 3u);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMinMaxWithoutIndices)
|
||||
{
|
||||
int arr[] = {5, 1, 3, 9, 2};
|
||||
int min, max;
|
||||
uMinMax(arr, 5, min, max);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(max, 9);
|
||||
|
||||
std::vector<int> v = {5, 1, 3, 9, 2};
|
||||
uMinMax(v, min, max);
|
||||
EXPECT_EQ(min, 1);
|
||||
EXPECT_EQ(max, 9);
|
||||
}
|
||||
|
||||
TEST(UMathTest, USign)
|
||||
{
|
||||
EXPECT_EQ(uSign(5), 1);
|
||||
EXPECT_EQ(uSign(-5), -1);
|
||||
EXPECT_EQ(uSign(0), 1);
|
||||
EXPECT_EQ(uSign(3.14f), 1);
|
||||
EXPECT_EQ(uSign(-3.14f), -1);
|
||||
}
|
||||
|
||||
TEST(UMathTest, USumList)
|
||||
{
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
int sum = uSum(list);
|
||||
EXPECT_EQ(sum, 15);
|
||||
}
|
||||
|
||||
TEST(UMathTest, USumArray)
|
||||
{
|
||||
int arr[] = {1, 2, 3, 4, 5};
|
||||
int sum = uSum(arr, 5);
|
||||
EXPECT_EQ(sum, 15);
|
||||
}
|
||||
|
||||
TEST(UMathTest, USumVector)
|
||||
{
|
||||
std::vector<int> v = {1, 2, 3, 4, 5};
|
||||
int sum = uSum(v);
|
||||
EXPECT_EQ(sum, 15);
|
||||
}
|
||||
|
||||
TEST(UMathTest, USumSquared)
|
||||
{
|
||||
int arr[] = {1, 2, 3};
|
||||
int sum = uSumSquared(arr, 3);
|
||||
EXPECT_EQ(sum, 14); // 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14
|
||||
|
||||
int sum2 = uSumSquared(arr, 3, 2); // subtract 2
|
||||
EXPECT_EQ(sum2, 2); // (-1)*(-1) + 0*0 + 1*1 = 1 + 0 + 1 = 2
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMean)
|
||||
{
|
||||
int arr[] = {1, 2, 3, 4, 5};
|
||||
float mean = uMean(arr, 5);
|
||||
EXPECT_FLOAT_EQ(mean, 3.0f);
|
||||
|
||||
std::vector<int> v = {1, 2, 3, 4, 5};
|
||||
float mean2 = uMean(v);
|
||||
EXPECT_FLOAT_EQ(mean2, 3.0f);
|
||||
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
float mean3 = uMean(list);
|
||||
EXPECT_FLOAT_EQ(mean3, 3.0f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UMeanSquaredError)
|
||||
{
|
||||
float x[] = {1.0f, 2.0f, 3.0f};
|
||||
float y[] = {1.1f, 2.1f, 3.1f};
|
||||
float mse = uMeanSquaredError(x, 3, y, 3);
|
||||
EXPECT_NEAR(mse, 0.01f, 0.001f); // (0.1^2 + 0.1^2 + 0.1^2) / 3 = 0.01
|
||||
|
||||
std::vector<float> vx = {1.0f, 2.0f, 3.0f};
|
||||
std::vector<float> vy = {1.1f, 2.1f, 3.1f};
|
||||
float mse2 = uMeanSquaredError(vx, vy);
|
||||
EXPECT_NEAR(mse2, 0.01f, 0.001f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UVariance)
|
||||
{
|
||||
float arr[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
|
||||
float mean = uMean(arr, 5);
|
||||
float variance = uVariance(arr, 5, mean);
|
||||
EXPECT_NEAR(variance, 2.5f, 0.1f); // Variance of 1,2,3,4,5 is 2.5
|
||||
|
||||
float variance2 = uVariance(arr, 5);
|
||||
EXPECT_NEAR(variance2, 2.5f, 0.1f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UNormSquared)
|
||||
{
|
||||
std::vector<float> v = {3.0f, 4.0f};
|
||||
float normSq = uNormSquared(v);
|
||||
EXPECT_FLOAT_EQ(normSq, 25.0f); // 3*3 + 4*4 = 9 + 16 = 25
|
||||
|
||||
float normSq2 = uNormSquared(3.0f, 4.0f);
|
||||
EXPECT_FLOAT_EQ(normSq2, 25.0f);
|
||||
|
||||
float normSq3 = uNormSquared(1.0f, 2.0f, 3.0f);
|
||||
EXPECT_FLOAT_EQ(normSq3, 14.0f); // 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14
|
||||
}
|
||||
|
||||
TEST(UMathTest, UNorm)
|
||||
{
|
||||
std::vector<float> v = {3.0f, 4.0f};
|
||||
float norm = uNorm(v);
|
||||
EXPECT_FLOAT_EQ(norm, 5.0f); // sqrt(3*3 + 4*4) = sqrt(25) = 5
|
||||
|
||||
float norm2 = uNorm(3.0f, 4.0f);
|
||||
EXPECT_FLOAT_EQ(norm2, 5.0f);
|
||||
|
||||
float norm3 = uNorm(1.0f, 2.0f, 3.0f);
|
||||
EXPECT_NEAR(norm3, 3.741657f, 0.001f); // sqrt(14)
|
||||
}
|
||||
|
||||
TEST(UMathTest, UNormalize)
|
||||
{
|
||||
std::vector<float> v = {3.0f, 4.0f};
|
||||
std::vector<float> normalized = uNormalize(v);
|
||||
float norm = uNorm(normalized);
|
||||
EXPECT_NEAR(norm, 1.0f, 0.001f);
|
||||
|
||||
std::vector<float> zero = {0.0f, 0.0f};
|
||||
std::vector<float> normalizedZero = uNormalize(zero);
|
||||
EXPECT_EQ(normalizedZero.size(), 2u);
|
||||
EXPECT_FLOAT_EQ(normalizedZero[0], 0.0f);
|
||||
EXPECT_FLOAT_EQ(normalizedZero[1], 0.0f);
|
||||
}
|
||||
|
||||
TEST(UMathTest, ULocalMaxima)
|
||||
{
|
||||
float arr[] = {1.0f, 3.0f, 2.0f, 5.0f, 4.0f, 6.0f, 3.0f};
|
||||
std::list<unsigned int> maxima = uLocalMaxima(arr, 7);
|
||||
// Should find maxima at indices 1, 3, 5
|
||||
EXPECT_GT(maxima.size(), 0u);
|
||||
|
||||
std::vector<float> v = {1.0f, 3.0f, 2.0f, 5.0f, 4.0f};
|
||||
std::list<unsigned int> maxima2 = uLocalMaxima(v);
|
||||
EXPECT_GT(maxima2.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(UMathTest, UHamming)
|
||||
{
|
||||
std::vector<float> window = uHamming(10);
|
||||
EXPECT_EQ(window.size(), 10u);
|
||||
// Hamming window values should be between 0 and 1
|
||||
for(float w : window)
|
||||
{
|
||||
EXPECT_GE(w, 0.0f);
|
||||
EXPECT_LE(w, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UMathTest, UIsInBounds)
|
||||
{
|
||||
EXPECT_TRUE(uIsInBounds(5, 0, 10));
|
||||
EXPECT_FALSE(uIsInBounds(10, 0, 10));
|
||||
EXPECT_FALSE(uIsInBounds(-1, 0, 10));
|
||||
EXPECT_FALSE(uIsInBounds(std::numeric_limits<float>::quiet_NaN(), 0.0f, 10.0f));
|
||||
EXPECT_FALSE(uIsInBounds(std::numeric_limits<float>::infinity(), 0.0f, 10.0f));
|
||||
}
|
||||
|
||||
178
utilite/test/test_umutex.cpp
Normal file
178
utilite/test/test_umutex.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UMutex.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
TEST(UMutexTest, Constructor)
|
||||
{
|
||||
UMutex mutex;
|
||||
// Should be constructible
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, LockUnlock)
|
||||
{
|
||||
UMutex mutex;
|
||||
// lock()/unlock() return 0 on success (an error code otherwise).
|
||||
EXPECT_EQ(mutex.lock(), 0);
|
||||
EXPECT_EQ(mutex.unlock(), 0);
|
||||
}
|
||||
|
||||
TEST(UMutexTest, LockTry)
|
||||
{
|
||||
UMutex mutex;
|
||||
|
||||
// Try to lock when not locked: lockTry() returns 0 when it took the lock.
|
||||
EXPECT_EQ(mutex.lockTry(), 0);
|
||||
|
||||
// Unlock
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, LockTryWhenLocked)
|
||||
{
|
||||
UMutex mutex;
|
||||
|
||||
// Lock the mutex
|
||||
mutex.lock();
|
||||
|
||||
// Try to lock from another thread
|
||||
std::atomic<int> tryResult(0);
|
||||
std::thread t([&mutex, &tryResult]() {
|
||||
tryResult = mutex.lockTry(); // Should fail (EBUSY or similar)
|
||||
});
|
||||
|
||||
t.join();
|
||||
|
||||
EXPECT_NE(tryResult, 0);
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, ThreadSafety)
|
||||
{
|
||||
UMutex mutex;
|
||||
std::atomic<int> counter(0);
|
||||
static constexpr int numThreads = 4;
|
||||
static constexpr int iterations = 1000;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
threads.emplace_back([&mutex, &counter]() {
|
||||
for(int j = 0; j < iterations; ++j)
|
||||
{
|
||||
mutex.lock();
|
||||
int val = counter.load();
|
||||
// yield() instead of sleep_for(1us): the original sleep was
|
||||
// ~1us intended (negligible on Linux/macOS) but ~15ms on
|
||||
// Windows because of the default 15.6ms timer tick -- that
|
||||
// turned this test into a 60s test on Windows CI. yield()
|
||||
// still scrambles interleaving without the wall-clock cost.
|
||||
std::this_thread::yield();
|
||||
counter.store(val + 1);
|
||||
mutex.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for(auto& t : threads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(counter.load(), numThreads * iterations);
|
||||
}
|
||||
|
||||
TEST(UMutexTest, RecursiveLock)
|
||||
{
|
||||
UMutex mutex;
|
||||
|
||||
// Lock multiple times from same thread (should work on Unix)
|
||||
EXPECT_EQ(mutex.lock(), 0);
|
||||
EXPECT_EQ(mutex.lock(), 0);
|
||||
EXPECT_EQ(mutex.lock(), 0);
|
||||
|
||||
// Unlock multiple times
|
||||
EXPECT_EQ(mutex.unlock(), 0);
|
||||
EXPECT_EQ(mutex.unlock(), 0);
|
||||
EXPECT_EQ(mutex.unlock(), 0);
|
||||
}
|
||||
|
||||
TEST(UMutexTest, UScopeMutex)
|
||||
{
|
||||
UMutex mutex;
|
||||
{
|
||||
UScopeMutex scopeMutex(mutex);
|
||||
// Mutex should be locked here
|
||||
|
||||
// Try to lock from another thread
|
||||
std::thread t([&mutex]() {
|
||||
EXPECT_NE(mutex.lockTry(), 0); // Should fail
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
// Mutex should be unlocked here
|
||||
|
||||
// Try to lock from another thread
|
||||
std::thread t([&mutex]() {
|
||||
EXPECT_EQ(mutex.lockTry(), 0); // Should succeed
|
||||
mutex.unlock();
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, UScopeMutexWithPointer)
|
||||
{
|
||||
UMutex mutex;
|
||||
|
||||
{
|
||||
UScopeMutex scopeMutex(&mutex);
|
||||
// Mutex should be locked here
|
||||
|
||||
// Try to lock from another thread
|
||||
std::thread t([&mutex]() {
|
||||
EXPECT_NE(mutex.lockTry(), 0); // Should fail
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
// Mutex should be unlocked here
|
||||
|
||||
// Try to lock from another thread
|
||||
std::thread t([&mutex]() {
|
||||
EXPECT_EQ(mutex.lockTry(), 0); // Should succeed
|
||||
mutex.unlock();
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, MultipleMutexes)
|
||||
{
|
||||
UMutex mutex1;
|
||||
UMutex mutex2;
|
||||
|
||||
mutex1.lock();
|
||||
mutex2.lock();
|
||||
|
||||
mutex1.unlock();
|
||||
mutex2.unlock();
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UMutexTest, LockUnlockSequence)
|
||||
{
|
||||
UMutex mutex;
|
||||
|
||||
for(int i = 0; i < 10; ++i)
|
||||
{
|
||||
mutex.lock();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
30
utilite/test/test_uprocessinfo.cpp
Normal file
30
utilite/test/test_uprocessinfo.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UProcessInfo.h"
|
||||
|
||||
TEST(UProcessInfoTest, Constructor)
|
||||
{
|
||||
UProcessInfo info;
|
||||
// Should be constructible
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UProcessInfoTest, GetMemoryUsage)
|
||||
{
|
||||
long int memory = UProcessInfo::getMemoryUsage();
|
||||
// Memory usage should be positive
|
||||
EXPECT_GT(memory, 0);
|
||||
// Should be reasonable (less than 1TB)
|
||||
EXPECT_LT(memory, 1024LL * 1024 * 1024 * 1024);
|
||||
}
|
||||
|
||||
TEST(UProcessInfoTest, GetMemoryUsageMultipleCalls)
|
||||
{
|
||||
long int memory1 = UProcessInfo::getMemoryUsage();
|
||||
long int memory2 = UProcessInfo::getMemoryUsage();
|
||||
// Both calls should return valid values
|
||||
EXPECT_GT(memory1, 0);
|
||||
EXPECT_GT(memory2, 0);
|
||||
// Values should be similar (might vary slightly)
|
||||
EXPECT_NEAR(memory1, memory2, memory1 * 0.1); // Allow 10% variation
|
||||
}
|
||||
|
||||
341
utilite/test/test_usemaphore.cpp
Normal file
341
utilite/test/test_usemaphore.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/USemaphore.h"
|
||||
#include "rtabmap/utilite/UMutex.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
TEST(USemaphoreTest, ConstructorDefault)
|
||||
{
|
||||
USemaphore sem;
|
||||
// Should be constructible with default value (0)
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, ConstructorWithValue)
|
||||
{
|
||||
USemaphore sem(5);
|
||||
EXPECT_EQ(sem.value(), 5);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, Release)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
|
||||
sem.release();
|
||||
EXPECT_EQ(sem.value(), 1);
|
||||
|
||||
sem.release(3);
|
||||
EXPECT_EQ(sem.value(), 4);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, Acquire)
|
||||
{
|
||||
USemaphore sem(2);
|
||||
|
||||
bool result = sem.acquire();
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 1);
|
||||
|
||||
result = sem.acquire();
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, AcquireMultiple)
|
||||
{
|
||||
USemaphore sem(5);
|
||||
|
||||
bool result = sem.acquire(3);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 2);
|
||||
|
||||
result = sem.acquire(2);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, AcquireBlocks)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
std::atomic<bool> acquired(false);
|
||||
std::atomic<bool> threadStarted(false);
|
||||
|
||||
std::thread t([&sem, &acquired, &threadStarted]() {
|
||||
threadStarted = true;
|
||||
bool result = sem.acquire();
|
||||
acquired = result;
|
||||
});
|
||||
|
||||
// Wait for thread to start and try to acquire
|
||||
while(!threadStarted)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// Thread should be blocked
|
||||
EXPECT_FALSE(acquired);
|
||||
|
||||
// Release to unblock
|
||||
sem.release();
|
||||
|
||||
t.join();
|
||||
|
||||
EXPECT_TRUE(acquired);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, AcquireWithTimeout)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
|
||||
// Try to acquire with timeout
|
||||
bool result = sem.acquire(1, 50); // 50ms timeout
|
||||
EXPECT_FALSE(result); // Should timeout
|
||||
|
||||
// Release and try again
|
||||
sem.release();
|
||||
result = sem.acquire(1, 50);
|
||||
EXPECT_TRUE(result);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, AcquireTry)
|
||||
{
|
||||
USemaphore sem(3);
|
||||
|
||||
// Try to acquire when available
|
||||
#ifdef _WIN32
|
||||
// Windows overload returns 0 on success, EAGAIN when nothing is available.
|
||||
int result = sem.acquireTry();
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(sem.value(), 2);
|
||||
result = sem.acquireTry();
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(sem.value(), 1);
|
||||
result = sem.acquireTry();
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
// Semaphore is exhausted: the next non-blocking try must fail.
|
||||
result = sem.acquireTry();
|
||||
EXPECT_NE(result, 0);
|
||||
#else
|
||||
// Same convention as the Windows overload: 0 on success, EAGAIN otherwise.
|
||||
int result = sem.acquireTry(1);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(sem.value(), 2);
|
||||
|
||||
result = sem.acquireTry(2);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
|
||||
// Try to acquire when not available
|
||||
result = sem.acquireTry(1);
|
||||
EXPECT_NE(result, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, Value)
|
||||
{
|
||||
USemaphore sem(10);
|
||||
|
||||
int value = sem.value();
|
||||
EXPECT_EQ(value, 10);
|
||||
|
||||
sem.acquire(3);
|
||||
value = sem.value();
|
||||
EXPECT_EQ(value, 7);
|
||||
|
||||
sem.release(2);
|
||||
value = sem.value();
|
||||
EXPECT_EQ(value, 9);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, ThreadSafety)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
std::atomic<int> counter(0);
|
||||
static constexpr int numThreads = 4;
|
||||
static constexpr int iterations = 100;
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
// Create threads that will wait on semaphore
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
threads.emplace_back([&sem, &counter]() {
|
||||
for(int j = 0; j < iterations; ++j)
|
||||
{
|
||||
sem.acquire();
|
||||
counter++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Release semaphore to allow threads to proceed
|
||||
std::thread releaseThread([&sem]() {
|
||||
for(int i = 0; i < numThreads * iterations; ++i)
|
||||
{
|
||||
sem.release();
|
||||
// yield() instead of sleep_for(10us): Windows's default 15.6ms
|
||||
// timer tick turns "10us" into ~15ms, blowing this 400-iter loop
|
||||
// up to ~6s on Windows CI for no benefit. yield() still lets the
|
||||
// waiting acquirers run between releases without the wall-clock
|
||||
// cost.
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
|
||||
for(auto& t : threads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
|
||||
releaseThread.join();
|
||||
|
||||
EXPECT_EQ(counter.load(), numThreads * iterations);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, ProducerConsumer)
|
||||
{
|
||||
USemaphore full(0);
|
||||
USemaphore empty(10);
|
||||
std::vector<int> buffer;
|
||||
UMutex bufferMutex;
|
||||
static constexpr int items = 20;
|
||||
std::atomic<int> produced(0);
|
||||
std::atomic<int> consumed(0);
|
||||
|
||||
// Producer thread
|
||||
std::thread producer([&full, &empty, &buffer, &bufferMutex, &produced]() {
|
||||
for(int i = 0; i < items; ++i)
|
||||
{
|
||||
empty.acquire();
|
||||
{
|
||||
UScopeMutex lock(bufferMutex);
|
||||
buffer.push_back(i);
|
||||
}
|
||||
full.release();
|
||||
produced++;
|
||||
}
|
||||
});
|
||||
|
||||
// Consumer thread
|
||||
std::thread consumer([&full, &empty, &buffer, &bufferMutex, &consumed]() {
|
||||
for(int i = 0; i < items; ++i)
|
||||
{
|
||||
full.acquire();
|
||||
{
|
||||
UScopeMutex lock(bufferMutex);
|
||||
if(!buffer.empty())
|
||||
{
|
||||
buffer.pop_back();
|
||||
}
|
||||
}
|
||||
empty.release();
|
||||
consumed++;
|
||||
}
|
||||
});
|
||||
|
||||
producer.join();
|
||||
consumer.join();
|
||||
|
||||
EXPECT_EQ(produced.load(), items);
|
||||
EXPECT_EQ(consumed.load(), items);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, MultipleAcquireRelease)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
|
||||
// Release multiple times
|
||||
sem.release(5);
|
||||
EXPECT_EQ(sem.value(), 5);
|
||||
|
||||
// Acquire multiple times
|
||||
bool result = sem.acquire(3);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 2);
|
||||
|
||||
result = sem.acquire(2);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, AcquireZero)
|
||||
{
|
||||
USemaphore sem(5);
|
||||
|
||||
// Acquire 0 should not block
|
||||
bool result = sem.acquire(0);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_EQ(sem.value(), 5);
|
||||
}
|
||||
|
||||
TEST(USemaphoreTest, ReleaseZero)
|
||||
{
|
||||
USemaphore sem(5);
|
||||
|
||||
// Release 0 should not change value
|
||||
sem.release(0);
|
||||
EXPECT_EQ(sem.value(), 5);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(USemaphoreTest, Reset)
|
||||
{
|
||||
USemaphore sem(5);
|
||||
EXPECT_EQ(sem.value(), 5);
|
||||
|
||||
sem.reset(10);
|
||||
EXPECT_EQ(sem.value(), 10);
|
||||
|
||||
sem.reset(0);
|
||||
EXPECT_EQ(sem.value(), 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(USemaphoreTest, ConcurrentAcquireRelease)
|
||||
{
|
||||
USemaphore sem(0);
|
||||
std::atomic<int> acquiredCount(0);
|
||||
const int numThreads = 5;
|
||||
|
||||
std::vector<std::thread> acquireThreads;
|
||||
std::vector<std::thread> releaseThreads;
|
||||
|
||||
// Create threads that acquire
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
acquireThreads.emplace_back([&sem, &acquiredCount]() {
|
||||
sem.acquire();
|
||||
acquiredCount++;
|
||||
});
|
||||
}
|
||||
|
||||
// Wait a bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// Create threads that release
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
releaseThreads.emplace_back([&sem]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
sem.release();
|
||||
});
|
||||
}
|
||||
|
||||
for(auto& t : acquireThreads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
|
||||
for(auto& t : releaseThreads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(acquiredCount.load(), numThreads);
|
||||
}
|
||||
|
||||
347
utilite/test/test_ustl.cpp
Normal file
347
utilite/test/test_ustl.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
#include <map>
|
||||
|
||||
TEST(UStlTest, UUniqueKeys)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({1, "b"});
|
||||
mm.insert({2, "c"});
|
||||
mm.insert({3, "d"});
|
||||
mm.insert({3, "e"});
|
||||
|
||||
std::list<int> uniqueKeys = uUniqueKeys(mm);
|
||||
EXPECT_EQ(uniqueKeys.size(), 3u);
|
||||
EXPECT_EQ(uniqueKeys.front(), 1);
|
||||
EXPECT_EQ(uniqueKeys.back(), 3);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UKeysMultimap)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({2, "b"});
|
||||
mm.insert({3, "c"});
|
||||
|
||||
std::vector<int> keys = uKeys(mm);
|
||||
EXPECT_EQ(keys.size(), 3u);
|
||||
|
||||
std::list<int> keysList = uKeysList(mm);
|
||||
EXPECT_EQ(keysList.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UValuesMultimap)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({1, "b"});
|
||||
mm.insert({2, "c"});
|
||||
|
||||
std::vector<std::string> values = uValues(mm);
|
||||
EXPECT_EQ(values.size(), 3u);
|
||||
|
||||
std::list<std::string> valuesList = uValuesList(mm);
|
||||
EXPECT_EQ(valuesList.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UValuesMultimapForKey)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({1, "b"});
|
||||
mm.insert({2, "c"});
|
||||
|
||||
std::list<std::string> values = uValues(mm, 1);
|
||||
EXPECT_EQ(values.size(), 2u);
|
||||
EXPECT_EQ(values.front(), "a");
|
||||
EXPECT_EQ(values.back(), "b");
|
||||
}
|
||||
|
||||
TEST(UStlTest, UKeysMap)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
m[1] = "a";
|
||||
m[2] = "b";
|
||||
m[3] = "c";
|
||||
|
||||
std::vector<int> keys = uKeys(m);
|
||||
EXPECT_EQ(keys.size(), 3u);
|
||||
|
||||
std::list<int> keysList = uKeysList(m);
|
||||
EXPECT_EQ(keysList.size(), 3u);
|
||||
|
||||
std::set<int> keysSet = uKeysSet(m);
|
||||
EXPECT_EQ(keysSet.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UValuesMap)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
m[1] = "a";
|
||||
m[2] = "b";
|
||||
m[3] = "c";
|
||||
|
||||
std::vector<std::string> values = uValues(m);
|
||||
EXPECT_EQ(values.size(), 3u);
|
||||
|
||||
std::list<std::string> valuesList = uValuesList(m);
|
||||
EXPECT_EQ(valuesList.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UValueMap)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
m[1] = "a";
|
||||
m[2] = "b";
|
||||
|
||||
std::string value = uValue(m, 1, std::string("default"));
|
||||
EXPECT_EQ(value, "a");
|
||||
|
||||
std::string defaultValue = uValue(m, 99, std::string("default"));
|
||||
EXPECT_EQ(defaultValue, "default");
|
||||
}
|
||||
|
||||
TEST(UStlTest, UTake)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
m[1] = "a";
|
||||
m[2] = "b";
|
||||
|
||||
std::string value = uTake(m, 1, std::string("default"));
|
||||
EXPECT_EQ(value, "a");
|
||||
EXPECT_EQ(m.size(), 1u);
|
||||
EXPECT_EQ(m.find(1), m.end());
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIteratorAtList)
|
||||
{
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
auto iter = uIteratorAt(list, 2);
|
||||
EXPECT_EQ(*iter, 3);
|
||||
|
||||
const std::list<int>& constList = list;
|
||||
auto constIter = uIteratorAt(constList, 2);
|
||||
EXPECT_EQ(*constIter, 3);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIteratorAtSet)
|
||||
{
|
||||
std::set<int> s = {1, 2, 3, 4, 5};
|
||||
auto iter = uIteratorAt(s, 2);
|
||||
EXPECT_NE(iter, s.end());
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIteratorAtVector)
|
||||
{
|
||||
std::vector<int> v = {1, 2, 3, 4, 5};
|
||||
auto iter = uIteratorAt(v, 2);
|
||||
EXPECT_EQ(*iter, 3);
|
||||
|
||||
const std::vector<int>& constV = v;
|
||||
auto constIter = uIteratorAt(constV, 2);
|
||||
EXPECT_EQ(*constIter, 3);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UValueAtList)
|
||||
{
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
int& value = uValueAt(list, 2);
|
||||
EXPECT_EQ(value, 3);
|
||||
value = 10;
|
||||
EXPECT_EQ(uValueAt(list, 2), 10);
|
||||
|
||||
const std::list<int>& constList = list;
|
||||
const int& constValue = uValueAt(constList, 2);
|
||||
EXPECT_EQ(constValue, 10);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UContainsList)
|
||||
{
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
EXPECT_TRUE(uContains(list, 3));
|
||||
EXPECT_FALSE(uContains(list, 10));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UContainsMap)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
m[1] = "a";
|
||||
m[2] = "b";
|
||||
EXPECT_TRUE(uContains(m, 1));
|
||||
EXPECT_FALSE(uContains(m, 10));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UContainsMultimap)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({2, "b"});
|
||||
EXPECT_TRUE(uContains(mm, 1));
|
||||
EXPECT_FALSE(uContains(mm, 10));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UInsert)
|
||||
{
|
||||
std::map<int, std::string> m;
|
||||
uInsert(m, std::make_pair(1, std::string("a")));
|
||||
EXPECT_EQ(m[1], "a");
|
||||
|
||||
uInsert(m, std::make_pair(1, std::string("b")));
|
||||
EXPECT_EQ(m[1], "b"); // Should replace
|
||||
|
||||
std::map<int, std::string> m2;
|
||||
m2[2] = "c";
|
||||
m2[3] = "d";
|
||||
uInsert(m, m2);
|
||||
EXPECT_EQ(m.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UListToVector)
|
||||
{
|
||||
std::list<int> list = {1, 2, 3, 4, 5};
|
||||
std::vector<int> v = uListToVector(list);
|
||||
EXPECT_EQ(v.size(), 5u);
|
||||
EXPECT_EQ(v[0], 1);
|
||||
EXPECT_EQ(v[4], 5);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UVectorToList)
|
||||
{
|
||||
std::vector<int> v = {1, 2, 3, 4, 5};
|
||||
std::list<int> list = uVectorToList(v);
|
||||
EXPECT_EQ(list.size(), 5u);
|
||||
EXPECT_EQ(list.front(), 1);
|
||||
EXPECT_EQ(list.back(), 5);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UMultimapToMap)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({1, "b"});
|
||||
mm.insert({2, "c"});
|
||||
|
||||
std::map<int, std::string> m = uMultimapToMap(mm);
|
||||
EXPECT_EQ(m.size(), 2u); // All entries
|
||||
}
|
||||
|
||||
TEST(UStlTest, UMultimapToMapUnique)
|
||||
{
|
||||
std::multimap<int, std::string> mm;
|
||||
mm.insert({1, "a"});
|
||||
mm.insert({1, "b"});
|
||||
mm.insert({2, "c"});
|
||||
|
||||
std::map<int, std::string> m = uMultimapToMapUnique(mm);
|
||||
EXPECT_EQ(m.size(), 1u); // Only unique keys with single value
|
||||
EXPECT_EQ(m[2], "c");
|
||||
}
|
||||
|
||||
TEST(UStlTest, UAppend)
|
||||
{
|
||||
std::list<int> list1 = {1, 2, 3};
|
||||
std::list<int> list2 = {4, 5, 6};
|
||||
uAppend(list1, list2);
|
||||
EXPECT_EQ(list1.size(), 6u);
|
||||
EXPECT_EQ(list1.back(), 6);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIndexOf)
|
||||
{
|
||||
std::vector<int> v = {1, 2, 3, 4, 5};
|
||||
EXPECT_EQ(uIndexOf(v, 3), 2);
|
||||
EXPECT_EQ(uIndexOf(v, 10), -1);
|
||||
}
|
||||
|
||||
TEST(UStlTest, USplit)
|
||||
{
|
||||
std::list<std::string> result = uSplit("Hello the world!", ' ');
|
||||
EXPECT_EQ(result.size(), 3u);
|
||||
EXPECT_EQ(result.front(), "Hello");
|
||||
EXPECT_EQ(result.back(), "world!");
|
||||
|
||||
std::list<std::string> result2 = uSplit("a,b,c", ',');
|
||||
EXPECT_EQ(result2.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UJoin)
|
||||
{
|
||||
std::list<std::string> strings;
|
||||
strings.push_back("Hello");
|
||||
strings.push_back("world!");
|
||||
std::string joined = uJoin(strings, " ");
|
||||
EXPECT_EQ(joined, "Hello world!");
|
||||
|
||||
std::string joined2 = uJoin(strings, "");
|
||||
EXPECT_EQ(joined2, "Helloworld!");
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIsDigit)
|
||||
{
|
||||
EXPECT_TRUE(uIsDigit('0'));
|
||||
EXPECT_TRUE(uIsDigit('5'));
|
||||
EXPECT_TRUE(uIsDigit('9'));
|
||||
EXPECT_FALSE(uIsDigit('a'));
|
||||
EXPECT_FALSE(uIsDigit('A'));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIsInteger)
|
||||
{
|
||||
EXPECT_TRUE(uIsInteger("123"));
|
||||
EXPECT_TRUE(uIsInteger("-123"));
|
||||
EXPECT_FALSE(uIsInteger("12.3"));
|
||||
EXPECT_FALSE(uIsInteger("abc"));
|
||||
|
||||
EXPECT_TRUE(uIsInteger("123", true));
|
||||
EXPECT_TRUE(uIsInteger("123", false));
|
||||
EXPECT_FALSE(uIsInteger("-123", false));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UIsNumber)
|
||||
{
|
||||
EXPECT_TRUE(uIsNumber("123"));
|
||||
EXPECT_TRUE(uIsNumber("12.3"));
|
||||
EXPECT_TRUE(uIsNumber("-12.3"));
|
||||
EXPECT_FALSE(uIsNumber("abc"));
|
||||
EXPECT_FALSE(uIsNumber("12.3.4"));
|
||||
}
|
||||
|
||||
TEST(UStlTest, USplitNumChar)
|
||||
{
|
||||
std::list<std::string> result = uSplitNumChar("Hello 03 my 65 world!");
|
||||
EXPECT_GT(result.size(), 0u);
|
||||
// Should contain numbers and characters separately
|
||||
}
|
||||
|
||||
TEST(UStlTest, UStrNumCmp)
|
||||
{
|
||||
EXPECT_LT(uStrNumCmp("Image9.jpg", "Image10.jpg"), 0);
|
||||
EXPECT_GT(uStrNumCmp("Image10.jpg", "Image9.jpg"), 0);
|
||||
EXPECT_EQ(uStrNumCmp("Image10.jpg", "Image10.jpg"), 0);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UStrContains)
|
||||
{
|
||||
EXPECT_TRUE(uStrContains("Hello World", "World"));
|
||||
EXPECT_TRUE(uStrContains("Hello World", "Hello"));
|
||||
EXPECT_FALSE(uStrContains("Hello World", "xyz"));
|
||||
}
|
||||
|
||||
TEST(UStlTest, UCompareVersion)
|
||||
{
|
||||
EXPECT_GT(uCompareVersion("2.0.0", 1, 0, 0), 0);
|
||||
EXPECT_EQ(uCompareVersion("1.0.0", 1, 0, 0), 0);
|
||||
EXPECT_LT(uCompareVersion("0.9.0", 1, 0, 0), 0);
|
||||
}
|
||||
|
||||
TEST(UStlTest, UPad)
|
||||
{
|
||||
std::string result = uPad("Hello", 10);
|
||||
EXPECT_GE(result.size(), 10u);
|
||||
EXPECT_EQ(result.substr(0, 5), "Hello");
|
||||
|
||||
std::string result2 = uPad("Hello", 3);
|
||||
EXPECT_EQ(result2, "Hello"); // Should not truncate
|
||||
}
|
||||
|
||||
362
utilite/test/test_uthread.cpp
Normal file
362
utilite/test/test_uthread.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UThread.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
|
||||
// Simple test thread
|
||||
class SimpleThread : public UThread
|
||||
{
|
||||
public:
|
||||
SimpleThread() : UThread(), counter_(0), started_(false) {}
|
||||
virtual ~SimpleThread() {join(true);}
|
||||
int getCounter() const { return counter_; }
|
||||
bool hasStarted() const { return started_; }
|
||||
void reset() { counter_ = 0; started_ = false; }
|
||||
|
||||
protected:
|
||||
virtual void mainLoop()
|
||||
{
|
||||
started_ = true;
|
||||
counter_++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
kill(); // Stop after one iteration
|
||||
}
|
||||
|
||||
private:
|
||||
int counter_;
|
||||
bool started_;
|
||||
};
|
||||
|
||||
// Thread that runs multiple iterations
|
||||
class CountingThread : public UThread
|
||||
{
|
||||
public:
|
||||
CountingThread() : UThread(), counter_(0), maxIterations_(10) {}
|
||||
virtual ~CountingThread() {join(true);}
|
||||
void setMaxIterations(int max) { maxIterations_ = max; }
|
||||
int getCounter() const { return counter_; }
|
||||
|
||||
protected:
|
||||
virtual void mainLoop()
|
||||
{
|
||||
counter_++;
|
||||
if(counter_ >= maxIterations_)
|
||||
{
|
||||
kill();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
private:
|
||||
int counter_;
|
||||
int maxIterations_;
|
||||
};
|
||||
|
||||
// Thread with initialization
|
||||
class InitThread : public UThread
|
||||
{
|
||||
public:
|
||||
InitThread() : UThread(), initialized_(false), counter_(0) {}
|
||||
virtual ~InitThread() {join(true);}
|
||||
bool isInitialized() const { return initialized_; }
|
||||
int getCounter() const { return counter_; }
|
||||
|
||||
protected:
|
||||
virtual void mainLoopBegin()
|
||||
{
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
virtual void mainLoop()
|
||||
{
|
||||
counter_++;
|
||||
if(counter_ >= 5)
|
||||
{
|
||||
kill();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
private:
|
||||
bool initialized_;
|
||||
int counter_;
|
||||
};
|
||||
|
||||
// Thread with cleanup
|
||||
class CleanupThread : public UThread
|
||||
{
|
||||
public:
|
||||
CleanupThread() : UThread(), cleanedUp_(false), counter_(0) {}
|
||||
virtual ~CleanupThread() {join(true);}
|
||||
bool isCleanedUp() const { return cleanedUp_; }
|
||||
int getCounter() const { return counter_; }
|
||||
|
||||
protected:
|
||||
virtual void mainLoop()
|
||||
{
|
||||
counter_++;
|
||||
if(counter_ >= 3)
|
||||
{
|
||||
kill();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
virtual void mainLoopEnd()
|
||||
{
|
||||
cleanedUp_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool cleanedUp_;
|
||||
int counter_;
|
||||
};
|
||||
|
||||
TEST(UThreadTest, Constructor)
|
||||
{
|
||||
SimpleThread thread;
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
EXPECT_FALSE(thread.isRunning());
|
||||
EXPECT_FALSE(thread.isCreating());
|
||||
EXPECT_FALSE(thread.isKilled());
|
||||
}
|
||||
|
||||
TEST(UThreadTest, Start)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.start();
|
||||
|
||||
EXPECT_TRUE(thread.isRunning() || thread.isCreating());
|
||||
|
||||
// Wait up to 1 s for mainLoop to actually execute. We can't reliably
|
||||
// assert isRunning() at a fixed offset: SimpleThread sleeps ~10 ms then
|
||||
// self-kills, so on a slow CI VM the thread can already be in kSKilled
|
||||
// by the time we poll. hasStarted() flips to true on first entry to
|
||||
// mainLoop and stays true, so it's race-free.
|
||||
UTimer waitForStart;
|
||||
while(!thread.hasStarted() && waitForStart.ticks() < 1.0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
EXPECT_TRUE(thread.hasStarted());
|
||||
|
||||
// Poll for cleanup. SimpleThread sleeps ~10 ms then self-kills; on slow
|
||||
// CI VMs the kSRunning -> kSKilled -> kSIdle settle can take longer than
|
||||
// a single fixed sleep, so wait up to 1 s.
|
||||
UTimer waitForFinish;
|
||||
while(!(thread.isKilled() || thread.isIdle()) && waitForFinish.ticks() < 1.0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
EXPECT_TRUE(thread.isKilled() || thread.isIdle());
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, Join)
|
||||
{
|
||||
SimpleThread thread;
|
||||
UTimer timer;
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
EXPECT_GT(timer.elapsed(), 0.01);
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
EXPECT_GT(thread.getCounter(), 0);
|
||||
}
|
||||
|
||||
TEST(UThreadTest, JoinWait)
|
||||
{
|
||||
CountingThread thread;
|
||||
thread.setMaxIterations(10);
|
||||
thread.start();
|
||||
|
||||
// Join wait
|
||||
thread.join(false);
|
||||
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
EXPECT_EQ(thread.getCounter(), 10); // Should have processed all iterations
|
||||
}
|
||||
|
||||
TEST(UThreadTest, JoinKillFirst)
|
||||
{
|
||||
CountingThread thread;
|
||||
thread.setMaxIterations(1000);
|
||||
thread.start();
|
||||
|
||||
// Wait a bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
// Join with kill
|
||||
thread.join(true);
|
||||
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
EXPECT_LT(thread.getCounter(), 1000); // Should have stopped early
|
||||
}
|
||||
|
||||
TEST(UThreadTest, Kill)
|
||||
{
|
||||
CountingThread thread;
|
||||
thread.setMaxIterations(1000);
|
||||
thread.start();
|
||||
|
||||
// Wait a bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
thread.kill();
|
||||
|
||||
// Wait for it to finish
|
||||
thread.join();
|
||||
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
EXPECT_LT(thread.getCounter(), 1000); // Should have stopped early
|
||||
}
|
||||
|
||||
TEST(UThreadTest, MultipleStart)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.start();
|
||||
|
||||
// Try to start again (should be ignored)
|
||||
thread.start();
|
||||
|
||||
thread.join();
|
||||
|
||||
EXPECT_TRUE(thread.isIdle());
|
||||
}
|
||||
|
||||
TEST(UThreadTest, MainLoopBegin)
|
||||
{
|
||||
InitThread thread;
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
EXPECT_TRUE(thread.isInitialized());
|
||||
EXPECT_GT(thread.getCounter(), 0);
|
||||
}
|
||||
|
||||
TEST(UThreadTest, MainLoopEnd)
|
||||
{
|
||||
CleanupThread thread;
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
EXPECT_TRUE(thread.isCleanedUp());
|
||||
EXPECT_GT(thread.getCounter(), 0);
|
||||
}
|
||||
|
||||
TEST(UThreadTest, ThreadId)
|
||||
{
|
||||
SimpleThread thread;
|
||||
unsigned long mainThreadId = UThread::currentThreadId();
|
||||
|
||||
thread.start();
|
||||
unsigned long threadId = thread.getThreadId();
|
||||
|
||||
EXPECT_NE(threadId, 0u);
|
||||
EXPECT_NE(threadId, mainThreadId);
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, CurrentThreadId)
|
||||
{
|
||||
unsigned long id1 = UThread::currentThreadId();
|
||||
unsigned long id2 = UThread::currentThreadId();
|
||||
|
||||
EXPECT_EQ(id1, id2);
|
||||
EXPECT_NE(id1, 0u);
|
||||
}
|
||||
|
||||
TEST(UThreadTest, SetPriority)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.setPriority(UThread::kPLow);
|
||||
thread.setPriority(UThread::kPNormal);
|
||||
thread.setPriority(UThread::kPAboveNormal);
|
||||
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, SetAffinity)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.setAffinity(0); // No affinity
|
||||
thread.setAffinity(1); // CPU 1
|
||||
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, ThreadHandle)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.start();
|
||||
|
||||
UThread::Handle handle = thread.getThreadHandle();
|
||||
EXPECT_NE(handle, static_cast<UThread::Handle>(InvalidHandle));
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, MultipleThreads)
|
||||
{
|
||||
std::vector<SimpleThread*> threads;
|
||||
const int numThreads = 5;
|
||||
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
threads.push_back(new SimpleThread());
|
||||
threads[i]->start();
|
||||
}
|
||||
|
||||
for(int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
threads[i]->join();
|
||||
EXPECT_GT(threads[i]->getCounter(), 0);
|
||||
delete threads[i];
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UThreadTest, DestructorJoin)
|
||||
{
|
||||
// Test that destructor properly cleans up
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.start();
|
||||
// Destructor should call join(true) if implemented
|
||||
}
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, KillBeforeStart)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.kill(); // Kill before start
|
||||
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
// Should still work
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UThreadTest, JoinBeforeStart)
|
||||
{
|
||||
SimpleThread thread;
|
||||
thread.join(); // Join before start
|
||||
|
||||
thread.start();
|
||||
thread.join();
|
||||
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
129
utilite/test/test_utimer.cpp
Normal file
129
utilite/test/test_utimer.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
|
||||
// Requested sleep durations (seconds)
|
||||
constexpr double kSleep10Ms = 0.01;
|
||||
constexpr double kSleep20Ms = 0.02;
|
||||
constexpr double kSleep5Ms = 0.005;
|
||||
|
||||
// Upper bounds: macOS/CI runners often wake threads much later than requested
|
||||
constexpr double kMaxSlack10Ms = 0.25;
|
||||
constexpr double kMaxSlack20Ms = 0.40;
|
||||
constexpr double kMaxSlack5Ms = 0.15;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UTimerTest, Constructor)
|
||||
{
|
||||
UTimer timer;
|
||||
// Timer should be constructible
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(UTimerTest, StartStop)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
timer.stop();
|
||||
double elapsed = timer.getElapsedTime();
|
||||
EXPECT_GE(elapsed, kSleep10Ms);
|
||||
EXPECT_LT(elapsed, kMaxSlack10Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, Elapsed)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double elapsed = timer.elapsed();
|
||||
EXPECT_GE(elapsed, kSleep10Ms);
|
||||
EXPECT_LT(elapsed, kMaxSlack10Ms);
|
||||
|
||||
// Timer should still be running after elapsed()
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double ticks2 = timer.ticks();
|
||||
EXPECT_GE(ticks2, kSleep20Ms);
|
||||
EXPECT_LT(ticks2, kMaxSlack20Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, GetElapsedTime)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double elapsed = timer.getElapsedTime();
|
||||
EXPECT_GE(elapsed, kSleep10Ms);
|
||||
EXPECT_LT(elapsed, kMaxSlack10Ms);
|
||||
|
||||
// Timer should still be running after getElapsedTime()
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double ticks2 = timer.ticks();
|
||||
EXPECT_GE(ticks2, kSleep20Ms);
|
||||
EXPECT_LT(ticks2, kMaxSlack20Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, Ticks)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double ticks1 = timer.ticks();
|
||||
EXPECT_GE(ticks1, kSleep10Ms);
|
||||
EXPECT_LT(ticks1, kMaxSlack10Ms);
|
||||
|
||||
// Timer should have retarted and still be running after ticks()
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double ticks2 = timer.ticks();
|
||||
EXPECT_GE(ticks2, kSleep10Ms);
|
||||
EXPECT_LT(ticks2, kMaxSlack10Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, Restart)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double elapsed1 = timer.restart();
|
||||
EXPECT_GE(elapsed1, kSleep10Ms);
|
||||
EXPECT_LT(elapsed1, kMaxSlack10Ms);
|
||||
|
||||
// After restart, timer should be running again
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double elapsed2 = timer.elapsed();
|
||||
EXPECT_GE(elapsed2, kSleep10Ms);
|
||||
EXPECT_LT(elapsed2, kMaxSlack10Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, Now)
|
||||
{
|
||||
double time1 = UTimer::now();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
double time2 = UTimer::now();
|
||||
EXPECT_GE(time2-time1, kSleep10Ms);
|
||||
EXPECT_LT(time2-time1, kMaxSlack10Ms);
|
||||
}
|
||||
|
||||
TEST(UTimerTest, MultipleStartStop)
|
||||
{
|
||||
UTimer timer;
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
timer.stop();
|
||||
double elapsed1 = timer.getElapsedTime();
|
||||
|
||||
timer.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
timer.stop();
|
||||
double elapsed2 = timer.getElapsedTime();
|
||||
|
||||
EXPECT_GE(elapsed1, kSleep5Ms);
|
||||
EXPECT_LT(elapsed1, kMaxSlack5Ms);
|
||||
EXPECT_GE(elapsed2, kSleep5Ms);
|
||||
EXPECT_LT(elapsed2, kMaxSlack5Ms);
|
||||
}
|
||||
|
||||
213
utilite/test/test_uvariant.cpp
Normal file
213
utilite/test/test_uvariant.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "rtabmap/utilite/UVariant.h"
|
||||
|
||||
TEST(UVariantTest, DefaultConstructor)
|
||||
{
|
||||
UVariant v;
|
||||
EXPECT_TRUE(v.isUndef());
|
||||
EXPECT_EQ(v.type(), UVariant::kUndef);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, BoolConstructor)
|
||||
{
|
||||
UVariant v(true);
|
||||
EXPECT_TRUE(v.isBool());
|
||||
EXPECT_EQ(v.type(), UVariant::kBool);
|
||||
EXPECT_TRUE(v.toBool());
|
||||
|
||||
UVariant v2(false);
|
||||
EXPECT_FALSE(v2.toBool());
|
||||
}
|
||||
|
||||
TEST(UVariantTest, CharConstructor)
|
||||
{
|
||||
signed char value = 42;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isChar());
|
||||
EXPECT_EQ(v.type(), UVariant::kChar);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toChar(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, UCharConstructor)
|
||||
{
|
||||
unsigned char value = 200;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isUChar());
|
||||
EXPECT_EQ(v.type(), UVariant::kUChar);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toUChar(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, ShortConstructor)
|
||||
{
|
||||
short value = -100;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isShort());
|
||||
EXPECT_EQ(v.type(), UVariant::kShort);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toShort(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, UShortConstructor)
|
||||
{
|
||||
unsigned short value = 500;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isUShort());
|
||||
EXPECT_EQ(v.type(), UVariant::kUShort);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toUShort(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, IntConstructor)
|
||||
{
|
||||
int value = -1000;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isInt());
|
||||
EXPECT_EQ(v.type(), UVariant::kInt);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toInt(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, UIntConstructor)
|
||||
{
|
||||
unsigned int value = 100000;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isUInt());
|
||||
EXPECT_EQ(v.type(), UVariant::kUInt);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toUInt(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, FloatConstructor)
|
||||
{
|
||||
float value = 3.14f;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isFloat());
|
||||
EXPECT_EQ(v.type(), UVariant::kFloat);
|
||||
bool ok = false;
|
||||
EXPECT_NEAR(v.toFloat(&ok), value, 0.001f);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, DoubleConstructor)
|
||||
{
|
||||
double value = 3.14159;
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isDouble());
|
||||
EXPECT_EQ(v.type(), UVariant::kDouble);
|
||||
bool ok = false;
|
||||
EXPECT_NEAR(v.toDouble(&ok), value, 0.00001);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, StringConstructor)
|
||||
{
|
||||
std::string value = "Hello World";
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isStr());
|
||||
EXPECT_EQ(v.type(), UVariant::kStr);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toStr(&ok), value);
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, CStringConstructor)
|
||||
{
|
||||
const char* value = "Hello World";
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isStr());
|
||||
EXPECT_EQ(v.type(), UVariant::kStr);
|
||||
bool ok = false;
|
||||
EXPECT_EQ(v.toStr(&ok), std::string(value));
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
|
||||
TEST(UVariantTest, CharArrayConstructor)
|
||||
{
|
||||
std::vector<signed char> value = {1, 2, 3, -4};
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isCharArray());
|
||||
EXPECT_EQ(v.type(), UVariant::kCharArray);
|
||||
bool ok = false;
|
||||
std::vector<signed char> result = v.toCharArray(&ok);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_EQ(result.size(), value.size());
|
||||
for(size_t i = 0; i < value.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(result[i], value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UVariantTest, UCharArrayConstructor)
|
||||
{
|
||||
std::vector<unsigned char> value = {1, 2, 3, 4};
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isUCharArray());
|
||||
EXPECT_EQ(v.type(), UVariant::kUCharArray);
|
||||
bool ok = false;
|
||||
std::vector<unsigned char> result = v.toUCharArray(&ok);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_EQ(result.size(), value.size());
|
||||
for(size_t i = 0; i < value.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(result[i], value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UVariantTest, IntArrayConstructor)
|
||||
{
|
||||
std::vector<int> value = {1, -2, 3, -4, 5};
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isIntArray());
|
||||
EXPECT_EQ(v.type(), UVariant::kIntArray);
|
||||
bool ok = false;
|
||||
std::vector<int> result = v.toIntArray(&ok);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_EQ(result.size(), value.size());
|
||||
for(size_t i = 0; i < value.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(result[i], value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UVariantTest, FloatArrayConstructor)
|
||||
{
|
||||
std::vector<float> value = {1.1f, 2.2f, 3.3f};
|
||||
UVariant v(value);
|
||||
EXPECT_TRUE(v.isFloatArray());
|
||||
EXPECT_EQ(v.type(), UVariant::kFloatArray);
|
||||
bool ok = false;
|
||||
std::vector<float> result = v.toFloatArray(&ok);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_EQ(result.size(), value.size());
|
||||
for(size_t i = 0; i < value.size(); ++i)
|
||||
{
|
||||
EXPECT_NEAR(result[i], value[i], 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UVariantTest, TypeChecks)
|
||||
{
|
||||
UVariant v(42);
|
||||
EXPECT_TRUE(v.isInt());
|
||||
EXPECT_FALSE(v.isBool());
|
||||
EXPECT_FALSE(v.isFloat());
|
||||
EXPECT_FALSE(v.isStr());
|
||||
EXPECT_FALSE(v.isUndef());
|
||||
}
|
||||
|
||||
TEST(UVariantTest, ConversionFailure)
|
||||
{
|
||||
UVariant v(42); // int
|
||||
bool ok = true;
|
||||
v.toFloat(&ok);
|
||||
EXPECT_FALSE(ok); // Should fail to convert int to float
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user