diff --git a/CMakeLists.txt b/CMakeLists.txt index 2587740e..20a5c67e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_SCRIPT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -option(EIVE_BUILD_UNITTESTS "Build Catch2 unittests" OFF) + option(EIVE_ADD_ETL_LIB "Add ETL library" ON) option(EIVE_ADD_JSON_LIB "Add JSON library" ON) @@ -52,6 +52,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set(OBSW_NAME ${CMAKE_PROJECT_NAME}) set(WATCHDOG_NAME eive-watchdog) set(SIMPLE_OBSW_NAME eive-simple) +set(UNITTEST_NAME eive-unittest) set(LIB_FSFW_NAME fsfw) set(LIB_EIVE_MISSION eive-mission) set(LIB_ETL_NAME etl) @@ -85,13 +86,6 @@ set(LIB_JSON_PATH ${THIRD_PARTY_FOLDER}/json) set(FSFW_WARNING_SHADOW_LOCAL_GCC OFF) set(EIVE_ADD_LINUX_FILES False) -if(EIVE_BUILD_UNITTESTS) -endif() - -if(FSFW_ADD_UNITTESTS) - set(CATCH2_TARGET Catch2) -endif() - # Analyse different OS and architecture/target options, determine BSP_PATH, # display information about compiler etc. include (${CMAKE_SCRIPT_PATH}/HardwareOsPreConfig.cmake) @@ -135,10 +129,6 @@ else() set(FSFW_CONFIG_PATH "${BSP_PATH}/fsfwconfig") endif() -if(EIVE_BUILD_UNITTESTS) - # configure_file(${UNITTEST_CFG_PATH}/TestsConfig.h.in TestsConfig.h) - # set(FSFW_CONFIG_PATH ${UNITTEST_CFG_PATH}) -endif() # Configuration files configure_file(${COMMON_CONFIG_PATH}/commonConfig.h.in commonConfig.h) @@ -160,6 +150,9 @@ set(FSFW_ADDITIONAL_INC_PATHS ${CMAKE_CURRENT_BINARY_DIR} ) +# Check whether the user has already installed Catch2 first +find_package(Catch2 3) + ################################################################################ # Executable and Sources ################################################################################ @@ -173,7 +166,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") "-Wno-unused-parameter" "-Wno-psabi" ) - # Remove unused sections. add_compile_options( "-ffunction-sections" @@ -189,11 +181,29 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(COMPILER_FLAGS "/permissive-") endif() +# Not installed, so use FetchContent to download and provide Catch2 +if(NOT Catch2_FOUND) + include(FetchContent) + + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.0.0-preview4 + ) + + FetchContent_MakeAvailable(Catch2) + #fixes regression -preview4, to be confirmed in later releases + set_target_properties(Catch2 PROPERTIES DEBUG_POSTFIX "") + set_target_properties(Catch2 PROPERTIES EXCLUDE_FROM_ALL "true") + set_target_properties(Catch2WithMain PROPERTIES EXCLUDE_FROM_ALL "true") +endif() + + add_library(${LIB_EIVE_MISSION}) -# Add executable +# Add main executable add_executable(${OBSW_NAME}) if(EIVE_CREATE_UNIQUE_OBSW_BIN) set(OBSW_BIN_NAME ${CMAKE_PROJECT_NAME}-$ENV{USERNAME}) @@ -208,11 +218,15 @@ add_subdirectory(${WATCHDOG_PATH}) target_link_libraries(${WATCHDOG_NAME} PUBLIC ${LIB_CXX_FS} ) - target_include_directories(${WATCHDOG_NAME} PUBLIC ${CMAKE_BINARY_DIR} ) +#unittests +add_executable(${UNITTEST_NAME} EXCLUDE_FROM_ALL) + + + if(EIVE_ADD_ETL_LIB) add_subdirectory(${LIB_ETL_PATH}) endif() @@ -222,16 +236,16 @@ if(EIVE_ADD_JSON_LIB) endif() -if(NOT EIVE_BUILD_UNITTESTS) - if(EIVE_ADD_LINUX_FILES) - add_subdirectory(${LIB_ARCSEC_PATH}) - add_subdirectory(${LINUX_PATH}) - endif() - add_subdirectory(${BSP_PATH}) - if(ADD_CSP_LIB) - add_subdirectory(${LIB_CSP_PATH}) - endif() + +if(EIVE_ADD_LINUX_FILES) + add_subdirectory(${LIB_ARCSEC_PATH}) + add_subdirectory(${LINUX_PATH}) endif() +add_subdirectory(${BSP_PATH}) +if(ADD_CSP_LIB) + add_subdirectory(${LIB_CSP_PATH}) +endif() + add_subdirectory(${COMMON_PATH}) @@ -243,10 +257,8 @@ add_subdirectory(${LIB_EIVE_MISSION_PATH}) add_subdirectory(${TEST_PATH}) -if(EIVE_BUILD_UNITTESTS) - # add_subdirectory(${LIB_CATCH2_PATH}) - add_subdirectory(${UNITTEST_PATH}) -endif() +add_subdirectory(${UNITTEST_PATH}) + ################################################################################ # Post-Sources preparation @@ -272,6 +284,11 @@ if(TGT_BSP MATCHES "arm/q7s") ) endif() +target_link_libraries(${UNITTEST_NAME} PRIVATE + Catch2 + ${LIB_EIVE_MISSION} +) + if(TGT_BSP MATCHES "arm/egse") target_link_libraries(${OBSW_NAME} PRIVATE ${LIB_ARCSEC} @@ -297,12 +314,6 @@ if(EIVE_ADD_JSON_LIB) ) endif() -if(EIVE_BUILD_UNITTESTS) - target_link_libraries(${TARGET_NAME} PRIVATE - ${CATCH2_TARGET} - ) -endif() - target_link_libraries(${LIB_EIVE_MISSION} PUBLIC ${LIB_CXX_FS} ) diff --git a/automation/Dockerfile-q7s b/automation/Dockerfile similarity index 74% rename from automation/Dockerfile-q7s rename to automation/Dockerfile index 207afa04..9042cf13 100644 --- a/automation/Dockerfile-q7s +++ b/automation/Dockerfile @@ -2,7 +2,9 @@ FROM ubuntu:focal RUN apt-get update RUN apt-get --yes upgrade -RUN apt-get --yes install cmake libgpiod-dev xz-utils nano curl +#tzdata is a dependency, won't install otherwise +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get --yes install cmake libgpiod-dev xz-utils nano curl git gcc g++ lcov valgrind libgps-dev # Q7S root filesystem, required for cross-compilation. RUN mkdir -p /usr/rootfs; \ @@ -15,4 +17,4 @@ curl https://buggy.irs.uni-stuttgart.de/eive/tools/gcc-arm-8.3-2019.03-x86_64-ar | tar -xz -C /usr/tools ENV Q7S_SYSROOT="/usr/rootfs/cortexa9hf-neon-xiphos-linux-gnueabi" -ENV PATH=$PATH:"/usr/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" +ENV PATH=$PATH:"/usr/tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin" \ No newline at end of file diff --git a/automation/Jenkinsfile b/automation/Jenkinsfile index 8d45b2d7..300a7749 100644 --- a/automation/Jenkinsfile +++ b/automation/Jenkinsfile @@ -1,51 +1,36 @@ pipeline { - agent any - stages { - stage('Build Container') { - when { - anyOf { - changeset "automation/Dockerfile-q7s" - branch 'develop'; - changelog 'cleanDocker' - } - } - steps { - sh 'docker build -t eive-fsw-build-q7s:gcc8 - < automation/Dockerfile-q7s' - - } + environment { + BUILDDIR_Q7 = 'build_q7' + BUILDDIR_LINUX = 'build_linux' + } + agent { + docker { + image 'eive-obsw-ci:d2' + args '--sysctl fs.mqueue.msg_max=100' } + } + stages { stage('Clean') { - when { - anyOf { - changelog 'cleanCI' - changeset '*.cmake' - changeset 'CMakeLists.txt' - } - } steps { - sh 'rm -rf build-q7s-debug' + sh 'rm -rf $BUILDDIR_Q7' + sh 'rm -rf $BUILDDIR_LINUX' } } stage('Build Q7S') { - agent { - docker { - image 'eive-fsw-build-q7s:gcc8' - reuseNode true - } - } steps { - dir('build-q7s-debug') { - sh 'cmake -DTGT_BSP="arm/q7s" -DCMAKE_BUILD_TYPE=Debug -DFSFW_OSAL=linux ..' + dir(BUILDDIR_Q7) { + sh 'cmake -DTGT_BSP="arm/q7s" -DCMAKE_BUILD_TYPE=Debug ..' sh 'cmake --build . -j' } } } - stage('Deploy') { - when { - tag 'v*.*.*' - } + stage('Unittests') { steps { - sh 'echo Deploying' + dir(BUILDDIR_LINUX) { + sh 'cmake ..' + sh 'cmake --build . -t eive-unittest -j' + sh './eive-unittest' + } } } } diff --git a/bsp_hosted/fsfwconfig/CMakeLists.txt b/bsp_hosted/fsfwconfig/CMakeLists.txt index 04a8907e..4a483eb7 100644 --- a/bsp_hosted/fsfwconfig/CMakeLists.txt +++ b/bsp_hosted/fsfwconfig/CMakeLists.txt @@ -11,6 +11,9 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") target_sources(${OBSW_NAME} PRIVATE objects/translateObjects.cpp ) + target_sources(${UNITTEST_NAME} PRIVATE + objects/translateObjects.cpp + ) endif() # If a special translation file for events exists, compile it. @@ -18,4 +21,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") target_sources(${OBSW_NAME} PRIVATE events/translateEvents.cpp ) + target_sources(${UNITTEST_NAME} PRIVATE + events/translateEvents.cpp + ) endif() diff --git a/bsp_hosted/fsfwconfig/devices/gpioIds.h b/bsp_hosted/fsfwconfig/devices/gpioIds.h index 1ede0571..0c426ccd 100644 --- a/bsp_hosted/fsfwconfig/devices/gpioIds.h +++ b/bsp_hosted/fsfwconfig/devices/gpioIds.h @@ -47,7 +47,10 @@ enum gpioId_t { SPI_MUX_BIT_3, SPI_MUX_BIT_4, SPI_MUX_BIT_5, - SPI_MUX_BIT_6 + SPI_MUX_BIT_6, + + CS_RAD_SENSOR, + ENABLE_RADFET }; } diff --git a/bsp_q7s/boardtest/Q7STestTask.cpp b/bsp_q7s/boardtest/Q7STestTask.cpp index bd9890a7..1f14d0e1 100644 --- a/bsp_q7s/boardtest/Q7STestTask.cpp +++ b/bsp_q7s/boardtest/Q7STestTask.cpp @@ -142,13 +142,13 @@ void Q7STestTask::testDummyParams() { param.print(); int test = 0; - result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_1, &test); + result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_1, test); if (result != HasReturnvaluesIF::RETURN_OK) { sif::warning << "Q7STestTask::testDummyParams: Key " << DummyParameter::DUMMY_KEY_PARAM_1 << " does not exist" << std::endl; } std::string test2; - result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_2, &test2); + result = param.getValue(DummyParameter::DUMMY_KEY_PARAM_2, test2); if (result != HasReturnvaluesIF::RETURN_OK) { sif::warning << "Q7STestTask::testDummyParams: Key " << DummyParameter::DUMMY_KEY_PARAM_1 << " does not exist" << std::endl; diff --git a/common/config/commonClassIds.h b/common/config/commonClassIds.h index ab8e06d5..01f186a4 100644 --- a/common/config/commonClassIds.h +++ b/common/config/commonClassIds.h @@ -27,6 +27,7 @@ enum commonClassIds: uint8_t { RATE_SETTER, //RS ARCSEC_JSON_BASE, //JSONBASE NVM_PARAM_BASE, //NVMB + SA_DEPL_HANDLER, //SADPL COMMON_CLASS_ID_END // [EXPORT] : [END] }; diff --git a/linux/devices/CMakeLists.txt b/linux/devices/CMakeLists.txt index 769c21f8..7d39837d 100644 --- a/linux/devices/CMakeLists.txt +++ b/linux/devices/CMakeLists.txt @@ -1,5 +1,7 @@ -target_sources(${OBSW_NAME} PRIVATE - GPSHyperionLinuxController.cpp -) +if(EIVE_BUILD_GPSD_GPS_HANDLER) + target_sources(${OBSW_NAME} PRIVATE + GPSHyperionLinuxController.cpp + ) +endif() -add_subdirectory(startracker) \ No newline at end of file +add_subdirectory(startracker) diff --git a/linux/fsfwconfig/CMakeLists.txt b/linux/fsfwconfig/CMakeLists.txt index ea8375fa..8d13e67b 100644 --- a/linux/fsfwconfig/CMakeLists.txt +++ b/linux/fsfwconfig/CMakeLists.txt @@ -12,6 +12,9 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") target_sources(${OBSW_NAME} PRIVATE objects/translateObjects.cpp ) + target_sources(${UNITTEST_NAME} PRIVATE + objects/translateObjects.cpp + ) endif() # If a special translation file for events exists, compile it. @@ -19,4 +22,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/objects/translateObjects.cpp") target_sources(${OBSW_NAME} PRIVATE events/translateEvents.cpp ) + target_sources(${UNITTEST_NAME} PRIVATE + events/translateEvents.cpp + ) endif() diff --git a/linux/fsfwconfig/returnvalues/classIds.h b/linux/fsfwconfig/returnvalues/classIds.h index 86edb85f..ecfc8ca9 100644 --- a/linux/fsfwconfig/returnvalues/classIds.h +++ b/linux/fsfwconfig/returnvalues/classIds.h @@ -12,7 +12,6 @@ namespace CLASS_ID { enum { CLASS_ID_START = COMMON_CLASS_ID_END, - SA_DEPL_HANDLER, // SADPL SD_CARD_MANAGER, // SDMA SCRATCH_BUFFER, // SCBU CLASS_ID_END // [EXPORT] : [END] diff --git a/mission/CMakeLists.txt b/mission/CMakeLists.txt index b0fc4d00..7fa8fe56 100644 --- a/mission/CMakeLists.txt +++ b/mission/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(controller) add_subdirectory(core) add_subdirectory(devices) add_subdirectory(utility) diff --git a/mission/controller/CMakeLists.txt b/mission/controller/CMakeLists.txt new file mode 100644 index 00000000..2b0bac62 --- /dev/null +++ b/mission/controller/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${LIB_EIVE_MISSION} PRIVATE + ThermalController.cpp +) diff --git a/mission/controller/ThermalController.cpp b/mission/controller/ThermalController.cpp index cd8c8b22..2b6f704f 100644 --- a/mission/controller/ThermalController.cpp +++ b/mission/controller/ThermalController.cpp @@ -1,42 +1,48 @@ #include "ThermalController.h" -#include +ThermalController::ThermalController(object_id_t objectId, object_id_t parentId) + : ExtendedControllerBase(objectId, parentId), + sensorTemperatures(this), + componentTemperatures(this) {} -ThermalController::ThermalController(object_id_t objectId) - : ExtendedControllerBase(objectId, objects::NO_OBJECT), thermalControllerSet(objectId) {} +ReturnValue_t ThermalController::initialize() { return ControllerBase::initialize(); } -ReturnValue_t ThermalController::handleCommandMessage(CommandMessage *message) { - return HasReturnvaluesIF::RETURN_OK; +ReturnValue_t ThermalController::handleCommandMessage(CommandMessage* message) { + return RETURN_FAILED; +} +void ThermalController::performControlOperation() { + ReturnValue_t result = sensorTemperatures.read(); + if (result != RETURN_OK) { + return; + } + result = componentTemperatures.read(); + if (result != RETURN_OK) { + return; + } + + componentTemperatures.rw = (sensorTemperatures.rw.value + sensorTemperatures.gps.value) / 2; + + sensorTemperatures.commit(); + componentTemperatures.commit(); } -void ThermalController::performControlOperation() {} +ReturnValue_t ThermalController::initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) { + localDataPoolMap.emplace(thermalControllerDefinitions::SENSOR_RW, new PoolEntry({0.0})); + localDataPoolMap.emplace(thermalControllerDefinitions::SENSOR_GPS, new PoolEntry({0.0})); + localDataPoolMap.emplace(thermalControllerDefinitions::COMPONENT_RW, new PoolEntry({0.0})); -void ThermalController::handleChangedDataset(sid_t sid, store_address_t storeId) { - if (sid == sid_t(TSensorDefinitions::ObjIds::TEST_HKB_HANDLER, - TSensorDefinitions::THERMAL_SENSOR_SET_ID)) { - sif::info << "Update registered!" << std::endl; - } -} - -ReturnValue_t ThermalController::initializeAfterTaskCreation() { - ReturnValue_t result = ExtendedControllerBase::initializeAfterTaskCreation(); - if (result != HasReturnvaluesIF::RETURN_OK) { - sif::error << "ThermalController::initializeAfterTaskCreation: Base" - << " class initialization failed!" << std::endl; - } - HasLocalDataPoolIF *testHkbHandler = ObjectManager::instance()->get( - TSensorDefinitions::ObjIds::TEST_HKB_HANDLER); - if (testHkbHandler == nullptr) { - sif::warning << "ThermalController::initializeAfterTaskCreation: Test" - << " HKB Handler invalid!" << std::endl; - } - // Test normal notifications without data packet first. - testHkbHandler->getHkManagerHandle()->subscribeForSetUpdateMessages( - TSensorDefinitions::THERMAL_SENSOR_SET_ID, this->getObjectId(), commandQueue->getId(), false); - return result; + return RETURN_OK; } +LocalPoolDataSetBase* ThermalController::getDataSetHandle(sid_t sid) { return nullptr; } ReturnValue_t ThermalController::checkModeCommand(Mode_t mode, Submode_t submode, - uint32_t *msToReachTheMode) { - return HasReturnvaluesIF::RETURN_OK; -} + uint32_t* msToReachTheMode) { + if (submode != SUBMODE_NONE) { + return INVALID_SUBMODE; + } + if ((mode != MODE_OFF) && (mode != MODE_ON) && (mode != MODE_NORMAL)) { + return INVALID_MODE; + } + return RETURN_OK; +} \ No newline at end of file diff --git a/mission/controller/ThermalController.h b/mission/controller/ThermalController.h index 2d76ce8d..b52796d6 100644 --- a/mission/controller/ThermalController.h +++ b/mission/controller/ThermalController.h @@ -2,30 +2,28 @@ #define MISSION_CONTROLLER_THERMALCONTROLLER_H_ #include - -#include "ctrldefinitions/ThermalCtrlPackets.h" +#include class ThermalController : public ExtendedControllerBase { public: - ThermalController(object_id_t objectId); + ThermalController(object_id_t objectId, object_id_t parentId); + + ReturnValue_t initialize() override; + + protected: + virtual ReturnValue_t handleCommandMessage(CommandMessage* message) override; + virtual void performControlOperation() override; + virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool& localDataPoolMap, + LocalDataPoolManager& poolManager) override; + virtual LocalPoolDataSetBase* getDataSetHandle(sid_t sid) override; + + // Mode abstract functions + virtual ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, + uint32_t* msToReachTheMode) override; private: - // TODO: Add stubs for thermal components. Each device / assembly with one - // or multiple redundant sensors will have a thermal component. - - /** ExtendedControllerBase overrides */ - virtual ReturnValue_t handleCommandMessage(CommandMessage *message) override; - - virtual void performControlOperation() override; - - virtual ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode, - uint32_t *msToReachTheMode) override; - - ReturnValue_t initializeAfterTaskCreation() override; - - void handleChangedDataset(sid_t sid, store_address_t storeId) override; - - ThermalCtrl::ThermalControllerTemperatureSet thermalControllerSet; + thermalControllerDefinitions::SensorTemperatures sensorTemperatures; + thermalControllerDefinitions::ComponentTemperatures componentTemperatures; }; #endif /* MISSION_CONTROLLER_THERMALCONTROLLER_H_ */ diff --git a/mission/controller/controllerdefinitions/ThermalControllerDefinitions.h b/mission/controller/controllerdefinitions/ThermalControllerDefinitions.h new file mode 100644 index 00000000..48853778 --- /dev/null +++ b/mission/controller/controllerdefinitions/ThermalControllerDefinitions.h @@ -0,0 +1,43 @@ +#ifndef MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ +#define MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ + +#include +#include + +namespace thermalControllerDefinitions { + +enum SetIds : uint32_t { SENSOR_TEMPERATURES, COMPONENT_TEMPERATURES }; + +enum PoolIds : lp_id_t { SENSOR_RW, SENSOR_GPS, COMPONENT_RW }; + +/** + * @brief This dataset can be used to store the collected temperatures of all temperature sensors + */ +class SensorTemperatures : public StaticLocalDataSet<2> { + public: + SensorTemperatures(HasLocalDataPoolIF* owner) : StaticLocalDataSet(owner, SENSOR_TEMPERATURES) {} + + SensorTemperatures(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, SENSOR_TEMPERATURES)) {} + + lp_var_t rw = lp_var_t(sid.objectId, PoolIds::SENSOR_RW, this); + lp_var_t gps = lp_var_t(sid.objectId, PoolIds::SENSOR_GPS, this); +}; + +/** + * @brief This dataset can be used to store the collected temperatures of all components + */ +class ComponentTemperatures : public StaticLocalDataSet<2> { + public: + ComponentTemperatures(HasLocalDataPoolIF* owner) + : StaticLocalDataSet(owner, SENSOR_TEMPERATURES) {} + + ComponentTemperatures(object_id_t objectId) + : StaticLocalDataSet(sid_t(objectId, SENSOR_TEMPERATURES)) {} + + lp_var_t rw = lp_var_t(sid.objectId, PoolIds::COMPONENT_RW, this); +}; + +} // namespace thermalControllerDefinitions + +#endif /* MISSION_CONTROLLER_CONTROLLERDEFINITIONS_THERMALCONTROLLERDEFINITIONS_H_ */ \ No newline at end of file diff --git a/mission/devices/RadiationSensorHandler.h b/mission/devices/RadiationSensorHandler.h index 5aeb4add..cc5f4d4f 100644 --- a/mission/devices/RadiationSensorHandler.h +++ b/mission/devices/RadiationSensorHandler.h @@ -3,8 +3,8 @@ #include #include +#include -class GpioIF; /** * @brief This is the device handler class for radiation sensor on the OBC IF Board. The diff --git a/mission/memory/NVMParameterBase.cpp b/mission/memory/NVMParameterBase.cpp index 0802a392..c7ac7560 100644 --- a/mission/memory/NVMParameterBase.cpp +++ b/mission/memory/NVMParameterBase.cpp @@ -19,7 +19,7 @@ ReturnValue_t NVMParameterBase::readJsonFile() { ReturnValue_t NVMParameterBase::writeJsonFile() { std::ofstream o(fullName); - o << std::setw(4) << json; + o << std::setw(4) << json << std::endl; return HasReturnvaluesIF::RETURN_OK; } diff --git a/mission/memory/NVMParameterBase.h b/mission/memory/NVMParameterBase.h index aafbf8cc..eb839905 100644 --- a/mission/memory/NVMParameterBase.h +++ b/mission/memory/NVMParameterBase.h @@ -34,7 +34,7 @@ class NVMParameterBase : public HasReturnvaluesIF { ReturnValue_t setValue(std::string key, T value); template - ReturnValue_t getValue(std::string key, T* value) const; + ReturnValue_t getValue(std::string key, T& value) const; void printKeys() const; void print() const; @@ -67,11 +67,11 @@ inline ReturnValue_t NVMParameterBase::setValue(std::string key, T value) { } template -inline ReturnValue_t NVMParameterBase::getValue(std::string key, T* value) const { +inline ReturnValue_t NVMParameterBase::getValue(std::string key, T& value) const { if (!json.contains(key)) { return KEY_NOT_EXISTS; } - *value = json[key]; + value = json[key]; return RETURN_OK; } diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 0c799d45..1786b71e 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -1,5 +1,8 @@ -add_subdirectory(testcfg) +add_subdirectory(controller) +add_subdirectory(mocks) -target_sources(${TARGET_NAME} PRIVATE +target_sources(${UNITTEST_NAME} PRIVATE main.cpp + testEnvironment.cpp + printChar.cpp ) \ No newline at end of file diff --git a/unittest/controller/CMakeLists.txt b/unittest/controller/CMakeLists.txt new file mode 100644 index 00000000..ee9b1a8b --- /dev/null +++ b/unittest/controller/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(${UNITTEST_NAME} PRIVATE + testThermalController.cpp +) \ No newline at end of file diff --git a/unittest/controller/testThermalController.cpp b/unittest/controller/testThermalController.cpp new file mode 100644 index 00000000..a0e96d68 --- /dev/null +++ b/unittest/controller/testThermalController.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include + +#include "../testEnvironment.h" + +TEST_CASE("Thermal Controller", "[ThermalController]") { + const object_id_t THERMAL_CONTROLLER_ID = 0x123; + + ThermalController controller(THERMAL_CONTROLLER_ID, objects::NO_OBJECT); + + testEnvironment::initialize(); + + REQUIRE(controller.initializeAfterTaskCreation() == HasReturnvaluesIF::RETURN_OK); + + testEnvironment::eventManager->clearEventList(); + + MessageQueueId_t controllerQueue = controller.getCommandQueue(); + + CommandMessage modeMessage; + + ModeMessage::setModeMessage(&modeMessage, ModeMessage::CMD_MODE_COMMAND, + ControllerBase::MODE_NORMAL, HasModesIF::SUBMODE_NONE); + + MessageQueueIF* commandQueue = + QueueFactory::instance()->createMessageQueue(5, MessageQueueMessage::MAX_MESSAGE_SIZE); + + commandQueue->sendMessage(controllerQueue, &modeMessage); + + REQUIRE(controller.performOperation(0) == HasReturnvaluesIF::RETURN_OK); + + REQUIRE(testEnvironment::eventManager->isEventInEventList( + THERMAL_CONTROLLER_ID, HasModesIF::MODE_INFO, ControllerBase::MODE_NORMAL, + HasModesIF::SUBMODE_NONE) == true); + + thermalControllerDefinitions::ComponentTemperatures componentTemperatures(THERMAL_CONTROLLER_ID); + + componentTemperatures.read(); + REQUIRE(componentTemperatures.rw == 0); + + componentTemperatures.commit(); + + QueueFactory::instance()->deleteMessageQueue(commandQueue); +} \ No newline at end of file diff --git a/unittest/main.cpp b/unittest/main.cpp index 1e274ab2..6438577c 100644 --- a/unittest/main.cpp +++ b/unittest/main.cpp @@ -1,6 +1,13 @@ -#include "fsfw/serviceinterface/ServiceInterfaceStream.h" -#include "fsfw_tests/unit/CatchRunner.h" +#include + +#include "testEnvironment.h" int main(int argc, char* argv[]) { - // fsfwtest::customMain(argc, argv); + testEnvironment::setup(); + + // Catch internal function call + int result = Catch::Session().run(argc, argv); + + // global clean-up + return result; } diff --git a/unittest/mocks/CMakeLists.txt b/unittest/mocks/CMakeLists.txt new file mode 100644 index 00000000..8f4a09ca --- /dev/null +++ b/unittest/mocks/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${UNITTEST_NAME} PRIVATE + EventManagerMock.cpp + HouseKeepingMock.cpp +) \ No newline at end of file diff --git a/unittest/mocks/EventManagerMock.cpp b/unittest/mocks/EventManagerMock.cpp new file mode 100644 index 00000000..6956c516 --- /dev/null +++ b/unittest/mocks/EventManagerMock.cpp @@ -0,0 +1,63 @@ +#include "EventManagerMock.h" + +#include + +EventManagerMock::EventManagerMock() : EventManager(objects::EVENT_MANAGER) {} + +ReturnValue_t EventManagerMock::performOperation(uint8_t opCode) { + ReturnValue_t result = HasReturnvaluesIF::RETURN_OK; + while (result == HasReturnvaluesIF::RETURN_OK) { + EventMessage message; + result = eventReportQueue->receiveMessage(&message); + if (result == HasReturnvaluesIF::RETURN_OK) { + notifyListeners(&message); + eventList.emplace_back(message); + } + } + return HasReturnvaluesIF::RETURN_OK; +} + +const std::list* EventManagerMock::getEventList() { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + return &eventList; +} + +void EventManagerMock::clearEventList() { // call performOperation() to get all events still in the + // message Queue into the list + this->performOperation(0); + eventList.clear(); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, Event event) { + return isEventInEventList(object, event::getEventId(event)); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, EventId_t eventId) { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + for (auto iter = eventList.begin(); iter != eventList.end(); iter++) { + if ((iter->getReporter() == object) && (iter->getEventId() == eventId)) { + return true; + } + } + return false; +} + +bool EventManagerMock::isEventInEventList(object_id_t object, Event event, uint32_t parameter1, + uint32_t parameter2) { + return isEventInEventList(object, event::getEventId(event), parameter1, parameter2); +} + +bool EventManagerMock::isEventInEventList(object_id_t object, EventId_t eventId, + uint32_t parameter1, uint32_t parameter2) { + // call performOperation() to get all events still in the message Queue into the list + this->performOperation(0); + for (auto iter = eventList.begin(); iter != eventList.end(); iter++) { + if ((iter->getReporter() == object) && (iter->getEventId() == eventId) && + (iter->getParameter1() == parameter1) && (iter->getParameter2() == parameter2)) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/unittest/mocks/EventManagerMock.h b/unittest/mocks/EventManagerMock.h new file mode 100644 index 00000000..84588a97 --- /dev/null +++ b/unittest/mocks/EventManagerMock.h @@ -0,0 +1,27 @@ +#ifndef EVENTMANAGERMOCK_H_ +#define EVENTMANAGERMOCK_H_ + +#include + +#include + +class EventManagerMock : public EventManager { + public: + EventManagerMock(); + + virtual ReturnValue_t performOperation(uint8_t opCode) override; + + const std::list* getEventList(); + void clearEventList(); + + bool isEventInEventList(object_id_t object, Event event); + bool isEventInEventList(object_id_t object, Event event, uint32_t parameter1, uint32_t parameter2); + + bool isEventInEventList(object_id_t object, EventId_t eventId); + bool isEventInEventList(object_id_t object, EventId_t eventId, uint32_t parameter1, uint32_t parameter2); + + private: + std::list eventList; +}; + +#endif /* EVENTMANAGERMOCK_H_ */ \ No newline at end of file diff --git a/unittest/mocks/HouseKeepingMock.cpp b/unittest/mocks/HouseKeepingMock.cpp new file mode 100644 index 00000000..d2592c93 --- /dev/null +++ b/unittest/mocks/HouseKeepingMock.cpp @@ -0,0 +1,8 @@ +#include "HouseKeepingMock.h" + +#include + + +HouseKeepingMock::HouseKeepingMock() : SystemObject(objects::PUS_SERVICE_3_HOUSEKEEPING) {} + +MessageQueueId_t HouseKeepingMock::getHkQueue() const { return MessageQueueIF::NO_QUEUE; } \ No newline at end of file diff --git a/unittest/mocks/HouseKeepingMock.h b/unittest/mocks/HouseKeepingMock.h new file mode 100644 index 00000000..9cc933ed --- /dev/null +++ b/unittest/mocks/HouseKeepingMock.h @@ -0,0 +1,16 @@ +#ifndef HOUSEKEEPINGMOCK_H_ +#define HOUSEKEEPINGMOCK_H_ + +#include +#include +#include + +class HouseKeepingMock : public SystemObject, public AcceptsHkPacketsIF { + public: + HouseKeepingMock(); + + virtual MessageQueueId_t getHkQueue() const; +}; + + +#endif /*HOUSEKEEPINGMOCK_H_*/ \ No newline at end of file diff --git a/unittest/printChar.cpp b/unittest/printChar.cpp new file mode 100644 index 00000000..0e29fe2b --- /dev/null +++ b/unittest/printChar.cpp @@ -0,0 +1,11 @@ +#include "printChar.h" + +#include + +void printChar(const char* character, bool errStream) { + if (errStream) { + std::putc(*character, stderr); + return; + } + std::putc(*character, stdout); +} diff --git a/unittest/printChar.h b/unittest/printChar.h new file mode 100644 index 00000000..1991161f --- /dev/null +++ b/unittest/printChar.h @@ -0,0 +1,6 @@ +#ifndef FSFW_UNITTEST_CORE_PRINTCHAR_H_ +#define FSFW_UNITTEST_CORE_PRINTCHAR_H_ + +extern "C" void printChar(const char*, bool errStream); + +#endif /* FSFW_UNITTEST_CORE_PRINTCHAR_H_ */ diff --git a/unittest/testEnvironment.cpp b/unittest/testEnvironment.cpp new file mode 100644 index 00000000..e4d5bed2 --- /dev/null +++ b/unittest/testEnvironment.cpp @@ -0,0 +1,59 @@ +#include "testEnvironment.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINUX +ServiceInterfaceStream sif::debug("DEBUG "); +ServiceInterfaceStream sif::info("INFO "); +ServiceInterfaceStream sif::warning("WARNING"); +ServiceInterfaceStream sif::error("ERROR ", false, true, true); +#else +ServiceInterfaceStream sif::debug("DEBUG", true); +ServiceInterfaceStream sif::info("INFO", true); +ServiceInterfaceStream sif::warning("WARNING", true); +ServiceInterfaceStream sif::error("ERROR", true, false, true); +#endif + +namespace testEnvironment { + +void factory(void* args) { + new HouseKeepingMock(); + eventManager = new EventManagerMock(); + new HealthTable(objects::HEALTH_TABLE); + new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER); + new TimeStamper(objects::TIME_STAMPER); + + { + PoolManager::LocalPoolConfig poolCfg = {{300, 16}, {200, 32}, {150, 64}, {150, 128}, + {100, 256}, {50, 512}, {50, 1024}, {10, 2048}}; + new PoolManager(objects::IPC_STORE, poolCfg); + } +} + +void setup() { ObjectManager::instance()->setObjectFactoryFunction(factory, nullptr); } + +void initialize() { ObjectManager::instance()->initialize(); } + +} // namespace testEnvironment + +EventManagerMock* testEnvironment::eventManager = nullptr; + +#include +#include + +namespace messagetypes { +enum MESSAGE_TYPE { + MISSION_MESSAGE_TYPE_START = FW_MESSAGES_COUNT, +}; + +void clearMissionMessage(CommandMessage* message); +} // namespace messagetypes + +void messagetypes::clearMissionMessage(CommandMessage* message) {} diff --git a/unittest/testEnvironment.h b/unittest/testEnvironment.h new file mode 100644 index 00000000..79ff8544 --- /dev/null +++ b/unittest/testEnvironment.h @@ -0,0 +1,36 @@ +#ifndef UNITTEST_TESTENVIRONMENT_H_ +#define UNITTEST_TESTENVIRONMENT_H_ + +#include "mocks/EventManagerMock.h" +#include "mocks/HouseKeepingMock.h" + +/* + * This namespace sets up a general environment for unittests + * + * Only objects generally used in all unittest are created here. Objects needed for + * individual tests are to be contstructed in the individual tests. + * + * The object manager can be initialized by the initialize() call + * + * It also caches pointers to generally useful objects so they do + * not need to be gotten from the object manager each time + */ +namespace testEnvironment { + +/* + * Setup code goes here, called by main() befor any tests + */ +void setup(); + +/* + * Initializes the object manager, to be called at the start of each test, after test specific + * objects are constructed + * + * All objects defined in the factory method are created here + */ +void initialize(); + +extern EventManagerMock* eventManager; +} // namespace testEnvironment + +#endif /*UNITTEST_TESTENVIRONMENT_H_*/ \ No newline at end of file