From 9e3804f8d40df7c2b72725c2a1cee6e1739c3d97 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 10 Feb 2024 15:54:53 -0800 Subject: [PATCH 01/11] Restructure --- functions.cmake | 20 +++++++++- lib/CMakeLists.txt | 1 + lib/sailbot_db/CMakeLists.txt | 38 +++++++++++++++++++ .../sailbot_db}/inc/sailbot_db.h | 0 .../sailbot_db}/src/sailbot_db.cpp | 0 lib/sailbot_db/test/test_sailbot_db.cpp | 0 projects/can_transceiver/CMakeLists.txt | 2 +- projects/example/CMakeLists.txt | 2 +- projects/local_transceiver/CMakeLists.txt | 2 +- projects/mock_ais/CMakeLists.txt | 2 +- projects/remote_transceiver/CMakeLists.txt | 9 ++--- 11 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 lib/sailbot_db/CMakeLists.txt rename {projects/remote_transceiver => lib/sailbot_db}/inc/sailbot_db.h (100%) rename {projects/remote_transceiver => lib/sailbot_db}/src/sailbot_db.cpp (100%) create mode 100644 lib/sailbot_db/test/test_sailbot_db.cpp diff --git a/functions.cmake b/functions.cmake index 6110fe1..73ad85e 100644 --- a/functions.cmake +++ b/functions.cmake @@ -12,8 +12,26 @@ function(make_lib module srcs link_libs inc_dirs compile_defs) add_dependencies(${module} ${AUTOGEN_TARGETS}) endfunction() -# Create module ROS executable +# Create lib non-ROS executable function(make_exe module srcs link_libs inc_dirs ${compile_defs}) + set(bin_module bin_${module}) + add_executable(${bin_module} ${srcs}) + target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) + target_link_libraries(${bin_module} PUBLIC ${link_libs}) + target_include_directories( + ${bin_module} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/inc + ${CMAKE_SOURCE_DIR}/lib + ${inc_dirs} + ) + add_dependencies(${bin_module} ${AUTOGEN_TARGETS}) + install(TARGETS ${bin_module} DESTINATION lib/${PROJECT_NAME}) + # Rename the output binary to just be the module name + set_target_properties(${bin_module} PROPERTIES OUTPUT_NAME ${module}) +endfunction() + +# Create project module ROS executable +function(make_ros_exe module srcs link_libs inc_dirs ${compile_defs}) set(bin_module bin_${module}) add_executable(${bin_module} ${srcs}) target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 74dee69..d05824d 100755 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(cmn_hdrs) add_subdirectory(protofiles) +add_subdirectory(sailbot_db) # add directories as needed diff --git a/lib/sailbot_db/CMakeLists.txt b/lib/sailbot_db/CMakeLists.txt new file mode 100644 index 0000000..c2704c2 --- /dev/null +++ b/lib/sailbot_db/CMakeLists.txt @@ -0,0 +1,38 @@ +set(module sailbot_db) + +set(link_libs + ${PROTOBUF_LINK_LIBS} + mongo::mongocxx_shared + mongo::bsoncxx_shared +) + +set(inc_dirs + ${PROTOBUF_INCLUDE_PATH} + ${LIBMONGOCXX_INCLUDE_DIRS} + ${LIBBSONCXX_INCLUDE_DIRS} +) + +set(compile_defs +) + +set(srcs + ${CMAKE_CURRENT_LIST_DIR}/src/sailbot_db.cpp +) + +# make sailbot_db library +make_lib(${module} "${srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") + +set(bin_srcs + ${srcs} + ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp +) + +# Make executable +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") + +# Create unit test +set(test_srcs + ${srcs} + ${CMAKE_CURRENT_LIST_DIR}/test/test_sailbot_db.cpp +) +make_unit_test(${module} "${test_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") diff --git a/projects/remote_transceiver/inc/sailbot_db.h b/lib/sailbot_db/inc/sailbot_db.h similarity index 100% rename from projects/remote_transceiver/inc/sailbot_db.h rename to lib/sailbot_db/inc/sailbot_db.h diff --git a/projects/remote_transceiver/src/sailbot_db.cpp b/lib/sailbot_db/src/sailbot_db.cpp similarity index 100% rename from projects/remote_transceiver/src/sailbot_db.cpp rename to lib/sailbot_db/src/sailbot_db.cpp diff --git a/lib/sailbot_db/test/test_sailbot_db.cpp b/lib/sailbot_db/test/test_sailbot_db.cpp new file mode 100644 index 0000000..e69de29 diff --git a/projects/can_transceiver/CMakeLists.txt b/projects/can_transceiver/CMakeLists.txt index 995d20b..1a70897 100644 --- a/projects/can_transceiver/CMakeLists.txt +++ b/projects/can_transceiver/CMakeLists.txt @@ -19,7 +19,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/can_transceiver_ros_intf.cpp ) -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") set(test_srcs ${srcs} diff --git a/projects/example/CMakeLists.txt b/projects/example/CMakeLists.txt index 910bc89..cf0460b 100755 --- a/projects/example/CMakeLists.txt +++ b/projects/example/CMakeLists.txt @@ -21,7 +21,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/cached_fib_ros_intf.cpp ) -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/local_transceiver/CMakeLists.txt b/projects/local_transceiver/CMakeLists.txt index be94351..b20f8ce 100644 --- a/projects/local_transceiver/CMakeLists.txt +++ b/projects/local_transceiver/CMakeLists.txt @@ -22,7 +22,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/local_transceiver_ros_intf.cpp ) -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/mock_ais/CMakeLists.txt b/projects/mock_ais/CMakeLists.txt index 837c8d7..2d66222 100644 --- a/projects/mock_ais/CMakeLists.txt +++ b/projects/mock_ais/CMakeLists.txt @@ -21,7 +21,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/mock_ais_ros_intf.cpp ) -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/remote_transceiver/CMakeLists.txt b/projects/remote_transceiver/CMakeLists.txt index 46cdb25..05258e5 100644 --- a/projects/remote_transceiver/CMakeLists.txt +++ b/projects/remote_transceiver/CMakeLists.txt @@ -12,12 +12,11 @@ set(inc_dirs ${LIBBSONCXX_INCLUDE_DIRS} ) -set(srcs - ${CMAKE_CURRENT_LIST_DIR}/src/sailbot_db.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/remote_transceiver.cpp +set(compile_defs ) -set(compile_defs +set(srcs + ${CMAKE_CURRENT_LIST_DIR}/src/remote_transceiver.cpp ) set(bin_srcs @@ -26,7 +25,7 @@ set(bin_srcs ) # Make executable -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs From fafabb2f076dbfd96e4245f91a1b699239341ea2 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Fri, 23 Feb 2024 00:46:51 -0800 Subject: [PATCH 02/11] Need to resolve gtest lib linking --- functions.cmake | 29 +- lib/sailbot_db/CMakeLists.txt | 2 + lib/sailbot_db/inc/sailbot_db.h | 2 +- lib/sailbot_db/inc/util_db.h | 120 ++++ lib/sailbot_db/src/main.cpp | 1 + lib/sailbot_db/src/util_db.cpp | 379 +++++++++++++ lib/sailbot_db/test/test_sailbot_db.cpp | 43 ++ projects/can_transceiver/CMakeLists.txt | 2 +- projects/example/CMakeLists.txt | 2 +- projects/local_transceiver/CMakeLists.txt | 2 +- projects/mock_ais/CMakeLists.txt | 4 +- projects/remote_transceiver/CMakeLists.txt | 4 +- .../test/test_remote_transceiver.cpp | 515 +----------------- 13 files changed, 581 insertions(+), 524 deletions(-) create mode 100644 lib/sailbot_db/inc/util_db.h create mode 100644 lib/sailbot_db/src/main.cpp create mode 100644 lib/sailbot_db/src/util_db.cpp diff --git a/functions.cmake b/functions.cmake index 73ad85e..9be0d59 100644 --- a/functions.cmake +++ b/functions.cmake @@ -1,10 +1,11 @@ # Create module library function(make_lib module srcs link_libs inc_dirs compile_defs) - add_library(${module} INTERFACE ${srcs}) - target_compile_definitions(${module} INTERFACE ${compile_defs}) - target_link_libraries(${module} INTERFACE ${link_libs}) + add_library(${module} ${srcs}) + ament_target_dependencies(${module} PUBLIC ${ROS_DEPS}) + target_compile_definitions(${module} PUBLIC ${compile_defs}) + target_link_libraries(${module} PUBLIC ${link_libs}) target_include_directories( - ${module} INTERFACE + ${module} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc ${CMAKE_SOURCE_DIR}/lib ${inc_dirs} @@ -12,26 +13,8 @@ function(make_lib module srcs link_libs inc_dirs compile_defs) add_dependencies(${module} ${AUTOGEN_TARGETS}) endfunction() -# Create lib non-ROS executable -function(make_exe module srcs link_libs inc_dirs ${compile_defs}) - set(bin_module bin_${module}) - add_executable(${bin_module} ${srcs}) - target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) - target_link_libraries(${bin_module} PUBLIC ${link_libs}) - target_include_directories( - ${bin_module} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/inc - ${CMAKE_SOURCE_DIR}/lib - ${inc_dirs} - ) - add_dependencies(${bin_module} ${AUTOGEN_TARGETS}) - install(TARGETS ${bin_module} DESTINATION lib/${PROJECT_NAME}) - # Rename the output binary to just be the module name - set_target_properties(${bin_module} PROPERTIES OUTPUT_NAME ${module}) -endfunction() - # Create project module ROS executable -function(make_ros_exe module srcs link_libs inc_dirs ${compile_defs}) +function(make_exe module srcs link_libs inc_dirs ${compile_defs}) set(bin_module bin_${module}) add_executable(${bin_module} ${srcs}) target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) diff --git a/lib/sailbot_db/CMakeLists.txt b/lib/sailbot_db/CMakeLists.txt index c2704c2..5ecaebc 100644 --- a/lib/sailbot_db/CMakeLists.txt +++ b/lib/sailbot_db/CMakeLists.txt @@ -17,10 +17,12 @@ set(compile_defs set(srcs ${CMAKE_CURRENT_LIST_DIR}/src/sailbot_db.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/util_db.cpp ) # make sailbot_db library make_lib(${module} "${srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +set(SAILBOT_DB_INC_DIR ${CMAKE_CURRENT_LIST_DIR}/inc CACHE INTERNAL "Sailbot DB header include directory") set(bin_srcs ${srcs} diff --git a/lib/sailbot_db/inc/sailbot_db.h b/lib/sailbot_db/inc/sailbot_db.h index 9ea958c..0bbfdfd 100644 --- a/lib/sailbot_db/inc/sailbot_db.h +++ b/lib/sailbot_db/inc/sailbot_db.h @@ -141,7 +141,7 @@ class SailbotDB * @param db_name name of desired database * @param mongodb_conn_str URL for mongodb database (ex. mongodb://localhost:27017) */ - explicit SailbotDB(const std::string & db_name, const std::string & mongodb_conn_str); + SailbotDB(const std::string & db_name, const std::string & mongodb_conn_str); /** * @brief Format and print a document in the DB diff --git a/lib/sailbot_db/inc/util_db.h b/lib/sailbot_db/inc/util_db.h new file mode 100644 index 0000000..1f67781 --- /dev/null +++ b/lib/sailbot_db/inc/util_db.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include + +#include "sailbot_db.h" +#include "sensors.pb.h" + +class UtilDB : public SailbotDB +{ +public: + static constexpr int NUM_AIS_SHIPS = 15; // arbitrary number + static constexpr int NUM_GENERIC_SENSORS = 5; // arbitrary number + static constexpr int NUM_PATH_WAYPOINTS = 5; // arbitrary number + + /** + * @brief Construct a UtilDB. Private because there should only ever be one instance, which should be + * inialized using the initUtilDB() function + * + * @param db_name + * @param mongodb_conn_str + * @param rng + */ + UtilDB(const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng); + + /** + * @brief Initialize a static instance of the UtilDB + * + * @param db_name + * @param mongodb_conn_str + * @param rng + * @return shared pointer to the static instance + */ + static std::shared_ptr initUtilDB( + const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng); + + /** + * @brief Delete all documents in all collections + */ + void cleanDB(); + + /** + * @brief Generate random data for all sensors + * + * @return Sensors object + */ + Polaris::Sensors genRandSensors(); + + /** + * @brief Generate random sensors and Iridium msg info + * + * @return std::pair + */ + std::pair genRandData(); + + /** + * @brief Retrieve all sensors from the database sorted by timestamp + * + * @param num_docs expected number of documents for each collection, default 1 + * + * @return Vector of sensors objects: gps, ais, generic, batteries, wind, local path + * @return Vector of timestamps + * both vectors will be num_docs in size + */ + std::pair, std::vector> dumpSensors(size_t num_docs = 1); + + /** + * @brief Query the database and check that the sensor and message are correct + * + * @param expected_sensors + * @param expected_msg_info + */ + void verifyDBWrite( + std::span expected_sensors, std::span expected_msg_info); + +private: + std::shared_ptr rng_; + + /** + * @brief generate random GPS data + * + * @param gps_data pointer to generated gps_data + */ + void genRandGpsData(Polaris::Sensors::Gps & gps_data); + + /** + * @brief generate random ais ships data + * + * @param ais_ship pointer to generated ais data + */ + void genRandAisData(Polaris::Sensors::Ais & ais_ship); + + /** + * @brief generate random generic sensor data + * + * @return pointer to generated generic sensor data + */ + void genRandGenericSensorData(Polaris::Sensors::Generic & generic_sensor); + + /** + * @brief generate random battery data + * + * @return pointer to generated battery data + */ + void genRandBatteryData(Polaris::Sensors::Battery & battery); + + /** + * @brief generate random wind sensors data + * + * @return pointer to generated wind sensors data + */ + void genRandWindData(Polaris::Sensors::Wind & wind_data); + + /** + * @brief generate random path data + * + * @return pointer to generated path data + */ + void genRandPathData(Polaris::Sensors::Path & path_data); +}; diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp new file mode 100644 index 0000000..76e8197 --- /dev/null +++ b/lib/sailbot_db/src/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/lib/sailbot_db/src/util_db.cpp b/lib/sailbot_db/src/util_db.cpp new file mode 100644 index 0000000..a19e069 --- /dev/null +++ b/lib/sailbot_db/src/util_db.cpp @@ -0,0 +1,379 @@ + +#include "util_db.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cmn_hdrs/shared_constants.h" + +using Polaris::Sensors; + +static std::shared_ptr g_util_db; + +std::shared_ptr UtilDB::initUtilDB( + const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng) +{ + static bool initialized = false; + if (!initialized) { + g_util_db = std::make_shared(db_name, mongodb_conn_str, rng); + initialized = true; + } + return g_util_db; +} + +void UtilDB::cleanDB() +{ + mongocxx::pool::entry entry = pool_->acquire(); + mongocxx::database db = (*entry)[db_name_]; + + mongocxx::collection gps_coll = db[COLLECTION_GPS]; + mongocxx::collection ais_coll = db[COLLECTION_AIS_SHIPS]; + mongocxx::collection generic_coll = db[COLLECTION_DATA_SENSORS]; + mongocxx::collection batteries_coll = db[COLLECTION_BATTERIES]; + mongocxx::collection wind_coll = db[COLLECTION_WIND_SENSORS]; + mongocxx::collection local_path_coll = db[COLLECTION_LOCAL_PATH]; + + gps_coll.delete_many(bsoncxx::builder::basic::make_document()); + ais_coll.delete_many(bsoncxx::builder::basic::make_document()); + generic_coll.delete_many(bsoncxx::builder::basic::make_document()); + batteries_coll.delete_many(bsoncxx::builder::basic::make_document()); + wind_coll.delete_many(bsoncxx::builder::basic::make_document()); + local_path_coll.delete_many(bsoncxx::builder::basic::make_document()); +} + +Sensors UtilDB::genRandSensors() +{ + Sensors sensors; + + // gps + genRandGpsData(*sensors.mutable_gps()); + + // ais ships, TODO(): Polaris should be included as one of the AIS ships + for (int i = 0; i < NUM_AIS_SHIPS; i++) { + genRandAisData(*sensors.add_ais_ships()); + } + + // generic sensors + for (int i = 0; i < NUM_GENERIC_SENSORS; i++) { + genRandGenericSensorData(*sensors.add_data_sensors()); + } + + // batteries + for (int i = 0; i < NUM_BATTERIES; i++) { + genRandBatteryData(*sensors.add_batteries()); + } + + // wind sensors + for (int i = 0; i < NUM_WIND_SENSORS; i++) { + genRandWindData(*sensors.add_wind_sensors()); + } + + // path waypoints + genRandPathData(*sensors.mutable_local_path_data()); + + return sensors; +} + +std::pair UtilDB::genRandData() +{ + Sensors rand_sensors = genRandSensors(); + SailbotDB::RcvdMsgInfo rand_info{ + .lat_ = 0, // Not processed yet, so just set to 0 + .lon_ = 0, // Not processed yet, so just set to 0 + .cep_ = 0, // Not processed yet, so just set to 0 + .timestamp_ = std::to_string(rand_sensors.gps().heading())}; // Some random string + return {rand_sensors, rand_info}; +} + +std::pair, std::vector> UtilDB::dumpSensors(size_t num_docs) +{ + std::vector sensors_vec(num_docs); + std::vector timestamp_vec(num_docs); + mongocxx::pool::entry entry = pool_->acquire(); + mongocxx::database db = (*entry)[db_name_]; + // Set the find options to sort by timestamp + bsoncxx::document::value order = bsoncxx::builder::stream::document{} << "timestamp" << 1 + << bsoncxx::builder::stream::finalize; + mongocxx::options::find opts = mongocxx::options::find{}; + opts.sort(order.view()); + + // gps + mongocxx::collection gps_coll = db[COLLECTION_GPS]; + mongocxx::cursor gps_docs = gps_coll.find({}, opts); + EXPECT_EQ(gps_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, gps_docs_it] = std::tuple{size_t{0}, gps_docs.begin()}; i < num_docs; i++, gps_docs_it++) { + Sensors & sensors = sensors_vec[i]; + std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view gps_doc = *gps_docs_it; + + Sensors::Gps * gps = sensors.mutable_gps(); + gps->set_latitude(static_cast(gps_doc["latitude"].get_double().value)); + gps->set_longitude(static_cast(gps_doc["longitude"].get_double().value)); + gps->set_speed(static_cast(gps_doc["speed"].get_double().value)); + gps->set_heading(static_cast(gps_doc["heading"].get_double().value)); + timestamp = gps_doc["timestamp"].get_utf8().value.to_string(); + } + + // ais ships + mongocxx::collection ais_coll = db[COLLECTION_AIS_SHIPS]; + mongocxx::cursor ais_docs = ais_coll.find({}, opts); + EXPECT_EQ(ais_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, ais_docs_it] = std::tuple{size_t{0}, ais_docs.begin()}; i < num_docs; i++, ais_docs_it++) { + Sensors & sensors = sensors_vec[i]; + const std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view ais_ships_doc = *ais_docs_it; + + for (bsoncxx::array::element ais_ships_doc : ais_ships_doc["ships"].get_array().value) { + Sensors::Ais * ais_ship = sensors.add_ais_ships(); + ais_ship->set_id(static_cast(ais_ships_doc["id"].get_int64().value)); + ais_ship->set_latitude(static_cast(ais_ships_doc["latitude"].get_double().value)); + ais_ship->set_longitude(static_cast(ais_ships_doc["longitude"].get_double().value)); + ais_ship->set_sog(static_cast(ais_ships_doc["sog"].get_double().value)); + ais_ship->set_cog(static_cast(ais_ships_doc["cog"].get_double().value)); + ais_ship->set_rot(static_cast(ais_ships_doc["rot"].get_double().value)); + ais_ship->set_width(static_cast(ais_ships_doc["width"].get_double().value)); + ais_ship->set_length(static_cast(ais_ships_doc["length"].get_double().value)); + } + EXPECT_EQ(sensors.ais_ships().size(), NUM_AIS_SHIPS) << "Size mismatch when reading AIS ships from DB"; + EXPECT_EQ(ais_ships_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + } + + // generic sensor + mongocxx::collection generic_coll = db[COLLECTION_DATA_SENSORS]; + mongocxx::cursor generic_sensor_docs = generic_coll.find({}, opts); + EXPECT_EQ(generic_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, generic_sensor_docs_it] = std::tuple{size_t{0}, generic_sensor_docs.begin()}; i < num_docs; + i++, generic_sensor_docs_it++) { + Sensors & sensors = sensors_vec[i]; + const std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view generic_doc = *generic_sensor_docs_it; + + for (bsoncxx::array::element generic_doc : generic_doc["genericSensors"].get_array().value) { + Sensors::Generic * generic = sensors.add_data_sensors(); + generic->set_id(static_cast(generic_doc["id"].get_int64().value)); + generic->set_data(static_cast(generic_doc["data"].get_int64().value)); + } + EXPECT_EQ(generic_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + } + + // battery + mongocxx::collection batteries_coll = db[COLLECTION_BATTERIES]; + mongocxx::cursor batteries_data_docs = batteries_coll.find({}, opts); + EXPECT_EQ(batteries_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, batteries_doc_it] = std::tuple{size_t{0}, batteries_data_docs.begin()}; i < num_docs; + i++, batteries_doc_it++) { + Sensors & sensors = sensors_vec[i]; + const std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view batteries_doc = *batteries_doc_it; + + for (bsoncxx::array::element batteries_doc : batteries_doc["batteries"].get_array().value) { + Sensors::Battery * battery = sensors.add_batteries(); + battery->set_voltage(static_cast(batteries_doc["voltage"].get_double().value)); + battery->set_current(static_cast(batteries_doc["current"].get_double().value)); + } + EXPECT_EQ(sensors.batteries().size(), NUM_BATTERIES) << "Size mismatch when reading batteries from DB"; + EXPECT_EQ(batteries_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + } + + // wind sensor + mongocxx::collection wind_coll = db[COLLECTION_WIND_SENSORS]; + mongocxx::cursor wind_sensors_docs = wind_coll.find({}, opts); + EXPECT_EQ(wind_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, wind_doc_it] = std::tuple{size_t{0}, wind_sensors_docs.begin()}; i < num_docs; i++, wind_doc_it++) { + Sensors & sensors = sensors_vec[i]; + const std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view wind_doc = *wind_doc_it; + for (bsoncxx::array::element wind_doc : wind_doc["windSensors"].get_array().value) { + Sensors::Wind * wind = sensors.add_wind_sensors(); + wind->set_speed(static_cast(wind_doc["speed"].get_double().value)); + wind->set_direction(static_cast(wind_doc["direction"].get_int32().value)); + } + EXPECT_EQ(sensors.wind_sensors().size(), NUM_WIND_SENSORS) << "Size mismatch when reading batteries from DB"; + EXPECT_EQ(wind_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + } + + // local path + mongocxx::collection path_coll = db[COLLECTION_LOCAL_PATH]; + mongocxx::cursor local_path_docs = path_coll.find({}, opts); + EXPECT_EQ(path_coll.count_documents({}), num_docs) + << "Error: TestDB should only have " << num_docs << " documents per collection"; + + for (auto [i, path_doc_it] = std::tuple{size_t{0}, local_path_docs.begin()}; i < num_docs; i++, path_doc_it++) { + Sensors & sensors = sensors_vec[i]; + const std::string & timestamp = timestamp_vec[i]; + const bsoncxx::document::view path_doc = *path_doc_it; + for (bsoncxx::array::element path_doc : path_doc["waypoints"].get_array().value) { + Polaris::Waypoint * path = sensors.mutable_local_path_data()->add_waypoints(); + path->set_latitude(static_cast(path_doc["latitude"].get_double().value)); + path->set_longitude(static_cast(path_doc["longitude"].get_double().value)); + } + EXPECT_EQ(sensors.local_path_data().waypoints_size(), NUM_PATH_WAYPOINTS) + << "Size mismatch when reading path waypoints from DB"; + EXPECT_EQ(path_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + } + + return {sensors_vec, timestamp_vec}; +} +void UtilDB::verifyDBWrite(std::span expected_sensors, std::span expected_msg_info) +{ + ASSERT_EQ(expected_sensors.size(), expected_msg_info.size()) << "Must have msg info for each set of Sensors"; + size_t num_docs = expected_sensors.size(); + auto [dumped_sensors, dumped_timestamps] = dumpSensors(num_docs); + + EXPECT_EQ(dumped_sensors.size(), num_docs); + EXPECT_EQ(dumped_timestamps.size(), num_docs); + + for (size_t i = 0; i < num_docs; i++) { + EXPECT_EQ(dumped_timestamps[i], expected_msg_info[i].timestamp_); + + // gps + EXPECT_FLOAT_EQ(dumped_sensors[i].gps().latitude(), expected_sensors[i].gps().latitude()); + EXPECT_FLOAT_EQ(dumped_sensors[i].gps().longitude(), expected_sensors[i].gps().longitude()); + EXPECT_FLOAT_EQ(dumped_sensors[i].gps().speed(), expected_sensors[i].gps().speed()); + EXPECT_FLOAT_EQ(dumped_sensors[i].gps().heading(), expected_sensors[i].gps().heading()); + + // ais ships + for (int j = 0; j < NUM_AIS_SHIPS; j++) { + const Sensors::Ais & dumped_ais_ship = dumped_sensors[i].ais_ships(j); + const Sensors::Ais & expected_ais_ship = expected_sensors[i].ais_ships(j); + EXPECT_EQ(dumped_ais_ship.id(), expected_ais_ship.id()); + EXPECT_FLOAT_EQ(dumped_ais_ship.latitude(), expected_ais_ship.latitude()); + EXPECT_FLOAT_EQ(dumped_ais_ship.longitude(), expected_ais_ship.longitude()); + EXPECT_FLOAT_EQ(dumped_ais_ship.sog(), expected_ais_ship.sog()); + EXPECT_FLOAT_EQ(dumped_ais_ship.cog(), expected_ais_ship.cog()); + EXPECT_FLOAT_EQ(dumped_ais_ship.rot(), expected_ais_ship.rot()); + EXPECT_FLOAT_EQ(dumped_ais_ship.width(), expected_ais_ship.width()); + EXPECT_FLOAT_EQ(dumped_ais_ship.length(), expected_ais_ship.length()); + } + + // generic sensors + for (int j = 0; j < NUM_GENERIC_SENSORS; j++) { + const Sensors::Generic & dumped_data_sensor = dumped_sensors[i].data_sensors(j); + const Sensors::Generic & expected_data_sensor = expected_sensors[i].data_sensors(j); + EXPECT_EQ(dumped_data_sensor.id(), expected_data_sensor.id()); + EXPECT_EQ(dumped_data_sensor.data(), expected_data_sensor.data()); + } + + // batteries + for (int j = 0; j < NUM_BATTERIES; j++) { + const Sensors::Battery & dumped_battery = dumped_sensors[i].batteries(j); + const Sensors::Battery & expected_battery = expected_sensors[i].batteries(j); + EXPECT_EQ(dumped_battery.voltage(), expected_battery.voltage()); + EXPECT_EQ(dumped_battery.current(), expected_battery.current()); + } + + // wind sensors + for (int j = 0; j < NUM_WIND_SENSORS; j++) { + const Sensors::Wind & dumped_wind_sensor = dumped_sensors[i].wind_sensors(j); + const Sensors::Wind & expected_wind_sensor = expected_sensors[i].wind_sensors(j); + EXPECT_EQ(dumped_wind_sensor.speed(), expected_wind_sensor.speed()); + EXPECT_EQ(dumped_wind_sensor.direction(), expected_wind_sensor.direction()); + } + + // path waypoints + for (int j = 0; j < NUM_PATH_WAYPOINTS; j++) { + const Polaris::Waypoint & dumped_path_waypoint = dumped_sensors[i].local_path_data().waypoints(j); + const Polaris::Waypoint & expected_path_waypoint = expected_sensors[i].local_path_data().waypoints(j); + EXPECT_EQ(dumped_path_waypoint.latitude(), expected_path_waypoint.latitude()); + EXPECT_EQ(dumped_path_waypoint.longitude(), expected_path_waypoint.longitude()); + } + } +} + +UtilDB::UtilDB(const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng) +: SailbotDB(db_name, mongodb_conn_str), rng_(rng) +{ +} + +void UtilDB::genRandGpsData(Sensors::Gps & gps_data) +{ + std::uniform_real_distribution lat_dist(LAT_LBND, LAT_UBND); + std::uniform_real_distribution lon_dist(LON_LBND, LON_UBND); + std::uniform_real_distribution speed_dist(SPEED_LBND, SPEED_UBND); + std::uniform_real_distribution heading_dist(HEADING_LBND, HEADING_UBND); + gps_data.set_latitude(lat_dist(*rng_)); + gps_data.set_longitude(lon_dist(*rng_)); + gps_data.set_speed(speed_dist(*rng_)); + gps_data.set_heading(heading_dist(*rng_)); +} + +void UtilDB::genRandAisData(Sensors::Ais & ais_ship) +{ + std::uniform_int_distribution id_dist(0, UINT32_MAX); + std::uniform_real_distribution lat_dist(LAT_LBND, LAT_UBND); + std::uniform_real_distribution lon_dist(LON_LBND, LON_UBND); + std::uniform_real_distribution speed_dist(SPEED_LBND, SPEED_UBND); + std::uniform_real_distribution heading_dist(HEADING_LBND, HEADING_UBND); + std::uniform_real_distribution rot_dist(ROT_LBND, ROT_UBND); + std::uniform_real_distribution width_dist(SHIP_DIMENSION_LBND, SHIP_DIMENSION_UBND); + std::uniform_real_distribution length_dist(SHIP_DIMENSION_LBND, SHIP_DIMENSION_UBND); + + ais_ship.set_id(id_dist(*rng_)); + ais_ship.set_latitude(lat_dist(*rng_)); + ais_ship.set_longitude(lon_dist(*rng_)); + ais_ship.set_sog(speed_dist(*rng_)); + ais_ship.set_cog(heading_dist(*rng_)); + ais_ship.set_rot(rot_dist(*rng_)); + ais_ship.set_width(width_dist(*rng_)); + ais_ship.set_length(length_dist(*rng_)); +} + +void UtilDB::genRandGenericSensorData(Sensors::Generic & generic_sensor) +{ + std::uniform_int_distribution id_generic(0, UINT8_MAX); + std::uniform_int_distribution data_generic(0, UINT64_MAX); + + generic_sensor.set_id(id_generic(*rng_)); + generic_sensor.set_data(data_generic(*rng_)); +} + +void UtilDB::genRandBatteryData(Sensors::Battery & battery) +{ + std::uniform_real_distribution voltage_battery(BATT_VOLT_LBND, BATT_VOLT_UBND); + std::uniform_real_distribution current_battery(BATT_CURR_LBND, BATT_CURR_UBND); + + battery.set_voltage(voltage_battery(*rng_)); + battery.set_current(current_battery(*rng_)); +} + +void UtilDB::genRandWindData(Sensors::Wind & wind_data) +{ + std::uniform_real_distribution speed_wind(SPEED_LBND, SPEED_UBND); + std::uniform_int_distribution direction_wind(WIND_DIRECTION_LBND, WIND_DIRECTION_UBND); + + wind_data.set_speed(speed_wind(*rng_)); + wind_data.set_direction(direction_wind(*rng_)); +} + +void UtilDB::genRandPathData(Sensors::Path & path_data) +{ + std::uniform_real_distribution latitude_path(LAT_LBND, LAT_UBND); + std::uniform_real_distribution longitude_path(LON_LBND, LON_UBND); + + for (int i = 0; i < NUM_PATH_WAYPOINTS; i++) { + Polaris::Waypoint * waypoint = path_data.add_waypoints(); + waypoint->set_latitude(latitude_path(*rng_)); + waypoint->set_longitude(longitude_path(*rng_)); + } +} diff --git a/lib/sailbot_db/test/test_sailbot_db.cpp b/lib/sailbot_db/test/test_sailbot_db.cpp index e69de29..4212d8c 100644 --- a/lib/sailbot_db/test/test_sailbot_db.cpp +++ b/lib/sailbot_db/test/test_sailbot_db.cpp @@ -0,0 +1,43 @@ +#include + +#include "sailbot_db.h" +#include "util_db.h" + +using Polaris::Sensors; + +static std::random_device g_rd = std::random_device(); // random number sampler +static uint32_t g_rand_seed = g_rd(); // seed used for random number generation +static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed +// static std::shared_ptr g_test_db = +// UtilDB::initUtilDB("test", MONGODB_CONN_STR, std::make_shared(g_mt)); +static std::shared_ptr g_test_db = + std::make_shared("test", MONGODB_CONN_STR, std::make_shared(g_mt)); +class TestSailbotDB : public ::testing::Test +{ +protected: + TestSailbotDB() { g_test_db->cleanDB(); } + ~TestSailbotDB() {} +}; + +/** + * @brief Check that MongoDB is running + */ +TEST_F(TestSailbotDB, TestConnection) +{ + ASSERT_TRUE(g_test_db->testConnection()) << "MongoDB not running - remember to connect!"; +} + +/** + * @brief Write random sensor data to the TestDB - read and verify said data + */ +TEST_F(TestSailbotDB, TestStoreSensors) +{ + SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure + auto [rand_sensors, rand_info] = g_test_db->genRandData(); + ASSERT_TRUE(g_test_db->storeNewSensors(rand_sensors, rand_info)); + + std::array expected_sensors = {rand_sensors}; + std::array expected_info = {rand_info}; + + g_test_db->verifyDBWrite(expected_sensors, expected_info); +} diff --git a/projects/can_transceiver/CMakeLists.txt b/projects/can_transceiver/CMakeLists.txt index 1a70897..995d20b 100644 --- a/projects/can_transceiver/CMakeLists.txt +++ b/projects/can_transceiver/CMakeLists.txt @@ -19,7 +19,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/can_transceiver_ros_intf.cpp ) -make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") set(test_srcs ${srcs} diff --git a/projects/example/CMakeLists.txt b/projects/example/CMakeLists.txt index cf0460b..910bc89 100755 --- a/projects/example/CMakeLists.txt +++ b/projects/example/CMakeLists.txt @@ -21,7 +21,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/cached_fib_ros_intf.cpp ) -make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/local_transceiver/CMakeLists.txt b/projects/local_transceiver/CMakeLists.txt index b20f8ce..be94351 100644 --- a/projects/local_transceiver/CMakeLists.txt +++ b/projects/local_transceiver/CMakeLists.txt @@ -22,7 +22,7 @@ set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/local_transceiver_ros_intf.cpp ) -make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/mock_ais/CMakeLists.txt b/projects/mock_ais/CMakeLists.txt index 2d66222..71978a1 100644 --- a/projects/mock_ais/CMakeLists.txt +++ b/projects/mock_ais/CMakeLists.txt @@ -10,18 +10,16 @@ set(inc_dirs set(compile_defs ) -# Create module library set(srcs ${CMAKE_CURRENT_LIST_DIR}/src/mock_ais.cpp ) -make_lib(${module} "${srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create module ROS executable set(bin_srcs ${srcs} ${CMAKE_CURRENT_LIST_DIR}/src/mock_ais_ros_intf.cpp ) -make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/remote_transceiver/CMakeLists.txt b/projects/remote_transceiver/CMakeLists.txt index 05258e5..aeb4c01 100644 --- a/projects/remote_transceiver/CMakeLists.txt +++ b/projects/remote_transceiver/CMakeLists.txt @@ -4,12 +4,14 @@ set(link_libs ${PROTOBUF_LINK_LIBS} mongo::mongocxx_shared mongo::bsoncxx_shared + sailbot_db ) set(inc_dirs ${PROTOBUF_INCLUDE_PATH} ${LIBMONGOCXX_INCLUDE_DIRS} ${LIBBSONCXX_INCLUDE_DIRS} + ${SAILBOT_DB_INC_DIR} ) set(compile_defs @@ -25,7 +27,7 @@ set(bin_srcs ) # Make executable -make_ros_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/projects/remote_transceiver/test/test_remote_transceiver.cpp b/projects/remote_transceiver/test/test_remote_transceiver.cpp index 04f08eb..275df48 100644 --- a/projects/remote_transceiver/test/test_remote_transceiver.cpp +++ b/projects/remote_transceiver/test/test_remote_transceiver.cpp @@ -4,19 +4,9 @@ #include #include #include -#include -#include -#include -#include -#include #include #include #include -#include -#include -#include -#include -#include #include #include #include @@ -26,12 +16,9 @@ #include "remote_transceiver.h" #include "sailbot_db.h" #include "sensors.pb.h" +#include "util_db.h" #include "waypoint.pb.h" -constexpr int NUM_AIS_SHIPS = 15; // arbitrary number -constexpr int NUM_GENERIC_SENSORS = 5; // arbitrary number -constexpr int NUM_PATH_WAYPOINTS = 5; // arbitrary number - using Polaris::Sensors; using remote_transceiver::HTTPServer; using remote_transceiver::Listener; @@ -39,468 +26,13 @@ using remote_transceiver::TESTING_HOST; using remote_transceiver::TESTING_PORT; namespace http_client = remote_transceiver::http_client; -//Child class of SailbotDB that includes additional database utility functions to help testing -class TestDB : public SailbotDB -{ -public: - static constexpr auto TEST_DB = "test"; - TestDB() : SailbotDB(TEST_DB, MONGODB_CONN_STR) {} - - /** - * @brief Delete all documents in all collections - */ - void cleanDB() - { - mongocxx::pool::entry entry = pool_->acquire(); - mongocxx::database db = (*entry)[db_name_]; - - mongocxx::collection gps_coll = db[COLLECTION_GPS]; - mongocxx::collection ais_coll = db[COLLECTION_AIS_SHIPS]; - mongocxx::collection generic_coll = db[COLLECTION_DATA_SENSORS]; - mongocxx::collection batteries_coll = db[COLLECTION_BATTERIES]; - mongocxx::collection wind_coll = db[COLLECTION_WIND_SENSORS]; - mongocxx::collection local_path_coll = db[COLLECTION_LOCAL_PATH]; - - gps_coll.delete_many(bsoncxx::builder::basic::make_document()); - ais_coll.delete_many(bsoncxx::builder::basic::make_document()); - generic_coll.delete_many(bsoncxx::builder::basic::make_document()); - batteries_coll.delete_many(bsoncxx::builder::basic::make_document()); - wind_coll.delete_many(bsoncxx::builder::basic::make_document()); - local_path_coll.delete_many(bsoncxx::builder::basic::make_document()); - } - - /** - * @brief Retrieve all sensors from the database sorted by timestamp - * - * @param num_docs expected number of documents for each collection, default 1 - * - * @return Vector of sensors objects: gps, ais, generic, batteries, wind, local path - * @return Vector of timestamps - * both vectors will be num_docs in size - */ - std::pair, std::vector> dumpSensors(size_t num_docs = 1) - { - std::vector sensors_vec(num_docs); - std::vector timestamp_vec(num_docs); - mongocxx::pool::entry entry = pool_->acquire(); - mongocxx::database db = (*entry)[db_name_]; - // Set the find options to sort by timestamp - bsoncxx::document::value order = bsoncxx::builder::stream::document{} << "timestamp" << 1 - << bsoncxx::builder::stream::finalize; - mongocxx::options::find opts = mongocxx::options::find{}; - opts.sort(order.view()); - - // gps - mongocxx::collection gps_coll = db[COLLECTION_GPS]; - mongocxx::cursor gps_docs = gps_coll.find({}, opts); - EXPECT_EQ(gps_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, gps_docs_it] = std::tuple{size_t{0}, gps_docs.begin()}; i < num_docs; i++, gps_docs_it++) { - Sensors & sensors = sensors_vec[i]; - std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view gps_doc = *gps_docs_it; - - Sensors::Gps * gps = sensors.mutable_gps(); - gps->set_latitude(static_cast(gps_doc["latitude"].get_double().value)); - gps->set_longitude(static_cast(gps_doc["longitude"].get_double().value)); - gps->set_speed(static_cast(gps_doc["speed"].get_double().value)); - gps->set_heading(static_cast(gps_doc["heading"].get_double().value)); - timestamp = gps_doc["timestamp"].get_utf8().value.to_string(); - } - - // ais ships - mongocxx::collection ais_coll = db[COLLECTION_AIS_SHIPS]; - mongocxx::cursor ais_docs = ais_coll.find({}, opts); - EXPECT_EQ(ais_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, ais_docs_it] = std::tuple{size_t{0}, ais_docs.begin()}; i < num_docs; i++, ais_docs_it++) { - Sensors & sensors = sensors_vec[i]; - const std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view ais_ships_doc = *ais_docs_it; - - for (bsoncxx::array::element ais_ships_doc : ais_ships_doc["ships"].get_array().value) { - Sensors::Ais * ais_ship = sensors.add_ais_ships(); - ais_ship->set_id(static_cast(ais_ships_doc["id"].get_int64().value)); - ais_ship->set_latitude(static_cast(ais_ships_doc["latitude"].get_double().value)); - ais_ship->set_longitude(static_cast(ais_ships_doc["longitude"].get_double().value)); - ais_ship->set_sog(static_cast(ais_ships_doc["sog"].get_double().value)); - ais_ship->set_cog(static_cast(ais_ships_doc["cog"].get_double().value)); - ais_ship->set_rot(static_cast(ais_ships_doc["rot"].get_double().value)); - ais_ship->set_width(static_cast(ais_ships_doc["width"].get_double().value)); - ais_ship->set_length(static_cast(ais_ships_doc["length"].get_double().value)); - } - EXPECT_EQ(sensors.ais_ships().size(), NUM_AIS_SHIPS) << "Size mismatch when reading AIS ships from DB"; - EXPECT_EQ(ais_ships_doc["timestamp"].get_utf8().value.to_string(), timestamp) - << "Document timestamp mismatch"; - } - - // generic sensor - mongocxx::collection generic_coll = db[COLLECTION_DATA_SENSORS]; - mongocxx::cursor generic_sensor_docs = generic_coll.find({}, opts); - EXPECT_EQ(generic_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, generic_sensor_docs_it] = std::tuple{size_t{0}, generic_sensor_docs.begin()}; i < num_docs; - i++, generic_sensor_docs_it++) { - Sensors & sensors = sensors_vec[i]; - const std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view generic_doc = *generic_sensor_docs_it; - - for (bsoncxx::array::element generic_doc : generic_doc["genericSensors"].get_array().value) { - Sensors::Generic * generic = sensors.add_data_sensors(); - generic->set_id(static_cast(generic_doc["id"].get_int64().value)); - generic->set_data(static_cast(generic_doc["data"].get_int64().value)); - } - EXPECT_EQ(generic_doc["timestamp"].get_utf8().value.to_string(), timestamp) - << "Document timestamp mismatch"; - } - - // battery - mongocxx::collection batteries_coll = db[COLLECTION_BATTERIES]; - mongocxx::cursor batteries_data_docs = batteries_coll.find({}, opts); - EXPECT_EQ(batteries_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, batteries_doc_it] = std::tuple{size_t{0}, batteries_data_docs.begin()}; i < num_docs; - i++, batteries_doc_it++) { - Sensors & sensors = sensors_vec[i]; - const std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view batteries_doc = *batteries_doc_it; - - for (bsoncxx::array::element batteries_doc : batteries_doc["batteries"].get_array().value) { - Sensors::Battery * battery = sensors.add_batteries(); - battery->set_voltage(static_cast(batteries_doc["voltage"].get_double().value)); - battery->set_current(static_cast(batteries_doc["current"].get_double().value)); - } - EXPECT_EQ(sensors.batteries().size(), NUM_BATTERIES) << "Size mismatch when reading batteries from DB"; - EXPECT_EQ(batteries_doc["timestamp"].get_utf8().value.to_string(), timestamp) - << "Document timestamp mismatch"; - } - - // wind sensor - mongocxx::collection wind_coll = db[COLLECTION_WIND_SENSORS]; - mongocxx::cursor wind_sensors_docs = wind_coll.find({}, opts); - EXPECT_EQ(wind_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, wind_doc_it] = std::tuple{size_t{0}, wind_sensors_docs.begin()}; i < num_docs; - i++, wind_doc_it++) { - Sensors & sensors = sensors_vec[i]; - const std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view wind_doc = *wind_doc_it; - for (bsoncxx::array::element wind_doc : wind_doc["windSensors"].get_array().value) { - Sensors::Wind * wind = sensors.add_wind_sensors(); - wind->set_speed(static_cast(wind_doc["speed"].get_double().value)); - wind->set_direction(static_cast(wind_doc["direction"].get_int32().value)); - } - EXPECT_EQ(sensors.wind_sensors().size(), NUM_WIND_SENSORS) - << "Size mismatch when reading batteries from DB"; - EXPECT_EQ(wind_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; - } - - // local path - mongocxx::collection path_coll = db[COLLECTION_LOCAL_PATH]; - mongocxx::cursor local_path_docs = path_coll.find({}, opts); - EXPECT_EQ(path_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; - - for (auto [i, path_doc_it] = std::tuple{size_t{0}, local_path_docs.begin()}; i < num_docs; i++, path_doc_it++) { - Sensors & sensors = sensors_vec[i]; - const std::string & timestamp = timestamp_vec[i]; - const bsoncxx::document::view path_doc = *path_doc_it; - for (bsoncxx::array::element path_doc : path_doc["waypoints"].get_array().value) { - Polaris::Waypoint * path = sensors.mutable_local_path_data()->add_waypoints(); - path->set_latitude(static_cast(path_doc["latitude"].get_double().value)); - path->set_longitude(static_cast(path_doc["longitude"].get_double().value)); - } - EXPECT_EQ(sensors.local_path_data().waypoints_size(), NUM_PATH_WAYPOINTS) - << "Size mismatch when reading path waypoints from DB"; - EXPECT_EQ(path_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; - } - - return {sensors_vec, timestamp_vec}; - } -}; - -static TestDB g_test_db = TestDB(); // initialize the TestDB instance -static std::random_device g_rd = std::random_device(); // random number sampler -static uint32_t g_rand_seed = g_rd(); // seed used for random number generation -static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed -// Use a static counter for creating testing timestamps as the internals of the -// remote transceiver do not care about the format. -// The counter is of type char because of how strings are sorted in alphabetical order, -// where a sequence of strings "1", "2", "10" are sorted to "1", "10", "2". -// Incrementing a char prevents this issue for up to 256 numbers as it increments -// '0', '1', ..., '9', '', ... -static char g_doc_num = '0'; - -class TestSailbotDB : public ::testing::Test -{ -protected: - TestSailbotDB() - { - g_test_db.cleanDB(); - g_doc_num = '0'; - } - ~TestSailbotDB() override {} -}; - -/** - * @brief generate random GPS data - * - * @param gps_data pointer to generated gps_data - */ -void * genRandGpsData(Sensors::Gps * gps_data) -{ - std::uniform_real_distribution lat_dist(LAT_LBND, LAT_UBND); - std::uniform_real_distribution lon_dist(LON_LBND, LON_UBND); - std::uniform_real_distribution speed_dist(SPEED_LBND, SPEED_UBND); - std::uniform_real_distribution heading_dist(HEADING_LBND, HEADING_UBND); - gps_data->set_latitude(lat_dist(g_mt)); - gps_data->set_longitude(lon_dist(g_mt)); - gps_data->set_speed(speed_dist(g_mt)); - gps_data->set_heading(heading_dist(g_mt)); - - return gps_data; -} - -/** - * @brief generate random ais ships data - * - * @param ais_ship pointer to generated ais data - */ -void genRandAisData(Sensors::Ais * ais_ship) -{ - std::uniform_int_distribution id_dist(0, UINT32_MAX); - std::uniform_real_distribution lat_dist(LAT_LBND, LAT_UBND); - std::uniform_real_distribution lon_dist(LON_LBND, LON_UBND); - std::uniform_real_distribution speed_dist(SPEED_LBND, SPEED_UBND); - std::uniform_real_distribution heading_dist(HEADING_LBND, HEADING_UBND); - std::uniform_real_distribution rot_dist(ROT_LBND, ROT_UBND); - std::uniform_real_distribution width_dist(SHIP_DIMENSION_LBND, SHIP_DIMENSION_UBND); - std::uniform_real_distribution length_dist(SHIP_DIMENSION_LBND, SHIP_DIMENSION_UBND); - - ais_ship->set_id(id_dist(g_mt)); - ais_ship->set_latitude(lat_dist(g_mt)); - ais_ship->set_longitude(lon_dist(g_mt)); - ais_ship->set_sog(speed_dist(g_mt)); - ais_ship->set_cog(heading_dist(g_mt)); - ais_ship->set_rot(rot_dist(g_mt)); - ais_ship->set_width(width_dist(g_mt)); - ais_ship->set_length(length_dist(g_mt)); -} - -/** - * @brief generate random generic sensor data - * - * @return pointer to generated generic sensor data - */ -void genRandGenericSensorData(Sensors::Generic * generic_sensor) -{ - std::uniform_int_distribution id_generic(0, UINT8_MAX); - std::uniform_int_distribution data_generic(0, UINT64_MAX); - - generic_sensor->set_id(id_generic(g_mt)); - generic_sensor->set_data(data_generic(g_mt)); -} - -/** - * @brief generate random battery data - * - * @return pointer to generated battery data - */ -void genRandBatteriesData(Sensors::Battery * battery) -{ - std::uniform_real_distribution voltage_battery(BATT_VOLT_LBND, BATT_VOLT_UBND); - std::uniform_real_distribution current_battery(BATT_CURR_LBND, BATT_CURR_UBND); - - battery->set_voltage(voltage_battery(g_mt)); - battery->set_current(current_battery(g_mt)); -} - -/** - * @brief generate random wind sensors data - * - * @return pointer to generated wind sensors data - */ -void genRandWindData(Sensors::Wind * wind_data) -{ - std::uniform_real_distribution speed_wind(SPEED_LBND, SPEED_UBND); - std::uniform_int_distribution direction_wind(WIND_DIRECTION_LBND, WIND_DIRECTION_UBND); - - wind_data->set_speed(speed_wind(g_mt)); - wind_data->set_direction(direction_wind(g_mt)); -} - -/** - * @brief generate random path data - * - * @return pointer to generated path data - */ -void genRandPathData(Sensors::Path * path_data) -{ - std::uniform_real_distribution latitude_path(LAT_LBND, LAT_UBND); - std::uniform_real_distribution longitude_path(LON_LBND, LON_UBND); - - for (int i = 0; i < NUM_PATH_WAYPOINTS; i++) { - Polaris::Waypoint * waypoint = path_data->add_waypoints(); - waypoint->set_latitude(latitude_path(g_mt)); - waypoint->set_longitude(longitude_path(g_mt)); - } -} - -/** - * @brief Generate random data for all sensors - * - * @return Sensors object - */ -Sensors genRandSensors() -{ - Sensors sensors; - - // gps - genRandGpsData(sensors.mutable_gps()); - - // ais ships, TODO(): Polaris should be included as one of the AIS ships - for (int i = 0; i < NUM_AIS_SHIPS; i++) { - genRandAisData(sensors.add_ais_ships()); - } - - // generic sensors - for (int i = 0; i < NUM_GENERIC_SENSORS; i++) { - genRandGenericSensorData(sensors.add_data_sensors()); - } - - // batteries - for (int i = 0; i < NUM_BATTERIES; i++) { - genRandBatteriesData(sensors.add_batteries()); - } - - // wind sensors - for (int i = 0; i < NUM_WIND_SENSORS; i++) { - genRandWindData(sensors.add_wind_sensors()); - } - - // path waypoints - genRandPathData(sensors.mutable_local_path_data()); - - return sensors; -} - -/** - * @brief Generate random sensors and Iridium msg info - * - * @return std::pair - */ -std::pair genRandData() -{ - Sensors rand_sensors = genRandSensors(); - SailbotDB::RcvdMsgInfo rand_info{ - .lat_ = 0, // Not processed yet, so just set to 0 - .lon_ = 0, // Not processed yet, so just set to 0 - .cep_ = 0, // Not processed yet, so just set to 0 - .timestamp_ = std::to_string(g_doc_num++)}; // increment counter after converting and storing - return {rand_sensors, rand_info}; -} - -/** - * @brief Query the database and check that the sensor and message are correct - * - * @param expected_sensors - * @param expected_msg_info - */ -void verifyDBWrite(std::span expected_sensors, std::span expected_msg_info) -{ - ASSERT_EQ(expected_sensors.size(), expected_msg_info.size()) << "Must have msg info for each set of Sensors"; - size_t num_docs = expected_sensors.size(); - auto [dumped_sensors, dumped_timestamps] = g_test_db.dumpSensors(num_docs); - - EXPECT_EQ(dumped_sensors.size(), num_docs); - EXPECT_EQ(dumped_timestamps.size(), num_docs); - - for (size_t i = 0; i < num_docs; i++) { - EXPECT_EQ(dumped_timestamps[i], expected_msg_info[i].timestamp_); - - // gps - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().latitude(), expected_sensors[i].gps().latitude()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().longitude(), expected_sensors[i].gps().longitude()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().speed(), expected_sensors[i].gps().speed()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().heading(), expected_sensors[i].gps().heading()); - - // ais ships - for (int j = 0; j < NUM_AIS_SHIPS; j++) { - const Sensors::Ais & dumped_ais_ship = dumped_sensors[i].ais_ships(j); - const Sensors::Ais & expected_ais_ship = expected_sensors[i].ais_ships(j); - EXPECT_EQ(dumped_ais_ship.id(), expected_ais_ship.id()); - EXPECT_FLOAT_EQ(dumped_ais_ship.latitude(), expected_ais_ship.latitude()); - EXPECT_FLOAT_EQ(dumped_ais_ship.longitude(), expected_ais_ship.longitude()); - EXPECT_FLOAT_EQ(dumped_ais_ship.sog(), expected_ais_ship.sog()); - EXPECT_FLOAT_EQ(dumped_ais_ship.cog(), expected_ais_ship.cog()); - EXPECT_FLOAT_EQ(dumped_ais_ship.rot(), expected_ais_ship.rot()); - EXPECT_FLOAT_EQ(dumped_ais_ship.width(), expected_ais_ship.width()); - EXPECT_FLOAT_EQ(dumped_ais_ship.length(), expected_ais_ship.length()); - } - - // generic sensors - for (int j = 0; j < NUM_GENERIC_SENSORS; j++) { - const Sensors::Generic & dumped_data_sensor = dumped_sensors[i].data_sensors(j); - const Sensors::Generic & expected_data_sensor = expected_sensors[i].data_sensors(j); - EXPECT_EQ(dumped_data_sensor.id(), expected_data_sensor.id()); - EXPECT_EQ(dumped_data_sensor.data(), expected_data_sensor.data()); - } - - // batteries - for (int j = 0; j < NUM_BATTERIES; j++) { - const Sensors::Battery & dumped_battery = dumped_sensors[i].batteries(j); - const Sensors::Battery & expected_battery = expected_sensors[i].batteries(j); - EXPECT_EQ(dumped_battery.voltage(), expected_battery.voltage()); - EXPECT_EQ(dumped_battery.current(), expected_battery.current()); - } - - // wind sensors - for (int j = 0; j < NUM_WIND_SENSORS; j++) { - const Sensors::Wind & dumped_wind_sensor = dumped_sensors[i].wind_sensors(j); - const Sensors::Wind & expected_wind_sensor = expected_sensors[i].wind_sensors(j); - EXPECT_EQ(dumped_wind_sensor.speed(), expected_wind_sensor.speed()); - EXPECT_EQ(dumped_wind_sensor.direction(), expected_wind_sensor.direction()); - } - - // path waypoints - for (int j = 0; j < NUM_PATH_WAYPOINTS; j++) { - const Polaris::Waypoint & dumped_path_waypoint = dumped_sensors[i].local_path_data().waypoints(j); - const Polaris::Waypoint & expected_path_waypoint = expected_sensors[i].local_path_data().waypoints(j); - EXPECT_EQ(dumped_path_waypoint.latitude(), expected_path_waypoint.latitude()); - EXPECT_EQ(dumped_path_waypoint.longitude(), expected_path_waypoint.longitude()); - } - } -} - -/** - * @brief Check that MongoDB is running - */ -TEST_F(TestSailbotDB, TestConnection) -{ - ASSERT_TRUE(g_test_db.testConnection()) << "MongoDB not running - remember to connect!"; -} - -/** - * @brief Write random sensor data to the TestDB - read and verify said data - */ -TEST_F(TestSailbotDB, TestStoreSensors) -{ - SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = genRandData(); - ASSERT_TRUE(g_test_db.storeNewSensors(rand_sensors, rand_info)); - - std::array expected_sensors = {rand_sensors}; - std::array expected_info = {rand_info}; +static std::random_device g_rd = std::random_device(); // random number sampler +static uint32_t g_rand_seed = g_rd(); // seed used for random number generation +static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed +static std::shared_ptr g_test_db = + std::make_shared("test", MONGODB_CONN_STR, std::make_shared(g_mt)); - verifyDBWrite(expected_sensors, expected_info); -} - -class TestHTTP : public TestSailbotDB +class TestRemoteTransceiver : public ::testing::Test { protected: static constexpr int NUM_THREADS = 4; @@ -532,27 +64,24 @@ class TestHTTP : public TestSailbotDB } } - TestHTTP() - { - // Automatically calls TestSailbotDB's constructor to setup tests - } + TestRemoteTransceiver() { g_test_db->cleanDB(); } - ~TestHTTP() override {} + ~TestRemoteTransceiver() override {} }; // Initialize static objects -bio::io_context TestHTTP::io_{TestHTTP::NUM_THREADS}; -tcp::acceptor TestHTTP::acceptor_{io_, {bio::ip::make_address(TESTING_HOST), TESTING_PORT}}; -tcp::socket TestHTTP::socket_{io_}; -std::vector TestHTTP::io_threads_ = std::vector(NUM_THREADS); -SailbotDB TestHTTP::server_db_ = TestDB(); -Listener TestHTTP::listener_ = Listener(io_, std::move(acceptor_), std::move(server_db_)); +bio::io_context TestRemoteTransceiver::io_{TestRemoteTransceiver::NUM_THREADS}; +tcp::acceptor TestRemoteTransceiver::acceptor_{io_, {bio::ip::make_address(TESTING_HOST), TESTING_PORT}}; +tcp::socket TestRemoteTransceiver::socket_{io_}; +std::vector TestRemoteTransceiver::io_threads_ = std::vector(NUM_THREADS); +SailbotDB TestRemoteTransceiver::server_db_ = UtilDB("test", MONGODB_CONN_STR, std::make_shared(g_mt)); +Listener TestRemoteTransceiver::listener_ = Listener(io_, std::move(acceptor_), std::move(server_db_)); /** * @brief Test HTTP GET request sending and handling. Currently just retrieves a placeholder string. * */ -TEST_F(TestHTTP, TestGet) +TEST_F(TestRemoteTransceiver, TestGet) { auto [status, result] = http_client::get({TESTING_HOST, std::to_string(TESTING_PORT), remote_transceiver::targets::ROOT}); @@ -580,10 +109,10 @@ std::string createPostBody(remote_transceiver::MOMsgParams::Params params) * @brief Test that we can POST sensor data to the server * */ -TEST_F(TestHTTP, TestPostSensors) +TEST_F(TestRemoteTransceiver, TestPostSensors) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = genRandData(); + auto [rand_sensors, rand_info] = g_test_db->genRandData(); std::string rand_sensors_str; ASSERT_TRUE(rand_sensors.SerializeToString(&rand_sensors_str)); @@ -608,14 +137,14 @@ TEST_F(TestHTTP, TestPostSensors) std::array expected_sensors = {rand_sensors}; std::array expected_info = {rand_info}; - verifyDBWrite(expected_sensors, expected_info); + g_test_db->verifyDBWrite(expected_sensors, expected_info); } /** * @brief Test that the server can multiple POST requests at once * */ -TEST_F(TestHTTP, TestPostSensorsMult) +TEST_F(TestRemoteTransceiver, TestPostSensorsMult) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure @@ -628,7 +157,7 @@ TEST_F(TestHTTP, TestPostSensorsMult) // Prepare all queries for (int i = 0; i < NUM_REQS; i++) { - auto [rand_sensors, rand_info] = genRandData(); + auto [rand_sensors, rand_info] = g_test_db->genRandData(); expected_sensors[i] = rand_sensors; expected_info[i] = rand_info; std::string rand_sensors_str; @@ -665,5 +194,5 @@ TEST_F(TestHTTP, TestPostSensorsMult) std::this_thread::sleep_for(WAIT_AFTER_RES); // Check that DB is updated properly for all requests - verifyDBWrite(expected_sensors, expected_info); + g_test_db->verifyDBWrite(expected_sensors, expected_info); } From 6341529dd219f9aa606b503e0a6b285405bbaa95 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Fri, 23 Feb 2024 10:35:10 -0800 Subject: [PATCH 03/11] Building and linking work --- lib/sailbot_db/inc/util_db.h | 31 +-- lib/sailbot_db/src/util_db.cpp | 230 ++++++++++-------- lib/sailbot_db/test/test_sailbot_db.cpp | 15 +- lib/utils/utils.h | 55 +++++ .../test/test_remote_transceiver.cpp | 19 +- 5 files changed, 206 insertions(+), 144 deletions(-) diff --git a/lib/sailbot_db/inc/util_db.h b/lib/sailbot_db/inc/util_db.h index 1f67781..ae39835 100644 --- a/lib/sailbot_db/inc/util_db.h +++ b/lib/sailbot_db/inc/util_db.h @@ -5,6 +5,7 @@ #include "sailbot_db.h" #include "sensors.pb.h" +#include "utils/utils.h" class UtilDB : public SailbotDB { @@ -14,8 +15,7 @@ class UtilDB : public SailbotDB static constexpr int NUM_PATH_WAYPOINTS = 5; // arbitrary number /** - * @brief Construct a UtilDB. Private because there should only ever be one instance, which should be - * inialized using the initUtilDB() function + * @brief Construct a UtilDB. * * @param db_name * @param mongodb_conn_str @@ -23,17 +23,6 @@ class UtilDB : public SailbotDB */ UtilDB(const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng); - /** - * @brief Initialize a static instance of the UtilDB - * - * @param db_name - * @param mongodb_conn_str - * @param rng - * @return shared pointer to the static instance - */ - static std::shared_ptr initUtilDB( - const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng); - /** * @brief Delete all documents in all collections */ @@ -53,29 +42,21 @@ class UtilDB : public SailbotDB */ std::pair genRandData(); - /** - * @brief Retrieve all sensors from the database sorted by timestamp - * - * @param num_docs expected number of documents for each collection, default 1 - * - * @return Vector of sensors objects: gps, ais, generic, batteries, wind, local path - * @return Vector of timestamps - * both vectors will be num_docs in size - */ - std::pair, std::vector> dumpSensors(size_t num_docs = 1); - /** * @brief Query the database and check that the sensor and message are correct * * @param expected_sensors * @param expected_msg_info */ - void verifyDBWrite( + bool verifyDBWrite( std::span expected_sensors, std::span expected_msg_info); private: std::shared_ptr rng_; + std::pair, std::vector> dumpSensors( + utils::FailTracker & tracker, size_t num_docs = 1); + /** * @brief generate random GPS data * diff --git a/lib/sailbot_db/src/util_db.cpp b/lib/sailbot_db/src/util_db.cpp index a19e069..f013794 100644 --- a/lib/sailbot_db/src/util_db.cpp +++ b/lib/sailbot_db/src/util_db.cpp @@ -1,8 +1,6 @@ #include "util_db.h" -#include - #include #include #include @@ -16,21 +14,30 @@ #include #include "cmn_hdrs/shared_constants.h" +#include "utils/utils.h" using Polaris::Sensors; -static std::shared_ptr g_util_db; - -std::shared_ptr UtilDB::initUtilDB( - const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng) +namespace +{ +template +bool checkEQ(T rcvd, T expected, const std::string & err_msg) { - static bool initialized = false; - if (!initialized) { - g_util_db = std::make_shared(db_name, mongodb_conn_str, rng); - initialized = true; + if (rcvd != expected) { + std::cerr << "Expected: " << expected << " but received: " << rcvd << std::endl; + std::cerr << err_msg << std::endl; + return false; } - return g_util_db; -} + return true; +}; +template +bool checkFloatEQ(T rcvd, T expected, const std::string & err_msg) +{ + std::stringstream ss; + ss << "Expected: " << expected << " but received: " << rcvd << "\n" << err_msg; + return static_cast(utils::isFloatEQ(rcvd, expected, ss.str())); +}; +} // namespace void UtilDB::cleanDB() { @@ -96,8 +103,89 @@ std::pair UtilDB::genRandData() return {rand_sensors, rand_info}; } -std::pair, std::vector> UtilDB::dumpSensors(size_t num_docs) +bool UtilDB::verifyDBWrite(std::span expected_sensors, std::span expected_msg_info) { + utils::FailTracker tracker; + + auto expectEQ = [&tracker](T rcvd, T expected, const std::string & err_msg) -> void { + tracker.track(checkEQ(rcvd, expected, err_msg)); + }; + auto expectFloatEQ = [&tracker](T rcvd, T expected, const std::string & err_msg) -> void { + tracker.track(checkFloatEQ(rcvd, expected, err_msg)); + }; + + expectEQ(expected_sensors.size(), expected_msg_info.size(), "Must have msg info for each set of Sensors"); + size_t num_docs = expected_sensors.size(); + auto [dumped_sensors, dumped_timestamps] = dumpSensors(tracker, num_docs); + + expectEQ(dumped_sensors.size(), num_docs, ""); + expectEQ(dumped_timestamps.size(), num_docs, ""); + + for (size_t i = 0; i < num_docs; i++) { + expectEQ(dumped_timestamps[i], expected_msg_info[i].timestamp_, ""); + + // gps + expectFloatEQ(dumped_sensors[i].gps().latitude(), expected_sensors[i].gps().latitude(), ""); + expectFloatEQ(dumped_sensors[i].gps().longitude(), expected_sensors[i].gps().longitude(), ""); + expectFloatEQ(dumped_sensors[i].gps().speed(), expected_sensors[i].gps().speed(), ""); + expectFloatEQ(dumped_sensors[i].gps().heading(), expected_sensors[i].gps().heading(), ""); + + // ais ships + for (int j = 0; j < NUM_AIS_SHIPS; j++) { + const Sensors::Ais & dumped_ais_ship = dumped_sensors[i].ais_ships(j); + const Sensors::Ais & expected_ais_ship = expected_sensors[i].ais_ships(j); + expectEQ(dumped_ais_ship.id(), expected_ais_ship.id(), ""); + expectFloatEQ(dumped_ais_ship.latitude(), expected_ais_ship.latitude(), ""); + expectFloatEQ(dumped_ais_ship.longitude(), expected_ais_ship.longitude(), ""); + expectFloatEQ(dumped_ais_ship.sog(), expected_ais_ship.sog(), ""); + expectFloatEQ(dumped_ais_ship.cog(), expected_ais_ship.cog(), ""); + expectFloatEQ(dumped_ais_ship.rot(), expected_ais_ship.rot(), ""); + expectFloatEQ(dumped_ais_ship.width(), expected_ais_ship.width(), ""); + expectFloatEQ(dumped_ais_ship.length(), expected_ais_ship.length(), ""); + } + + // generic sensors + for (int j = 0; j < NUM_GENERIC_SENSORS; j++) { + const Sensors::Generic & dumped_data_sensor = dumped_sensors[i].data_sensors(j); + const Sensors::Generic & expected_data_sensor = expected_sensors[i].data_sensors(j); + expectEQ(dumped_data_sensor.id(), expected_data_sensor.id(), ""); + expectEQ(dumped_data_sensor.data(), expected_data_sensor.data(), ""); + } + + // batteries + for (int j = 0; j < NUM_BATTERIES; j++) { + const Sensors::Battery & dumped_battery = dumped_sensors[i].batteries(j); + const Sensors::Battery & expected_battery = expected_sensors[i].batteries(j); + expectFloatEQ(dumped_battery.voltage(), expected_battery.voltage(), ""); + expectFloatEQ(dumped_battery.current(), expected_battery.current(), ""); + } + + // wind sensors + for (int j = 0; j < NUM_WIND_SENSORS; j++) { + const Sensors::Wind & dumped_wind_sensor = dumped_sensors[i].wind_sensors(j); + const Sensors::Wind & expected_wind_sensor = expected_sensors[i].wind_sensors(j); + expectFloatEQ(dumped_wind_sensor.speed(), expected_wind_sensor.speed(), ""); + expectEQ(dumped_wind_sensor.direction(), expected_wind_sensor.direction(), ""); + } + + // path waypoints + for (int j = 0; j < NUM_PATH_WAYPOINTS; j++) { + const Polaris::Waypoint & dumped_path_waypoint = dumped_sensors[i].local_path_data().waypoints(j); + const Polaris::Waypoint & expected_path_waypoint = expected_sensors[i].local_path_data().waypoints(j); + expectFloatEQ(dumped_path_waypoint.latitude(), expected_path_waypoint.latitude(), ""); + expectFloatEQ(dumped_path_waypoint.longitude(), expected_path_waypoint.longitude(), ""); + } + } + return !tracker.failed(); +} + +std::pair, std::vector> UtilDB::dumpSensors( + utils::FailTracker & tracker, size_t num_docs) +{ + auto expectEQ = [&tracker](T rcvd, T expected, const std::string & err_msg) -> void { + tracker.track(checkEQ(rcvd, expected, err_msg)); + }; + std::vector sensors_vec(num_docs); std::vector timestamp_vec(num_docs); mongocxx::pool::entry entry = pool_->acquire(); @@ -111,8 +199,9 @@ std::pair, std::vector> UtilDB::dumpSensors(si // gps mongocxx::collection gps_coll = db[COLLECTION_GPS]; mongocxx::cursor gps_docs = gps_coll.find({}, opts); - EXPECT_EQ(gps_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(gps_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, gps_docs_it] = std::tuple{size_t{0}, gps_docs.begin()}; i < num_docs; i++, gps_docs_it++) { Sensors & sensors = sensors_vec[i]; @@ -130,8 +219,9 @@ std::pair, std::vector> UtilDB::dumpSensors(si // ais ships mongocxx::collection ais_coll = db[COLLECTION_AIS_SHIPS]; mongocxx::cursor ais_docs = ais_coll.find({}, opts); - EXPECT_EQ(ais_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(ais_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, ais_docs_it] = std::tuple{size_t{0}, ais_docs.begin()}; i < num_docs; i++, ais_docs_it++) { Sensors & sensors = sensors_vec[i]; @@ -149,15 +239,16 @@ std::pair, std::vector> UtilDB::dumpSensors(si ais_ship->set_width(static_cast(ais_ships_doc["width"].get_double().value)); ais_ship->set_length(static_cast(ais_ships_doc["length"].get_double().value)); } - EXPECT_EQ(sensors.ais_ships().size(), NUM_AIS_SHIPS) << "Size mismatch when reading AIS ships from DB"; - EXPECT_EQ(ais_ships_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + expectEQ(sensors.ais_ships().size(), NUM_AIS_SHIPS, "Size mismatch when reading AIS ships from DB"); + expectEQ(ais_ships_doc["timestamp"].get_utf8().value.to_string(), timestamp, "Document timestamp mismatch"); } // generic sensor mongocxx::collection generic_coll = db[COLLECTION_DATA_SENSORS]; mongocxx::cursor generic_sensor_docs = generic_coll.find({}, opts); - EXPECT_EQ(generic_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(generic_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, generic_sensor_docs_it] = std::tuple{size_t{0}, generic_sensor_docs.begin()}; i < num_docs; i++, generic_sensor_docs_it++) { @@ -170,14 +261,15 @@ std::pair, std::vector> UtilDB::dumpSensors(si generic->set_id(static_cast(generic_doc["id"].get_int64().value)); generic->set_data(static_cast(generic_doc["data"].get_int64().value)); } - EXPECT_EQ(generic_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + expectEQ(generic_doc["timestamp"].get_utf8().value.to_string(), timestamp, "Document timestamp mismatch"); } // battery mongocxx::collection batteries_coll = db[COLLECTION_BATTERIES]; mongocxx::cursor batteries_data_docs = batteries_coll.find({}, opts); - EXPECT_EQ(batteries_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(batteries_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, batteries_doc_it] = std::tuple{size_t{0}, batteries_data_docs.begin()}; i < num_docs; i++, batteries_doc_it++) { @@ -190,15 +282,16 @@ std::pair, std::vector> UtilDB::dumpSensors(si battery->set_voltage(static_cast(batteries_doc["voltage"].get_double().value)); battery->set_current(static_cast(batteries_doc["current"].get_double().value)); } - EXPECT_EQ(sensors.batteries().size(), NUM_BATTERIES) << "Size mismatch when reading batteries from DB"; - EXPECT_EQ(batteries_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + expectEQ(sensors.batteries().size(), NUM_BATTERIES, "Size mismatch when reading batteries from DB"); + expectEQ(batteries_doc["timestamp"].get_utf8().value.to_string(), timestamp, "Document timestamp mismatch"); } // wind sensor mongocxx::collection wind_coll = db[COLLECTION_WIND_SENSORS]; mongocxx::cursor wind_sensors_docs = wind_coll.find({}, opts); - EXPECT_EQ(wind_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(wind_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, wind_doc_it] = std::tuple{size_t{0}, wind_sensors_docs.begin()}; i < num_docs; i++, wind_doc_it++) { Sensors & sensors = sensors_vec[i]; @@ -209,15 +302,16 @@ std::pair, std::vector> UtilDB::dumpSensors(si wind->set_speed(static_cast(wind_doc["speed"].get_double().value)); wind->set_direction(static_cast(wind_doc["direction"].get_int32().value)); } - EXPECT_EQ(sensors.wind_sensors().size(), NUM_WIND_SENSORS) << "Size mismatch when reading batteries from DB"; - EXPECT_EQ(wind_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + expectEQ(sensors.wind_sensors().size(), NUM_WIND_SENSORS, "Size mismatch when reading batteries from DB"); + expectEQ(wind_doc["timestamp"].get_utf8().value.to_string(), timestamp, "Document timestamp mismatch"); } // local path mongocxx::collection path_coll = db[COLLECTION_LOCAL_PATH]; mongocxx::cursor local_path_docs = path_coll.find({}, opts); - EXPECT_EQ(path_coll.count_documents({}), num_docs) - << "Error: TestDB should only have " << num_docs << " documents per collection"; + expectEQ( + static_cast(path_coll.count_documents({})), num_docs, + "Error: TestDB should only have " + std::to_string(num_docs) + " documents per collection"); for (auto [i, path_doc_it] = std::tuple{size_t{0}, local_path_docs.begin()}; i < num_docs; i++, path_doc_it++) { Sensors & sensors = sensors_vec[i]; @@ -228,78 +322,14 @@ std::pair, std::vector> UtilDB::dumpSensors(si path->set_latitude(static_cast(path_doc["latitude"].get_double().value)); path->set_longitude(static_cast(path_doc["longitude"].get_double().value)); } - EXPECT_EQ(sensors.local_path_data().waypoints_size(), NUM_PATH_WAYPOINTS) - << "Size mismatch when reading path waypoints from DB"; - EXPECT_EQ(path_doc["timestamp"].get_utf8().value.to_string(), timestamp) << "Document timestamp mismatch"; + expectEQ( + sensors.local_path_data().waypoints_size(), NUM_PATH_WAYPOINTS, + "Size mismatch when reading path waypoints from DB"); + expectEQ(path_doc["timestamp"].get_utf8().value.to_string(), timestamp, "Document timestamp mismatch"); } return {sensors_vec, timestamp_vec}; } -void UtilDB::verifyDBWrite(std::span expected_sensors, std::span expected_msg_info) -{ - ASSERT_EQ(expected_sensors.size(), expected_msg_info.size()) << "Must have msg info for each set of Sensors"; - size_t num_docs = expected_sensors.size(); - auto [dumped_sensors, dumped_timestamps] = dumpSensors(num_docs); - - EXPECT_EQ(dumped_sensors.size(), num_docs); - EXPECT_EQ(dumped_timestamps.size(), num_docs); - - for (size_t i = 0; i < num_docs; i++) { - EXPECT_EQ(dumped_timestamps[i], expected_msg_info[i].timestamp_); - - // gps - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().latitude(), expected_sensors[i].gps().latitude()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().longitude(), expected_sensors[i].gps().longitude()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().speed(), expected_sensors[i].gps().speed()); - EXPECT_FLOAT_EQ(dumped_sensors[i].gps().heading(), expected_sensors[i].gps().heading()); - - // ais ships - for (int j = 0; j < NUM_AIS_SHIPS; j++) { - const Sensors::Ais & dumped_ais_ship = dumped_sensors[i].ais_ships(j); - const Sensors::Ais & expected_ais_ship = expected_sensors[i].ais_ships(j); - EXPECT_EQ(dumped_ais_ship.id(), expected_ais_ship.id()); - EXPECT_FLOAT_EQ(dumped_ais_ship.latitude(), expected_ais_ship.latitude()); - EXPECT_FLOAT_EQ(dumped_ais_ship.longitude(), expected_ais_ship.longitude()); - EXPECT_FLOAT_EQ(dumped_ais_ship.sog(), expected_ais_ship.sog()); - EXPECT_FLOAT_EQ(dumped_ais_ship.cog(), expected_ais_ship.cog()); - EXPECT_FLOAT_EQ(dumped_ais_ship.rot(), expected_ais_ship.rot()); - EXPECT_FLOAT_EQ(dumped_ais_ship.width(), expected_ais_ship.width()); - EXPECT_FLOAT_EQ(dumped_ais_ship.length(), expected_ais_ship.length()); - } - - // generic sensors - for (int j = 0; j < NUM_GENERIC_SENSORS; j++) { - const Sensors::Generic & dumped_data_sensor = dumped_sensors[i].data_sensors(j); - const Sensors::Generic & expected_data_sensor = expected_sensors[i].data_sensors(j); - EXPECT_EQ(dumped_data_sensor.id(), expected_data_sensor.id()); - EXPECT_EQ(dumped_data_sensor.data(), expected_data_sensor.data()); - } - - // batteries - for (int j = 0; j < NUM_BATTERIES; j++) { - const Sensors::Battery & dumped_battery = dumped_sensors[i].batteries(j); - const Sensors::Battery & expected_battery = expected_sensors[i].batteries(j); - EXPECT_EQ(dumped_battery.voltage(), expected_battery.voltage()); - EXPECT_EQ(dumped_battery.current(), expected_battery.current()); - } - - // wind sensors - for (int j = 0; j < NUM_WIND_SENSORS; j++) { - const Sensors::Wind & dumped_wind_sensor = dumped_sensors[i].wind_sensors(j); - const Sensors::Wind & expected_wind_sensor = expected_sensors[i].wind_sensors(j); - EXPECT_EQ(dumped_wind_sensor.speed(), expected_wind_sensor.speed()); - EXPECT_EQ(dumped_wind_sensor.direction(), expected_wind_sensor.direction()); - } - - // path waypoints - for (int j = 0; j < NUM_PATH_WAYPOINTS; j++) { - const Polaris::Waypoint & dumped_path_waypoint = dumped_sensors[i].local_path_data().waypoints(j); - const Polaris::Waypoint & expected_path_waypoint = expected_sensors[i].local_path_data().waypoints(j); - EXPECT_EQ(dumped_path_waypoint.latitude(), expected_path_waypoint.latitude()); - EXPECT_EQ(dumped_path_waypoint.longitude(), expected_path_waypoint.longitude()); - } - } -} UtilDB::UtilDB(const std::string & db_name, const std::string & mongodb_conn_str, std::shared_ptr rng) : SailbotDB(db_name, mongodb_conn_str), rng_(rng) diff --git a/lib/sailbot_db/test/test_sailbot_db.cpp b/lib/sailbot_db/test/test_sailbot_db.cpp index 4212d8c..8329536 100644 --- a/lib/sailbot_db/test/test_sailbot_db.cpp +++ b/lib/sailbot_db/test/test_sailbot_db.cpp @@ -8,14 +8,11 @@ using Polaris::Sensors; static std::random_device g_rd = std::random_device(); // random number sampler static uint32_t g_rand_seed = g_rd(); // seed used for random number generation static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed -// static std::shared_ptr g_test_db = -// UtilDB::initUtilDB("test", MONGODB_CONN_STR, std::make_shared(g_mt)); -static std::shared_ptr g_test_db = - std::make_shared("test", MONGODB_CONN_STR, std::make_shared(g_mt)); +static UtilDB g_test_db("test", MONGODB_CONN_STR, std::make_shared(g_mt)); class TestSailbotDB : public ::testing::Test { protected: - TestSailbotDB() { g_test_db->cleanDB(); } + TestSailbotDB() { g_test_db.cleanDB(); } ~TestSailbotDB() {} }; @@ -24,7 +21,7 @@ class TestSailbotDB : public ::testing::Test */ TEST_F(TestSailbotDB, TestConnection) { - ASSERT_TRUE(g_test_db->testConnection()) << "MongoDB not running - remember to connect!"; + ASSERT_TRUE(g_test_db.testConnection()) << "MongoDB not running - remember to connect!"; } /** @@ -33,11 +30,11 @@ TEST_F(TestSailbotDB, TestConnection) TEST_F(TestSailbotDB, TestStoreSensors) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = g_test_db->genRandData(); - ASSERT_TRUE(g_test_db->storeNewSensors(rand_sensors, rand_info)); + auto [rand_sensors, rand_info] = g_test_db.genRandData(); + ASSERT_TRUE(g_test_db.storeNewSensors(rand_sensors, rand_info)); std::array expected_sensors = {rand_sensors}; std::array expected_info = {rand_info}; - g_test_db->verifyDBWrite(expected_sensors, expected_info); + EXPECT_TRUE(g_test_db.verifyDBWrite(expected_sensors, expected_info)); } diff --git a/lib/utils/utils.h b/lib/utils/utils.h index 16d6817..a92cfa5 100644 --- a/lib/utils/utils.h +++ b/lib/utils/utils.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,6 +8,8 @@ // Define a concept for arithmetic types template concept arithmetic = std::integral or std::floating_point; +template +concept not_float = not std::floating_point; namespace utils { @@ -33,4 +36,56 @@ std::optional isOutOfBounds(T val, T lbnd, T ubnd) return std::nullopt; } +/** + * @brief Calculate floating point equality using the GoogleTest default definition + * http://google.github.io/googletest/reference/assertions.html#floating-point + * + * @tparam T A floating point type (float, double, etc) + * @param to_check Value to compare to expected + * @param expected Expected value + * @param err_msg String that gets printed to stderr on failure + * @return true if "to_check" is close enough to "expected", false otherwise + */ +template +bool isFloatEQ(T to_check, T expected, const std::string & err_msg) +{ + constexpr int ALLOWED_ULP_DIFF = 4; + + int diff = boost::math::float_distance(to_check, expected); + if (std::abs(diff) <= ALLOWED_ULP_DIFF) { + return true; + } + if (!err_msg.empty()) { + std::cerr << err_msg << std::endl; + } + return false; +} + +/** + * @brief Calls default isFloatEq(T, T, string) with an empty error string + * + */ +template +bool isFloatEQ(T to_check, T expected) +{ + return isFloatEQ(to_check, expected, ""); +} + +class FailTracker +{ +public: + void track(bool was_success) + { + if (!was_success) { + fail_count_++; + } + } + void reset() { fail_count_ = 0; } + bool failed() const { return fail_count_ != 0; } + uint32_t failCount() const { return fail_count_; } + +private: + uint32_t fail_count_ = 0; +}; + } // namespace utils diff --git a/projects/remote_transceiver/test/test_remote_transceiver.cpp b/projects/remote_transceiver/test/test_remote_transceiver.cpp index 275df48..59a939d 100644 --- a/projects/remote_transceiver/test/test_remote_transceiver.cpp +++ b/projects/remote_transceiver/test/test_remote_transceiver.cpp @@ -26,11 +26,10 @@ using remote_transceiver::TESTING_HOST; using remote_transceiver::TESTING_PORT; namespace http_client = remote_transceiver::http_client; -static std::random_device g_rd = std::random_device(); // random number sampler -static uint32_t g_rand_seed = g_rd(); // seed used for random number generation -static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed -static std::shared_ptr g_test_db = - std::make_shared("test", MONGODB_CONN_STR, std::make_shared(g_mt)); +static std::random_device g_rd = std::random_device(); // random number sampler +static uint32_t g_rand_seed = g_rd(); // seed used for random number generation +static std::mt19937 g_mt(g_rand_seed); // initialize random number generator with seed +static UtilDB g_test_db("test", MONGODB_CONN_STR, std::make_shared(g_mt)); class TestRemoteTransceiver : public ::testing::Test { @@ -64,7 +63,7 @@ class TestRemoteTransceiver : public ::testing::Test } } - TestRemoteTransceiver() { g_test_db->cleanDB(); } + TestRemoteTransceiver() { g_test_db.cleanDB(); } ~TestRemoteTransceiver() override {} }; @@ -112,7 +111,7 @@ std::string createPostBody(remote_transceiver::MOMsgParams::Params params) TEST_F(TestRemoteTransceiver, TestPostSensors) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = g_test_db->genRandData(); + auto [rand_sensors, rand_info] = g_test_db.genRandData(); std::string rand_sensors_str; ASSERT_TRUE(rand_sensors.SerializeToString(&rand_sensors_str)); @@ -137,7 +136,7 @@ TEST_F(TestRemoteTransceiver, TestPostSensors) std::array expected_sensors = {rand_sensors}; std::array expected_info = {rand_info}; - g_test_db->verifyDBWrite(expected_sensors, expected_info); + EXPECT_TRUE(g_test_db.verifyDBWrite(expected_sensors, expected_info)); } /** @@ -157,7 +156,7 @@ TEST_F(TestRemoteTransceiver, TestPostSensorsMult) // Prepare all queries for (int i = 0; i < NUM_REQS; i++) { - auto [rand_sensors, rand_info] = g_test_db->genRandData(); + auto [rand_sensors, rand_info] = g_test_db.genRandData(); expected_sensors[i] = rand_sensors; expected_info[i] = rand_info; std::string rand_sensors_str; @@ -194,5 +193,5 @@ TEST_F(TestRemoteTransceiver, TestPostSensorsMult) std::this_thread::sleep_for(WAIT_AFTER_RES); // Check that DB is updated properly for all requests - g_test_db->verifyDBWrite(expected_sensors, expected_info); + EXPECT_TRUE(g_test_db.verifyDBWrite(expected_sensors, expected_info)); } From 8996aae60ceb3ade840b41f48ce16e548b873a46 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Fri, 23 Feb 2024 14:29:38 -0800 Subject: [PATCH 04/11] Old tests working --- lib/sailbot_db/inc/sailbot_db.h | 11 +++------ lib/sailbot_db/inc/util_db.h | 8 ++++++- lib/sailbot_db/src/sailbot_db.cpp | 21 +++++++++++++++++ lib/sailbot_db/src/util_db.cpp | 23 ++++++++++++++----- lib/sailbot_db/test/test_sailbot_db.cpp | 2 +- .../test/test_remote_transceiver.cpp | 18 +++++++++------ 6 files changed, 60 insertions(+), 23 deletions(-) diff --git a/lib/sailbot_db/inc/sailbot_db.h b/lib/sailbot_db/inc/sailbot_db.h index 0bbfdfd..5046149 100644 --- a/lib/sailbot_db/inc/sailbot_db.h +++ b/lib/sailbot_db/inc/sailbot_db.h @@ -125,14 +125,9 @@ class SailbotDB /** * @brief overload stream operator */ - friend std::ostream & operator<<(std::ostream & os, const RcvdMsgInfo & info) - { - os << "Latitude: " << info.lat_ << "\n" - << "Longitude: " << info.lon_ << "\n" - << "Accuracy (km): " << info.cep_ << "\n" - << "Timestamp: " << info.timestamp_; - return os; - } + friend std::ostream & operator<<(std::ostream & os, const RcvdMsgInfo & info); + + static std::string mkTimestamp(const std::tm & tm); }; /** diff --git a/lib/sailbot_db/inc/util_db.h b/lib/sailbot_db/inc/util_db.h index ae39835..b31c808 100644 --- a/lib/sailbot_db/inc/util_db.h +++ b/lib/sailbot_db/inc/util_db.h @@ -35,12 +35,18 @@ class UtilDB : public SailbotDB */ Polaris::Sensors genRandSensors(); + /** + * @return timestamp for the current time + */ + static std::tm getTimestamp(); + /** * @brief Generate random sensors and Iridium msg info * + * @param tm Timestamp to associate with the generated data * @return std::pair */ - std::pair genRandData(); + std::pair genRandData(const std::tm & tm); /** * @brief Query the database and check that the sensor and message are correct diff --git a/lib/sailbot_db/src/sailbot_db.cpp b/lib/sailbot_db/src/sailbot_db.cpp index b1bb5db..6bf3551 100644 --- a/lib/sailbot_db/src/sailbot_db.cpp +++ b/lib/sailbot_db/src/sailbot_db.cpp @@ -5,12 +5,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "sensors.pb.h" #include "waypoint.pb.h" @@ -22,6 +24,25 @@ mongocxx::instance SailbotDB::inst_{}; // staticallly initialize instance // PUBLIC +std::ostream & operator<<(std::ostream & os, const SailbotDB::RcvdMsgInfo & info) +{ + os << "Latitude: " << info.lat_ << "\n" + << "Longitude: " << info.lon_ << "\n" + << "Accuracy (km): " << info.cep_ << "\n" + << "Timestamp: " << info.timestamp_; + return os; +} + +std::string SailbotDB::RcvdMsgInfo::mkTimestamp(const std::tm & tm) +{ + std::stringstream tm_ss; + tm_ss << std::setfill('0') << std::setw(2) << tm.tm_year << "-" << std::setfill('0') << std::setw(2) << tm.tm_mon + << "-" << std::setfill('0') << std::setw(2) << tm.tm_mday << " " << std::setfill('0') << std::setw(2) + << tm.tm_hour << ":" << std::setfill('0') << std::setw(2) << tm.tm_min << ":" << std::setfill('0') + << std::setw(2) << tm.tm_sec; + return tm_ss.str(); +} + SailbotDB::SailbotDB(const std::string & db_name, const std::string & mongodb_conn_str) : db_name_(db_name) { mongocxx::uri uri = mongocxx::uri{mongodb_conn_str}; diff --git a/lib/sailbot_db/src/util_db.cpp b/lib/sailbot_db/src/util_db.cpp index f013794..d5d9b6d 100644 --- a/lib/sailbot_db/src/util_db.cpp +++ b/lib/sailbot_db/src/util_db.cpp @@ -92,14 +92,25 @@ Sensors UtilDB::genRandSensors() return sensors; } -std::pair UtilDB::genRandData() +std::tm UtilDB::getTimestamp() { - Sensors rand_sensors = genRandSensors(); + // Get the current time + std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::tm * tm = std::gmtime(&t); // NOLINT(concurrency-mt-unsafe) + // tm stores years since 1900 by default, the schema expects years since 2000 + tm->tm_year -= 100; // NOLINT(readability-magic-numbers) + return *tm; +} + +std::pair UtilDB::genRandData(const std::tm & tm) +{ + Sensors rand_sensors = genRandSensors(); + SailbotDB::RcvdMsgInfo rand_info{ - .lat_ = 0, // Not processed yet, so just set to 0 - .lon_ = 0, // Not processed yet, so just set to 0 - .cep_ = 0, // Not processed yet, so just set to 0 - .timestamp_ = std::to_string(rand_sensors.gps().heading())}; // Some random string + .lat_ = 0, // Not processed yet, so just set to 0 + .lon_ = 0, // Not processed yet, so just set to 0 + .cep_ = 0, // Not processed yet, so just set to 0 + .timestamp_ = SailbotDB::RcvdMsgInfo::mkTimestamp(tm)}; return {rand_sensors, rand_info}; } diff --git a/lib/sailbot_db/test/test_sailbot_db.cpp b/lib/sailbot_db/test/test_sailbot_db.cpp index 8329536..f2fa59e 100644 --- a/lib/sailbot_db/test/test_sailbot_db.cpp +++ b/lib/sailbot_db/test/test_sailbot_db.cpp @@ -30,7 +30,7 @@ TEST_F(TestSailbotDB, TestConnection) TEST_F(TestSailbotDB, TestStoreSensors) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = g_test_db.genRandData(); + auto [rand_sensors, rand_info] = g_test_db.genRandData(UtilDB::getTimestamp()); ASSERT_TRUE(g_test_db.storeNewSensors(rand_sensors, rand_info)); std::array expected_sensors = {rand_sensors}; diff --git a/projects/remote_transceiver/test/test_remote_transceiver.cpp b/projects/remote_transceiver/test/test_remote_transceiver.cpp index 59a939d..2b0f8c1 100644 --- a/projects/remote_transceiver/test/test_remote_transceiver.cpp +++ b/projects/remote_transceiver/test/test_remote_transceiver.cpp @@ -111,7 +111,7 @@ std::string createPostBody(remote_transceiver::MOMsgParams::Params params) TEST_F(TestRemoteTransceiver, TestPostSensors) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - auto [rand_sensors, rand_info] = g_test_db.genRandData(); + auto [rand_sensors, rand_info] = g_test_db.genRandData(UtilDB::getTimestamp()); std::string rand_sensors_str; ASSERT_TRUE(rand_sensors.SerializeToString(&rand_sensors_str)); @@ -147,16 +147,20 @@ TEST_F(TestRemoteTransceiver, TestPostSensorsMult) { SCOPED_TRACE("Seed: " + std::to_string(g_rand_seed)); // Print seed on any failure - constexpr int NUM_REQS = 50; - std::array queries; - std::array req_threads; - std::array res_statuses; - std::array expected_sensors; + constexpr int NUM_REQS = 50; // Keep this number under 60 to simplify timestamp logic + std::array queries; + std::array req_threads; + std::array res_statuses; + std::array expected_sensors; std::array expected_info; + std::tm tm = UtilDB::getTimestamp(); // Prepare all queries for (int i = 0; i < NUM_REQS; i++) { - auto [rand_sensors, rand_info] = g_test_db.genRandData(); + // Timestamps are only granular to the second, so if we want to maintain document ordering by time + // without adding a lot of 1 second delays, then the time must be modified + tm.tm_sec = i; + auto [rand_sensors, rand_info] = g_test_db.genRandData(tm); expected_sensors[i] = rand_sensors; expected_info[i] = rand_info; std::string rand_sensors_str; From 6d59186da6af4de3eb106237b26221ed0eda94c1 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 24 Feb 2024 14:43:59 -0800 Subject: [PATCH 05/11] Add CLI arg parsing --- CMakeLists.txt | 1 + functions.cmake | 2 +- lib/sailbot_db/src/main.cpp | 68 ++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb6f877..19587bc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ endforeach() # Boost find_package(Boost 1.74.0 COMPONENTS REQUIRED + program_options serialization system thread diff --git a/functions.cmake b/functions.cmake index 9be0d59..45fa0e5 100644 --- a/functions.cmake +++ b/functions.cmake @@ -19,7 +19,7 @@ function(make_exe module srcs link_libs inc_dirs ${compile_defs}) add_executable(${bin_module} ${srcs}) target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) ament_target_dependencies(${bin_module} PUBLIC ${ROS_DEPS}) - target_link_libraries(${bin_module} PUBLIC ${link_libs}) + target_link_libraries(${bin_module} PUBLIC ${link_libs} boost_program_options) target_include_directories( ${bin_module} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index 76e8197..b8c5ed9 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -1 +1,67 @@ -int main() { return 0; } +#include + +#include "util_db.h" + +namespace po = boost::program_options; + +enum class DBCommand { Clear, Populate, DumpSensors, DumpGlobalPath }; + +std::string to_string(DBCommand c) +{ + switch (c) { + case DBCommand::Clear: + return "clear"; + case DBCommand::Populate: + return "populate"; + case DBCommand::DumpSensors: + return "dump-sensors"; + case DBCommand::DumpGlobalPath: + return "dump-global-path"; + } +}; + +const std::map DBCommandDesc{ + {DBCommand::Clear, "Clear the contents of a database collection"}, + {DBCommand::Populate, "Populate a database collection with random data"}, + {DBCommand::DumpSensors, "Dump all sensor data in the database collection"}, + {DBCommand::DumpGlobalPath, "Dump the Global Paths stored in the database collection"}, +}; + +int main(int argc, char ** argv) +{ + // Formatting is weird, see: https://www.boost.org/doc/libs/1_63_0/doc/html/program_options/tutorial.html + po::options_description o_desc("Options"); + o_desc.add_options()("help,h", "Help message")( + "db-name", po::value>(), "Name of db collection to target")( + to_string(DBCommand::Clear).c_str(), DBCommandDesc.at(DBCommand::Clear).c_str()), + (to_string(DBCommand::Populate).c_str(), DBCommandDesc.at(DBCommand::Populate).c_str()); + + po::positional_options_description po_desc; + po_desc.add("db-name", -1); + + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(o_desc).positional(po_desc).run(), vm); + po::notify(vm); + + const std::string usage_instructions = [&o_desc]() { + std::stringstream ss; + ss << "Usage: sailbot_db DB-NAME [COMMAND]\n\n" + // Need to separately print that DB-NAME is a positional argument + << "DB-NAME: Name of db collection to target\n\n" + << "COMMAND:\n" + << o_desc << std::endl; + return ss.str(); + }(); + + if (vm.count("help") != 0U) { + std::cout << usage_instructions << std::endl; + return 0; + } + + if (vm.count("db-name") != 0U) { + std::cout << "TODO" << std::endl; + } else { + std::cout << usage_instructions << std::endl; + return -1; + } +} From f1af74f464f0ee4e1d9ac1221d7d5046557ae278 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 24 Feb 2024 20:48:08 -0800 Subject: [PATCH 06/11] CLI works --- lib/sailbot_db/inc/util_db.h | 6 +-- lib/sailbot_db/src/main.cpp | 99 +++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/lib/sailbot_db/inc/util_db.h b/lib/sailbot_db/inc/util_db.h index b31c808..9a1555b 100644 --- a/lib/sailbot_db/inc/util_db.h +++ b/lib/sailbot_db/inc/util_db.h @@ -57,12 +57,12 @@ class UtilDB : public SailbotDB bool verifyDBWrite( std::span expected_sensors, std::span expected_msg_info); -private: - std::shared_ptr rng_; - std::pair, std::vector> dumpSensors( utils::FailTracker & tracker, size_t num_docs = 1); +private: + std::shared_ptr rng_; + /** * @brief generate random GPS data * diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index b8c5ed9..3ea3b3a 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -4,40 +4,55 @@ namespace po = boost::program_options; -enum class DBCommand { Clear, Populate, DumpSensors, DumpGlobalPath }; +enum class CLIOpt { Help, Clear, Populate, Seed, DumpSensors, DumpGlobalPath, DBName }; -std::string to_string(DBCommand c) +std::string to_string(CLIOpt c) { switch (c) { - case DBCommand::Clear: + case CLIOpt::Help: + return "help"; + case CLIOpt::Clear: return "clear"; - case DBCommand::Populate: + case CLIOpt::Populate: return "populate"; - case DBCommand::DumpSensors: + case CLIOpt::Seed: + return "seed"; + case CLIOpt::DumpSensors: return "dump-sensors"; - case DBCommand::DumpGlobalPath: + case CLIOpt::DumpGlobalPath: return "dump-global-path"; + case CLIOpt::DBName: + return "db-name"; } }; -const std::map DBCommandDesc{ - {DBCommand::Clear, "Clear the contents of a database collection"}, - {DBCommand::Populate, "Populate a database collection with random data"}, - {DBCommand::DumpSensors, "Dump all sensor data in the database collection"}, - {DBCommand::DumpGlobalPath, "Dump the Global Paths stored in the database collection"}, +const std::map CLIOptDesc{ + {CLIOpt::Help, "Help message"}, + {CLIOpt::Clear, "Clear the contents of a database collection"}, + {CLIOpt::Populate, "Populate a database collection with random data"}, + {CLIOpt::Seed, "(Optional) Unsigned integer random seed to generate random data used to populate db collection"}, + {CLIOpt::DumpSensors, "Dump latest sensor data in the database collection"}, + {CLIOpt::DumpGlobalPath, "Dump latest Global Path stored in the database collection"}, + {CLIOpt::DBName, "Name of db collection to target"}, }; int main(int argc, char ** argv) { // Formatting is weird, see: https://www.boost.org/doc/libs/1_63_0/doc/html/program_options/tutorial.html - po::options_description o_desc("Options"); - o_desc.add_options()("help,h", "Help message")( - "db-name", po::value>(), "Name of db collection to target")( - to_string(DBCommand::Clear).c_str(), DBCommandDesc.at(DBCommand::Clear).c_str()), - (to_string(DBCommand::Populate).c_str(), DBCommandDesc.at(DBCommand::Populate).c_str()); + po::options_description o_desc("COMMAND(s)"); + // The ",h" allows for a shortened -h flag + o_desc.add_options()((to_string(CLIOpt::Help) + ",h").c_str(), CLIOptDesc.at(CLIOpt::Help).c_str()); + o_desc.add_options()(to_string(CLIOpt::Clear).c_str(), CLIOptDesc.at(CLIOpt::Clear).c_str()); + o_desc.add_options()(to_string(CLIOpt::Populate).c_str(), CLIOptDesc.at(CLIOpt::Populate).c_str()); + o_desc.add_options()(to_string(CLIOpt::Seed).c_str(), po::value(), CLIOptDesc.at(CLIOpt::Seed).c_str()); + o_desc.add_options()(to_string(CLIOpt::DumpSensors).c_str(), CLIOptDesc.at(CLIOpt::DumpSensors).c_str()); + o_desc.add_options()(to_string(CLIOpt::DumpGlobalPath).c_str(), CLIOptDesc.at(CLIOpt::DumpGlobalPath).c_str()); + o_desc.add_options()( + to_string(CLIOpt::DBName).c_str(), po::value(), CLIOptDesc.at(CLIOpt::DBName).c_str()); + // Make DBName a positional argument so we don't have to specify --db-name po::positional_options_description po_desc; - po_desc.add("db-name", -1); + po_desc.add(to_string(CLIOpt::DBName).c_str(), -1); po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(o_desc).positional(po_desc).run(), vm); @@ -47,21 +62,59 @@ int main(int argc, char ** argv) std::stringstream ss; ss << "Usage: sailbot_db DB-NAME [COMMAND]\n\n" // Need to separately print that DB-NAME is a positional argument - << "DB-NAME: Name of db collection to target\n\n" - << "COMMAND:\n" + << "DB-NAME: " << CLIOptDesc.at(CLIOpt::DBName) << "\n\n" << o_desc << std::endl; return ss.str(); }(); - if (vm.count("help") != 0U) { + if (vm.count(to_string(CLIOpt::Help)) != 0) { std::cout << usage_instructions << std::endl; return 0; } - if (vm.count("db-name") != 0U) { - std::cout << "TODO" << std::endl; + if (vm.count(to_string(CLIOpt::DBName)) == 0) { + std::cerr << usage_instructions << std::endl; + return -1; + } + + uint32_t seed; + if (vm.count(to_string(CLIOpt::Seed)) != 0) { + seed = vm[to_string(CLIOpt::Seed)].as(); } else { - std::cout << usage_instructions << std::endl; + seed = std::random_device()(); // initialize seed with random device value + } + std::mt19937 mt(seed); + + std::string db_name = vm[to_string(CLIOpt::DBName)].as(); + UtilDB db(db_name, MONGODB_CONN_STR, std::make_shared(mt)); + + if (!db.testConnection()) { + std::cerr << "Failed to establish connection to DB \"" << db_name << "\"" << std::endl; return -1; } + + if (vm.count(to_string(CLIOpt::Clear)) != 0) { + db.cleanDB(); + } + + if (vm.count(to_string(CLIOpt::Populate)) != 0) { + std::cout << "Populating random sensors with seed: " << seed << std::endl; + auto [rand_sensors, info] = db.genRandData(UtilDB::getTimestamp()); + db.storeNewSensors(rand_sensors, info); + // TODO(hsn200406,vaibhavambastha): Add code to store global path + } + + if (vm.count(to_string(CLIOpt::DumpSensors)) != 0) { + utils::FailTracker t; + auto [sensors_vec, timestamp_vec] = db.dumpSensors(t, 1); + std::cout << "Latest sensors:\n\n" << sensors_vec[0].DebugString() << std::endl; + std::cout << "Timestamp: " << timestamp_vec[0] << std::endl; + } + + if (vm.count(to_string(CLIOpt::DumpGlobalPath)) != 0) { + std::cerr << "Dump global path not implemented!" << std::endl; + // TODO(hsn200406,vaibhavambastha): Add code to dump global path + } + + return 0; } From 86b7735c200463cee444d966e1af681f5a8ec75e Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 24 Feb 2024 21:13:28 -0800 Subject: [PATCH 07/11] Add script to run db_util --- functions.cmake | 2 +- lib/sailbot_db/CMakeLists.txt | 5 ++++- lib/sailbot_db/src/main.cpp | 3 ++- scripts/db_util | 10 ++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100755 scripts/db_util diff --git a/functions.cmake b/functions.cmake index 45fa0e5..1e42196 100644 --- a/functions.cmake +++ b/functions.cmake @@ -14,7 +14,7 @@ function(make_lib module srcs link_libs inc_dirs compile_defs) endfunction() # Create project module ROS executable -function(make_exe module srcs link_libs inc_dirs ${compile_defs}) +function(make_exe module srcs link_libs inc_dirs compile_defs) set(bin_module bin_${module}) add_executable(${bin_module} ${srcs}) target_compile_definitions(${bin_module} PUBLIC ${compile_defs}) diff --git a/lib/sailbot_db/CMakeLists.txt b/lib/sailbot_db/CMakeLists.txt index 5ecaebc..8514833 100644 --- a/lib/sailbot_db/CMakeLists.txt +++ b/lib/sailbot_db/CMakeLists.txt @@ -1,5 +1,7 @@ set(module sailbot_db) +set(exe_file db_util) + set(link_libs ${PROTOBUF_LINK_LIBS} mongo::mongocxx_shared @@ -13,6 +15,7 @@ set(inc_dirs ) set(compile_defs + EXE_FILE="${exe_file}" ) set(srcs @@ -30,7 +33,7 @@ set(bin_srcs ) # Make executable -make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${exe_file} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index 3ea3b3a..6374936 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -60,7 +60,8 @@ int main(int argc, char ** argv) const std::string usage_instructions = [&o_desc]() { std::stringstream ss; - ss << "Usage: sailbot_db DB-NAME [COMMAND]\n\n" + ss << "Usage: " << EXE_FILE + << " DB-NAME [COMMAND]\n\n" // Need to separately print that DB-NAME is a positional argument << "DB-NAME: " << CLIOptDesc.at(CLIOpt::DBName) << "\n\n" << o_desc << std::endl; diff --git a/scripts/db_util b/scripts/db_util new file mode 100755 index 0000000..b252276 --- /dev/null +++ b/scripts/db_util @@ -0,0 +1,10 @@ +#!/bin/bash + +# IMPORTANT: the executable file at the end of this path must match what is defined in sailbot_db's CMakeLists.txt +EXE=$ROS_WORKSPACE/build/network_systems/lib/sailbot_db/db_util + +if [ -f $EXE ]; then + $EXE +else + echo "$EXE not found! Did you build network_systems?" +fi From cdb82bdc5c542121f979776f05962e0a03a3ee09 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 24 Feb 2024 21:48:11 -0800 Subject: [PATCH 08/11] Documentation --- lib/sailbot_db/inc/sailbot_db.h | 90 +++---------------------------- lib/sailbot_db/inc/util_db.h | 27 ++++++---- lib/sailbot_db/src/main.cpp | 4 +- lib/sailbot_db/src/sailbot_db.cpp | 2 + lib/sailbot_db/src/util_db.cpp | 27 ++-------- lib/utils/utils.h | 66 ++++++++++++++++++++++- scripts/README.md | 13 +++++ scripts/db_util | 2 +- 8 files changed, 110 insertions(+), 121 deletions(-) diff --git a/lib/sailbot_db/inc/sailbot_db.h b/lib/sailbot_db/inc/sailbot_db.h index 5046149..b429d52 100644 --- a/lib/sailbot_db/inc/sailbot_db.h +++ b/lib/sailbot_db/inc/sailbot_db.h @@ -10,88 +10,8 @@ #include "sensors.pb.h" #include "waypoint.pb.h" -// BSON document formats (from: https://ubcsailbot.atlassian.net/wiki/spaces/prjt22/pages/1907589126/Database+Schemas): - -// GPS -// { -// latitude: decimal, -// longitude: decimal, -// speed: decimal, -// heading: decimal, -// timestamp: -- :: -// } - -// Global Path -// { -// waypoints: [ -// { -// latitude: decimal, -// longitude: decimal -// } -// ], -// timestamp: -- :: -// } - -// Local Path -// { -// waypoints: [ -// { -// latitude: decimal, -// longitude: decimal -// } -// ], -// timestamp: -- :: -// } - -// AIS Ships -// { -// ships: [ -// { -// id: Number, -// latitude: decimal, -// longitude: decimal, -// cog: decimal, -// rot: decimal, -// sog: decimal, -// width: decimal, -// length: decimal -// } -// ], -// timestamp: -- :: -// } - -// Generic Sensors -// { -// genericSensors: [ -// { -// id: integer -// data: long -// } -// ], -// timestamp: -- :: -// } - -// Wind Sensors -// { -// windSensors: [ -// { -// speed: decimal, -// direction: number -// } -// ], -// timestamp: -- :: -// } - -// Batteries -// { -// batteries: [ -// { -// voltage: decimal, -// current: decimal -// } -// ], -// timestamp: -- :: -// } +// >>>>IMPORTANT<<<<< +// BSON document formats from: https://ubcsailbot.atlassian.net/wiki/spaces/prjt22/pages/1907589126/Database+Schemas: const std::string COLLECTION_AIS_SHIPS = "ais_ships"; const std::string COLLECTION_BATTERIES = "batteries"; @@ -127,6 +47,12 @@ class SailbotDB */ friend std::ostream & operator<<(std::ostream & os, const RcvdMsgInfo & info); + /** + * @brief Get a properly formatted timestamp string + * + * @param tm standard C/C++ time structure + * @return tm converted to a timestamp string + */ static std::string mkTimestamp(const std::tm & tm); }; diff --git a/lib/sailbot_db/inc/util_db.h b/lib/sailbot_db/inc/util_db.h index 9a1555b..d4d4d4f 100644 --- a/lib/sailbot_db/inc/util_db.h +++ b/lib/sailbot_db/inc/util_db.h @@ -15,7 +15,7 @@ class UtilDB : public SailbotDB static constexpr int NUM_PATH_WAYPOINTS = 5; // arbitrary number /** - * @brief Construct a UtilDB. + * @brief Construct a UtilDB, which has debug utilities for SailbotDB * * @param db_name * @param mongodb_conn_str @@ -43,7 +43,7 @@ class UtilDB : public SailbotDB /** * @brief Generate random sensors and Iridium msg info * - * @param tm Timestamp to associate with the generated data + * @param tm Timestamp returned by getTimestamp() (with any modifications made to it) * @return std::pair */ std::pair genRandData(const std::tm & tm); @@ -57,51 +57,58 @@ class UtilDB : public SailbotDB bool verifyDBWrite( std::span expected_sensors, std::span expected_msg_info); + /** + * @brief Dump and check all sensors and timestamps from the database + * + * @param tracker FailureTracker that gets if any unexpected results are dumped + * @param expected_num_docs Expected number of documents. tracker is updated if there's a mismatch + * @return std::pair{Vector of dumped Sensors, Vector of dumped timestamps} + */ std::pair, std::vector> dumpSensors( - utils::FailTracker & tracker, size_t num_docs = 1); + utils::FailTracker & tracker, size_t expected_num_docs = 1); private: - std::shared_ptr rng_; + std::shared_ptr rng_; // random number generator /** * @brief generate random GPS data * - * @param gps_data pointer to generated gps_data + * @param gps_data GPS data to modify */ void genRandGpsData(Polaris::Sensors::Gps & gps_data); /** * @brief generate random ais ships data * - * @param ais_ship pointer to generated ais data + * @param ais_ship AIS ship data to modify */ void genRandAisData(Polaris::Sensors::Ais & ais_ship); /** * @brief generate random generic sensor data * - * @return pointer to generated generic sensor data + * @param generic_sensor Generic sensor data to modify */ void genRandGenericSensorData(Polaris::Sensors::Generic & generic_sensor); /** * @brief generate random battery data * - * @return pointer to generated battery data + * @param battery battery data to modify */ void genRandBatteryData(Polaris::Sensors::Battery & battery); /** * @brief generate random wind sensors data * - * @return pointer to generated wind sensors data + * @param wind_data Wind sensor data to modify */ void genRandWindData(Polaris::Sensors::Wind & wind_data); /** * @brief generate random path data * - * @return pointer to generated path data + * @param path_data Path data to modify */ void genRandPathData(Polaris::Sensors::Path & path_data); }; diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index 6374936..e6d6d28 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -108,8 +108,8 @@ int main(int argc, char ** argv) if (vm.count(to_string(CLIOpt::DumpSensors)) != 0) { utils::FailTracker t; auto [sensors_vec, timestamp_vec] = db.dumpSensors(t, 1); - std::cout << "Latest sensors:\n\n" << sensors_vec[0].DebugString() << std::endl; - std::cout << "Timestamp: " << timestamp_vec[0] << std::endl; + std::cout << "Latest sensors:\n\n" << sensors_vec.at(sensors_vec.size() - 1).DebugString() << std::endl; + std::cout << "Timestamp: " << timestamp_vec.at(sensors_vec.size() - 1) << std::endl; } if (vm.count(to_string(CLIOpt::DumpGlobalPath)) != 0) { diff --git a/lib/sailbot_db/src/sailbot_db.cpp b/lib/sailbot_db/src/sailbot_db.cpp index 6bf3551..588b5a0 100644 --- a/lib/sailbot_db/src/sailbot_db.cpp +++ b/lib/sailbot_db/src/sailbot_db.cpp @@ -35,6 +35,8 @@ std::ostream & operator<<(std::ostream & os, const SailbotDB::RcvdMsgInfo & info std::string SailbotDB::RcvdMsgInfo::mkTimestamp(const std::tm & tm) { + // This is impossible to read. It's reading each field of tm and 0 padding it to 2 digits with either "-" or ":" + // in between each number std::stringstream tm_ss; tm_ss << std::setfill('0') << std::setw(2) << tm.tm_year << "-" << std::setfill('0') << std::setw(2) << tm.tm_mon << "-" << std::setfill('0') << std::setw(2) << tm.tm_mday << " " << std::setfill('0') << std::setw(2) diff --git a/lib/sailbot_db/src/util_db.cpp b/lib/sailbot_db/src/util_db.cpp index d5d9b6d..e74e834 100644 --- a/lib/sailbot_db/src/util_db.cpp +++ b/lib/sailbot_db/src/util_db.cpp @@ -18,27 +18,6 @@ using Polaris::Sensors; -namespace -{ -template -bool checkEQ(T rcvd, T expected, const std::string & err_msg) -{ - if (rcvd != expected) { - std::cerr << "Expected: " << expected << " but received: " << rcvd << std::endl; - std::cerr << err_msg << std::endl; - return false; - } - return true; -}; -template -bool checkFloatEQ(T rcvd, T expected, const std::string & err_msg) -{ - std::stringstream ss; - ss << "Expected: " << expected << " but received: " << rcvd << "\n" << err_msg; - return static_cast(utils::isFloatEQ(rcvd, expected, ss.str())); -}; -} // namespace - void UtilDB::cleanDB() { mongocxx::pool::entry entry = pool_->acquire(); @@ -119,10 +98,10 @@ bool UtilDB::verifyDBWrite(std::span expected_sensors, std::span(T rcvd, T expected, const std::string & err_msg) -> void { - tracker.track(checkEQ(rcvd, expected, err_msg)); + tracker.track(utils::checkEQ(rcvd, expected, err_msg)); }; auto expectFloatEQ = [&tracker](T rcvd, T expected, const std::string & err_msg) -> void { - tracker.track(checkFloatEQ(rcvd, expected, err_msg)); + tracker.track(utils::checkEQ(rcvd, expected, err_msg)); }; expectEQ(expected_sensors.size(), expected_msg_info.size(), "Must have msg info for each set of Sensors"); @@ -194,7 +173,7 @@ std::pair, std::vector> UtilDB::dumpSensors( utils::FailTracker & tracker, size_t num_docs) { auto expectEQ = [&tracker](T rcvd, T expected, const std::string & err_msg) -> void { - tracker.track(checkEQ(rcvd, expected, err_msg)); + tracker.track(utils::checkEQ(rcvd, expected, err_msg)); }; std::vector sensors_vec(num_docs); diff --git a/lib/utils/utils.h b/lib/utils/utils.h index a92cfa5..6239101 100644 --- a/lib/utils/utils.h +++ b/lib/utils/utils.h @@ -71,17 +71,79 @@ bool isFloatEQ(T to_check, T expected) return isFloatEQ(to_check, expected, ""); } +/** + * @brief Check if two non-floating point values are equal and output an error on mismatch + * + * @tparam T non-floating point type + * @param rcvd received value + * @param expected expected value + * @param err_msg error string + * @return true if rcvd and expected match + * @return false otherwise + */ +template +bool checkEQ(T rcvd, T expected, const std::string & err_msg) +{ + if (rcvd != expected) { + std::cerr << "Expected: " << expected << " but received: " << rcvd << std::endl; + std::cerr << err_msg << std::endl; + return false; + } + return true; +}; + +/** + * @brief Check if two floating point values are equal using isFloatEq() and output an error on mismatch + * + * @tparam T floating point values (float, double, etc...) + * @param rcvd received value + * @param expected expected value + * @param err_msg error string + * @return true if rcvd and expected match + * @return false otherwise + */ +template +bool checkEQ(T rcvd, T expected, const std::string & err_msg) +{ + std::stringstream ss; + ss << "Expected: " << expected << " but received: " << rcvd << "\n" << err_msg; + return static_cast(utils::isFloatEQ(rcvd, expected, ss.str())); +}; + +/** + * @brief Simple class to count number of failures + * + */ class FailTracker { public: + /** + * @brief Update the tracker + * + * @param was_success result of operation to check + */ void track(bool was_success) { if (!was_success) { fail_count_++; } } - void reset() { fail_count_ = 0; } - bool failed() const { return fail_count_ != 0; } + + /** + * @brief Reset fail count + * + */ + void reset() { fail_count_ = 0; } + + /** + * @return true if any failures were tracked + * @return false otherwise + */ + bool failed() const { return fail_count_ != 0; } + + /** + * @return number of failures + */ uint32_t failCount() const { return fail_count_; } private: diff --git a/scripts/README.md b/scripts/README.md index ed1aab5..a3476f4 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -8,6 +8,19 @@ Given an input text file where each line is the name of a ROS topic, generates a C++ header file matching those names. +## DB Util + +```shell +./db_util [COMMAND] +./db_util --help +``` + +Wrapper for the [SailbotDB Utility DB tool](../lib/sailbot_db/src/main.cpp). + +- Requires network_systems to be built +- Run with `--help` for full details on how to run +- Can clear, populate, and dump data from a DB + ## Run Virtual Iridium ```shell diff --git a/scripts/db_util b/scripts/db_util index b252276..32df18d 100755 --- a/scripts/db_util +++ b/scripts/db_util @@ -4,7 +4,7 @@ EXE=$ROS_WORKSPACE/build/network_systems/lib/sailbot_db/db_util if [ -f $EXE ]; then - $EXE + $EXE "$@" else echo "$EXE not found! Did you build network_systems?" fi From a51fe1b71370cc9c1bfc2fb1f235d103628ccb6b Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 24 Feb 2024 22:11:10 -0800 Subject: [PATCH 09/11] Fix linting --- lib/sailbot_db/src/main.cpp | 160 +++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index e6d6d28..2f9f26a 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -38,84 +38,90 @@ const std::map CLIOptDesc{ int main(int argc, char ** argv) { - // Formatting is weird, see: https://www.boost.org/doc/libs/1_63_0/doc/html/program_options/tutorial.html - po::options_description o_desc("COMMAND(s)"); - // The ",h" allows for a shortened -h flag - o_desc.add_options()((to_string(CLIOpt::Help) + ",h").c_str(), CLIOptDesc.at(CLIOpt::Help).c_str()); - o_desc.add_options()(to_string(CLIOpt::Clear).c_str(), CLIOptDesc.at(CLIOpt::Clear).c_str()); - o_desc.add_options()(to_string(CLIOpt::Populate).c_str(), CLIOptDesc.at(CLIOpt::Populate).c_str()); - o_desc.add_options()(to_string(CLIOpt::Seed).c_str(), po::value(), CLIOptDesc.at(CLIOpt::Seed).c_str()); - o_desc.add_options()(to_string(CLIOpt::DumpSensors).c_str(), CLIOptDesc.at(CLIOpt::DumpSensors).c_str()); - o_desc.add_options()(to_string(CLIOpt::DumpGlobalPath).c_str(), CLIOptDesc.at(CLIOpt::DumpGlobalPath).c_str()); - o_desc.add_options()( - to_string(CLIOpt::DBName).c_str(), po::value(), CLIOptDesc.at(CLIOpt::DBName).c_str()); - - // Make DBName a positional argument so we don't have to specify --db-name - po::positional_options_description po_desc; - po_desc.add(to_string(CLIOpt::DBName).c_str(), -1); - - po::variables_map vm; - po::store(po::command_line_parser(argc, argv).options(o_desc).positional(po_desc).run(), vm); - po::notify(vm); - - const std::string usage_instructions = [&o_desc]() { - std::stringstream ss; - ss << "Usage: " << EXE_FILE - << " DB-NAME [COMMAND]\n\n" - // Need to separately print that DB-NAME is a positional argument - << "DB-NAME: " << CLIOptDesc.at(CLIOpt::DBName) << "\n\n" - << o_desc << std::endl; - return ss.str(); - }(); - - if (vm.count(to_string(CLIOpt::Help)) != 0) { - std::cout << usage_instructions << std::endl; - return 0; - } - - if (vm.count(to_string(CLIOpt::DBName)) == 0) { - std::cerr << usage_instructions << std::endl; - return -1; - } + try { + // Formatting is weird, see: https://www.boost.org/doc/libs/1_63_0/doc/html/program_options/tutorial.html + po::options_description o_desc("COMMAND(s)"); + // The ",h" allows for a shortened -h flag + o_desc.add_options()((to_string(CLIOpt::Help) + ",h").c_str(), CLIOptDesc.at(CLIOpt::Help).c_str()); + o_desc.add_options()(to_string(CLIOpt::Clear).c_str(), CLIOptDesc.at(CLIOpt::Clear).c_str()); + o_desc.add_options()(to_string(CLIOpt::Populate).c_str(), CLIOptDesc.at(CLIOpt::Populate).c_str()); + o_desc.add_options()( + to_string(CLIOpt::Seed).c_str(), po::value(), CLIOptDesc.at(CLIOpt::Seed).c_str()); + o_desc.add_options()(to_string(CLIOpt::DumpSensors).c_str(), CLIOptDesc.at(CLIOpt::DumpSensors).c_str()); + o_desc.add_options()(to_string(CLIOpt::DumpGlobalPath).c_str(), CLIOptDesc.at(CLIOpt::DumpGlobalPath).c_str()); + o_desc.add_options()( + to_string(CLIOpt::DBName).c_str(), po::value(), CLIOptDesc.at(CLIOpt::DBName).c_str()); + + // Make DBName a positional argument so we don't have to specify --db-name + po::positional_options_description po_desc; + po_desc.add(to_string(CLIOpt::DBName).c_str(), -1); + + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(o_desc).positional(po_desc).run(), vm); + po::notify(vm); + + const std::string usage_instructions = [&o_desc]() { + std::stringstream ss; + ss << "Usage: " << EXE_FILE + << " DB-NAME [COMMAND]\n\n" + // Need to separately print that DB-NAME is a positional argument + << "DB-NAME: " << CLIOptDesc.at(CLIOpt::DBName) << "\n\n" + << o_desc << std::endl; + return ss.str(); + }(); + + if (vm.count(to_string(CLIOpt::Help)) != 0) { + std::cout << usage_instructions << std::endl; + return 0; + } + + if (vm.count(to_string(CLIOpt::DBName)) == 0) { + std::cerr << usage_instructions << std::endl; + return -1; + } + + uint32_t seed; + if (vm.count(to_string(CLIOpt::Seed)) != 0) { + seed = vm[to_string(CLIOpt::Seed)].as(); + } else { + seed = std::random_device()(); // initialize seed with random device value + } + std::mt19937 mt(seed); + + std::string db_name = vm[to_string(CLIOpt::DBName)].as(); + UtilDB db(db_name, MONGODB_CONN_STR, std::make_shared(mt)); + + if (!db.testConnection()) { + std::cerr << "Failed to establish connection to DB \"" << db_name << "\"" << std::endl; + return -1; + } + + if (vm.count(to_string(CLIOpt::Clear)) != 0) { + db.cleanDB(); + } + + if (vm.count(to_string(CLIOpt::Populate)) != 0) { + std::cout << "Populating random sensors with seed: " << seed << std::endl; + auto [rand_sensors, info] = db.genRandData(UtilDB::getTimestamp()); + db.storeNewSensors(rand_sensors, info); + // TODO(hsn200406,vaibhavambastha): Add code to store global path + } + + if (vm.count(to_string(CLIOpt::DumpSensors)) != 0) { + utils::FailTracker t; + auto [sensors_vec, timestamp_vec] = db.dumpSensors(t, 1); + std::cout << "Latest sensors:\n\n" << sensors_vec.at(sensors_vec.size() - 1).DebugString() << std::endl; + std::cout << "Timestamp: " << timestamp_vec.at(sensors_vec.size() - 1) << std::endl; + } + + if (vm.count(to_string(CLIOpt::DumpGlobalPath)) != 0) { + std::cerr << "Dump global path not implemented!" << std::endl; + // TODO(hsn200406,vaibhavambastha): Add code to dump global path + } - uint32_t seed; - if (vm.count(to_string(CLIOpt::Seed)) != 0) { - seed = vm[to_string(CLIOpt::Seed)].as(); - } else { - seed = std::random_device()(); // initialize seed with random device value - } - std::mt19937 mt(seed); - - std::string db_name = vm[to_string(CLIOpt::DBName)].as(); - UtilDB db(db_name, MONGODB_CONN_STR, std::make_shared(mt)); - - if (!db.testConnection()) { - std::cerr << "Failed to establish connection to DB \"" << db_name << "\"" << std::endl; + return 0; + } catch (std::exception & e) { + std::cerr << e.what() << std::endl; return -1; } - - if (vm.count(to_string(CLIOpt::Clear)) != 0) { - db.cleanDB(); - } - - if (vm.count(to_string(CLIOpt::Populate)) != 0) { - std::cout << "Populating random sensors with seed: " << seed << std::endl; - auto [rand_sensors, info] = db.genRandData(UtilDB::getTimestamp()); - db.storeNewSensors(rand_sensors, info); - // TODO(hsn200406,vaibhavambastha): Add code to store global path - } - - if (vm.count(to_string(CLIOpt::DumpSensors)) != 0) { - utils::FailTracker t; - auto [sensors_vec, timestamp_vec] = db.dumpSensors(t, 1); - std::cout << "Latest sensors:\n\n" << sensors_vec.at(sensors_vec.size() - 1).DebugString() << std::endl; - std::cout << "Timestamp: " << timestamp_vec.at(sensors_vec.size() - 1) << std::endl; - } - - if (vm.count(to_string(CLIOpt::DumpGlobalPath)) != 0) { - std::cerr << "Dump global path not implemented!" << std::endl; - // TODO(hsn200406,vaibhavambastha): Add code to dump global path - } - - return 0; } From 33859348ca2ae06a50e97cb75ec7d38b6897c003 Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sun, 25 Feb 2024 09:37:08 -0800 Subject: [PATCH 10/11] Make build flow easier to follow --- functions.cmake | 1 + lib/sailbot_db/CMakeLists.txt | 9 +-------- lib/sailbot_db/src/main.cpp | 3 +-- scripts/README.md | 6 +++--- scripts/db_util | 10 ---------- scripts/sailbot_db | 9 +++++++++ 6 files changed, 15 insertions(+), 23 deletions(-) delete mode 100755 scripts/db_util create mode 100755 scripts/sailbot_db diff --git a/functions.cmake b/functions.cmake index 1e42196..3de6ca9 100644 --- a/functions.cmake +++ b/functions.cmake @@ -30,6 +30,7 @@ function(make_exe module srcs link_libs inc_dirs compile_defs) install(TARGETS ${bin_module} DESTINATION lib/${PROJECT_NAME}) # Rename the output binary to just be the module name set_target_properties(${bin_module} PROPERTIES OUTPUT_NAME ${module}) + set(${module}_inc_dir ${CMAKE_CURRENT_LIST_DIR}/inc CACHE INTERNAL "${module} header include directory") endfunction() # Create unit test diff --git a/lib/sailbot_db/CMakeLists.txt b/lib/sailbot_db/CMakeLists.txt index 8514833..43c9be1 100644 --- a/lib/sailbot_db/CMakeLists.txt +++ b/lib/sailbot_db/CMakeLists.txt @@ -1,7 +1,5 @@ set(module sailbot_db) -set(exe_file db_util) - set(link_libs ${PROTOBUF_LINK_LIBS} mongo::mongocxx_shared @@ -14,10 +12,6 @@ set(inc_dirs ${LIBBSONCXX_INCLUDE_DIRS} ) -set(compile_defs - EXE_FILE="${exe_file}" -) - set(srcs ${CMAKE_CURRENT_LIST_DIR}/src/sailbot_db.cpp ${CMAKE_CURRENT_LIST_DIR}/src/util_db.cpp @@ -25,7 +19,6 @@ set(srcs # make sailbot_db library make_lib(${module} "${srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") -set(SAILBOT_DB_INC_DIR ${CMAKE_CURRENT_LIST_DIR}/inc CACHE INTERNAL "Sailbot DB header include directory") set(bin_srcs ${srcs} @@ -33,7 +26,7 @@ set(bin_srcs ) # Make executable -make_exe(${exe_file} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") +make_exe(${module} "${bin_srcs}" "${link_libs}" "${inc_dirs}" "${compile_defs}") # Create unit test set(test_srcs diff --git a/lib/sailbot_db/src/main.cpp b/lib/sailbot_db/src/main.cpp index 2f9f26a..56249c3 100644 --- a/lib/sailbot_db/src/main.cpp +++ b/lib/sailbot_db/src/main.cpp @@ -62,8 +62,7 @@ int main(int argc, char ** argv) const std::string usage_instructions = [&o_desc]() { std::stringstream ss; - ss << "Usage: " << EXE_FILE - << " DB-NAME [COMMAND]\n\n" + ss << "Usage: sailbot_db DB-NAME [COMMAND]\n\n" // Need to separately print that DB-NAME is a positional argument << "DB-NAME: " << CLIOptDesc.at(CLIOpt::DBName) << "\n\n" << o_desc << std::endl; diff --git a/scripts/README.md b/scripts/README.md index a3476f4..5dc3c5a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -8,11 +8,11 @@ Given an input text file where each line is the name of a ROS topic, generates a C++ header file matching those names. -## DB Util +## Sailbot DB ```shell -./db_util [COMMAND] -./db_util --help +./sailbot_db [COMMAND] +./sailbot_db --help ``` Wrapper for the [SailbotDB Utility DB tool](../lib/sailbot_db/src/main.cpp). diff --git a/scripts/db_util b/scripts/db_util deleted file mode 100755 index 32df18d..0000000 --- a/scripts/db_util +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# IMPORTANT: the executable file at the end of this path must match what is defined in sailbot_db's CMakeLists.txt -EXE=$ROS_WORKSPACE/build/network_systems/lib/sailbot_db/db_util - -if [ -f $EXE ]; then - $EXE "$@" -else - echo "$EXE not found! Did you build network_systems?" -fi diff --git a/scripts/sailbot_db b/scripts/sailbot_db new file mode 100755 index 0000000..854efb9 --- /dev/null +++ b/scripts/sailbot_db @@ -0,0 +1,9 @@ +#!/bin/bash + +EXE=$ROS_WORKSPACE/build/network_systems/lib/sailbot_db/sailbot_db + +if [ -f $EXE ]; then + $EXE "$@" +else + echo "$EXE not found! Did you build network_systems?" +fi From 4f5c90c231559568a6c01e630b6a9a15ecbc90ba Mon Sep 17 00:00:00 2001 From: hhenry01 Date: Sat, 2 Mar 2024 10:54:52 -0800 Subject: [PATCH 11/11] Fix CMake include directories --- functions.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions.cmake b/functions.cmake index 3de6ca9..92d0408 100644 --- a/functions.cmake +++ b/functions.cmake @@ -11,6 +11,7 @@ function(make_lib module srcs link_libs inc_dirs compile_defs) ${inc_dirs} ) add_dependencies(${module} ${AUTOGEN_TARGETS}) + set(${module}_inc_dir ${CMAKE_CURRENT_LIST_DIR}/inc CACHE INTERNAL "${module} header include directory") endfunction() # Create project module ROS executable @@ -30,7 +31,6 @@ function(make_exe module srcs link_libs inc_dirs compile_defs) install(TARGETS ${bin_module} DESTINATION lib/${PROJECT_NAME}) # Rename the output binary to just be the module name set_target_properties(${bin_module} PROPERTIES OUTPUT_NAME ${module}) - set(${module}_inc_dir ${CMAKE_CURRENT_LIST_DIR}/inc CACHE INTERNAL "${module} header include directory") endfunction() # Create unit test