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:
matlabbe
2026-08-06 13:32:20 -07:00
committed by GitHub
parent bcdb4b4546
commit ee49beaf4f
309 changed files with 67469 additions and 3070 deletions

View File

@@ -8,3 +8,7 @@ ENDIF(UNIX AND NOT ANDROID)
ADD_SUBDIRECTORY( src )
ADD_SUBDIRECTORY( resource_generator )
if(BUILD_TESTING)
ADD_SUBDIRECTORY( test )
endif()

View File

@@ -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
/**

View File

@@ -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".

View File

@@ -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);
}
/**

View File

@@ -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
{

View File

@@ -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();
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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 */

View File

@@ -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"

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View 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)

View 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);
}

View 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);
}

View 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
View 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");
}

View 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
View 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));
}

View 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();
}

View 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
}

View 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
View 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
}

View 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();
}

View 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);
}

View 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
}