diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 85e049725..9caa9816f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -56,29 +56,29 @@ jobs: if [ -d "build" ]; then rm -Rf build; fi mkdir build cd build - cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTS_MODE=ON -DBUILD_CODE_COVERAGE=ON -DBUILD_VERBOSE_MODE=ON + cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTS_MODE=ON -DBUILD_CODE_COVERAGE=ON -DBUILD_COVERAGE_WATCH=OFF -DBUILD_VERBOSE_MODE=ON make -j8 - - name: Test + - name: Run Tests run: | cd /opt/Autonomy_Software/build - ctest --output-on-failure -T Test -T Coverage - ctest --output-on-failure --output-junit Test.xml - gcovr --root .. --xml-pretty --output Coverage.xml - lcov -c -d . -o coverage.info - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + ctest --output-on-failure -T Test -T Coverage -R UTest --output-junit UTests.xml + ctest --output-on-failure -T Test -T Coverage -R ITest --output-junit ITests.xml + gcovr --root .. --xml-pretty --output AllCoverage.xml + + - name: Upload Test Coverage to Codecov + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} root_dir: /opt/Autonomy_Software working-directory: /opt/Autonomy_Software verbose: true - plugin: gcov + plugins: gcov directory: /opt/Autonomy_Software/build - file: /opt/Autonomy_Software/build/Coverage.xml - - - name: Upload test results to Codecov + files: /opt/Autonomy_Software/build/AllCoverage.xml + + - name: Upload Test Results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: @@ -87,7 +87,7 @@ jobs: working-directory: /opt/Autonomy_Software/build verbose: true directory: /opt/Autonomy_Software/build/ - file: Test.xml + files: UTests.xml, ITests.xml - name: Cleanup Action Environment if: always() diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36365c2b5..f6cc29b6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -166,4 +166,4 @@ jobs: if: always() run: | cd /opt - rm -rf /opt/Autonomy_Software + rm -rf /opt/Autonomy_Software \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b3b7705f1..5dd28cb99 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,9 @@ option(BUILD_SIM_MODE "Enable Simulation Mode" OFF) ## Enable or Disable Code Coverage Mode option(BUILD_CODE_COVERAGE "Enable Code Coverage Mode" OFF) +## Enable or Disable Coverage Watch Mode +option(BUILD_COVERAGE_WATCH "Enable Code Coverage Watch Mode" ON) + ## Enable or Disable Verbose Makefile option(BUILD_VERBOSE_MODE "Enable Verbose Makefile" OFF) @@ -77,18 +80,6 @@ if (BUILD_CODE_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage --coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") - - add_custom_target(run_coverage ALL - COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure - COMMAND gcovr --root ${CMAKE_SOURCE_DIR} --xml-pretty --output Coverage.xml - COMMAND lcov -c -d . -o coverage.info - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Running tests and generating coverage reports..." - VERBATIM - ) - - # Ensure dependencies for the run_coverage target. Tests must be built first. - add_dependencies(run_coverage ${EXE_NAME}_UnitTests ${EXE_NAME}_IntegrationTests) else() message("-- [ ]: Code Coverage: Disabled") endif() @@ -343,8 +334,12 @@ endif() ## Tests Mode if (BUILD_TESTS_MODE) - file(GLOB_RECURSE UnitTests_SRC CONFIGURE_DEPENDS "tests/Unit/*.cc") - file(GLOB_RECURSE IntegrationTests_SRC CONFIGURE_DEPENDS "tests/Integration/*.cc") + ## Find Unit and Integration Tests + file(GLOB_RECURSE UTests_SRC CONFIGURE_DEPENDS "tests/Unit/src/*.cc") + file(GLOB_RECURSE ITests_SRC CONFIGURE_DEPENDS "tests/Integration/src/*.cc") + file(GLOB Runner_SRC CONFIGURE_DEPENDS "tests/main.cc") + + ## Find Source Files Required for Tests file(GLOB_RECURSE Algorithms_SRC CONFIGURE_DEPENDS "src/algorithms/*.cpp") file(GLOB_RECURSE Drivers_SRC CONFIGURE_DEPENDS "src/drivers/*.cpp") file(GLOB_RECURSE Vision_SRC CONFIGURE_DEPENDS "src/vision/*.cpp") @@ -352,24 +347,58 @@ if (BUILD_TESTS_MODE) file(GLOB Logging_SRC CONFIGURE_DEPENDS "src/AutonomyLogging.cpp") file(GLOB Globals_SRC CONFIGURE_DEPENDS "src/AutonomyGlobals.cpp") - list(LENGTH UnitTests_SRC UnitTests_LEN) - list(LENGTH IntegrationTests_SRC IntegrationTests_LEN) + list(LENGTH UTests_SRC UnitTests_LEN) + list(LENGTH ITests_SRC IntegrationTests_LEN) + + string(TIMESTAMP CURRENT_TIMESTAMP "%Y%m%d-%H%M%S") + message("-- Current Timestamp: ${CURRENT_TIMESTAMP}") if (UnitTests_LEN GREATER 0) - add_executable(${EXE_NAME}_UnitTests ${UnitTests_SRC} ${Algorithms_SRC} ${Drivers_SRC} ${Vision_SRC} ${Network_SRC} ${Logging_SRC} ${Globals_SRC}) + add_executable(${EXE_NAME}_UnitTests ${Runner_SRC} ${UTests_SRC} ${Algorithms_SRC} ${Drivers_SRC} ${Vision_SRC} ${Network_SRC} ${Logging_SRC} ${Globals_SRC}) target_link_libraries(${EXE_NAME}_UnitTests GTest::gtest GTest::gtest_main ${AUTONOMY_LIBRARIES}) - add_test(Unit_Tests ${EXE_NAME}_UnitTests) + + foreach(test_file ${UTests_SRC}) + get_filename_component(test_name ${test_file} NAME_WE) + add_test(NAME UTest_${test_name} COMMAND ${EXE_NAME}_UnitTests --gtest_filter=${test_name}Test.* --timestamp=${CURRENT_TIMESTAMP}) + endforeach() else() message("No Unit Tests!") endif() if (IntegrationTests_LEN GREATER 0) - add_executable(${EXE_NAME}_IntegrationTests ${IntegrationTests_SRC} ${Algorithms_SRC} ${Drivers_SRC} ${Vision_SRC} ${Network_SRC} ${Logging_SRC} ${Globals_SRC}) + add_executable(${EXE_NAME}_IntegrationTests ${Runner_SRC} ${ITests_SRC} ${Algorithms_SRC} ${Drivers_SRC} ${Vision_SRC} ${Network_SRC} ${Logging_SRC} ${Globals_SRC}) target_link_libraries(${EXE_NAME}_IntegrationTests GTest::gtest GTest::gtest_main ${AUTONOMY_LIBRARIES}) - add_test(Integration_Tests ${EXE_NAME}_IntegrationTests) + + foreach(test_file ${ITests_SRC}) + get_filename_component(test_name ${test_file} NAME_WE) + add_test(NAME ITest_${test_name} COMMAND ${EXE_NAME}_IntegrationTests --gtest_filter=${test_name}Test.* --timestamp=${CURRENT_TIMESTAMP}) + endforeach() else() message("No Integration Tests!") endif() + + if (BUILD_CODE_COVERAGE AND BUILD_COVERAGE_WATCH) + add_custom_target(run_coverage ALL + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + COMMAND gcovr --root ${CMAKE_SOURCE_DIR} --xml-pretty --output Coverage.xml + COMMAND lcov -c -d . -o coverage.info + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running tests and generating coverage reports..." + VERBATIM + ) + + # Ensure dependencies for the run_coverage target. Tests must be built first. + if (UnitTests_LEN GREATER 0 AND IntegrationTests_LEN GREATER 0) + add_dependencies(run_coverage ${EXE_NAME}_UnitTests ${EXE_NAME}_IntegrationTests) + elseif (UnitTests_LEN GREATER 0) + add_dependencies(run_coverage ${EXE_NAME}_UnitTests) + elseif (IntegrationTests_LEN GREATER 0) + add_dependencies(run_coverage ${EXE_NAME}_IntegrationTests) + else() + message("No Tests to Run! run_coverage target will not be created.") + set_target_properties(run_coverage PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() + endif() endif() #################################################################################################################### diff --git a/codecov.yml b/codecov.yml index 65760f2f5..3039684ac 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,9 +6,18 @@ coverage: project: default: target: auto + informational: true + threshold: 5% + flags: + - unit_tests + - integration_tests patch: default: target: auto + threshold: 5% + flags: + - unit_tests + - integration_tests changes: false ignore: @@ -67,7 +76,7 @@ component_management: - src/*.cpp comment: - layout: "header, diff, flags, files, components" + layout: "header, diff, files, flags, components" behavior: default require_changes: false require_base: false diff --git a/data/Custom_Dictionaries/Autonomy-Dictionary.txt b/data/Custom_Dictionaries/Autonomy-Dictionary.txt index 58e906c64..68154446b 100644 --- a/data/Custom_Dictionaries/Autonomy-Dictionary.txt +++ b/data/Custom_Dictionaries/Autonomy-Dictionary.txt @@ -40,6 +40,7 @@ bytem byteswap CAFFE CALIB +carryforward CELLVOLTAGE charconv chromaprint diff --git a/src/AutonomyLogging.cpp b/src/AutonomyLogging.cpp index a902d75cc..86b4620cd 100644 --- a/src/AutonomyLogging.cpp +++ b/src/AutonomyLogging.cpp @@ -55,30 +55,15 @@ namespace logging * @author Eli Byrd (edbgkk@mst.edu) * @date 2023-08-22 ******************************************************************************/ - void InitializeLoggers(std::string szLoggingOutputPath) + void InitializeLoggers(std::string szLoggingOutputPath, std::string szProgramTimeLogsDir) { - // Retrieve the current time for the log file name - std::chrono::time_point tmCurrentTime = std::chrono::system_clock::now(); - std::time_t tCurrentTime = std::chrono::system_clock::to_time_t(tmCurrentTime); - - // Convert time to local time - std::tm* tLocalTime = std::localtime(&tCurrentTime); - - // Format the current time in a format that can be used as a file name. - std::array cCurrentTime; - size_t siTimeCharacters; - siTimeCharacters = std::strftime(cCurrentTime.data(), cCurrentTime.size(), "%Y%m%d-%H%M%S", tLocalTime); - if (siTimeCharacters == 0) - { - std::cerr << "Unable to format calendar date & time (exceeds string length)" << std::endl; - } // Store start time string in member variable. - g_szProgramStartTimeString = cCurrentTime.data(); + g_szProgramStartTimeString = szProgramTimeLogsDir; // Assemble filepath string. std::filesystem::path szFilePath; std::filesystem::path szFilename; - szFilePath = szLoggingOutputPath + "/"; // Main location for all recordings. + szFilePath = szLoggingOutputPath; // Main location for all recordings. szFilePath += g_szProgramStartTimeString + "/"; // Folder for each program run. szFilename = "console_output"; // Base file name. @@ -126,22 +111,26 @@ namespace logging szFullOutputPath.replace_extension(".log"), // Log Output Path []() { - return quill::RotatingFileSinkConfig(); // Rotating File Sink Configs + quill::RotatingFileSinkConfig cfg; + cfg.set_open_mode('a'); + return cfg; // Rotating File Sink Configs }(), - szLogFilePattern, // Log Output Pattern - szTimestampPattern, // Log Timestamp Pattern - quill::Timezone::LocalTime // Log Timezone + szLogFilePattern, // Log Output Pattern + szTimestampPattern, // Log Timestamp Pattern + quill::Timezone::LocalTime // Log Timezone ); std::shared_ptr qCSVFileSink = quill::Frontend::create_or_get_sink( szFullOutputPath.replace_extension(".csv"), // Log Output Path []() { - return quill::RotatingFileSinkConfig(); // Rotating File Sink Configs + quill::RotatingFileSinkConfig cfg; + cfg.set_open_mode('a'); + return cfg; // Rotating File Sink Configs }(), - szCSVFilePattern, // Log Output Pattern - szTimestampPattern, // Log Timestamp Pattern - quill::Timezone::LocalTime // Log Timezone + szCSVFilePattern, // Log Output Pattern + szTimestampPattern, // Log Timestamp Pattern + quill::Timezone::LocalTime // Log Timezone ); std::shared_ptr qConsoleSink = quill::Frontend::create_or_get_sink("ConsoleSink", // Log Name diff --git a/src/AutonomyLogging.h b/src/AutonomyLogging.h index 0820aee9b..be3a44be8 100644 --- a/src/AutonomyLogging.h +++ b/src/AutonomyLogging.h @@ -36,6 +36,7 @@ /// \endcond #include "./AutonomyConstants.h" +#include "./util/TimeOperations.hpp" #ifndef AUTONOMY_LOGGING_H #define AUTONOMY_LOGGING_H @@ -86,7 +87,7 @@ namespace logging // Declare namespace methods. ///////////////////////////////////////// - void InitializeLoggers(std::string szLoggingOutputPath); + void InitializeLoggers(std::string szLoggingOutputPath, std::string szProgramTimeLogsDir = timeops::GetTimestamp()); ///////////////////////////////////////// // Declare namespace callbacks. diff --git a/src/util/TimeOperations.hpp b/src/util/TimeOperations.hpp new file mode 100644 index 000000000..9bcadcff1 --- /dev/null +++ b/src/util/TimeOperations.hpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * @brief Defines and implements functions related to operations on time and + * date within the timeops namespace. + * + * @file TimeOperations.hpp + * @author Eli Byrd (edbgkk@mst.edu) + * @date 2025-01-07 + * + * @copyright Copyright Mars Rover Design Team 2025 - All Rights Reserved + ******************************************************************************/ + +#ifndef TIME_OPERATIONS_HPP +#define TIME_OPERATIONS_HPP + +/// \cond +#include +#include +#include + +/// \endcond + +/****************************************************************************** + * @brief Namespace containing functions related to operations on time and + * date related data types. + * + * @author Eli Byrd (edbgkk@mst.edu) + * @date 2025-01-07 + ******************************************************************************/ +namespace timeops +{ + /****************************************************************************** + * @brief Accessor for getting the current time in a specified format. + * + * @param szFormat - The format to return the time in. + * @return std::string - The current time in the specified format. + * + * @author Eli Byrd (edbgkk@mst.edu) + * @date 2025-01-07 + ******************************************************************************/ + inline std::string GetTimestamp(std::string szFormat = "%Y%m%d-%H%M%S") + { + // Retrieve the current time for the log file name + std::chrono::time_point tmCurrentTime = std::chrono::system_clock::now(); + std::time_t tCurrentTime = std::chrono::system_clock::to_time_t(tmCurrentTime); + + // Convert time to local time + std::tm* tLocalTime = std::localtime(&tCurrentTime); + + // Format the current time in a format that can be used as a file name. + std::array cCurrentTime; + size_t siTimeCharacters; + siTimeCharacters = std::strftime(cCurrentTime.data(), cCurrentTime.size(), szFormat.c_str(), tLocalTime); + if (siTimeCharacters == 0) + { + std::cerr << "Unable to format calendar date & time (exceeds string length)" << std::endl; + } + + return cCurrentTime.data(); + } +} // namespace timeops + +#endif // TIME_OPERATIONS_HPP diff --git a/tests/Integration/main.cc b/tests/Integration/main.cc deleted file mode 100644 index 95c4c053b..000000000 --- a/tests/Integration/main.cc +++ /dev/null @@ -1,28 +0,0 @@ -/****************************************************************************** - * @brief Main integration test file. Calls for all Integration Tests to be executed. - * - * @file main.cc - * @author Eli Byrd (edbgkk@mst.edu) - * @date 2023-07-24 - * - * @copyright Copyright Mars Rover Design Team 2023 - All Rights Reserved - ******************************************************************************/ - -/// \cond -#include - -/// \endcond - -/****************************************************************************** - * @brief Integration Tests - Main Function - * - * @return int - Integration Tests Exit Status - * - * @author Eli Byrd (edbgkk@mst.edu) - * @date 2023-07-24 - ******************************************************************************/ -int main() -{ - ::testing::InitGoogleTest(); - return RUN_ALL_TESTS(); -} diff --git a/tests/Unit/main.cc b/tests/Unit/main.cc deleted file mode 100644 index 8122f906d..000000000 --- a/tests/Unit/main.cc +++ /dev/null @@ -1,35 +0,0 @@ -/****************************************************************************** - * @brief Main unit test file. Calls for all Unit Tests to be executed. - * - * @file main.cc - * @author Eli Byrd (edbgkk@mst.edu) - * @date 2023-07-24 - * - * @copyright Copyright Mars Rover Design Team 2023 - All Rights Reserved - ******************************************************************************/ - -#include "../../src/AutonomyConstants.h" -#include "../../src/AutonomyLogging.h" - -/// \cond -#include - -/// \endcond - -/****************************************************************************** - * @brief Unit Tests - Main Function - * - * @return int - Unit Tests Exit Status - * - * @author Eli Byrd (edbgkk@mst.edu) - * @date 2023-07-24 - ******************************************************************************/ -int main() -{ - // Setup logging. - logging::InitializeLoggers(constants::LOGGING_OUTPUT_PATH_ABSOLUTE); - - // Initialize tests. - testing::InitGoogleTest(); - return RUN_ALL_TESTS(); -} diff --git a/tests/main.cc b/tests/main.cc new file mode 100644 index 000000000..b002ce91c --- /dev/null +++ b/tests/main.cc @@ -0,0 +1,60 @@ +/****************************************************************************** + * @brief Main test file. Acts as a runner for all Unit and Integration Tests. + * + * @file main.cc + * @author Eli Byrd (edbgkk@mst.edu) + * @date 2023-07-24 + * + * @copyright Copyright Mars Rover Design Team 2023 - All Rights Reserved + ******************************************************************************/ + +#include "../src/AutonomyConstants.h" +#include "../src/AutonomyLogging.h" + +/// \cond +#include + +/// \endcond + +/****************************************************************************** + * @brief Unit/Integration Tests Runner - Main Function + * + * @return int - Unit/Integration Tests Exit Status + * + * @author Eli Byrd (edbgkk@mst.edu) + * @date 2023-07-24 + ******************************************************************************/ +int main(int argc, char** argv) +{ + bool bSkipLogging = false; + std::vector vConditionsToSkipLogging = {"--gtest_filter=AutonomyLoggingTest"}; + std::string szTimestamp = timeops::GetTimestamp(); + + // Check if the test being run is in the list of tests to skip setting up logging. + for (int i = 1; i < argc; ++i) + { + if (std::string(argv[i]).find("--timestamp=") != std::string::npos) + { + szTimestamp = std::string(argv[i]).substr(std::string(argv[i]).find("=") + 1); + } + + for (const std::string& szCondition : vConditionsToSkipLogging) + { + if (std::string(argv[i]).find(szCondition) != std::string::npos) + { + bSkipLogging = true; + break; + } + } + } + + // Setup logging if not skipped. + if (!bSkipLogging) + { + logging::InitializeLoggers(constants::LOGGING_OUTPUT_PATH_ABSOLUTE, szTimestamp); + } + + // Initialize tests. + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}