From a5a24fa3162150751be6e0218269ac2678a3914a Mon Sep 17 00:00:00 2001 From: "Mohamed Ali Khalifi (AE/PJ-SW4)" Date: Thu, 12 Mar 2020 16:28:30 +0100 Subject: [PATCH 01/29] Add test executor and support code Co-authored-by: ChiefGokhlayehBosch Signed-off-by: ChiefGokhlayehBosch --- CMakeLists.txt | 29 +- .../config/core/essentials/Kiso_BSPConfig.h | 4 + .../NucleoF767/bsp/include/BSP_NucleoF767.h | 1 + boards/NucleoF767/bsp/source/bsp_api_button.c | 1 - .../bsp/source/bsp_api_genericuart.c | 230 ++++++++++++++ boards/NucleoF767/bsp/source/bsp_api_testif.c | 45 +-- boards/NucleoF767/bsp/source/protected/gpio.h | 4 + .../core/essentials/Kiso_BSPConfig.h | 1 + cmake/KisoLibsConfig.cmake | 1 - .../test/integration/CMakeLists.txt | 30 ++ core/essentials/test/integration/readme.md | 7 + .../test/integration/source/TestEntry.c | 68 +++++ .../test/integration/source/TestSuiteUart.c | 245 +++++++++++++++ .../test/integration/source/TestSuiteUart.h | 55 ++++ .../test/integration/specs/I2C_Test_Spec.md | 283 ++++++++++++++++++ .../test/integration/specs/SPI_Test_Spec.md | 207 +++++++++++++ .../test/integration/specs/UART_Test_Spec.md | 212 +++++++++++++ .../test/integration/test-protocol.txt | 0 testing/integration/readme.md | 8 + testing/integration/test-auxiliary/readme.md | 3 + .../integration/test-coordinator/readme.md | 3 + .../integration/test-executor/CMakeLists.txt | 24 ++ testing/integration/test-executor/readme.md | 3 + .../testapp/config/utils/Kiso_UtilsConfig.h | 107 +++++++ .../test-executor/testapp/source/AppModules.h | 17 ++ .../test-executor/testapp/source/BSP_Proxy.h | 53 ++++ .../test-executor/testapp/source/main.c | 260 ++++++++++++++++ testing/integration/test-scheduler/readme.md | 3 + 28 files changed, 1874 insertions(+), 30 deletions(-) create mode 100644 boards/NucleoF767/bsp/source/bsp_api_genericuart.c create mode 100644 core/essentials/test/integration/CMakeLists.txt create mode 100644 core/essentials/test/integration/readme.md create mode 100644 core/essentials/test/integration/source/TestEntry.c create mode 100644 core/essentials/test/integration/source/TestSuiteUart.c create mode 100644 core/essentials/test/integration/source/TestSuiteUart.h create mode 100644 core/essentials/test/integration/specs/I2C_Test_Spec.md create mode 100644 core/essentials/test/integration/specs/SPI_Test_Spec.md create mode 100644 core/essentials/test/integration/specs/UART_Test_Spec.md create mode 100644 core/essentials/test/integration/test-protocol.txt create mode 100644 testing/integration/readme.md create mode 100644 testing/integration/test-auxiliary/readme.md create mode 100644 testing/integration/test-coordinator/readme.md create mode 100644 testing/integration/test-executor/CMakeLists.txt create mode 100644 testing/integration/test-executor/readme.md create mode 100644 testing/integration/test-executor/testapp/config/utils/Kiso_UtilsConfig.h create mode 100644 testing/integration/test-executor/testapp/source/AppModules.h create mode 100644 testing/integration/test-executor/testapp/source/BSP_Proxy.h create mode 100644 testing/integration/test-executor/testapp/source/main.c create mode 100644 testing/integration/test-scheduler/readme.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ce1a276..c5f5ec78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ option(ENABLE_FORMAT_CHECKS "Run format checks during configuration stage and ge option(SKIP_FORMAT_REPORTS "Skip generation of format XML reports in build directory" ON) option(ENABLE_STATIC_CHECKS "Configure a build tree for static code analysis" OFF) option(ENABLE_COVERAGE "Build unit tests with coverage information to use with GCOV and LCOV" ON) +option(ENABLE_INTEGRATION_TESTING "Build for integration tests application" OFF) ## Include config file if it exists include(kiso_defaults.cmake OPTIONAL) @@ -17,14 +18,16 @@ if (NOT DEFINED CMAKE_TOOLCHAIN_FILE AND NOT ${ENABLE_TESTING}) include(cmake/ArmToolchain.cmake) endif() -message("------------- KISO CONFIG -------------") -message("Building Kiso tests: ${ENABLE_TESTING}") -message(" ... with coverage: ${ENABLE_COVERAGE}") -message("Kiso Board Path: ${KISO_BOARD_PATH}") -message("Kiso OS: ${KISO_OS_LIB}") -message("Kiso Application Path: ${KISO_APPLICATION_PATH}") -message("Project Config Path: ${PROJECT_CONFIG_PATH}") -message("------------- KISO CONFIG -------------") +message("---------------------------------- KISO CONFIG ----------------------------------") +message("Building Kiso tests: ${ENABLE_TESTING}") +message(" ... with coverage: ${ENABLE_COVERAGE}") +message("Building Kiso integration tests: ${ENABLE_INTEGRATION_TESTING}") +message(" ... with entry in: ${KISO_INTEGRATION_TEST_NAME}") +message("Kiso Board Path: ${KISO_BOARD_PATH}") +message("Kiso OS: ${KISO_OS_LIB}") +message("Kiso Application Path: ${KISO_APPLICATION_PATH}") +message("Project Config Path: ${PROJECT_CONFIG_PATH}") +message("---------------------------------- KISO CONFIG ----------------------------------") project (Kiso C) @@ -89,7 +92,12 @@ if(${ENABLE_STATIC_CHECKS}) endif() ## Add application code -add_subdirectory(${KISO_APPLICATION_PATH} ${CMAKE_CURRENT_BINARY_DIR}/applications/${KISO_APPLICATION_NAME}) +if(${ENABLE_INTEGRATION_TESTING}) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/testing/integration/test-executor) + add_subdirectory(${KISO_INTEGRATION_TEST_NAME}/test/integration) +else(${ENABLE_INTEGRATION_TESTING}) + add_subdirectory(${KISO_APPLICATION_PATH} ${CMAKE_CURRENT_BINARY_DIR}/applications/${KISO_APPLICATION_NAME}) +endif(${ENABLE_INTEGRATION_TESTING}) include(KisoLibsConfig) @@ -98,6 +106,9 @@ include(KisoLibsConfig) add_subdirectory(core/essentials) add_subdirectory(core/utils) add_subdirectory(core/connectivity/cellular) +if(${ENABLE_INTEGRATION_TESTING}) + add_subdirectory(core/testing) +endif(${ENABLE_INTEGRATION_TESTING}) ## Add thirdparty libs add_subdirectory(thirdparty) diff --git a/boards/NucleoF767/bsp/config/core/essentials/Kiso_BSPConfig.h b/boards/NucleoF767/bsp/config/core/essentials/Kiso_BSPConfig.h index 52c59475..49616eb1 100644 --- a/boards/NucleoF767/bsp/config/core/essentials/Kiso_BSPConfig.h +++ b/boards/NucleoF767/bsp/config/core/essentials/Kiso_BSPConfig.h @@ -48,6 +48,10 @@ #define KISO_FEATURE_BSP_BUTTON 1 #endif +#ifndef KISO_FEATURE_BSP_GENERIC_UART +#define KISO_FEATURE_BSP_GENERIC_UART 1 +#endif + #ifndef KISO_FEATURE_BSP_CELLULAR_SARAR4N4 #define KISO_FEATURE_BSP_CELLULAR_SARAR4N4 0 #endif diff --git a/boards/NucleoF767/bsp/include/BSP_NucleoF767.h b/boards/NucleoF767/bsp/include/BSP_NucleoF767.h index 5486b4a6..188e2bb8 100644 --- a/boards/NucleoF767/bsp/include/BSP_NucleoF767.h +++ b/boards/NucleoF767/bsp/include/BSP_NucleoF767.h @@ -38,6 +38,7 @@ enum BSP_NUCLEOF767y_Modules_E MODULE_BSP_API_BOARD, MODULE_BSP_API_LED, MODULE_BSP_API_TEST_IF, + MODULE_BSP_API_GENERICUART, MODULE_BSP_TIME, MODULE_BSP_BUTTON, }; diff --git a/boards/NucleoF767/bsp/source/bsp_api_button.c b/boards/NucleoF767/bsp/source/bsp_api_button.c index 631a6f00..708e679e 100644 --- a/boards/NucleoF767/bsp/source/bsp_api_button.c +++ b/boards/NucleoF767/bsp/source/bsp_api_button.c @@ -28,7 +28,6 @@ /*---------------------- LOCAL FUNCTIONS DECLARATION ----------------------------------------------------------------*/ void EXTI15_10_IRQHandler(void); -void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); /*---------------------- VARIABLES DECLARATION ----------------------------------------------------------------------*/ diff --git a/boards/NucleoF767/bsp/source/bsp_api_genericuart.c b/boards/NucleoF767/bsp/source/bsp_api_genericuart.c new file mode 100644 index 00000000..ec27839f --- /dev/null +++ b/boards/NucleoF767/bsp/source/bsp_api_genericuart.c @@ -0,0 +1,230 @@ +/******************************************************************************** +* Copyright (c) 2010-2020 Robert Bosch GmbH +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Robert Bosch GmbH - initial contribution +* +********************************************************************************/ + +#include "Kiso_BSP_GenericUart.h" + +#if KISO_FEATURE_BSP_GENERIC_UART + +#include "Kiso_Basics.h" +#include "Kiso_Retcode.h" +#include "stm32/stm32f7/Kiso_MCU_STM32F7_UART_Handle.h" +#include "Kiso_HAL_Delay.h" +#include "BSP_NucleoF767.h" +#include "protected/gpio.h" + +/*---------------------- MACROS DEFINITION --------------------------------------------------------------------------*/ + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID MODULE_BSP_API_GENERICUART + +#define UART_INT_PRIORITY UINT32_C(10) +#define UART_SUBPRIORITY UINT32_C(1) + +/*---------------------- LOCAL FUNCTIONS DECLARATION ----------------------------------------------------------------*/ + +void USART2_IRQHandler(void); + +/*---------------------- VARIABLES DECLARATION ----------------------------------------------------------------------*/ + +static uint8_t bspState = (uint8_t)BSP_STATE_INIT; /**< BSP State of the cellular module */ + +/** + * Static structure storing the UART handle for Test Interface + */ +static struct MCU_UART_S uartCtrlStruct = + { + .TxMode = KISO_HAL_TRANSFER_MODE_INTERRUPT, + .RxMode = KISO_HAL_TRANSFER_MODE_INTERRUPT, + .Datarate = 115200U, + .huart.Instance = USART2, + .huart.Init.BaudRate = 115200U, + .huart.Init.WordLength = UART_WORDLENGTH_8B, + .huart.Init.StopBits = UART_STOPBITS_1, + .huart.Init.Parity = UART_PARITY_NONE, + .huart.Init.Mode = UART_MODE_TX_RX, + .huart.Init.HwFlowCtl = UART_HWCONTROL_NONE, + .huart.Init.OverSampling = UART_OVERSAMPLING_16, + .huart.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE, + .huart.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT, +}; +/*---------------------- EXPOSED FUNCTIONS IMPLEMENTATION -----------------------------------------------------------*/ + +/** + * See API interface for function documentation + * @retval RETCODE_OK in case of success. + * @retval RETCODE_INCONSISTENT_STATE in case the module is not in a state to allow connecting. + */ +Retcode_T BSP_GenericUart_Connect(uint32_t id) +{ + KISO_UNUSED(id); + Retcode_T retcode = RETCODE_OK; + + if (!(bspState & (uint8_t)BSP_STATE_TO_CONNECTED)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_INCONSISTENT_STATE); + } + if (RETCODE_OK == retcode) + { + /* IOSV bit MUST be set to access GPIO port G[2:15] */ + __HAL_RCC_PWR_CLK_ENABLE(); + __HAL_RCC_LPTIM1_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + /* UART RX/TX GPIO pin configuration */ + GPIO_OpenClockGate(GPIO_PORT_D, PIND_USART2_TX | PIND_USART2_RX); + + GPIO_InitStruct.Pin = PIND_USART2_TX | PIND_USART2_RX; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF7_USART2; + + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); + + bspState = (uint8_t)BSP_STATE_CONNECTED; + } + return retcode; +} + +/** + * See API interface for function documentation + * @retval RETCODE_OK in case of success. + * @retval RETCODE_INCONSISTENT_STATE in case the module is not in a state to allow enabling. + */ +Retcode_T BSP_GenericUart_Enable(uint32_t id) +{ + KISO_UNUSED(id); + Retcode_T retcode = RETCODE_OK; + + if (!(bspState & (uint8_t)BSP_STATE_TO_ENABLED)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_INCONSISTENT_STATE); + } + if (RETCODE_OK == retcode) + { + __HAL_RCC_USART2_CLK_ENABLE(); + __HAL_RCC_USART2_FORCE_RESET(); + __HAL_RCC_USART2_RELEASE_RESET(); + __GPIOD_CLK_ENABLE(); + /* Configure the UART resource */ + if (HAL_OK != HAL_UART_Init(&uartCtrlStruct.huart)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_BSP_UART_INIT_FAILED); + } + } + if (RETCODE_OK == retcode) + { + NVIC_ClearPendingIRQ(USART2_IRQn); + HAL_NVIC_SetPriority(USART2_IRQn, UART_INT_PRIORITY, UART_SUBPRIORITY); + HAL_NVIC_EnableIRQ(USART2_IRQn); + + bspState = (uint8_t)BSP_STATE_ENABLED; + } + return retcode; +} + +/** + * See API interface for function documentation + * @retval RETCODE_OK in case of success. + * @retval RETCODE_INCONSISTENT_STATE in case the module is not in a state to allow disabling. + */ +Retcode_T BSP_GenericUart_Disable(uint32_t id) +{ + KISO_UNUSED(id); + Retcode_T retcode = RETCODE_OK; + + if (!(bspState & (uint8_t)BSP_STATE_TO_DISABLED)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_INCONSISTENT_STATE); + } + if (RETCODE_OK == retcode) + { + /* Disable interrupts and deactivate UART peripheral */ + HAL_NVIC_DisableIRQ(USART2_IRQn); + /* Clear the pending interrupt */ + HAL_NVIC_ClearPendingIRQ(USART2_IRQn); + + if (HAL_OK != HAL_UART_DeInit(&uartCtrlStruct.huart)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_BSP_UART_DEINIT_FAILED); + } + } + if (RETCODE_OK == retcode) + { + __USART2_CLK_DISABLE(); + bspState = (uint8_t)BSP_STATE_DISABLED; + } + return retcode; +} + +/** + * See API interface for function documentation + * @retval RETCODE_OK in case of success. + * @retval RETCODE_INCONSISTENT_STATE in case the module is not in a state to allow disconnecting. + */ +Retcode_T BSP_GenericUart_Disconnect(uint32_t id) +{ + KISO_UNUSED(id); + Retcode_T retcode = RETCODE_OK; + if (!(bspState & (uint8_t)BSP_STATE_TO_DISCONNECTED)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_INCONSISTENT_STATE); + } + if (RETCODE_OK == retcode) + { + HAL_GPIO_DeInit(GPIOD, PIND_USART2_TX | PIND_USART2_RX); + GPIO_CloseClockGate(GPIO_PORT_D, PIND_USART2_TX | PIND_USART2_RX); + } + if (RETCODE_OK == retcode) + { + bspState = (uint8_t)BSP_STATE_DISCONNECTED; + } + return retcode; +} + +/** + * See API interface for function documentation + * @return A pointer to the UART control structure + */ +HWHandle_T BSP_GenericUart_GetHandle(uint32_t id) +{ + KISO_UNUSED(id); + return (HWHandle_T)&uartCtrlStruct; +} + +/** + * This function is not in use. + */ +Retcode_T BSP_GenericUart_UserControl(uint32_t control, void *param) +{ + KISO_UNUSED(control); + KISO_UNUSED(param); + + return RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NOT_SUPPORTED); +} + +/*---------------------- LOCAL FUNCTIONS IMPLEMENTATION -------------------------------------------------------------*/ + +/** + * Interrupt Service Routine handling USART2 IRQ. Forwards call to MCU Layer for handling. + */ +void USART2_IRQHandler(void) +{ + if (uartCtrlStruct.IrqCallback) + { + uartCtrlStruct.IrqCallback((UART_T)&uartCtrlStruct); + } +} +#endif /* KISO_FEATURE_BSP_TEST_INTERFACE */ diff --git a/boards/NucleoF767/bsp/source/bsp_api_testif.c b/boards/NucleoF767/bsp/source/bsp_api_testif.c index 1b4bd875..324f55fc 100644 --- a/boards/NucleoF767/bsp/source/bsp_api_testif.c +++ b/boards/NucleoF767/bsp/source/bsp_api_testif.c @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2010-2019 Robert Bosch GmbH +* Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -16,6 +16,7 @@ #if KISO_FEATURE_BSP_TEST_INTERFACE +#include "Kiso_Basics.h" #include "stm32/stm32f7/Kiso_MCU_STM32F7_UART_Handle.h" #include "Kiso_HAL_Delay.h" #include "BSP_NucleoF767.h" @@ -27,10 +28,7 @@ #define KISO_MODULE_ID MODULE_BSP_API_TEST_IF #define TESTIF_UART_INT_PRIORITY UINT32_C(10) -#define TESTIF_UART_SUBPRIORITY UINT32_C(0) - -#define PINB_DBG_TX GPIO_PIN_8 -#define PINB_DBG_RX GPIO_PIN_9 +#define TESTIF_UART_SUBPRIORITY UINT32_C(1) /*---------------------- LOCAL FUNCTIONS DECLARATION ----------------------------------------------------------------*/ @@ -77,16 +75,22 @@ Retcode_T BSP_TestInterface_Connect(void) } if (RETCODE_OK == retcode) { - GPIO_InitTypeDef BSP_GPIOInitStruct = {0}; + /* IOSV bit MUST be set to access GPIO port G[2:15] */ + __HAL_RCC_PWR_CLK_ENABLE(); + __HAL_RCC_LPTIM1_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + /* UART RX/TX GPIO pin configuration */ + GPIO_OpenClockGate(GPIO_PORT_D, PIND_USART3_TX | PIND_USART3_RX); - GPIO_OpenClockGate(GPIO_PORT_D, PINB_DBG_TX | PINB_DBG_RX); - /* Configure RX TX as alternate function push pull */ - BSP_GPIOInitStruct.Pin = PINB_DBG_TX | PINB_DBG_RX; - BSP_GPIOInitStruct.Mode = GPIO_MODE_AF_PP; - BSP_GPIOInitStruct.Pull = GPIO_NOPULL; - BSP_GPIOInitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - BSP_GPIOInitStruct.Alternate = GPIO_AF7_USART3; - HAL_GPIO_Init(GPIOD, &BSP_GPIOInitStruct); + GPIO_InitStruct.Pin = PIND_USART3_TX | PIND_USART3_RX; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF7_USART3; + + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); bspState = (uint8_t)BSP_STATE_CONNECTED; } @@ -108,11 +112,10 @@ Retcode_T BSP_TestInterface_Enable(void) } if (RETCODE_OK == retcode) { - /* Enable the UART clock */ __HAL_RCC_USART3_CLK_ENABLE(); __HAL_RCC_USART3_FORCE_RESET(); __HAL_RCC_USART3_RELEASE_RESET(); - + __GPIOD_CLK_ENABLE(); /* Configure the UART resource */ if (HAL_OK != HAL_UART_Init(&testIf_UARTStruct.huart)) { @@ -121,6 +124,7 @@ Retcode_T BSP_TestInterface_Enable(void) } if (RETCODE_OK == retcode) { + NVIC_ClearPendingIRQ(USART3_IRQn); HAL_NVIC_SetPriority(USART3_IRQn, TESTIF_UART_INT_PRIORITY, TESTIF_UART_SUBPRIORITY); HAL_NVIC_EnableIRQ(USART3_IRQn); @@ -146,6 +150,9 @@ Retcode_T BSP_TestInterface_Disable(void) { /* Disable interrupts and deactivate UART peripheral */ HAL_NVIC_DisableIRQ(USART3_IRQn); + /* Clear the pending interrupt */ + HAL_NVIC_ClearPendingIRQ(USART3_IRQn); + if (HAL_OK != HAL_UART_DeInit(&testIf_UARTStruct.huart)) { retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_BSP_UART_DEINIT_FAILED); @@ -173,8 +180,8 @@ Retcode_T BSP_TestInterface_Disconnect(void) } if (RETCODE_OK == retcode) { - HAL_GPIO_DeInit(GPIOD, PINB_DBG_TX | PINB_DBG_RX); - GPIO_CloseClockGate(GPIO_PORT_D, PINB_DBG_TX | PINB_DBG_RX); + HAL_GPIO_DeInit(GPIOD, PIND_USART3_TX | PIND_USART3_RX); + GPIO_CloseClockGate(GPIO_PORT_D, PIND_USART3_TX | PIND_USART3_RX); } if (RETCODE_OK == retcode) { @@ -206,7 +213,7 @@ Retcode_T BSP_TestInterface_Control(uint32_t command, void *arg) /*---------------------- LOCAL FUNCTIONS IMPLEMENTATION -------------------------------------------------------------*/ /** - * Interrupt Service Routine handling USART1 IRQ. Forwards call to MCU Layer for handling. + * Interrupt Service Routine handling USART3 IRQ. Forwards call to MCU Layer for handling. */ void USART3_IRQHandler(void) { diff --git a/boards/NucleoF767/bsp/source/protected/gpio.h b/boards/NucleoF767/bsp/source/protected/gpio.h index ba6217ce..8d6de803 100644 --- a/boards/NucleoF767/bsp/source/protected/gpio.h +++ b/boards/NucleoF767/bsp/source/protected/gpio.h @@ -35,6 +35,10 @@ #define PINB_LED_G GPIO_PIN_0 #define PINB_LED_B GPIO_PIN_7 #define PINC_USR_BUTTON GPIO_PIN_13 +#define PIND_USART2_TX GPIO_PIN_5 +#define PIND_USART2_RX GPIO_PIN_6 +#define PIND_USART3_TX GPIO_PIN_8 +#define PIND_USART3_RX GPIO_PIN_9 /*---------------------- EXPORTED TYPES ------------------------------------------------------------------------------*/ /** diff --git a/ci/testing_config/core/essentials/Kiso_BSPConfig.h b/ci/testing_config/core/essentials/Kiso_BSPConfig.h index c740d18e..be489aa2 100644 --- a/ci/testing_config/core/essentials/Kiso_BSPConfig.h +++ b/ci/testing_config/core/essentials/Kiso_BSPConfig.h @@ -42,6 +42,7 @@ /* BSP Features */ #define KISO_FEATURE_BSP_LED 1 #define KISO_FEATURE_BSP_BUTTON 1 +#define KISO_FEATURE_BSP_GENERIC_UART 1 #define KISO_FEATURE_BSP_CELLULAR_SARAR4N4 1 #define KISO_FEATURE_BSP_GNSS_MAXM8 1 #define KISO_FEATURE_BSP_BMA280 1 diff --git a/cmake/KisoLibsConfig.cmake b/cmake/KisoLibsConfig.cmake index cba4be35..fe23baff 100644 --- a/cmake/KisoLibsConfig.cmake +++ b/cmake/KisoLibsConfig.cmake @@ -72,7 +72,6 @@ if(NOT KISO_STATIC_CONFIG) configure_file(${ABS_BOARD_CONFIG_PATH}/${HEADER} ${DEST} COPYONLY) endforeach(HEADER ${BOARD_CONF_FILES}) - # Copy app-specific config files in intermediary directory # APP_CONFIG_PATH is not required - only act if present if(NOT APP_CONFIG_PATH) message(STATUS "APP_CONFIG_PATH not set to a valid path. Not using application-specific configuration.") diff --git a/core/essentials/test/integration/CMakeLists.txt b/core/essentials/test/integration/CMakeLists.txt new file mode 100644 index 00000000..81e8d939 --- /dev/null +++ b/core/essentials/test/integration/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.6) + +project ("Essentials integration test entry" C ASM) + +# the checks will be executed as it would be on the desired compile step +if(${ENABLE_STATIC_CHECKS}) + set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY} --extra-arg=--target=arm-none-eabi --extra-arg=-mthumb --extra-arg=--sysroot=${CMAKE_SYSROOT} -checks=-*,readability-*,clang-analyzer-*,-clang-analyzer-cplusplus*) + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY} --extra-arg=--target=arm-none-eabi --extra-arg=--sysroot=${CMAKE_SYSROOT} -checks=-*,readability-*,clang-analyzer-*,-clang-analyzer-cplusplus*) +endif() + +## Only compilable for a target +if(${CMAKE_CROSSCOMPILING}) + file(GLOB TEST_ENTRY_SOURCES + source/*.c + ) + add_library(testentry STATIC ${TEST_ENTRY_SOURCES}) + + target_include_directories(testentry + PRIVATE + source + ) + # List of additional libs from board_config.cmake + target_link_libraries(testentry testing essentials ${KISO_BOARD_LIBS}) +endif(${CMAKE_CROSSCOMPILING}) + +# Include the tests for this module +if(${CMAKE_TESTING_ENABLED}) + #add_subdirectory(testentry/test) +endif() + diff --git a/core/essentials/test/integration/readme.md b/core/essentials/test/integration/readme.md new file mode 100644 index 00000000..38315cda --- /dev/null +++ b/core/essentials/test/integration/readme.md @@ -0,0 +1,7 @@ +# Integration Test Framework + +It is composed by the following packages: +* Test-scheduler: Called in the continious integration. Define where the tests should be run. +* Test-coordinator: Coordiante the build and execution of the tests. +* Test-auxiliaries: List of elements that supports more complex tests (example: communication tests between a cloud service and a device) +* Test-executor: Software that will be flashed on the device under test. \ No newline at end of file diff --git a/core/essentials/test/integration/source/TestEntry.c b/core/essentials/test/integration/source/TestEntry.c new file mode 100644 index 00000000..974a9580 --- /dev/null +++ b/core/essentials/test/integration/source/TestEntry.c @@ -0,0 +1,68 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * + * @brief + * Implements the following functionalities specified in template.h + */ +/*###################### INCLUDED HEADERS ############################################################################*/ +#include "Kiso_Testing.h" +#include "TestSuiteUart.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TEST_ENTRY + +#define TEST_ENTRY_ID 1 +/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ + +/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ + +Retcode_T TestEntry_Initialize(void *param1, uint32_t param2); +static Retcode_T TestEntry_Setup(CCMsg_T *ccmsg); +static Retcode_T TestEntry_Teardown(CCMsg_T *ccmsg); + +/*###################### VARIABLES DECLARATION #######################################################################*/ + +/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ + +/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ + +Retcode_T TestEntry_Initialize(void *param1, uint32_t param2) +{ + KISO_UNUSED(param1); + KISO_UNUSED(param2); + + Retcode_T retcode = RETCODE_OK; + retcode = Tests_Initialize(TEST_ENTRY_ID, TestEntry_Setup, TestEntry_Teardown); + if (RETCODE_OK == retcode) + { + retcode = TestSuiteUart_Initialize((uint8_t)1); + } + return retcode; +} + +static Retcode_T TestEntry_Setup(CCMsg_T *ccmsg) +{ + KISO_UNUSED(ccmsg); + return RETCODE_OK; +} + +static Retcode_T TestEntry_Teardown(CCMsg_T *ccmsg) +{ + KISO_UNUSED(ccmsg); + return RETCODE_OK; +} \ No newline at end of file diff --git a/core/essentials/test/integration/source/TestSuiteUart.c b/core/essentials/test/integration/source/TestSuiteUart.c new file mode 100644 index 00000000..78019fb9 --- /dev/null +++ b/core/essentials/test/integration/source/TestSuiteUart.c @@ -0,0 +1,245 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * + * @brief + * Implements test cases for uart comminication verification + */ +/*###################### INCLUDED HEADERS ############################################################################*/ +#include "Kiso_Testing.h" +#include "Kiso_CmdProcessor.h" +#include "Kiso_MCU_UART.h" +#include "Kiso_BSP_GenericUart.h" +#include "TestSuiteUART.h" +#include +#include "FreeRTOS.h" +#include "semphr.h" +/*###################### MACROS DEFINITION ###########################################################################*/ +#undef KISO_MODULE_ID +#define KISO_MODULE_ID 0 + +#define UART_BUFFER_LEN 5 +#define DATA_TRANSFER_TIMEOUT_MS UINT32_C(1000) +#define UART_DEVICE UINT32_C(1) + +/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ +enum TestSuiteUart_TestCases_E +{ + TEST_CASE_FUNCTIONAL_TEST_ID = 1 +}; +/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ + +static Retcode_T TestCase_FctTest_Setup(CCMsg_T *ccmsg); +static void TestCase_FctTest_Run(CCMsg_T *ccmsg); +static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg); +static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event); + +/*###################### VARIABLES DECLARATION #######################################################################*/ + +/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ + +/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ + +static UART_T UartHdl = 0; +static xSemaphoreHandle UartLock = 0; + +Retcode_T TestSuiteUart_Initialize(uint8_t sId) +{ + Retcode_T retcode = RETCODE_OK; + + retcode = Tests_RegisterTestSuite(sId, TestCase_FctTest_Setup, TestCase_FctTest_Teardown); + + if (RETCODE_OK == retcode) + { + retcode = Tests_RegisterTestCase(sId, TEST_CASE_FUNCTIONAL_TEST_ID, TestCase_FctTest_Setup, TestCase_FctTest_Run, TestCase_FctTest_TearDown); + } + return retcode; +} + +/** + * @brief Performs the setup operation of the functional test of uart in interrupt mode + * @details This function initializes the uart interface in interrupt mode and creates the necessary + * synchronisation ressources. + */ +static Retcode_T TestCase_FctTest_Setup(CCMsg_T *ccmsg) +{ + KISO_UNUSED(ccmsg); + Retcode_T retcode = RETCODE_OK; + + UartHdl = (UART_T)BSP_GenericUart_GetHandle(UART_DEVICE); + if (NULL == UartHdl) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + } + if (RETCODE_OK == retcode) + { + UartLock = xSemaphoreCreateBinary(); + if (NULL == UartLock) + { + return RETCODE(RETCODE_SEVERITY_FATAL, RETCODE_SEMAPHORE_ERROR); + } + } + if (RETCODE_OK == retcode) + { + retcode = BSP_GenericUart_Connect(UART_DEVICE); + } + if (RETCODE_OK == retcode) + { + retcode = MCU_UART_Initialize(UartHdl, UartISRCallback); + } + if (RETCODE_OK == retcode) + { + retcode = BSP_GenericUart_Enable(UART_DEVICE); + } + return retcode; +} + +/** + * @brief Deinitializes the uart interface + */ +static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg) +{ + KISO_UNUSED(ccmsg); + Retcode_T retcode; + + retcode = MCU_UART_Deinitialize(UartHdl); + if (RETCODE_OK == retcode) + { + retcode = BSP_GenericUart_Disable(UART_DEVICE); + } + if (RETCODE_OK == retcode) + { + retcode = BSP_GenericUart_Disconnect(UART_DEVICE); + } + if (RETCODE_OK == retcode) + { + vSemaphoreDelete(UartLock); + } + return retcode; +} + +/** + * This Test will put the uart receiver into receive mode and send data via the transmitter the data will be + * looped back to the receiver at hardware level (e.g. wiring TX line to RX line) + * the test will succede if the transmit operation succeeded and if the received data matches the transmitted data + */ +static void TestCase_FctTest_Run(CCMsg_T *ccmsg) +{ + KISO_UNUSED(ccmsg); + + Retcode_T retcode; + uint8_t dataOut[UART_BUFFER_LEN]; + uint8_t dataIn[UART_BUFFER_LEN] = {0}; + char msg[30] = "SUCCESS"; + + for (uint8_t i = 0; i < UART_BUFFER_LEN; i++) + { + dataOut[i] = i; + } + + retcode = MCU_UART_Receive(UartHdl, dataIn, UART_BUFFER_LEN); + + if (RETCODE_OK == retcode) + { + retcode = MCU_UART_Send(UartHdl, dataOut, UART_BUFFER_LEN); + } + if (RETCODE_OK == retcode) + { + if (pdTRUE != xSemaphoreTake(UartLock, DATA_TRANSFER_TIMEOUT_MS)) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_SEMAPHORE_ERROR); + strcpy(msg, "FAIL"); + } + } + if (RETCODE_OK == retcode) + { + for (uint8_t i = 0; i < UART_BUFFER_LEN; i++) + { + if (dataIn[i] != dataOut[i]) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_UNEXPECTED_BEHAVIOR); + strcpy(msg, "FAIL"); + } + } + } + Tests_SendReport(Retcode_GetCode(retcode), msg); +} + +static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) +{ + KISO_UNUSED(uart); + Retcode_T Rc = RETCODE_OK; + + if (UINT8_C(1) == event.TxComplete) + { + + if (RETCODE_OK == Rc) + { + BaseType_t higherPriorityTaskWoken = pdFALSE; + + if (NULL != UartLock) + { + if (pdTRUE == xSemaphoreGiveFromISR(UartLock, &higherPriorityTaskWoken)) + { + portYIELD_FROM_ISR(higherPriorityTaskWoken); + } + else + { + /* ignore... semaphore has already been given */ + } + } + else + { + Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + } + } + } + + if (UINT8_C(1) == event.RxComplete) + { + + if (RETCODE_OK == Rc) + { + BaseType_t higherPriorityTaskWoken = pdFALSE; + + if (NULL != UartLock) + { + if (pdTRUE == xSemaphoreGiveFromISR(UartLock, &higherPriorityTaskWoken)) + { + portYIELD_FROM_ISR(higherPriorityTaskWoken); + } + else + { + /* ignore... semaphore has already been given */ + } + } + else + { + Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + } + } + } + + if (UINT8_C(1) == event.TxError) + { + Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); + } + + if (RETCODE_OK != Rc) + { + Retcode_RaiseErrorFromIsr(Rc); + } +} diff --git a/core/essentials/test/integration/source/TestSuiteUart.h b/core/essentials/test/integration/source/TestSuiteUart.h new file mode 100644 index 00000000..4319f201 --- /dev/null +++ b/core/essentials/test/integration/source/TestSuiteUart.h @@ -0,0 +1,55 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief Provides an API for the following functionality + * + */ +#ifndef TESTSUITE_UART_H_ +#define TESTSUITE_UART_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +/*###################### MACROS DEFINITION ###########################################################################*/ + +/*###################### TYPE DEFINITIONS ############################################################################*/ + +enum TestSuiteUart_Retcodes_E +{ + TESTSUITE_SETUP_TRIGGERED_SEVERAL_TIMES = RETCODE_FIRST_CUSTOM_CODE, + TESTSUITE_RUN_TRIGGERED_SEVERAL_TIMES, + TestSuite_Teardown_TRIGGERED_SEVERAL_TIMES, +}; + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Initializes the uart test suite + * @details This function will register the uart test suites in the Testing module TestSuites register and will + * also register for execution all the test cases belonging to this test suite + * @param id is the identifier to be given to the test suite it will be used in the communication protocol between + * the test executor and the test controller @see todo: add link to docu + */ +Retcode_T TestSuiteUart_Initialize(uint8_t id); + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +/** @} */ + +#endif /* TESTSUITE_UART_H_ */ diff --git a/core/essentials/test/integration/specs/I2C_Test_Spec.md b/core/essentials/test/integration/specs/I2C_Test_Spec.md new file mode 100644 index 00000000..2b5a1d83 --- /dev/null +++ b/core/essentials/test/integration/specs/I2C_Test_Spec.md @@ -0,0 +1,283 @@ +# Test Entry 3: "Essentials" + +## Test Suite 3.3: I2C_Selfcontained_Test +### Description + +* Purpose + + Test the I2C transfer functionality + +* Test participants: + - Device under Test (DUT): Stm32 - Bike Sensor Board (BSE) + - Test Coordinator: PC + +### Test Setup +The test setup consists of the test coordinator and 1 test participant + +* Test Coordinator (on PC) +* The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. +* The DUT is connected to a 12 V power source. +* For this selfcontained test, no external wires are necessary and only BSE-internal Sensors are polled via I2C. + +On the BSE testling, the initialization sequence setups the I2C Clock and the I2C GPIOs: + +* I2C SCL line: GPIO_PIN_14, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW +* I2C SDA line: GPIO_PIN_13, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW +* Alternative function: GPIO_AF4_I2C1 +* I2C Clock: PCLK1 +* enable GPIO bank G clock +* power up GPIO bank G (PWR_CR2_IOSV register) +* IRQs I2Cx_ER_IRQn and I2Cx_EV_IRQn are enabled + +### Teardown + +No special teardown + +### Test Cases + +#### TC 3.3.1 TestCase_I2C_initialize +##### Setup +No separate Test Case Setup required + +##### Run +1. For all present I2C Interfaces: BCDS_I2C1, BCDS_I2C2 + + * Call API: I2C_getVersion(), expect version + * Call API: I2C_getCapabilities(), expect only "address10bit" capability. + * Call API: I2C_initialize(), expect RETCODE_OK + * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_FULL), expect RETCODE_OK + * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_OFF), expect RETCODE_OK + * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_FULL), expect RETCODE_OK + * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_STANDARD), expect RETCODE_OK + * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_FAST), expect RETCODE_OK + * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_FAST_PLUS), expect RETCODE_OK + * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_OWN_ADDRESS,0x42), expect RETCODE_OK + * Call API: I2C_getStatus(), expect only flag "Initialized" + * Call API: I2C_uninitialize(), expect RETCODE_OK + +2. If all as expected, return Success. + +##### TearDown +No separate Test Case Tear Down required + +#### TC 3.3.2 TestCase_I2C_master_BMI160 + +On the BSE boardThe inertial measurement unit BMI160 is visible via BCDS_I2C1 on address 0x68. +To read a register, the register ID is first written via I2C to BMI160. +Maximum supported clock frequency is 1000khz, i.e., BCDS_I2C_BUS_SPEED_FAST_PLUS. + +##### Setup +Call API: I2C_initialize(), expect RETCODE_OK + +##### Run +1. Transmit 1 byte value 0 to BMI160 to access the CHIPID register: + + * Call API: I2C_masterTransmit(BCDS_I2C1 ,0x68, 0, 1, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. +2. Read 1 byte CHIPID value from BMI160: + + * Call API: I2C_masterReceive(BCDS_I2C1 ,0x68, &chipid, 1, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. + * Expect CHIPID to be ( 0b11010001 = 0xD1 ) +3. Transmit 1 byte value 0x04 to BMI160 to access the DATA registers: + + * Call API: I2C_masterTransmit(BCDS_I2C1 ,0x68, 0x04, 1, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. +4. Read 20 byte DATA register values from BMI160: + + * Call API: I2C_masterReceive(BCDS_I2C1 ,0x68, datareg, 20, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. + * Expect at least one DATA registers values to contain values different from 0 +5. If all as expected, return Success. + +##### TearDown +Call API: I2C_uninitialize(), expect RETCODE_OK + +## Test Suite 3.4: I2C_Arduino_Test +### Description + +1. Purpose: + + * Test the I2C transfer functionality with an Arduino counterpart that allows precise master AND slave communication tests. + +2. Test participants: + + * Test Coordinator: PC + * Device under Test (DUT): Stm32 - Bike Sensor Board (BSE) + * Arduino Due - the arduino code is contained in the Peripherals/test/integration/Testling/Tests/testSuiteI2c/Arduino_I2C_final_Testing_Code folder. + +### Setup +The test setup consists of the test coordinator and 2 test participants + +1. Test Coordinator (on PC) +2. The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. +3. The DUT is connected to a 12 V power source. +4. The Arduino is connected to the PC via UART to flash and power the Arduino board. +5. The Arduino testling code is flashed onto the Arduino. +6. The I2C bus I2C1 of the BSE board is led out and connected to the Arduino Due board such that: + + * BSE-PG14/C6 SCL line is connected to Arduino GPIO pin 21 + * BSE-PG13/C7 SDA line is connected to Arduino GPIO pin 20 + * a common ground line between the BSE board and the Arduino board is connected + +On the BSE testling, the initialization sequence setups the I2C Clock and the I2C GPIOs: + +7. I2C SCL line: GPIO_PIN_14, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW +8. I2C SDA line: GPIO_PIN_13, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW +9. Alternative function: GPIO_AF4_I2C1 +10. I2C Clock: PCLK1 +11. Enable GPIO bank G clock +12. Power up GPIO bank G (PWR_CR2_IOSV register) +13. IRQs I2Cx_ER_IRQn and I2Cx_EV_IRQn are enabled + +### Teardown +No special teardown + +### Test Cases + +#### TC 3.4.1 TestCase_I2C_master + +The arduino wire library has a maximum i2c buffer size of 32 bytes - so larger data streams cannot be tested. + +##### Setup +1. Call API: I2C_initialize(), expect RETCODE_OK +2. Send testcase ID (1) to arduino and expect acknowledge byte (1) + + * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 1, 1, 0); + * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); + * Expect arduinoAcknowledges == 1 +3. Call API: I2C_uninitialize(), expect RETCODE_OK +4. Call API: I2C_initialize(), expect RETCODE_OK + +##### Run +1. In Master mode send 1 byte to Arduino and receive it back: + + * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TESTBYTE, 1, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. + * Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, &receiveBuffer, 1, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. + * Verify (receiveBuffer == TESTBYTE) +2. In Master mode send 32 bytes to Arduino and receive them back + + * uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; + * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32, 0); + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. +3. Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32, 0); + + * Expect RETCODE_OK; + * Expect "TransferDone" Callback. +4. Verify (receiveBuffer32 identical to TEST32BYTES) +5. Return Success. + +##### TearDown +Call API: I2C_uninitialize(), expect RETCODE_OK + +#### TC 3.4.2 TestCase_I2C_slave_woCallback + +##### Setup +1. Call API: I2C_initialize(), expect RETCODE_OK +2. Send testcase ID (2) to arduino and expect acknowledge byte (1) + + * Byte to call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 2, 1, 0); + * Byte to call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); + * Expect arduinoAcknowledges == 1 +3. Call API: I2C_uninitialize(), expect RETCODE_OK +4. Call API: I2C_initialize(), expect RETCODE_OK +5. Set own slave address to 0x42 + +##### Run + +1. In Slave mode, wait for Arduino to request 1 byte, then wait until Arduino sends it back + + * Call API: I2C_slaveTransmit(BCDS_I2C1, TESTBYTE, 1); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Call API: I2C_slaveReceive(BCDS_I2C1, &receiveBuffer, 1); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Verify (receiveBuffer == TESTBYTE) +2. In Slave mode send 32 bytes to Arduino and receive them back + + * uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; + * Call API: I2C_slaveTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Call API: I2C_slaveReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Verify (receiveBuffer32 identical to TEST32BYTES) +3. Return Success. + +##### TearDown + +Call API: I2C_uninitialize(), expect RETCODE_OK + +#### TC 3.4.3 TestCase_I2C_slave_callbackDriven + +##### Setup +1. Call API: I2C_initialize(), expect RETCODE_OK +2. Send testcase ID (3) to arduino and expect acknowledge byte (1) + + * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 3, 1, 0); + * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); + * Expect arduinoAcknowledges == 1 +3. Call API: I2C_uninitialize(), expect RETCODE_OK + +##### Run +1. Define uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; +2. Call API: I2C_initialize(), expect RETCODE_OK +3. Set own slave address to 0x42 +4. Register new callback event handler that: + + * Answers a SlaveTransmit request with: I2C_slaveTransmit(BCDS_I2C1, TEST32BYTES, 32); + * expect RETCODE_OK; + * Answers a SlaveReceive request with: I2C_slaveReceive(BCDS_I2C1, receiveBuffer32, 32); + * expect RETCODE_OK; + * Updates 4-state-variable from waitForSlaveTransmit(0) to waitForSlaveReceive(1) to done(2) or alternatively error(3) +5. Wait with timeout until 4-state-variable goes through the states + + * WaitForSlaveTransmit(0) + * WaitForSlaveReceive(1) + * Done(2) +6. Make sure error(3) is not reached or return error-report. +7. When state done(2) is reached, verify (receiveBuffer32 identical to TEST32BYTES) +8. Return Success. + +##### TearDown + +Call API: I2C_uninitialize(), expect RETCODE_OK + +#### TC 3.4.4 TestCase_I2C_higherSpeeds + +##### Setup +1. Call API: I2C_initialize(), expect RETCODE_OK +2. Send testcase ID (4) to arduino and expect acknowledge byte (1) + + * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 4, 1, 0); + * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); + * Expect arduinoAcknowledges == 1 +3. Call API: I2C_uninitialize(), expect RETCODE_OK +4. Call API: I2C_initialize(), expect RETCODE_OK + +##### Run +1. uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; +2. Set the bus speeds [Standard Speed (100kHz), Fast Speed (400kHz), Fast+ Speed (1MHz)] try: + + * In Master mode send 32 bytes to Arduino and receive them back + * call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32, 0); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32, 0); + * expect RETCODE_OK; + * expect "TransferDone" Callback. + * Verify (receiveBuffer32 identical to TEST32BYTES) +3. Return Success. + +##### TearDown +Call API: I2C_uninitialize(), expect RETCODE_OK diff --git a/core/essentials/test/integration/specs/SPI_Test_Spec.md b/core/essentials/test/integration/specs/SPI_Test_Spec.md new file mode 100644 index 00000000..0542dfdb --- /dev/null +++ b/core/essentials/test/integration/specs/SPI_Test_Spec.md @@ -0,0 +1,207 @@ +Integration Test Specification for SPI + +# Test Suite 2: "Test Specification of SPI" + +## Description + +* Purpose + * Test the Peripheral SPI functionality +* Test participants which are involved + * Test participant :- BSE +* Test environment and setup + * BSE device be connected to the PC via USB Cable. + * A PC has a test coordinator. +* Setup for Flashing-Manual. + * SPI IntegrationTestApp binary file into BSE device + * once the flashing is done + * It automatically reset the device and IntegrationTestApps Boots-up + +## Setup +The test setup consists of the test coordinator and 2 test participants: + +* test coordinator (on PC) +* The test device BSE with the embedded C test participant + +The test device is connected to the test coordinator on the PC via UART as a Test Coordination Channel. + + +### Parameters +No special parameters + +## Teardown +* Deinitialize the SPI Port with SPI_deinitialize. + +## Test Suite 1.2: Non initialized - error test +### Description +* Test the SPI error cases for non initialized case + +### Setup +* No required separate Test Suite Setup for this example. + +### Teardown +* Not required separate Test Suite Tear Down for this example. + +### Test Cases + +#### TC 1.1.1 TestCase_SPI_Control_Error_unitialized +* Test case Setup + * Deinitialize the SPI. +* Test Case Run + * Parameter passed: valid handle, valid control, valid argument + * Run SPI_Control API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.1.2 TestCase_SPI_Send_Error_unitialized +* Test case Setup + * Deinitialize the SPI. +* Test Case Run + * Parameter passed: valid handle, valid sendValue, valid bufferSize + * Run SPI_Send API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.1.3 TestCase_SPI_Receive_Error_unitialized +* Test case Setup + * Deinitialize the SPI. +* Test Case Run + * Parameter passed: valid handle, valid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * No required separate Test Case Tear Down for this example. + +#### TC 1.1.4 TestCase_SPI_Transfer_Error_unitialized +* Test case Setup + * Deinitialize the SPI. +* Test Case Run + * Parameter passed: valid handle, valid sendValue, valid receiveValue, valid bufferSize + * Run SPI_Transfer API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * No required separate Test Case Tear Down for this example. + +################################################################################################################################## + + +#### TC 1.1.5 TestCase_SPI_Send_Error_invalidPort +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: invalid port, valid sendValue, valid bufferSize + * Run SPI_Send API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * No required separate Test Case Tear Down for this example. + +#### TC 1.1.6 TestCase_SPI_Send_Error_invalidSendValue +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, invalid sendValue, valid bufferSize + * Run SPI_Send API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.3 TestCase_SPI_Send_Error_invalidBufferSize +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, valid sendValue, invalid bufferSize + * Run SPI_Send API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.4 TestCase_SPI_Receive_Error_invalidPort +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: invalid port, valid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.5 TestCase_SPI_Receive_Error_invalidReceiveValue +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, invalid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.6 TestCase_SPI_Receive_Error_invalidReceiveValue +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, invalid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.7 TestCase_SPI_Transfer_Error_invalidPort +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: invalid port, valid sendValue, valid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.8 TestCase_SPI_Transfer_Error_invalidSendValue +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, invalid sendValue, valid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.9 TestCase_SPI_Transfer_Error_invalidReceiveValue +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, valid sendValue, invalid receiveValue, valid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +#### TC 1.2.10 TestCase_SPI_Transfer_Error_invalidBufferSize +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid port, valid sendValue, valid receiveValue, invalid bufferSize + * Run SPI_Receive API +* Expected return value from the API: RETCODE_FAILURE +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + + #### TC 1.2.11 TestCase_SPI_Control_Error_invalidParameter +* Test case Setup + * Initialize the SPI Port. +* Test Case Run + * Parameter passed: valid handle, invalid control, invalid argument + * Run SPI_Control API + * Expected return value from the API: RETCODE_NOT_SUPPORTED +* Test case Tear Down + * Not required separate Test Case Tear Down for this example. + +################################################################################################################################## + +## Test Suite 1.3: functional tests +### Description +* Currently it is not possible to have functional SPI tests, because there is neither implemented bus +partner for testing nor a possibility to test the GPIO pins. Instead, the (positive tests) +functionality will be tested by testing the external flash which is connected via SPI. +That means, if flash is working, SPI is working too. But that also means, if flash does not work, +its unclear if the error is in the flash driver or in the SPI driver. diff --git a/core/essentials/test/integration/specs/UART_Test_Spec.md b/core/essentials/test/integration/specs/UART_Test_Spec.md new file mode 100644 index 00000000..818e8a36 --- /dev/null +++ b/core/essentials/test/integration/specs/UART_Test_Spec.md @@ -0,0 +1,212 @@ +# Test Entry 3: "Essentials" +## Test Suite 3.1: UART +### Description +* This suite aims at testing the basic functionality of the UART APIs + +### Setup +The test setup consists of the test coordinator and 1 test participant + +* Test Coordinator (on PC) +* The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. + +### TearDown + +No special teardown + +### Test Cases + +#### TC 3.1.1:TestCase_Uart_getStatus +##### Setup +No special setup + +##### Run +1. Invoke the Uart_getStatus API +2. Expected return value from the API: HAL_UART_STATE_READY + +##### TearDown +No special teardown + +#### TC 3.1.2:TestCase_Uart_getVersion +##### Setup +No special setup + +##### Run +1. Invoke the Uart_getVersion API +2. API should return the version number of the UART + +##### TearDown +No special teardown + +#### TC 3.1.3:TestCase_Uart_getCapabilities +##### Setup +No special setup + +##### Run +1. Invoke the Uart_getCapabilities API +2. API should return the capabilities supported by the UART + +##### TearDown +No special teardown + + +#### TC 3.1.4:TestCase_Uart_initialize +##### Setup +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_initialize API +2. Expected return value from the API: RETCODE_SUCCESS + +##### TearDown +1. Invoke the Uart_uninitialize API +2. Expected return value from the API: RETCODE_SUCCESS + +#### TC 3.1.5:TestCase_Uart_control +##### Setup +No special setup + +##### Run +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS + +##### TearDown +No special teardown + + +#### TC 3.1.6:TestCase_Uart_send +##### Setup +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS +3. Invoke the Uart_initialize API +4. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_send API and send the data "BikeSensor" +2. Expected return value from the API: RETCODE_SUCCESS + +##### TearDown +1. Invoke the Uart_uninitialize API +2. Expected return value from the API: RETCODE_SUCCESS + +#### TC 3.1.7:TestCase_Uart_receive +##### Setup +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS +3. Invoke the Uart_initialize API +4. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_receive API +2. Expected return value from the API: RETCODE_FAILURE + +/* @todo: Expectation is a failure because no device is sending data to the DUT UART. Beaglebone has to be integrated for validating this test case */ + +##### TearDown +1. Invoke the Uart_uninitialize API +2. Expected return value from the API: RETCODE_SUCCESS + + +#### TC 3.1.8:TestCase_Uart_uninitialize +##### Setup +1. Invoke the Uart_control API with the following parameters + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_9 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 + * 9600 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS +3. Invoke the Uart_initialize API +4. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_uninitialize API +2. Expected return value from the API: RETCODE_SUCCESS + +##### TearDown +No special teardown + +#### TC 3.1.9:TestCase_Uart_powerControl_full +##### Setup +No special setup + +##### Run +1. Invoke the Uart_powerControl API with the following parameter + + * PERIPHERALS_POWER_STATE_FULL +2. Expected return value from the API: RETCODE_SUCCESS + +##### TearDown +No special teardown + +#### TC 3.1.10:TestCase_Uart_powerControl_low +##### Setup +No special setup + +##### Run +1. Invoke the Uart_powerControl API with the following parameter + + * PERIPHERALS_POWER_STATE_LOW +2. Expected return value from the API: RETCODE_FAILURE + +##### TearDown +No special teardown + +#### TC 3.1.11:Testcase_Uart_initialize_without_control +##### Setup +No special setup + +##### Run +1. Invoke the Uart_initialize API +2. Expected return value from the API:RETCODE_SUCCESS + +##### TearDown +1. Invoke the Uart_uninitialize API +2. Expected return value from the API: RETCODE_SUCCESS + +#### TC 3.1.12:Testcase_Uart_send_without_initialize +##### Setup +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_send API and send the data "Integration testing" +2. Expected return value from the API: RETCODE_FAILURE + +##### TearDown +No special teardown + +#### TC 3.1.13:Testcase_Uart_receive_without_initialize +##### Setup +1. Invoke the Uart_control API with the following parameters, + + * BCDS_Uart1 + * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE + * 115200 (Baud rate) +2. Expected return value from the API: RETCODE_SUCCESS + +##### Run +1. Invoke the Uart_receive API +2. Expected return value from the API: RETCODE_FAILURE + +##### TearDown +No special teardown \ No newline at end of file diff --git a/core/essentials/test/integration/test-protocol.txt b/core/essentials/test/integration/test-protocol.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/integration/readme.md b/testing/integration/readme.md new file mode 100644 index 00000000..127ae9a8 --- /dev/null +++ b/testing/integration/readme.md @@ -0,0 +1,8 @@ +# Integration Test Framework + +It is composed of the following packages: + +* Test-scheduler: Called in continuous integration. Defines where the tests should be run. +* Test-coordinator: Coordinate the build and execution of tests. +* Test-auxiliaries: List of elements that support more complex tests (example: communication tests between a cloud service and a device) +* Test-executor: Software that will be flashed on the device under test. diff --git a/testing/integration/test-auxiliary/readme.md b/testing/integration/test-auxiliary/readme.md new file mode 100644 index 00000000..a4822685 --- /dev/null +++ b/testing/integration/test-auxiliary/readme.md @@ -0,0 +1,3 @@ +# Test Auxiliary + +Contains different entities that will support the device under test to execute and verify the tests. diff --git a/testing/integration/test-coordinator/readme.md b/testing/integration/test-coordinator/readme.md new file mode 100644 index 00000000..7171acbf --- /dev/null +++ b/testing/integration/test-coordinator/readme.md @@ -0,0 +1,3 @@ +# Test Coordinator + +TBD diff --git a/testing/integration/test-executor/CMakeLists.txt b/testing/integration/test-executor/CMakeLists.txt new file mode 100644 index 00000000..04476bca --- /dev/null +++ b/testing/integration/test-executor/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.6) + +project("Kiso Integration Test Executor" C) + +if(${CMAKE_CROSSCOMPILING}) + set(APP_CONFIG_PATH ${CMAKE_CURRENT_LIST_DIR}/testapp/config PARENT_SCOPE) + + add_executable(TestExecutor + testapp/source/main.c + ) + + target_include_directories(TestExecutor PRIVATE source) + target_link_libraries(TestExecutor bsp essentials testing utils testentry ${KISO_OS_LIB} ${KISO_BOARD_LIBS}) + target_compile_definitions(TestExecutor PRIVATE ${KISO_BOARD_NAME}) + + add_custom_target(TestExecutor.bin ALL + COMMAND ${CMAKE_OBJCOPY} -O binary -R .usrpg $ ${CMAKE_CURRENT_BINARY_DIR}/TestExecutor.bin + COMMENT "Creating flashable binary ${CMAKE_CURRENT_BINARY_DIR}/TestExecutor.bin" + ) + add_dependencies(TestExecutor.bin TestExecutor) + + include(FlashTarget) + CREATE_FLASH_TARGET_JLINK(TestExecutor) +endif() diff --git a/testing/integration/test-executor/readme.md b/testing/integration/test-executor/readme.md new file mode 100644 index 00000000..ecb20e73 --- /dev/null +++ b/testing/integration/test-executor/readme.md @@ -0,0 +1,3 @@ +# Test Executor + +Embedded application linked with integration test fixtures and flashed onto the device under test. diff --git a/testing/integration/test-executor/testapp/config/utils/Kiso_UtilsConfig.h b/testing/integration/test-executor/testapp/config/utils/Kiso_UtilsConfig.h new file mode 100644 index 00000000..9c134ee5 --- /dev/null +++ b/testing/integration/test-executor/testapp/config/utils/Kiso_UtilsConfig.h @@ -0,0 +1,107 @@ +/******************************************************************************** +* Copyright (c) 2010-2019 Robert Bosch GmbH +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Robert Bosch GmbH - initial contribution +* +********************************************************************************/ + +/** + * @file + * @brief Utils config header. + * + * @details + * Provides configuration interface for the Utils components. + */ + +#ifndef KISO_UTILSCONFIG_H_ +#define KISO_UTILSCONFIG_H_ + +// clang-format off + +#ifndef KISO_FEATURE_CMDLINEDEBUGGER +/** @brief Enable (1) or disable (0) the CmdLineDebugger feature. */ +#define KISO_FEATURE_CMDLINEDEBUGGER 1 +#endif + +#ifndef KISO_FEATURE_CMDPROCESSOR +/** @brief Enable (1) or disable (0) the CmdProcessor feature. */ +#define KISO_FEATURE_CMDPROCESSOR 1 +#endif + +#ifndef KISO_FEATURE_CRC +/** @brief Enable (1) or disable (0) the Crc feature. */ +#define KISO_FEATURE_CRC 1 +#endif + +#ifndef KISO_FEATURE_EVENTHUB +/** @brief Enable (1) or disable (0) the EventHub feature. */ +#define KISO_FEATURE_EVENTHUB 1 +#endif + +#ifndef KISO_FEATURE_GUARDEDTASK +/** @brief Enable (1) or disable (0) the GuardedTask feature. */ +#define KISO_FEATURE_GUARDEDTASK 1 +#endif + +#ifndef KISO_FEATURE_ERRORLOGGER +/** @brief Enable (1) or disable (0) the ErrorLogger feature. */ +#define KISO_FEATURE_ERRORLOGGER 1 +#endif + +#ifndef KISO_FEATURE_LOGGING +/** @brief Enable (1) or disable (0) the Logging feature. */ +#define KISO_FEATURE_LOGGING 1 +#endif + +#ifndef KISO_FEATURE_RINGBUFFER +/** @brief Enable (1) or disable (0) the RingBuffer feature. */ +#define KISO_FEATURE_RINGBUFFER 1 +#endif + +#ifndef KISO_FEATURE_SLEEPCONTROL +/** @brief Enable (1) or disable (0) the SleepControl feature. */ +#define KISO_FEATURE_SLEEPCONTROL 0 +#endif + +#ifndef KISO_FEATURE_TASKMONITOR +/** @brief Enable (1) or disable (0) the TaskMonitor feature. */ +#define KISO_FEATURE_TASKMONITOR 1 +#endif + +#if KISO_FEATURE_TASKMONITOR + #ifndef KISO_TASKMONITOR_MAX_TASKS + /** @brief Maximum number of TaskMonitor tickets to reserve for the system. */ + #define KISO_TASKMONITOR_MAX_TASKS 10 + #endif +#endif /* if KISO_FEATURE_TASKMONITOR */ + +#ifndef KISO_FEATURE_UARTTRANSCEIVER +/** @brief Enable (1) or disable (0) the UartTransceiver feature. */ +#define KISO_FEATURE_UARTTRANSCEIVER 1 +#endif + +#ifndef KISO_FEATURE_I2CTRANSCEIVER +/** @brief Enable (1) or disable (0) the I2CTransceiver feature. */ +#define KISO_FEATURE_I2CTRANSCEIVER 1 +#endif + +#ifndef KISO_FEATURE_XPROTOCOL +/** @brief Enable (1) or disable (0) the XProtocol feature. */ +#define KISO_FEATURE_XPROTOCOL 1 +#endif + +#ifndef KISO_FEATURE_PIPEANDFILTER +/** @brief Enable (1) or disable (0) the pipe & filter pattern feature. */ +#define KISO_FEATURE_PIPEANDFILTER 1 +#endif + +// clang-format on + +#endif /* KISO_UTILSCONFIG_H_ */ diff --git a/testing/integration/test-executor/testapp/source/AppModules.h b/testing/integration/test-executor/testapp/source/AppModules.h new file mode 100644 index 00000000..cbd2681a --- /dev/null +++ b/testing/integration/test-executor/testapp/source/AppModules.h @@ -0,0 +1,17 @@ + +#ifndef APPMODULES_H_ +#define APPMODULES_H_ + +/** + * @brief Enumerates application modules which are reporting error codes according to RETCODE specification. + * @info usage: + * #undef KISO_APP_MODULE_ID + * #define KISO_APP_MODULE_ID APP_MODULE_ID_xxx + */ +enum App_ModuleID_E +{ + APP_MODULE_ID_MAIN = 1, + /* Define next module ID here and assign a value to it! */ +}; + +#endif diff --git a/testing/integration/test-executor/testapp/source/BSP_Proxy.h b/testing/integration/test-executor/testapp/source/BSP_Proxy.h new file mode 100644 index 00000000..08994ffc --- /dev/null +++ b/testing/integration/test-executor/testapp/source/BSP_Proxy.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial contribution + * + ******************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief Provides conversion from the bsp board interface to the testapp + * expected interface. + */ + +#if defined(CommonGateway) +#include "BSP_CommonGateway.h" +#elif defined(NucleoF767) +#include "BSP_NucleoF767.h" +#endif + +#if defined(CommonGateway) + +#define TEST_BOARD_LED_ALL COMMONGATEWAY_LED_ALL +#define TEST_BOARD_LED_PASS COMMONGATEWAY_LED_GREEN_ID +#define TEST_BOARD_LED_FAIL COMMONGATEWAY_LED_BLUE_ID +#define TEST_BOARD_LED_PANIC COMMONGATEWAY_LED_RED_ID +#define TEST_BOARD_LED_COMMAND_ON COMMONGATEWAY_LED_COMMAND_ON +#define TEST_BOARD_LED_COMMAND_OFF COMMONGATEWAY_LED_COMMAND_OFF +#define TEST_BOARD_LED_COMMAND_TOGGLE COMMONGATEWAY_LED_COMMAND_TOGGLE + +#elif defined(NucleoF767) + +#define TEST_BOARD_LED_ALL NUCLEOF767_LED_ALL +#define TEST_BOARD_LED_PASS NUCLEOF767_LED_GREEN_ID +#define TEST_BOARD_LED_FAIL NUCLEOF767_LED_BLUE_ID +#define TEST_BOARD_LED_PANIC NUCLEOF767_LED_RED_ID +#define TEST_BOARD_LED_COMMAND_ON NUCLEOF767_LED_COMMAND_ON +#define TEST_BOARD_LED_COMMAND_OFF NUCLEOF767_LED_COMMAND_OFF +#define TEST_BOARD_LED_COMMAND_TOGGLE NUCLEOF767_LED_COMMAND_TOGGLE + +#endif + +/** @} */ diff --git a/testing/integration/test-executor/testapp/source/main.c b/testing/integration/test-executor/testapp/source/main.c new file mode 100644 index 00000000..acad8f35 --- /dev/null +++ b/testing/integration/test-executor/testapp/source/main.c @@ -0,0 +1,260 @@ +/******************************************************************************* +* Copyright (c) 2010-2020 Robert Bosch GmbH +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0. +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Robert Bosch GmbH - initial contribution +* +*******************************************************************************/ + +/** + * @brief This file implements the #main() of the test executor software. After + * initializing the test board and starting the OS scheduler, it shall call the + * function in the feature integration tests responsible for initializing and + * starting the tests (e.g. #TestEntry_Initialize() under + * core/essentials/test/integration/source/TestEntry.c) + * + * @file + */ + +#include "AppModules.h" +#include "BSP_Proxy.h" +#include "Kiso_Retcode.h" +#include "Kiso_BSP_LED.h" +#include "Kiso_BSP_Board.h" +#include "Kiso_Basics.h" +#include "Kiso_CmdProcessor.h" +#include "FreeRTOS.h" +#include "task.h" +#include + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_APP_MODULE_MAIN + +#define TASK_PRIO_MAIN_CMD_PROCESSOR (UINT32_C(1)) +#define TASK_STACK_SIZE_MAIN_CMD_PROCESSOR (UINT16_C(700)) +#define TASK_Q_LEN_MAIN_CMD_PROCESSOR (UINT32_C(10)) + +extern void xPortSysTickHandler(void); /*link-time function to be provided by the freertos library */ +extern Retcode_T TestEntry_Initialize(void *param1, uint32_t param2); /* link-time function to be provided by the library implementing the feature integration tests*/ + +static Retcode_T systemStartup(void); +static void systemInit(void *param1, uint32_t param2); +static void ErrorHandler(Retcode_T error, bool isfromIsr); +static void assertIndicationMapping(const unsigned long line, const unsigned char *const file); +static void SysTickPreCallback(void); + +#if configSUPPORT_STATIC_ALLOCATION +void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, + StackType_t **ppxIdleTaskStackBuffer, + uint32_t *pulIdleTaskStackSize); +#endif +#if configSUPPORT_STATIC_ALLOCATION && configUSE_TIMERS +void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, + StackType_t **ppxTimerTaskStackBuffer, + uint32_t *pulTimerTaskStackSize); +#endif + +static CmdProcessor_T MainCmdProcessor; + +int main(void) +{ + /* Mapping Default Error Handling function */ + Retcode_T retcode = Retcode_Initialize(ErrorHandler); + +#ifndef NDEBUG + if (RETCODE_OK == retcode) + { + retcode = Assert_Initialize(assertIndicationMapping); + } +#endif /*NDEBUG*/ + if (RETCODE_OK == retcode) + { + retcode = systemStartup(); + } + if (RETCODE_OK == retcode) + { + retcode = CmdProcessor_Initialize(&MainCmdProcessor, + (char *)"MainCmdProcessor", + TASK_PRIO_MAIN_CMD_PROCESSOR, + TASK_STACK_SIZE_MAIN_CMD_PROCESSOR, + TASK_Q_LEN_MAIN_CMD_PROCESSOR); + } + if (RETCODE_OK == retcode) + { + /* Here we enqueue the application initialization into the command + * processor, such that the initialization function will be invoked + * once the RTOS scheduler is started below. + */ + retcode = CmdProcessor_Enqueue(&MainCmdProcessor, + systemInit, + &MainCmdProcessor, + UINT32_C(0)); + } + + if (RETCODE_OK != retcode) + { + printf("System Startup failed"); + assert(false); + } + /* start scheduler */ + vTaskStartScheduler(); +} + +/** + * @brief Starts the system up. + * @details This function will execute before the scheduler starts and it is + * intended to make the target board ready for operation. + * @return Returns RETCODE_OK in case of success, error code otherwise. + */ +Retcode_T systemStartup(void) +{ + Retcode_T retcode = RETCODE_OK; + uint32_t param1 = 0; + void *param2 = NULL; + + /* Initialize the callbacks for the system tick */ + BSP_Board_OSTickInitialize(SysTickPreCallback, NULL); + retcode = BSP_Board_Initialize(param1, param2); + if (RETCODE_OK == retcode) + { + retcode = BSP_LED_Connect(); + } + if (RETCODE_OK == retcode) + { + retcode = BSP_LED_Enable(TEST_BOARD_LED_ALL); + } + if (RETCODE_OK == retcode) + { + retcode = BSP_LED_Switch(TEST_BOARD_LED_ALL, TEST_BOARD_LED_COMMAND_ON); + } + return retcode; +} + +void systemInit(void *param1, uint32_t param2) +{ + KISO_UNUSED(param1); + KISO_UNUSED(param2); + Retcode_T retcode = TestEntry_Initialize(NULL, 0U); + if (RETCODE_OK != retcode) + { + Retcode_RaiseError(retcode); + } +} + +/** + * @brief Error handler function. + * @details This function is called when Retcode_RaiseError() function is + * invoked, it is used to report the error to the user. + * @param[in] error Error code raised. + * @param[in] isfromIsr if true then the ErrorHandler is being executed from an + * ISR context and not all the services are available. + */ +void ErrorHandler(Retcode_T error, bool isfromIsr) +{ + if (!isfromIsr) + { + /** \todo: ERROR HANDLING SHOULD BE DONE FOR THE ERRORS RAISED FROM PLATFORM */ + uint32_t PackageID = Retcode_GetPackage(error); + uint32_t ErrorCode = Retcode_GetCode(error); + uint32_t ModuleID = Retcode_GetModuleId(error); + Retcode_Severity_T SeverityCode = Retcode_GetSeverity(error); + + if (RETCODE_SEVERITY_FATAL == SeverityCode) + { + printf("Fatal Error:[%u] from Module:[%u] in Package:[%u]\r\n", + (unsigned int)ErrorCode, + (unsigned int)ModuleID, + (unsigned int)PackageID); + } + else if (RETCODE_SEVERITY_ERROR == SeverityCode) + { + printf("Severe Error:[%u] from Module:[%u] in Package:[%u]\r\n", + (unsigned int)ErrorCode, + (unsigned int)ModuleID, + (unsigned int)PackageID); + } + BSP_LED_Switch(TEST_BOARD_LED_ALL, TEST_BOARD_LED_COMMAND_OFF); + BSP_LED_Switch(TEST_BOARD_LED_PANIC, TEST_BOARD_LED_COMMAND_ON); + } + else + { + + BSP_LED_Switch(TEST_BOARD_LED_ALL, TEST_BOARD_LED_COMMAND_OFF); + BSP_LED_Switch(TEST_BOARD_LED_PANIC, TEST_BOARD_LED_COMMAND_ON); + } +} + +#ifndef NDEBUG +/** + * @brief This API is called when function enters an assert + * @param[in] line line number where asserted. + * @param[in] file file name which is asserted. + */ +void assertIndicationMapping(const unsigned long line, const unsigned char *const file) +{ + (void)BSP_LED_Switch(TEST_BOARD_LED_ALL, TEST_BOARD_LED_COMMAND_ON); + printf("asserted at Filename %s , line no %ld \n\r", file, line); +} +#endif /* NDEBUG */ + +/** + * @brief This function is a hook from FreeRTOS to systick. + * @details This function is called when ever the Systick IRQ is hit. This is a + * temporary implementation where the #SysTick_Handler() is not directly mapped + * to #xPortSysTickHandler(). Instead it is only called if the scheduler has + * started. + */ +static void SysTickPreCallback(void) +{ + if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) + { + xPortSysTickHandler(); + } +} + +#if configSUPPORT_STATIC_ALLOCATION + +static StaticTask_t xIdleTaskTCBBuffer; +static StackType_t xIdleStack[IDLE_TASK_SIZE]; + +/** + * @brief If static allocation is supported then the application must provide + * the following callback function + * @details Enables the application to optionally provide the memory that will + * be used by the idle task as the task's stack and TCB. + */ +void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, + StackType_t **ppxIdleTaskStackBuffer, + uint32_t *pulIdleTaskStackSize) +{ + *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; + *ppxIdleTaskStackBuffer = &xIdleStack[0]; + *pulIdleTaskStackSize = IDLE_TASK_SIZE; +} +#endif + +#if configSUPPORT_STATIC_ALLOCATION && configUSE_TIMERS +static StaticTask_t xTimerTaskTCBBuffer; +static StackType_t xTimerStack[configTIMER_TASK_STACK_DEPTH]; + +/** + * @brief If static allocation and timers are supported then the application + * must provide the following callback function + * @details Enables the application to optionally provide the memory that will + * be used by the timer task as the task's stack and TCB. + */ +void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, + StackType_t **ppxTimerTaskStackBuffer, + uint32_t *pulTimerTaskStackSize) +{ + *ppxTimerTaskTCBBuffer = &xTimerTaskTCBBuffer; + *ppxTimerTaskStackBuffer = &xTimerStack[0]; + *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; +} +#endif diff --git a/testing/integration/test-scheduler/readme.md b/testing/integration/test-scheduler/readme.md new file mode 100644 index 00000000..d049eab1 --- /dev/null +++ b/testing/integration/test-scheduler/readme.md @@ -0,0 +1,3 @@ +# Test Scheduler + +Schedules which tests should be executed where. Represents the link to the continuous integration setup on Jenkins. From 814f7209d14bf5727614e29dc1664ebefe01c3c6 Mon Sep 17 00:00:00 2001 From: "Mohamed Ali Khalifi (AE/PJ-SW4)" Date: Tue, 11 Feb 2020 10:27:30 +0100 Subject: [PATCH 02/29] Add testing to core Co-authored-by: ChiefGokhlayehBosch Signed-off-by: ChiefGokhlayehBosch --- core/essentials/test/integration/readme.md | 12 +- .../test/integration/source/TestSuiteUart.c | 40 +- .../test/integration/source/TestSuiteUart.h | 28 +- .../test/integration/specs/I2C_Test_Spec.md | 283 -------------- .../test/integration/specs/SPI_Test_Spec.md | 207 ---------- .../test/integration/specs/UART_Test_Spec.md | 218 ++--------- core/testing/CMakeLists.txt | 40 ++ core/testing/include/Kiso_Testing.h | 192 +++++++++ core/testing/include/Kiso_Testing_Config.h | 54 +++ core/testing/source/CChannel.c | 312 +++++++++++++++ core/testing/source/Serial.c | 219 +++++++++++ core/testing/source/SerialMsgTransceiver.c | 258 +++++++++++++ core/testing/source/TestRegistry.c | 339 ++++++++++++++++ core/testing/source/TestRunner.c | 363 ++++++++++++++++++ core/testing/source/Testing.c | 74 ++++ core/testing/source/protected/CChannel.h | 162 ++++++++ .../testing/source/protected/SerialCChannel.h | 77 ++++ .../source/protected/SerialMsgTransceiver.h | 58 +++ core/testing/source/protected/TestRegistry.h | 197 ++++++++++ core/testing/source/protected/TestRunner.h | 62 +++ 20 files changed, 2465 insertions(+), 730 deletions(-) delete mode 100644 core/essentials/test/integration/specs/I2C_Test_Spec.md delete mode 100644 core/essentials/test/integration/specs/SPI_Test_Spec.md create mode 100644 core/testing/CMakeLists.txt create mode 100644 core/testing/include/Kiso_Testing.h create mode 100644 core/testing/include/Kiso_Testing_Config.h create mode 100644 core/testing/source/CChannel.c create mode 100644 core/testing/source/Serial.c create mode 100644 core/testing/source/SerialMsgTransceiver.c create mode 100644 core/testing/source/TestRegistry.c create mode 100644 core/testing/source/TestRunner.c create mode 100644 core/testing/source/Testing.c create mode 100644 core/testing/source/protected/CChannel.h create mode 100644 core/testing/source/protected/SerialCChannel.h create mode 100644 core/testing/source/protected/SerialMsgTransceiver.h create mode 100644 core/testing/source/protected/TestRegistry.h create mode 100644 core/testing/source/protected/TestRunner.h diff --git a/core/essentials/test/integration/readme.md b/core/essentials/test/integration/readme.md index 38315cda..aa2d943b 100644 --- a/core/essentials/test/integration/readme.md +++ b/core/essentials/test/integration/readme.md @@ -1,7 +1,7 @@ -# Integration Test Framework +# Integration Tests: Essentials -It is composed by the following packages: -* Test-scheduler: Called in the continious integration. Define where the tests should be run. -* Test-coordinator: Coordiante the build and execution of the tests. -* Test-auxiliaries: List of elements that supports more complex tests (example: communication tests between a cloud service and a device) -* Test-executor: Software that will be flashed on the device under test. \ No newline at end of file +This is the integration test package for Essentials. It contains integration test suites for: + +* UART - testing sending and receiving data over standard UART. + +Check `TestEntry.c` to see which integration test suites are loaded. diff --git a/core/essentials/test/integration/source/TestSuiteUart.c b/core/essentials/test/integration/source/TestSuiteUart.c index 78019fb9..2ea102f4 100644 --- a/core/essentials/test/integration/source/TestSuiteUart.c +++ b/core/essentials/test/integration/source/TestSuiteUart.c @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -18,41 +18,33 @@ * @brief * Implements test cases for uart comminication verification */ -/*###################### INCLUDED HEADERS ############################################################################*/ +#include "TestSuiteUart.h" +#include "Kiso_Basics.h" #include "Kiso_Testing.h" #include "Kiso_CmdProcessor.h" #include "Kiso_MCU_UART.h" #include "Kiso_BSP_GenericUart.h" -#include "TestSuiteUART.h" -#include #include "FreeRTOS.h" #include "semphr.h" -/*###################### MACROS DEFINITION ###########################################################################*/ + #undef KISO_MODULE_ID #define KISO_MODULE_ID 0 -#define UART_BUFFER_LEN 5 +#define UART_BUFFER_LEN (5) #define DATA_TRANSFER_TIMEOUT_MS UINT32_C(1000) #define UART_DEVICE UINT32_C(1) +#define MSG_BUFFER_SIZE (32) -/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ enum TestSuiteUart_TestCases_E { TEST_CASE_FUNCTIONAL_TEST_ID = 1 }; -/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ static Retcode_T TestCase_FctTest_Setup(CCMsg_T *ccmsg); static void TestCase_FctTest_Run(CCMsg_T *ccmsg); static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg); static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event); -/*###################### VARIABLES DECLARATION #######################################################################*/ - -/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ - -/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ - static UART_T UartHdl = 0; static xSemaphoreHandle UartLock = 0; @@ -64,14 +56,14 @@ Retcode_T TestSuiteUart_Initialize(uint8_t sId) if (RETCODE_OK == retcode) { - retcode = Tests_RegisterTestCase(sId, TEST_CASE_FUNCTIONAL_TEST_ID, TestCase_FctTest_Setup, TestCase_FctTest_Run, TestCase_FctTest_TearDown); + retcode = Tests_RegisterTestCase(sId, TEST_CASE_FUNCTIONAL_TEST_ID, TestCase_FctTest_Setup, TestCase_FctTest_Run, TestCase_FctTest_Teardown); } return retcode; } /** - * @brief Performs the setup operation of the functional test of uart in interrupt mode - * @details This function initializes the uart interface in interrupt mode and creates the necessary + * @brief Performs the setup operation of the functional test of uart in interrupt mode + * @details This function initializes the uart interface in interrupt mode and creates the necessary * synchronisation ressources. */ static Retcode_T TestCase_FctTest_Setup(CCMsg_T *ccmsg) @@ -132,7 +124,7 @@ static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg) } /** - * This Test will put the uart receiver into receive mode and send data via the transmitter the data will be + * This Test will put the uart receiver into receive mode and send data via the transmitter the data will be * looped back to the receiver at hardware level (e.g. wiring TX line to RX line) * the test will succede if the transmit operation succeeded and if the received data matches the transmitted data */ @@ -143,7 +135,7 @@ static void TestCase_FctTest_Run(CCMsg_T *ccmsg) Retcode_T retcode; uint8_t dataOut[UART_BUFFER_LEN]; uint8_t dataIn[UART_BUFFER_LEN] = {0}; - char msg[30] = "SUCCESS"; + char msg[MSG_BUFFER_SIZE] = "SUCCESS"; for (uint8_t i = 0; i < UART_BUFFER_LEN; i++) { diff --git a/core/essentials/test/integration/source/TestSuiteUart.h b/core/essentials/test/integration/source/TestSuiteUart.h index 4319f201..f170590e 100644 --- a/core/essentials/test/integration/source/TestSuiteUart.h +++ b/core/essentials/test/integration/source/TestSuiteUart.h @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -19,16 +19,12 @@ * @{ * * @brief Provides an API for the following functionality - * + * */ #ifndef TESTSUITE_UART_H_ #define TESTSUITE_UART_H_ -/*###################### INCLUDED HEADERS ############################################################################*/ - -/*###################### MACROS DEFINITION ###########################################################################*/ - -/*###################### TYPE DEFINITIONS ############################################################################*/ +#include "Kiso_Retcode.h" enum TestSuiteUart_Retcodes_E { @@ -37,19 +33,15 @@ enum TestSuiteUart_Retcodes_E TestSuite_Teardown_TRIGGERED_SEVERAL_TIMES, }; -/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ - /** * @brief Initializes the uart test suite - * @details This function will register the uart test suites in the Testing module TestSuites register and will - * also register for execution all the test cases belonging to this test suite + * @details This function will register the uart test suites in the Testing module TestSuites register and will + * also register for execution all the test cases belonging to this test suite * @param id is the identifier to be given to the test suite it will be used in the communication protocol between * the test executor and the test controller @see todo: add link to docu */ Retcode_T TestSuiteUart_Initialize(uint8_t id); -/*###################### GLOBAL VARIABLES ###########################################################################*/ - /** @} */ #endif /* TESTSUITE_UART_H_ */ diff --git a/core/essentials/test/integration/specs/I2C_Test_Spec.md b/core/essentials/test/integration/specs/I2C_Test_Spec.md deleted file mode 100644 index 2b5a1d83..00000000 --- a/core/essentials/test/integration/specs/I2C_Test_Spec.md +++ /dev/null @@ -1,283 +0,0 @@ -# Test Entry 3: "Essentials" - -## Test Suite 3.3: I2C_Selfcontained_Test -### Description - -* Purpose - + Test the I2C transfer functionality - -* Test participants: - - Device under Test (DUT): Stm32 - Bike Sensor Board (BSE) - - Test Coordinator: PC - -### Test Setup -The test setup consists of the test coordinator and 1 test participant - -* Test Coordinator (on PC) -* The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. -* The DUT is connected to a 12 V power source. -* For this selfcontained test, no external wires are necessary and only BSE-internal Sensors are polled via I2C. - -On the BSE testling, the initialization sequence setups the I2C Clock and the I2C GPIOs: - -* I2C SCL line: GPIO_PIN_14, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW -* I2C SDA line: GPIO_PIN_13, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW -* Alternative function: GPIO_AF4_I2C1 -* I2C Clock: PCLK1 -* enable GPIO bank G clock -* power up GPIO bank G (PWR_CR2_IOSV register) -* IRQs I2Cx_ER_IRQn and I2Cx_EV_IRQn are enabled - -### Teardown - -No special teardown - -### Test Cases - -#### TC 3.3.1 TestCase_I2C_initialize -##### Setup -No separate Test Case Setup required - -##### Run -1. For all present I2C Interfaces: BCDS_I2C1, BCDS_I2C2 - - * Call API: I2C_getVersion(), expect version - * Call API: I2C_getCapabilities(), expect only "address10bit" capability. - * Call API: I2C_initialize(), expect RETCODE_OK - * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_FULL), expect RETCODE_OK - * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_OFF), expect RETCODE_OK - * Call API: I2C_powerControl(PERIPHERALS_POWER_STATE_FULL), expect RETCODE_OK - * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_STANDARD), expect RETCODE_OK - * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_FAST), expect RETCODE_OK - * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_BUS_SPEED_FAST_PLUS), expect RETCODE_OK - * Call API: I2C_control(BCDS_I2C_BUS_SPEED,BCDS_I2C_OWN_ADDRESS,0x42), expect RETCODE_OK - * Call API: I2C_getStatus(), expect only flag "Initialized" - * Call API: I2C_uninitialize(), expect RETCODE_OK - -2. If all as expected, return Success. - -##### TearDown -No separate Test Case Tear Down required - -#### TC 3.3.2 TestCase_I2C_master_BMI160 - -On the BSE boardThe inertial measurement unit BMI160 is visible via BCDS_I2C1 on address 0x68. -To read a register, the register ID is first written via I2C to BMI160. -Maximum supported clock frequency is 1000khz, i.e., BCDS_I2C_BUS_SPEED_FAST_PLUS. - -##### Setup -Call API: I2C_initialize(), expect RETCODE_OK - -##### Run -1. Transmit 1 byte value 0 to BMI160 to access the CHIPID register: - - * Call API: I2C_masterTransmit(BCDS_I2C1 ,0x68, 0, 1, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. -2. Read 1 byte CHIPID value from BMI160: - - * Call API: I2C_masterReceive(BCDS_I2C1 ,0x68, &chipid, 1, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. - * Expect CHIPID to be ( 0b11010001 = 0xD1 ) -3. Transmit 1 byte value 0x04 to BMI160 to access the DATA registers: - - * Call API: I2C_masterTransmit(BCDS_I2C1 ,0x68, 0x04, 1, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. -4. Read 20 byte DATA register values from BMI160: - - * Call API: I2C_masterReceive(BCDS_I2C1 ,0x68, datareg, 20, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. - * Expect at least one DATA registers values to contain values different from 0 -5. If all as expected, return Success. - -##### TearDown -Call API: I2C_uninitialize(), expect RETCODE_OK - -## Test Suite 3.4: I2C_Arduino_Test -### Description - -1. Purpose: - - * Test the I2C transfer functionality with an Arduino counterpart that allows precise master AND slave communication tests. - -2. Test participants: - - * Test Coordinator: PC - * Device under Test (DUT): Stm32 - Bike Sensor Board (BSE) - * Arduino Due - the arduino code is contained in the Peripherals/test/integration/Testling/Tests/testSuiteI2c/Arduino_I2C_final_Testing_Code folder. - -### Setup -The test setup consists of the test coordinator and 2 test participants - -1. Test Coordinator (on PC) -2. The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. -3. The DUT is connected to a 12 V power source. -4. The Arduino is connected to the PC via UART to flash and power the Arduino board. -5. The Arduino testling code is flashed onto the Arduino. -6. The I2C bus I2C1 of the BSE board is led out and connected to the Arduino Due board such that: - - * BSE-PG14/C6 SCL line is connected to Arduino GPIO pin 21 - * BSE-PG13/C7 SDA line is connected to Arduino GPIO pin 20 - * a common ground line between the BSE board and the Arduino board is connected - -On the BSE testling, the initialization sequence setups the I2C Clock and the I2C GPIOs: - -7. I2C SCL line: GPIO_PIN_14, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW -8. I2C SDA line: GPIO_PIN_13, Bank GPIOG, mode GPIO_PULLUP, GPIO_SPEED_LOW -9. Alternative function: GPIO_AF4_I2C1 -10. I2C Clock: PCLK1 -11. Enable GPIO bank G clock -12. Power up GPIO bank G (PWR_CR2_IOSV register) -13. IRQs I2Cx_ER_IRQn and I2Cx_EV_IRQn are enabled - -### Teardown -No special teardown - -### Test Cases - -#### TC 3.4.1 TestCase_I2C_master - -The arduino wire library has a maximum i2c buffer size of 32 bytes - so larger data streams cannot be tested. - -##### Setup -1. Call API: I2C_initialize(), expect RETCODE_OK -2. Send testcase ID (1) to arduino and expect acknowledge byte (1) - - * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 1, 1, 0); - * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); - * Expect arduinoAcknowledges == 1 -3. Call API: I2C_uninitialize(), expect RETCODE_OK -4. Call API: I2C_initialize(), expect RETCODE_OK - -##### Run -1. In Master mode send 1 byte to Arduino and receive it back: - - * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TESTBYTE, 1, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. - * Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, &receiveBuffer, 1, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. - * Verify (receiveBuffer == TESTBYTE) -2. In Master mode send 32 bytes to Arduino and receive them back - - * uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; - * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32, 0); - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. -3. Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32, 0); - - * Expect RETCODE_OK; - * Expect "TransferDone" Callback. -4. Verify (receiveBuffer32 identical to TEST32BYTES) -5. Return Success. - -##### TearDown -Call API: I2C_uninitialize(), expect RETCODE_OK - -#### TC 3.4.2 TestCase_I2C_slave_woCallback - -##### Setup -1. Call API: I2C_initialize(), expect RETCODE_OK -2. Send testcase ID (2) to arduino and expect acknowledge byte (1) - - * Byte to call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 2, 1, 0); - * Byte to call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); - * Expect arduinoAcknowledges == 1 -3. Call API: I2C_uninitialize(), expect RETCODE_OK -4. Call API: I2C_initialize(), expect RETCODE_OK -5. Set own slave address to 0x42 - -##### Run - -1. In Slave mode, wait for Arduino to request 1 byte, then wait until Arduino sends it back - - * Call API: I2C_slaveTransmit(BCDS_I2C1, TESTBYTE, 1); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Call API: I2C_slaveReceive(BCDS_I2C1, &receiveBuffer, 1); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Verify (receiveBuffer == TESTBYTE) -2. In Slave mode send 32 bytes to Arduino and receive them back - - * uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; - * Call API: I2C_slaveTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Call API: I2C_slaveReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Verify (receiveBuffer32 identical to TEST32BYTES) -3. Return Success. - -##### TearDown - -Call API: I2C_uninitialize(), expect RETCODE_OK - -#### TC 3.4.3 TestCase_I2C_slave_callbackDriven - -##### Setup -1. Call API: I2C_initialize(), expect RETCODE_OK -2. Send testcase ID (3) to arduino and expect acknowledge byte (1) - - * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 3, 1, 0); - * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); - * Expect arduinoAcknowledges == 1 -3. Call API: I2C_uninitialize(), expect RETCODE_OK - -##### Run -1. Define uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; -2. Call API: I2C_initialize(), expect RETCODE_OK -3. Set own slave address to 0x42 -4. Register new callback event handler that: - - * Answers a SlaveTransmit request with: I2C_slaveTransmit(BCDS_I2C1, TEST32BYTES, 32); - * expect RETCODE_OK; - * Answers a SlaveReceive request with: I2C_slaveReceive(BCDS_I2C1, receiveBuffer32, 32); - * expect RETCODE_OK; - * Updates 4-state-variable from waitForSlaveTransmit(0) to waitForSlaveReceive(1) to done(2) or alternatively error(3) -5. Wait with timeout until 4-state-variable goes through the states - - * WaitForSlaveTransmit(0) - * WaitForSlaveReceive(1) - * Done(2) -6. Make sure error(3) is not reached or return error-report. -7. When state done(2) is reached, verify (receiveBuffer32 identical to TEST32BYTES) -8. Return Success. - -##### TearDown - -Call API: I2C_uninitialize(), expect RETCODE_OK - -#### TC 3.4.4 TestCase_I2C_higherSpeeds - -##### Setup -1. Call API: I2C_initialize(), expect RETCODE_OK -2. Send testcase ID (4) to arduino and expect acknowledge byte (1) - - * Call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, 4, 1, 0); - * Call API: I2C_masterRecieve (BCDS_I2C1, ARDUINO_I2C_ADDRESS, &arduinoAcknowledges, 1, 0); - * Expect arduinoAcknowledges == 1 -3. Call API: I2C_uninitialize(), expect RETCODE_OK -4. Call API: I2C_initialize(), expect RETCODE_OK - -##### Run -1. uint8_t TEST32BYTES[] = {42, 43, ..., 73 }; -2. Set the bus speeds [Standard Speed (100kHz), Fast Speed (400kHz), Fast+ Speed (1MHz)] try: - - * In Master mode send 32 bytes to Arduino and receive them back - * call API: I2C_masterTransmit(BCDS_I2C1, ARDUINO_I2C_ADDRESS, TEST32BYTES, 32, 0); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Call API: I2C_masterReceive(BCDS_I2C1, ARDUINO_I2C_ADDRESS, receiveBuffer32, 32, 0); - * expect RETCODE_OK; - * expect "TransferDone" Callback. - * Verify (receiveBuffer32 identical to TEST32BYTES) -3. Return Success. - -##### TearDown -Call API: I2C_uninitialize(), expect RETCODE_OK diff --git a/core/essentials/test/integration/specs/SPI_Test_Spec.md b/core/essentials/test/integration/specs/SPI_Test_Spec.md deleted file mode 100644 index 0542dfdb..00000000 --- a/core/essentials/test/integration/specs/SPI_Test_Spec.md +++ /dev/null @@ -1,207 +0,0 @@ -Integration Test Specification for SPI - -# Test Suite 2: "Test Specification of SPI" - -## Description - -* Purpose - * Test the Peripheral SPI functionality -* Test participants which are involved - * Test participant :- BSE -* Test environment and setup - * BSE device be connected to the PC via USB Cable. - * A PC has a test coordinator. -* Setup for Flashing-Manual. - * SPI IntegrationTestApp binary file into BSE device - * once the flashing is done - * It automatically reset the device and IntegrationTestApps Boots-up - -## Setup -The test setup consists of the test coordinator and 2 test participants: - -* test coordinator (on PC) -* The test device BSE with the embedded C test participant - -The test device is connected to the test coordinator on the PC via UART as a Test Coordination Channel. - - -### Parameters -No special parameters - -## Teardown -* Deinitialize the SPI Port with SPI_deinitialize. - -## Test Suite 1.2: Non initialized - error test -### Description -* Test the SPI error cases for non initialized case - -### Setup -* No required separate Test Suite Setup for this example. - -### Teardown -* Not required separate Test Suite Tear Down for this example. - -### Test Cases - -#### TC 1.1.1 TestCase_SPI_Control_Error_unitialized -* Test case Setup - * Deinitialize the SPI. -* Test Case Run - * Parameter passed: valid handle, valid control, valid argument - * Run SPI_Control API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.1.2 TestCase_SPI_Send_Error_unitialized -* Test case Setup - * Deinitialize the SPI. -* Test Case Run - * Parameter passed: valid handle, valid sendValue, valid bufferSize - * Run SPI_Send API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.1.3 TestCase_SPI_Receive_Error_unitialized -* Test case Setup - * Deinitialize the SPI. -* Test Case Run - * Parameter passed: valid handle, valid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * No required separate Test Case Tear Down for this example. - -#### TC 1.1.4 TestCase_SPI_Transfer_Error_unitialized -* Test case Setup - * Deinitialize the SPI. -* Test Case Run - * Parameter passed: valid handle, valid sendValue, valid receiveValue, valid bufferSize - * Run SPI_Transfer API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * No required separate Test Case Tear Down for this example. - -################################################################################################################################## - - -#### TC 1.1.5 TestCase_SPI_Send_Error_invalidPort -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: invalid port, valid sendValue, valid bufferSize - * Run SPI_Send API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * No required separate Test Case Tear Down for this example. - -#### TC 1.1.6 TestCase_SPI_Send_Error_invalidSendValue -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, invalid sendValue, valid bufferSize - * Run SPI_Send API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.3 TestCase_SPI_Send_Error_invalidBufferSize -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, valid sendValue, invalid bufferSize - * Run SPI_Send API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.4 TestCase_SPI_Receive_Error_invalidPort -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: invalid port, valid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.5 TestCase_SPI_Receive_Error_invalidReceiveValue -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, invalid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.6 TestCase_SPI_Receive_Error_invalidReceiveValue -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, invalid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.7 TestCase_SPI_Transfer_Error_invalidPort -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: invalid port, valid sendValue, valid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.8 TestCase_SPI_Transfer_Error_invalidSendValue -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, invalid sendValue, valid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.9 TestCase_SPI_Transfer_Error_invalidReceiveValue -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, valid sendValue, invalid receiveValue, valid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -#### TC 1.2.10 TestCase_SPI_Transfer_Error_invalidBufferSize -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid port, valid sendValue, valid receiveValue, invalid bufferSize - * Run SPI_Receive API -* Expected return value from the API: RETCODE_FAILURE -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - - #### TC 1.2.11 TestCase_SPI_Control_Error_invalidParameter -* Test case Setup - * Initialize the SPI Port. -* Test Case Run - * Parameter passed: valid handle, invalid control, invalid argument - * Run SPI_Control API - * Expected return value from the API: RETCODE_NOT_SUPPORTED -* Test case Tear Down - * Not required separate Test Case Tear Down for this example. - -################################################################################################################################## - -## Test Suite 1.3: functional tests -### Description -* Currently it is not possible to have functional SPI tests, because there is neither implemented bus -partner for testing nor a possibility to test the GPIO pins. Instead, the (positive tests) -functionality will be tested by testing the external flash which is connected via SPI. -That means, if flash is working, SPI is working too. But that also means, if flash does not work, -its unclear if the error is in the flash driver or in the SPI driver. diff --git a/core/essentials/test/integration/specs/UART_Test_Spec.md b/core/essentials/test/integration/specs/UART_Test_Spec.md index 818e8a36..b2762d3a 100644 --- a/core/essentials/test/integration/specs/UART_Test_Spec.md +++ b/core/essentials/test/integration/specs/UART_Test_Spec.md @@ -1,212 +1,46 @@ -# Test Entry 3: "Essentials" -## Test Suite 3.1: UART +# Test Entry 1: Essentials + +## Test Suite 1.1: UART + ### Description -* This suite aims at testing the basic functionality of the UART APIs + +This suite aims at testing the basic functionality of the MCU UART APIs. ### Setup -The test setup consists of the test coordinator and 1 test participant + +The test setup consists of the test coordinator and one (1) test participant. * Test Coordinator (on PC) * The DUT is connected to the PC via UART which is the Test Coordination Channel. The embedded C testling code is flashed onto the DUT. -### TearDown +### Teardown No special teardown ### Test Cases -#### TC 3.1.1:TestCase_Uart_getStatus -##### Setup -No special setup - -##### Run -1. Invoke the Uart_getStatus API -2. Expected return value from the API: HAL_UART_STATE_READY - -##### TearDown -No special teardown - -#### TC 3.1.2:TestCase_Uart_getVersion -##### Setup -No special setup - -##### Run -1. Invoke the Uart_getVersion API -2. API should return the version number of the UART - -##### TearDown -No special teardown - -#### TC 3.1.3:TestCase_Uart_getCapabilities -##### Setup -No special setup - -##### Run -1. Invoke the Uart_getCapabilities API -2. API should return the capabilities supported by the UART - -##### TearDown -No special teardown - - -#### TC 3.1.4:TestCase_Uart_initialize -##### Setup -1. Invoke the Uart_control API with the following parameters, - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS - -##### Run -1. Invoke the Uart_initialize API -2. Expected return value from the API: RETCODE_SUCCESS - -##### TearDown -1. Invoke the Uart_uninitialize API -2. Expected return value from the API: RETCODE_SUCCESS - -#### TC 3.1.5:TestCase_Uart_control -##### Setup -No special setup - -##### Run -1. Invoke the Uart_control API with the following parameters, - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS - -##### TearDown -No special teardown - - -#### TC 3.1.6:TestCase_Uart_send -##### Setup -1. Invoke the Uart_control API with the following parameters, - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS -3. Invoke the Uart_initialize API -4. Expected return value from the API: RETCODE_SUCCESS - -##### Run -1. Invoke the Uart_send API and send the data "BikeSensor" -2. Expected return value from the API: RETCODE_SUCCESS - -##### TearDown -1. Invoke the Uart_uninitialize API -2. Expected return value from the API: RETCODE_SUCCESS +#### TC 1.1.1: Functional Test -#### TC 3.1.7:TestCase_Uart_receive ##### Setup -1. Invoke the Uart_control API with the following parameters, - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS -3. Invoke the Uart_initialize API -4. Expected return value from the API: RETCODE_SUCCESS +1. Get device handle of BSP initialized generic UART + * UART must be configured in loopback mode, echoing any data sent out +2. Allocate OS signal semaphore used as signal from IRQ +3. Connect the generic UART BSP +4. Initialize UART MCU with UART handle +5. Enable generic UART BSP ##### Run -1. Invoke the Uart_receive API -2. Expected return value from the API: RETCODE_FAILURE -/* @todo: Expectation is a failure because no device is sending data to the DUT UART. Beaglebone has to be integrated for validating this test case */ +1. Initiate UART receive process +2. Start send process dummy data of a few bytes size +3. Wait for send to completed + * Maximum timeout depends on baud-rate and data length +4. Expect to receive echo -##### TearDown -1. Invoke the Uart_uninitialize API -2. Expected return value from the API: RETCODE_SUCCESS - - -#### TC 3.1.8:TestCase_Uart_uninitialize -##### Setup -1. Invoke the Uart_control API with the following parameters - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_9 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 - * 9600 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS -3. Invoke the Uart_initialize API -4. Expected return value from the API: RETCODE_SUCCESS - -##### Run -1. Invoke the Uart_uninitialize API -2. Expected return value from the API: RETCODE_SUCCESS - -##### TearDown -No special teardown - -#### TC 3.1.9:TestCase_Uart_powerControl_full -##### Setup -No special setup - -##### Run -1. Invoke the Uart_powerControl API with the following parameter - - * PERIPHERALS_POWER_STATE_FULL -2. Expected return value from the API: RETCODE_SUCCESS - -##### TearDown -No special teardown - -#### TC 3.1.10:TestCase_Uart_powerControl_low -##### Setup -No special setup - -##### Run -1. Invoke the Uart_powerControl API with the following parameter - - * PERIPHERALS_POWER_STATE_LOW -2. Expected return value from the API: RETCODE_FAILURE - -##### TearDown -No special teardown - -#### TC 3.1.11:Testcase_Uart_initialize_without_control -##### Setup -No special setup - -##### Run -1. Invoke the Uart_initialize API -2. Expected return value from the API:RETCODE_SUCCESS - -##### TearDown -1. Invoke the Uart_uninitialize API -2. Expected return value from the API: RETCODE_SUCCESS - -#### TC 3.1.12:Testcase_Uart_send_without_initialize -##### Setup -1. Invoke the Uart_control API with the following parameters, - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS - -##### Run -1. Invoke the Uart_send API and send the data "Integration testing" -2. Expected return value from the API: RETCODE_FAILURE - -##### TearDown -No special teardown - -#### TC 3.1.13:Testcase_Uart_receive_without_initialize -##### Setup -1. Invoke the Uart_control API with the following parameters, - - * BCDS_Uart1 - * BCDS_UART_MODE_ASYNCHRONOUS | BCDS_UART_DATA_BITS_8 | BCDS_UART_PARITY_NONE | BCDS_UART_STOP_BITS_1 | BCDS_UART_FLOW_CONTROL_NONE - * 115200 (Baud rate) -2. Expected return value from the API: RETCODE_SUCCESS - -##### Run -1. Invoke the Uart_receive API -2. Expected return value from the API: RETCODE_FAILURE +##### Teardown -##### TearDown -No special teardown \ No newline at end of file +1. Deinitialize UART MCU, deactivating IRQs +2. Disable generic UART BSP +3. Disconnect generic UART BSP +4. Free OS signal semaphore diff --git a/core/testing/CMakeLists.txt b/core/testing/CMakeLists.txt new file mode 100644 index 00000000..eb7afcb4 --- /dev/null +++ b/core/testing/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.6) + +project ("Kiso Testing" C) + +## Interface library +add_library(testing_int INTERFACE) +target_include_directories(testing_int +INTERFACE + include +) +target_link_libraries(testing_int INTERFACE essentials_int) + +## Enable static code analysis +# the checks will be executed as it would be on the desired compile step +if(${ENABLE_STATIC_CHECKS}) + set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY} --extra-arg=--target=arm-none-eabi --extra-arg=--sysroot=${CMAKE_SYSROOT}) + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY} --extra-arg=--target=arm-none-eabi --extra-arg=--sysroot=${CMAKE_SYSROOT}) +endif() + +## Make sure we are only compiling them with the proper toolchain +if(${CMAKE_CROSSCOMPILING}) + + file(GLOB TESTING_SOURCES + ./source/*.c + ) + add_library(testing STATIC EXCLUDE_FROM_ALL ${TESTING_SOURCES}) + target_include_directories(testing + PUBLIC + include + PRIVATE + source/protected + ) + target_link_libraries(testing testing_int essentials utils ${KISO_OS_LIB}) + +endif(${CMAKE_CROSSCOMPILING}) + +## Add tests +if(${CMAKE_TESTING_ENABLED}) + #add_subdirectory(test) +endif() diff --git a/core/testing/include/Kiso_Testing.h b/core/testing/include/Kiso_Testing.h new file mode 100644 index 00000000..64080452 --- /dev/null +++ b/core/testing/include/Kiso_Testing.h @@ -0,0 +1,192 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief This module manages the integration testing protocol and communication between the device under test + and the test coordinator + * + */ + +#ifndef KISO_TESTING_H_ +#define KISO_TESTING_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Basics.h" +#include "Kiso_Retcode.h" +#include "Kiso_Assert.h" +#include "Kiso_Testing_Config.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +/*###################### TYPE DEFINITIONS ############################################################################*/ +/** + * @brief Enumerates the internal units in the Kiso_Testing library which could return Retcode conform error + * codes. In case of error, one can refer to this enumeration to identify which source file has issued + * it. +*/ +enum KISO_TESTING_Modules_E +{ + KISO_MODULE_ID_TESTING = 1, + KISO_MODULE_ID_TESTING_TESTREGISTRY, /**< KISO_TESTS_MODULE_ID_TESTREGISTRY */ + KISO_MODULE_ID_TESTING_CCHANNEL, /**< KISO_TESTS_MODULE_ID_CCHANNEL */ + KISO_MODULE_ID_TESTING_SERIALMSGTRANSCEIVER, /**< KISO_TESTS_MODULE_ID_SERIALMSGTRANSCEIVER */ + KISO_MODULE_ID_TESTING_TESTRUNNER, /**< KISO_TESTS_MODULE_ID_TESTRUNNER */ + KISO_MODULE_ID_TESTING_SERIAL, /**< KISO_TESTS_MODULE_ID_SERIAL */ + KISO_MODULE_ID_TESTING_TESTENTRY, +}; + +/** + * @brief Enumerates the special return codes that could be returned from the different units in the + * Kiso_Testing library. +*/ +enum KISO_TESTING_Retcodes_E +{ + RETCODE_TESTING_SUITE_ALREADY_REGISTERED = RETCODE_FIRST_CUSTOM_CODE, + RETCODE_TESTING_CASE_ALREADY_REGISTERED, + RETCODE_TESTING_CCHANNEL_INITIALISATION_FAILED, + RETCODE_TESTING_INCOMPLETE_MESSAGE_RECEIVED, + RETCODE_TESTING_CRC_MISMATCH, + RETCODE_TESTING_VERSION_MISMATCH, + RETCODE_TESTING_TLVELEMENT_NOT_FOUND, + RETCODE_TESTING_REPORT_TIMEOUT, + RETCODE_TESTING_CCHANNEL_NOT_SPECIFIED, +}; + +/** + * @brief Enumerates the types of messages exchanged between the Test_Executor and the Test_Controller + */ +enum CCMsg_MessageType_E +{ + CCMSG_TYPE_COMMAND, + CCMSG_TYPE_REPORT, + CCMSG_TYPE_ACK, +}; + +/** + * @brief Encapsulates the elements composing the message header. + */ +typedef struct MessageHeader_S +{ + uint8_t messageInfo; /**< version is 2 bits, message type is 2 bits and the remaining 4 bits are reserved */ + uint8_t messageToken; + uint8_t messageType; + uint8_t errorCode; + uint8_t testEntry; + uint8_t testSuite; + uint8_t testCase; + uint8_t payloadLength; +} MsgHdr_T; + +/** + * @brief Encapsulates the elements composing a TLV object(type, length and value) + */ +typedef struct TlvElement_S +{ + uint8_t type; + uint8_t length; + char *value; +} TlvElt_T; + +/** + * @brief Encapsulates the elements composing a message.. + */ +typedef struct CCMsg_S +{ + MsgHdr_T header; /**< Header of the message */ + uint8_t payload[TEST_MAX_PAYLOAD_LENGTH]; + TlvElt_T tlvArray[CCHANNEL_MAX_NUMBER_OF_TLV_ELEMENTS]; /**< Parsed TLV array (from receive buffer) */ + uint8_t payloadIndex; + uint8_t tlvArrayIndex; + uint8_t numberOfTlvElements; /**< Number of TLV elements for the message */ + uint8_t rebootCounter; + bool isFree : 1; /**< This bit indicates whether the received message is processed by test runner or not */ +} CCMsg_T; + +/** + * @brief Defines a prototype type for the setup functions of the test suites and test cases. + */ +typedef Retcode_T (*SetupFct_T)(CCMsg_T *ccmsg); + +/** + * @brief Defines a prototype type for the tear down functions of the test suites and test cases. + */ +typedef Retcode_T (*TearDownFct_T)(CCMsg_T *ccmsg); + +/** + * @brief Sefines a prototype type for the run functions of the test cases. + */ +typedef void (*RunFct_T)(CCMsg_T *ccmsg); + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Initializes the Testing Framework. + * @details this + * @param[in] eId Id of the Test Entry + * @return RETCODE_OK if initialized successfully error code otherwise + */ +Retcode_T Tests_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown); + +/** + * @brief Registers a Test Suite + * @details todo mak explain what the function does currently + * @param[in] sId The identifier of the TestSuite to register + * @param[in] setup A reference to the setup function of the TestSuite + * @param[in] teardown A reference to the tear down function of the TestSuite + * @note setup and tear down functions pointers can be null if nothing has to be done. + * @return RETCODE_OK in case of success, error code otherwise. + */ +Retcode_T Tests_RegisterTestSuite(uint8_t sId, SetupFct_T setup, TearDownFct_T teardown); + +/** + * @brief Registers a Test Case + * @details todo mak explain what the function does + * @param[in] sId identifier of the Test Suite the test to register + * @param[in] cId identifier of the Test Case to register + * @param[in] setup A reference to the setup function of the Test Suite + * @param[in] run A reference to the run function of the Test Suite + * @param[in] teardown A reference to the tear down function of the Test Suite + * @note setup and tear down functions pointers can be null if nothing has to be done. + * @return RETCODE_OK in case of success, error code otherwise. + */ +Retcode_T Tests_RegisterTestCase(uint8_t sId, uint8_t cId, SetupFct_T setup, RunFct_T run, TearDownFct_T teardown); + +/** + * @brief Sends a result of a test case execution. + * @details todo mak explain what the function does + * @param[in] result The test result code (0: success / otherwise: failure) + * @param[in] reason A 0-terminating string stating a reason. It can be NULL, if no reason should be sent. + */ +void Tests_SendReport(uint8_t result, char *reason); + +/** + * @brief Gets a tlv element using the type of the tlvElement input + * @details todo mak explain what the function does + * @param[in] ccmsg A reference to the message in which to look for the element + * @param[in,out] tlvElement A reference to where to store the element if found. + * tlvElement->type is used as input to find the element's value + * @return RETCODE_OK in case of success, error code otherwise. + * todo mak: make explicit the type search. interface is hiding information. + */ +Retcode_T Tests_GetTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement); + +#endif /* KISO_TESTING_H_ */ + +/** @} */ diff --git a/core/testing/include/Kiso_Testing_Config.h b/core/testing/include/Kiso_Testing_Config.h new file mode 100644 index 00000000..8e91d272 --- /dev/null +++ b/core/testing/include/Kiso_Testing_Config.h @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------*/ +/* + * Copyright (C) Bosch Connected Devices and Solutions GmbH. + * All Rights Reserved. Confidential. + * + * Distribution only to people who need to know this information in + * order to do their job.(Need-to-know principle). + * Distribution to persons outside the company, only if these persons + * signed a non-disclosure agreement. + * Electronic transmission, e.g. via electronic mail, must be made in + * encrypted form. + */ +/*----------------------------------------------------------------------------*/ + +/** + * @file Add a brief description here. + * + * Put here the documentation of this header file. Explain the interface exposed + * by this header, e.g. what is the purpose of use, how to use it, etc. + */ + +#ifndef KISO_TESTING_CONFIG_H +#define KISO_TESTING_CONFIG_H + +#ifndef TEST_RUNNER_TASK_PRIO +#define TEST_RUNNER_TASK_PRIO 2 +#endif + +#ifndef TEST_RUNNER_TASK_STACK_DEPTH +#define TEST_RUNNER_TASK_STACK_DEPTH 1024 +#endif + +#ifndef TEST_RUNNER_QUEUE_SIZE +#define TEST_RUNNER_QUEUE_SIZE 5U +#endif + +#ifndef TEST_MAX_NUMBER_OF_TEST_SUITES +#define TEST_MAX_NUMBER_OF_TEST_SUITES 16 +#endif + +/** Macros defining the maximum number of Test Cases per Test Suite */ +#ifndef TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE +#define TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE 16 +#endif + +#ifndef CCHANNEL_MAX_NUMBER_OF_TLV_ELEMENTS +#define CCHANNEL_MAX_NUMBER_OF_TLV_ELEMENTS 2 +#endif + +#ifndef TEST_MAX_PAYLOAD_LENGTH +#define TEST_MAX_PAYLOAD_LENGTH 248U +#endif + +#endif /* KISO_TESTING_CONFIG_H */ diff --git a/core/testing/source/CChannel.c b/core/testing/source/CChannel.c new file mode 100644 index 00000000..ba864be6 --- /dev/null +++ b/core/testing/source/CChannel.c @@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial contribution + * + ******************************************************************************/ + +/** + * @file + * + * @brief + * This module is responsible for the transmission and reception of data through + * the communication medium, which can be UART / UDP / BLE and so on. + */ + +#include "Kiso_Testing.h" +#include "CChannel.h" +#include "TestRunner.h" +#include "SerialCChannel.h" +#include "SerialMsgTransceiver.h" + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING_CCHANNEL + +#define MAX_NUMBER_OF_INCOMING_MESSAGE 2 + +#define CCHANNEL_TLV_TYPE_REASON 112 + +#define CCHANNEL_REPORT_TYPE 110 + +#define CCHANNEL_NUM_OF_SEND_RETRIES 4 + +#define MSG_INIT_OK ("CCHANNEL INIT OK\r\n") + +static CCMsg_T *allocCCMessage(void); +static Retcode_T sendMessage(CCMsg_T *ccmsg); +static void parseTlvElements(CCMsg_T *ccmsg); +static void freeAllCCMsg(void); + +static CCMsg_T msgPool[MAX_NUMBER_OF_INCOMING_MESSAGE]; +static CCMsg_T ackMessage; +static CCMsg_T reportMessage; + +Retcode_T CChannel_Initialize(void) +{ + freeAllCCMsg(); + Retcode_T retcode = Serial_Initialize(); + + if (RETCODE_OK == retcode) + { + char msg[sizeof(MSG_INIT_OK)] = MSG_INIT_OK; + retcode = Serial_Send((void *)msg, strlen(msg)); + } + return retcode; +} + +Retcode_T CChannel_Deinitialize(void) +{ + return Serial_Deinitialize(); +} + +void CChannel_FreeCCMsg(CCMsg_T *ccmsg) +{ + (void)memset(ccmsg, 0, sizeof(CCMsg_T)); + + ccmsg->isFree = true; +} + +void CChannel_ReceiveEventHandler(uint8_t *buffer, uint8_t length) +{ + if (CCMSG_VERSION != ((buffer[0] & CCMSG_VERSION_MASK) >> CCMSG_VERSION_SHIFT)) + { + //if the framework version does not match we just ignore this message + return; + } + + CCMsg_T *ccmsg = allocCCMessage(); + + if (NULL == ccmsg) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES)); + return; + } + + (void)memcpy(ccmsg, buffer, length); + + parseTlvElements(ccmsg); + + TestRunner_ProcessMessage(ccmsg); +} + +void CChannel_PrepareAck(CCMsg_T *ccmsg) +{ + (void)memset(&ackMessage, 0, sizeof(CCMsg_T)); + + (void)memcpy(&ackMessage, ccmsg, CCHANNEL_HEADER_LENGTH); + + ackMessage.header.messageInfo = CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_ACK); + ackMessage.header.payloadLength = 0; +} + +Retcode_T CChannel_SendAck(uint8_t result) +{ + ackMessage.header.errorCode = result; + + assert(ackMessage.header.messageInfo == CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_ACK)); + assert(ackMessage.header.payloadLength == 0); + + return sendMessage(&ackMessage); +} + +Retcode_T CChannel_ResendAck(void) +{ + return sendMessage(&ackMessage); +} + +void CChannel_PrepareReport(CCMsg_T *ccmsg) +{ + (void)memset(&reportMessage, 0, sizeof(CCMsg_T)); + + (void)memcpy(&reportMessage, ccmsg, CCHANNEL_HEADER_LENGTH); + + reportMessage.header.messageType = CCHANNEL_REPORT_TYPE; + reportMessage.header.messageInfo = CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_REPORT); + reportMessage.header.payloadLength = 0; +} + +Retcode_T CChannel_SendReport(uint8_t result, char *reason) +{ + Retcode_T retcode = RETCODE_OK; + + reportMessage.header.errorCode = result; + + assert(reportMessage.header.payloadLength == 0); + + if (NULL != reason) + { + TlvElt_T reasonTlv; + reasonTlv.type = CCHANNEL_TLV_TYPE_REASON; + reasonTlv.length = strlen(reason); + reasonTlv.value = reason; + + retcode = CChannel_AddTlvElement(&reportMessage, &reasonTlv); + + if ((strlen(reason) + 2) != reportMessage.header.payloadLength) + { + return RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_UNEXPECTED_BEHAVIOR); + } + } + + assert(reportMessage.header.messageInfo == CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_REPORT)); + + if (RETCODE_OK == retcode) + { + retcode = sendMessage(&reportMessage); + } + + return retcode; +} + +Retcode_T CChannel_ResendReport(void) +{ + Retcode_T retcode = RETCODE_OK; + + retcode = sendMessage(&reportMessage); + + return retcode; +} + +bool CChannel_DoesAckMatchReport(CCMsg_T *ccack) +{ + bool result = true; + MsgHdr_T msgHdr = reportMessage.header; + MsgHdr_T ackHdr = ccack->header; + + if ((ackHdr.messageToken != msgHdr.messageToken) || (ackHdr.testEntry != msgHdr.testEntry) || (ackHdr.testSuite != msgHdr.testSuite) || (ackHdr.testCase != msgHdr.testCase)) + { + result = false; + } + + return result; +} + +Retcode_T CChannel_GetTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement) +{ + if ((NULL == ccmsg) || (NULL == tlvElement)) + { + return RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_INVALID_PARAM); + } + else + { + for (uint32_t i = 0; i < ccmsg->numberOfTlvElements; i++) + { + if (tlvElement->type == ccmsg->tlvArray[i].type) + { + if (NULL == ccmsg->tlvArray[i].value) + { + return RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + } + + tlvElement->value = ccmsg->tlvArray[i].value; + tlvElement->length = ccmsg->tlvArray[i].length; + return RETCODE_OK; + } + } + } + return RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_TESTING_TLVELEMENT_NOT_FOUND); +} + +Retcode_T CChannel_AddTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement) +{ + if ((NULL == ccmsg) || (NULL == tlvElement) || (NULL == tlvElement->value)) + { + return (RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_NULL_POINTER)); + } + else + { + + if (CCHANNEL_MAX_NUMBER_OF_TLV_ELEMENTS == ccmsg->numberOfTlvElements) + { + //we allow only 20 TLV elements in a message + return RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_OUT_OF_RESOURCES); + } + + if (CCHANNEL_PAYLOAD_MAX_SIZE <= (ccmsg->payloadIndex + 2 + tlvElement->length)) + { + //we allow only 248 as the maximum paylaod size + return RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_OUT_OF_RESOURCES); + } + + TlvElt_T *currentTlvElement = &ccmsg->tlvArray[ccmsg->numberOfTlvElements]; + + currentTlvElement->type = tlvElement->type; + ccmsg->payload[ccmsg->payloadIndex++] = tlvElement->type; + currentTlvElement->length = tlvElement->length; + ccmsg->payload[ccmsg->payloadIndex++] = tlvElement->length; + + currentTlvElement->value = (char *)&(ccmsg->payload[ccmsg->payloadIndex]); + (void)memcpy(&ccmsg->payload[ccmsg->payloadIndex], tlvElement->value, tlvElement->length); + + ccmsg->payloadIndex += tlvElement->length; + + ccmsg->header.payloadLength += tlvElement->length + 2; + ccmsg->numberOfTlvElements++; + + return RETCODE_OK; + } +} + +static CCMsg_T *allocCCMessage(void) +{ + CCMsg_T *ccmsg = NULL; + + for (uint8_t i = 0; i < MAX_NUMBER_OF_INCOMING_MESSAGE; i++) + { + if (msgPool[i].isFree) + { + ccmsg = &msgPool[i]; + ccmsg->isFree = false; + break; + } + } + + return ccmsg; +} + +static Retcode_T sendMessage(CCMsg_T *ccmsg) +{ + Retcode_T retcode = RETCODE_OK; + uint32_t retries = CCHANNEL_NUM_OF_SEND_RETRIES; + do + { + retcode = SerialMsgTransceiver_Send((uint8_t *)ccmsg, CCHANNEL_HEADER_LENGTH + ccmsg->header.payloadLength); + retries--; + } while (retries > 0 && RETCODE_OK != retcode); + + return retcode; +} + +static void parseTlvElements(CCMsg_T *ccmsg) +{ + for (uint8_t i = 0; i < ccmsg->header.payloadLength;) + { + if (ccmsg->numberOfTlvElements == CCHANNEL_MAX_NUMBER_OF_TLV_ELEMENTS) + { + return; + } + + ccmsg->tlvArray[ccmsg->numberOfTlvElements].type = ccmsg->payload[i++]; + ccmsg->tlvArray[ccmsg->numberOfTlvElements].length = ccmsg->payload[i++]; + ccmsg->tlvArray[ccmsg->numberOfTlvElements].value = (char *)&(ccmsg->payload[i]); + i += ccmsg->tlvArray[ccmsg->numberOfTlvElements].length; + ccmsg->numberOfTlvElements++; + } +} + +static void freeAllCCMsg(void) +{ + (void)memset(msgPool, 0, sizeof(msgPool)); + + for (uint32_t i = 0; i < MAX_NUMBER_OF_INCOMING_MESSAGE; i++) + { + msgPool[i].isFree = true; + } +} diff --git a/core/testing/source/Serial.c b/core/testing/source/Serial.c new file mode 100644 index 00000000..d399bfba --- /dev/null +++ b/core/testing/source/Serial.c @@ -0,0 +1,219 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * + * @brief + * todo add brief description Implements the following functionalities specified in template.h + */ +/*###################### INCLUDED HEADERS ###########################################################################*/ + +#include "Kiso_Testing.h" +#include "Kiso_Testing_Config.h" +#include "Kiso_BSP_TestInterface.h" +#include "Kiso_MCU_UART.h" +#include "Kiso_GuardedTask.h" +#include "Kiso_RingBuffer.h" +#include "FreeRTOS.h" +#include "semphr.h" +#include "SerialMsgTransceiver.h" +#include "SerialCChannel.h" + +/*###################### MACROS DEFINITION ##########################################################################*/ +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING_SERIAL + +#define WAIT_TIME_FOR_SINGLE_UART_TRANSMISSION UINT32_C(500) + +#define MAX_BUFFER_SIZE UINT16_C(300) + +#ifndef SERIAL_TASK_PRIO +#define SERIAL_TASK_PRIO (1UL) +#endif /* SERiAL_TASK_PRIO */ + +#ifndef SERIAL_TASK_STACK_DEPTH +#define SERIAL_TASK_STACK_DEPTH (128UL) +#endif /* SERIAL_TASK_STACK_DEPTH */ + +/*###################### LOCAL_TYPES DEFINITION #####################################################################*/ + +/*###################### LOCAL FUNCTIONS DECLARATION ################################################################*/ + +static void uartEventsCallbackFunc(UART_T uart, struct MCU_UART_Event_S event); + +/*###################### VARIABLES DECLARATION ######################################################################*/ + +static GuardedTask_T serialGuardedTask; +volatile uint32_t serialReceivedCnt = 0; +static SemaphoreHandle_t TransmitDataSemaphoreHandle = NULL; +static RingBuffer_T serialRingBuffer; +static uint8_t serialBuffer[MAX_BUFFER_SIZE]; +static uint8_t RxBuffer; +static UART_T TestInterfaceUart; +#ifndef NDEBUG +volatile uint32_t TestUartErrorCount = 0; /* number of errors post mortem */ +#endif /* NDEBUG */ + +/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ###########################################################*/ + +/* The description is defined at function declaration */ +Retcode_T Serial_Initialize(void) +{ + Retcode_T rc = RETCODE_OK; + + rc = BSP_TestInterface_Connect(); + if (RETCODE_OK == rc) + { + TestInterfaceUart = (UART_T)BSP_TestInterface_GetUARTHandle(); + if (NULL == TestInterfaceUart) + { + rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + } + } + + if (RETCODE_OK == rc) + { + rc = MCU_UART_Initialize(TestInterfaceUart, uartEventsCallbackFunc); + } + + if (RETCODE_OK == rc) + { + rc = BSP_TestInterface_Enable(); + } + + if (RETCODE_OK == rc) + { + rc = GuardedTask_Initialize(&serialGuardedTask, SerialMsgTransceiver_Receive, "SERIAL_TASK", SERIAL_TASK_PRIO, SERIAL_TASK_STACK_DEPTH); + } + + if (RETCODE_OK == rc) + { + TransmitDataSemaphoreHandle = xSemaphoreCreateBinary(); + if (NULL == TransmitDataSemaphoreHandle) + { + rc = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_OUT_OF_RESOURCES); + } + } + + if (RETCODE_OK == rc) + { + RingBuffer_Initialize(&serialRingBuffer, serialBuffer, MAX_BUFFER_SIZE); + } + + if (RETCODE_OK == rc) + { + /* start the receive process, this will enable UART interrupts and trigger a callback on receive */ + rc = MCU_UART_Receive(TestInterfaceUart, &RxBuffer, 1UL); + } + + return (rc); +} + +Retcode_T Serial_Deinitialize(void) +{ + Retcode_T rc = MCU_UART_Deinitialize(TestInterfaceUart); + if (RETCODE_OK == rc) + { + rc = BSP_TestInterface_Disable(); + } + + if (RETCODE_OK == rc) + { + rc = BSP_TestInterface_Disconnect(); + } + + if (RETCODE_OK == rc) + { + rc = GuardedTask_Deinitialize(&serialGuardedTask); + } + return rc; +} + +Retcode_T Serial_Send(void *data, uint32_t len) +{ + Retcode_T retCode; + retCode = MCU_UART_Send(TestInterfaceUart, (uint8_t *)data, len); + + if (RETCODE_OK == retCode) + { + /* Waiting here for the transmit complete event */ + if (pdTRUE != xSemaphoreTake(TransmitDataSemaphoreHandle, (WAIT_TIME_FOR_SINGLE_UART_TRANSMISSION / portTICK_RATE_MS))) + { + retCode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_SEMAPHORE_ERROR); + } + } + + return (retCode); +} + +Retcode_T Serial_Receive(void *data, uint32_t len) +{ + uint32_t numberOfReadBytes = 0; + Retcode_T retcode = RETCODE_OK; + + numberOfReadBytes = RingBuffer_Read(&serialRingBuffer, (uint8_t *)data, len); + + if (numberOfReadBytes != len) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_UNEXPECTED_BEHAVIOR); + } + + return retcode; +} + +/*###################### LOCAL FUNCTIONS IMPLEMENTATION #############################################################*/ + +static void uartEventsCallbackFunc(UART_T uart, struct MCU_UART_Event_S event) +{ + KISO_UNUSED(uart); /* not used in One-Byte-Mode */ + + Retcode_T retcode = RETCODE_OK; + + /* Signal the guarded task to indicate that the receive is complete */ + if (event.RxComplete) + { + serialReceivedCnt++; + + if (1UL == RingBuffer_Write(&serialRingBuffer, (uint8_t *)&RxBuffer, 1UL)) + { + (void)GuardedTask_SignalFromIsr(&serialGuardedTask); + } + } + else if (event.TxComplete) + { + portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if (pdTRUE == xSemaphoreGiveFromISR(TransmitDataSemaphoreHandle, &xHigherPriorityTaskWoken)) + { + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } + else + { + /* ignore... semaphore has already been given */ + } + } + else if (event.RxError) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); + } + else if (event.TxError) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); + } + if (RETCODE_OK != retcode) + { + Retcode_RaiseErrorFromIsr(retcode); + } +} \ No newline at end of file diff --git a/core/testing/source/SerialMsgTransceiver.c b/core/testing/source/SerialMsgTransceiver.c new file mode 100644 index 00000000..147b7fce --- /dev/null +++ b/core/testing/source/SerialMsgTransceiver.c @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial contribution + * + ******************************************************************************/ + +/** + * @file + * + * @brief + * Implements the following functionalities specified in template.h + */ +#include "Kiso_Testing.h" +#include "SerialMsgTransceiver.h" +#include "SerialCChannel.h" +#include "CChannel.h" +#include "Kiso_GuardedTask.h" +#include + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING_SERIALMSGTRANSCEIVER + +#define PAYLOAD_LENGTH_INDEX 7 +#define RECEIVE_BUFFER_SIZE 256 + +#define START 0xC0 +#define ESC 0xDB +#define ESC_START 0xDC +#define ESC_ESC 0xDD + +#define CRC_DEFAULT UINT8_C(0) +#define CRC_BIT_SHIFT_4 UINT16_C(4) /** 4 bit shifting for CRC calculation */ +#define CRC_BIT_SHIFT_5 UINT16_C(5) /** 5 bit shifting for CRC calculation */ +#define CRC_BIT_SHIFT_8 UINT16_C(8) /** 8 bit shifting for CRC calculation */ +#define CRC_BIT_SHIFT_12 UINT16_C(12) /** 12 bit shifting for CRC calculation */ +#define CRC_INIT_VALUE UINT16_C(0) /** Default Initialize value for flag */ +#define CRC_BYTE_MASK UINT16_C(0xFF) + +enum Receiver_State_E +{ + WAITING_FOR_START, + RECEIVING_HEADER, + RECEIVING_PAYLOAD, + RECEIVED_DONE +}; + +static uint16_t calculateCRC(const void *buffer, uint8_t length); + +static uint8_t receiveBuffer[RECEIVE_BUFFER_SIZE]; +static uint32_t serialConsumedCnt = 0; /* number of characters received */ +static uint32_t msgCnt = 0; /* diagnostic: number of messages received */ +static uint32_t msgOK = 0; /* diagnostic: number of messages received */ +static uint32_t msgNOK = 0; /* diagnostic: number of messages received */ + +Retcode_T SerialMsgTransceiver_Send(uint8_t *message, uint8_t length) +{ + Retcode_T ReturnValue; + uint8_t sendBuffer[length * 2]; //todo variable length array dangerous + uint8_t j = 0; + + uint16_t crc = calculateCRC(message, length); + + sendBuffer[j++] = START; + + if (((crc >> CHAR_BIT) & 0xFF) == START) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_START; + } + else if (((crc >> CHAR_BIT) & 0xFF) == ESC) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_ESC; + } + else + { + sendBuffer[j++] = (crc >> CHAR_BIT) & 0xFF; + } + + if ((crc & 0xFF) == START) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_START; + } + else if ((crc & 0xFF & 0xFF) == ESC) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_ESC; + } + else + { + sendBuffer[j++] = crc & 0xFF; + } + + for (uint32_t i = 0; i < length; i++) + { + if (message[i] == START) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_START; + } + else if (message[i] == ESC) + { + sendBuffer[j++] = ESC; + sendBuffer[j++] = ESC_ESC; + } + else + { + sendBuffer[j++] = message[i]; + } + } + + ReturnValue = Serial_Send(sendBuffer, j); + + return (ReturnValue); +} + +//@todo dei9bue: verify total length, limited to 256! what to do if exceeded? +/* The description is defined at function declaration */ +void SerialMsgTransceiver_Receive(void) +{ + static uint8_t receivingState = WAITING_FOR_START; + static uint8_t receivedByte; + static uint8_t nReceivedByte = 0; + static uint8_t payloadLength = 0; + static bool receivedEsc = false; + static uint8_t testCnt = 0; + + /* The serialReceivedCnt is incremented with every received byte in the ISR + The difference betweeb the serialReceivedCnt and serialConsumedCnt repflects the number of bytes available in the buffer + We consume characters while the difference is > 0 + There is no need to check overflows since both numbers are of uint32_t and the result will always state the difference. + */ + while ((serialReceivedCnt - serialConsumedCnt) > 0) + { + if (RETCODE_OK != Serial_Receive((uint8_t *)&receivedByte, UINT32_C(1))) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_WARNING, RETCODE_FAILURE)); + return; + } + serialConsumedCnt++; + + if (WAITING_FOR_START == receivingState) + { + if (START == receivedByte) + { + nReceivedByte = 0; + (void)memset(receiveBuffer, 0, sizeof(receiveBuffer)); + receivingState = RECEIVING_HEADER; + } + } + else + { + if (START == receivedByte) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_WARNING, RETCODE_TESTING_INCOMPLETE_MESSAGE_RECEIVED)); + nReceivedByte = 0; + (void)memset(receiveBuffer, 0, sizeof(receiveBuffer)); + msgNOK++; + receivingState = RECEIVING_HEADER; + } + else if (receivedEsc) + { + receivedEsc = false; + if (ESC_START == receivedByte) + { + receiveBuffer[nReceivedByte++] = START; + } + else if (ESC_ESC == receivedByte) + { + receiveBuffer[nReceivedByte++] = ESC; + } + else + { + // error should not happen + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_WARNING, RETCODE_INCONSITENT_STATE)); + receivingState = WAITING_FOR_START; + } + } + else if (ESC == receivedByte) + { + receivedEsc = true; + } + else + { + receiveBuffer[nReceivedByte++] = receivedByte; + } + + if (RECEIVING_HEADER == receivingState) + { + if (CCHANNEL_HEADER_LENGTH + 2 == nReceivedByte) + { + payloadLength = receiveBuffer[PAYLOAD_LENGTH_INDEX + 2]; + + if (0 == payloadLength) + { + receivingState = RECEIVED_DONE; + } + else + { + receivingState = RECEIVING_PAYLOAD; + } + } + } + else if (RECEIVING_PAYLOAD == receivingState) + { + if (payloadLength + CCHANNEL_HEADER_LENGTH + 2 == nReceivedByte) + { + receivingState = RECEIVED_DONE; + } + } + } + } + + if (RECEIVED_DONE == receivingState) + { + uint16_t calculatedCRC = calculateCRC(&receiveBuffer[2], CCHANNEL_HEADER_LENGTH + payloadLength); + uint16_t expectedCRC = ((receiveBuffer[0] & CRC_BYTE_MASK) << CHAR_BIT) + (receiveBuffer[1] & CRC_BYTE_MASK); + + msgCnt++; + if (calculatedCRC == expectedCRC) + { + CChannel_ReceiveEventHandler(&receiveBuffer[2], CCHANNEL_HEADER_LENGTH + payloadLength); + receivingState = WAITING_FOR_START; + msgOK++; + } + else + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_WARNING, RETCODE_TESTING_CRC_MISMATCH)); + receivingState = WAITING_FOR_START; + msgNOK++; + } + testCnt++; + } +} + +static uint16_t calculateCRC(const void *buffer, uint8_t length) +{ + uint16_t crc = CRC_DEFAULT; + + for (uint8_t i = 0; i < length; i++) + { + crc = (crc >> (CRC_BIT_SHIFT_8)) | (crc << (CRC_BIT_SHIFT_8)); + crc ^= ((const uint8_t *)buffer)[i]; + crc ^= (crc & (CRC_BYTE_MASK)) >> (CRC_BIT_SHIFT_4); + crc ^= crc << (CRC_BIT_SHIFT_12); + crc ^= (crc & (CRC_BYTE_MASK)) << (CRC_BIT_SHIFT_5); + } + return crc; +} diff --git a/core/testing/source/TestRegistry.c b/core/testing/source/TestRegistry.c new file mode 100644 index 00000000..71a32c60 --- /dev/null +++ b/core/testing/source/TestRegistry.c @@ -0,0 +1,339 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * + * @brief + */ +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Testing.h" +#include "TestRegistry.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING_TESTREGISTRY + +/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ + +/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ + +static TstSte_T *lookupTestSuite(uint8_t sId); +static TstCse_T *lookupTestCase(TstSte_T *testSuite, uint8_t cId); + +/*###################### VARIABLES DECLARATION #######################################################################*/ + +static TstEnt_T testEntry; + +/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ + +/* @see TestRegistry.h for function description */ +void TestRegistry_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown) +{ + (void)memset(&testEntry, 0, sizeof(TstEnt_T)); + + testEntry.id = eId; + testEntry.setup = setup; + testEntry.teardown = teardown; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestRegistry_RegisterTestSuite(uint8_t sId, SetupFct_T setup, TearDownFct_T teardown) +{ + if (TEST_MAX_NUMBER_OF_TEST_SUITES <= testEntry.numTestSuites) + { + return RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_OUT_OF_RESOURCES); + } + + TstSte_T *suite = lookupTestSuite(sId); + + if (NULL != suite) + { + /* The test suite with the given ID is already registered */ + return RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_TESTING_SUITE_ALREADY_REGISTERED); + } + + suite = &testEntry.testSuites[testEntry.numTestSuites]; + testEntry.numTestSuites++; + + suite->id = sId; + suite->setup = setup; + suite->teardown = teardown; + + return RETCODE_OK; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestRegistry_RegisterTestCase(uint8_t sId, uint8_t cId, SetupFct_T setup, RunFct_T run, TearDownFct_T teardown) +{ + if (NULL == run) + { + return RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_INVALID_PARAM); + } + + TstSte_T *suite = lookupTestSuite(sId); + + if (NULL == suite) + { + /* The suite is not found */ + return RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_INVALID_PARAM); + } + + if (TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE <= suite->numTestCases) + { + return RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_OUT_OF_RESOURCES); + } + + TstCse_T *testCase = lookupTestCase(suite, cId); + + if (NULL != testCase) + { + /* A test case with the given ID is already registered */ + return RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_TESTING_CASE_ALREADY_REGISTERED); + } + + testCase = &suite->testCases[suite->numTestCases]; + suite->numTestCases++; + + testCase->id = cId; + testCase->setup = setup; + testCase->run = run; + testCase->teardown = teardown; + + return RETCODE_OK; +} + +/* @see TestRegistry.h for function description */ +TstEnt_T *TestRegistry_LookupTestEntry(uint8_t eId) +{ + TstEnt_T *theTestEntry = NULL; + + if (eId == testEntry.id) + { + theTestEntry = &testEntry; + } + + return theTestEntry; +} + +/* @see TestRegistry.h for function description */ +TstSte_T *TestRegistry_LookupTestSuite(uint8_t eId, uint8_t sId) +{ + TstSte_T *testSuite = NULL; + + if (eId == testEntry.id) + { + testSuite = lookupTestSuite(sId); + } + + return testSuite; +} + +/* @see TestRegistry.h for function description */ +TstCse_T *TestRegistry_LookupTestCase(uint8_t eId, uint8_t sId, uint8_t cId) +{ + TstSte_T *testSuite = TestRegistry_LookupTestSuite(eId, sId); + TstCse_T *testCase = NULL; + + if (NULL != testSuite) + { + testCase = lookupTestCase(testSuite, cId); + } + + return testCase; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestEntry_Setup(TstEnt_T *theTestEntry, CCMsg_T *ccmsg) +{ + + Retcode_T retcode = RETCODE_OK; + if ((NULL == theTestEntry) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + return (retcode); + } + + /* If test section setup pointer are null, it means that there is nothing to be done and that we can just send an + Acknowledgement with status OK.*/ + if (NULL != theTestEntry->setup) + { + retcode = theTestEntry->setup(ccmsg); + } + return retcode; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestEntry_Teardown(TstEnt_T *theTestEntry, CCMsg_T *ccmsg) +{ + + Retcode_T retcode = RETCODE_OK; + + if ((NULL == theTestEntry) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + return (retcode); + } + + /* + If test section teardown pointer are null, it means that there is nothing to be done and that we can just send an + Acknowledgement with status OK.*/ + if (NULL != theTestEntry->teardown) + { + retcode = theTestEntry->teardown(ccmsg); + } + return retcode; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestSuite_Setup(TstSte_T *testSuite, CCMsg_T *ccmsg) +{ + + Retcode_T retcode = RETCODE_OK; + + if ((NULL == testSuite) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + return (retcode); + } + /* If test suite setup pointer are null, it means that there is nothing to be done and that we can just send an + Acknowledgement with status OK.*/ + if (NULL != testSuite->setup) + { + retcode = testSuite->setup(ccmsg); + } + + return retcode; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestSuite_Teardown(TstSte_T *testSuite, CCMsg_T *ccmsg) +{ + Retcode_T retcode = RETCODE_OK; + if ((NULL == testSuite) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + return (retcode); + } + /* If test suite teardown pointer are null, it means that there is nothing to be done and that we can just send an + Acknowledgement with status OK.*/ + if (NULL != testSuite->teardown) + { + retcode = testSuite->teardown(ccmsg); + } + + return retcode; +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestCase_Setup(TstCse_T *testCase, CCMsg_T *ccmsg) +{ + + Retcode_T retcode = RETCODE_OK; + + if ((NULL == testCase) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + return (retcode); + } + if (NULL != testCase->setup) + { + retcode = testCase->setup(ccmsg); + } + + return retcode; +} + +/* @see TestRegistry.h for function description */ +void TestCase_Run(TstCse_T *testCase, CCMsg_T *ccmsg) +{ + + if ((NULL == testCase) || (NULL == ccmsg) || (NULL == testCase->run)) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_NULL_POINTER)); + } + else + { + testCase->run(ccmsg); + } +} + +/* @see TestRegistry.h for function description */ +Retcode_T TestCase_Teardown(TstCse_T *testCase, CCMsg_T *ccmsg) +{ + + Retcode_T retcode = RETCODE_OK; + + if ((NULL == testCase) || (NULL == ccmsg)) + { + /* The suite is not found */ + retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); + } + else + { + if (NULL != testCase->teardown) + { + retcode = testCase->teardown(ccmsg); + } + } + + return retcode; +} + +/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ + +/** + * @brief finds a test suite by suite Id in the test suites registry. +*/ +static TstSte_T *lookupTestSuite(uint8_t sId) +{ + TstSte_T *testSuite = NULL; + + for (uint32_t i = 0; i < testEntry.numTestSuites; i++) + { + if (sId == testEntry.testSuites[i].id) + { + testSuite = &testEntry.testSuites[i]; + break; + } + } + + return testSuite; +} + +/** + * @brief finds a test case by case Id in the test cases registry of the referenced test suite . +*/ +static TstCse_T *lookupTestCase(TstSte_T *testSuite, uint8_t cId) +{ + TstCse_T *testCase = NULL; + + for (uint32_t i = 0; i < testSuite->numTestCases; i++) + { + if (cId == testSuite->testCases[i].id) + { + testCase = &testSuite->testCases[i]; + break; + } + } + + return testCase; +} diff --git a/core/testing/source/TestRunner.c b/core/testing/source/TestRunner.c new file mode 100644 index 00000000..fe09b301 --- /dev/null +++ b/core/testing/source/TestRunner.c @@ -0,0 +1,363 @@ +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial contribution + * + ******************************************************************************/ + +/** + * @file + * + * @brief + * The TestRunner dispatches the commands to the test sections and test suites and processes the acknowledgments + * received from the test controller. + */ +#include "Kiso_Testing.h" +#include "TestRunner.h" +#include "Kiso_CmdProcessor.h" +#include "CChannel.h" +#include "TestRegistry.h" +#include "FreeRTOS.h" +#include "timers.h" + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING_TESTRUNNER + +#define TEST_ENTRY_SETUP 1 +#define TEST_SUITE_SETUP 2 +#define TEST_CASE_SETUP 3 +#define TEST_CASE_RUN 13 +#define TEST_ENTRY_TEARDOWN 21 +#define TEST_SUITE_TEARDOWN 22 +#define TEST_CASE_TEARDOWN 23 + +#define RUNNER_TIME_TO_WAIT_FOR_ACK_MS (UINT8_C(3000)) + +/** Structure containing the handler for the Test Runner */ +struct TestRunner_S +{ + uint8_t id; /**< Message token corresponding to the message type */ + uint8_t numberOfRetries; + CmdProcessor_T cmdProcessor; + TimerHandle_t timer; + uint8_t lastReceivedMessageToken; + bool waitingForAck; +}; + +static void dispatcher(CCMsg_T *ccmsg, uint32_t unusedParameter); +static void processAck(CCMsg_T *ccmsg); +static void processCommand(CCMsg_T *ccmsg); +static void ackTimerCallbackFunction(TimerHandle_t timer); +static bool isMsgAnAck(CCMsg_T *ccmsg); +static bool isMsgACommand(CCMsg_T *ccmsg); + +static struct TestRunner_S testRunner; + +/* @see TestRunner.h for function description */ +Retcode_T TestRunner_Initialize(void) +{ + Retcode_T retcode = RETCODE_OK; + testRunner.id = 0U; + + retcode = CmdProcessor_Initialize(&testRunner.cmdProcessor, + "Test Runner", + TEST_RUNNER_TASK_PRIO, + TEST_RUNNER_TASK_STACK_DEPTH, + TEST_RUNNER_QUEUE_SIZE); + + if (RETCODE_OK == retcode) + { + testRunner.timer = xTimerCreate((const char *const) "Test Runner Timer", + RUNNER_TIME_TO_WAIT_FOR_ACK_MS, + pdFALSE, + NULL, + ackTimerCallbackFunction); + if (NULL == testRunner.timer) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES); + } + } + if (RETCODE_OK == retcode) + { + retcode = CChannel_Initialize(); + } + return retcode; +} + +/* @see TestRunner.h for function description */ +void TestRunner_ProcessMessage(CCMsg_T *ccmsg) +{ + Retcode_T retcode = CmdProcessor_Enqueue(&testRunner.cmdProcessor, + (CmdProcessor_Func_T)dispatcher, + ccmsg, + 0); + + if (RETCODE_OK != retcode) + { + Retcode_RaiseError(retcode); + } +} + +/* @see TestRunner.h for function description */ +void TestRunner_SendReport(uint8_t result, char *reason) +{ + Retcode_T retcode = RETCODE_OK; + + retcode = CChannel_SendReport(result, reason); + + if (RETCODE_OK == retcode) + { + if (pdFAIL == xTimerStart(testRunner.timer, RUNNER_TIME_TO_WAIT_FOR_ACK_MS)) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES)); + } + testRunner.numberOfRetries = 3; + testRunner.waitingForAck = true; + } + else + { + Retcode_RaiseError(retcode); + } +} + +static void dispatcher(CCMsg_T *ccmsg, uint32_t unusedParameter) +{ + KISO_UNUSED(unusedParameter); + + if (isMsgAnAck(ccmsg)) + { + processAck(ccmsg); + } + else if (isMsgACommand(ccmsg)) + { + processCommand(ccmsg); + } + else + { + //@todo handle the fact that the type is not supported + // CChannel_PrepareAck(ccmsg); + // CChannel_SendAck(RETCODE_OK); + } + + CChannel_FreeCCMsg(ccmsg); +} + +static void processAck(CCMsg_T *ccmsg) +{ + if (CChannel_DoesAckMatchReport(ccmsg)) + { + + if (pdFAIL == xTimerStop(testRunner.timer, 0)) + { + //@todo raise some error? + } + + testRunner.waitingForAck = false; + testRunner.numberOfRetries = 0; + } + else + { + //@todo do we just ignore it? or raise an error? + } +} + +static void processCommand(CCMsg_T *ccmsg) +{ + Retcode_T retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); + + if (testRunner.waitingForAck) + { + /** + * this means the Test Coordinator did receive the Report but the ack was lost + * so we can just stop the timer and continue + */ + if (true != xTimerStop(testRunner.timer, 0)) + { + //@todo raise some error? + } + testRunner.waitingForAck = false; + testRunner.numberOfRetries = 0; + } + if (testRunner.lastReceivedMessageToken == ccmsg->header.messageToken) + { + /** + * if we receive again a command, we shouldn't do the associated action + * we should however resend the ack. + */ + retcode = CChannel_ResendAck(); + + if (RETCODE_OK != retcode) + { + Retcode_RaiseError(retcode); + } + return; + } + + uint8_t result = RETCODE_OK; + + TstEnt_T *testEntry = TestRegistry_LookupTestEntry(ccmsg->header.testEntry); + TstSte_T *testSuite = TestRegistry_LookupTestSuite(ccmsg->header.testEntry, ccmsg->header.testSuite); + TstCse_T *testCase = TestRegistry_LookupTestCase(ccmsg->header.testEntry, + ccmsg->header.testSuite, ccmsg->header.testCase); + + testRunner.lastReceivedMessageToken = ccmsg->header.messageToken; + + CChannel_PrepareAck(ccmsg); + + switch (ccmsg->header.messageType) + { + case TEST_ENTRY_SETUP: + if (NULL != testEntry) + { + retcode = TestEntry_Setup(testEntry, ccmsg); + } + else + { + /* @todo: The error code in Retcode_T occupies 16 bits. But, the argument type passed to CChannel_SendAck() is uint8_t. + * The test coordination protocol has an error code of 8 bits. This is a problem, as we would end up in overflow + * and due to the unsigned nature of the variable, we would wrap around. This needs to be fixed. */ + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_SUITE_SETUP: + if (NULL != testSuite) + { + retcode = TestSuite_Setup(testSuite, ccmsg); + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_CASE_SETUP: + if (NULL != testCase) + { + retcode = TestCase_Setup(testCase, ccmsg); + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_CASE_RUN: + if (NULL != testCase) + { + retcode = CChannel_SendAck(RETCODE_OK); + + if (RETCODE_OK != retcode) + { + Retcode_RaiseError(retcode); + } + else + { + CChannel_PrepareReport(ccmsg); + + TestCase_Run(testCase, ccmsg); + } + return; + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_ENTRY_TEARDOWN: + if (NULL != testEntry) + { + retcode = TestEntry_Teardown(testEntry, ccmsg); + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_SUITE_TEARDOWN: + if (NULL != testSuite) + { + retcode = TestSuite_Teardown(testSuite, ccmsg); + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + case TEST_CASE_TEARDOWN: + if (NULL != testCase) + { + retcode = TestCase_Teardown(testCase, ccmsg); + } + else + { + result = (uint8_t)RETCODE_INVALID_PARAM; + } + break; + default: + result = (uint8_t)RETCODE_INVALID_PARAM; + break; + } + + if ((uint8_t)RETCODE_INVALID_PARAM != result) + { + result = Retcode_GetCode(retcode); + } + + retcode = CChannel_SendAck(result); + + if (RETCODE_OK != retcode) + { + Retcode_RaiseError(retcode); + } +} + +static void ackTimerCallbackFunction(TimerHandle_t timer) +{ + KISO_UNUSED(timer); + Retcode_T retcode = RETCODE_OK; + + if (testRunner.numberOfRetries > 0) + { + testRunner.numberOfRetries--; + + retcode = CChannel_ResendReport(); + + if (RETCODE_OK == retcode) + { + if (true != xTimerStart(testRunner.timer, RUNNER_TIME_TO_WAIT_FOR_ACK_MS)) + { + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_ERROR, (uint32_t)RETCODE_OUT_OF_RESOURCES)); + } + } + else + { + Retcode_RaiseError(retcode); + } + } + else + { + testRunner.waitingForAck = false; + Retcode_RaiseError(RETCODE(RETCODE_SEVERITY_WARNING, RETCODE_TESTING_REPORT_TIMEOUT)); + } +} + +/** + * @brief checks the type of the message and returns true if it is an acknowledgement message. + */ +static bool isMsgAnAck(CCMsg_T *ccmsg) +{ + return CCMSG_GET_TYPE(ccmsg) == CCHANNEL_MSG_TYPE_ACK; +} + +/** + * @brief checks the type of the message and returns true if it is a command message. + */ +static bool isMsgACommand(CCMsg_T *ccmsg) +{ + return CCMSG_GET_TYPE(ccmsg) == CCHANNEL_MSG_TYPE_COMMAND; +} diff --git a/core/testing/source/Testing.c b/core/testing/source/Testing.c new file mode 100644 index 00000000..37f0503a --- /dev/null +++ b/core/testing/source/Testing.c @@ -0,0 +1,74 @@ + +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * + * @brief + * This file implements the promised API functions in Kiso_Testing.h with help of the components TestRunner TestRegistry + * and CChannel. + */ +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Testing.h" +#include "TestRegistry.h" +#include "TestRunner.h" +#include "CChannel.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +#undef KISO_MODULE_ID +#define KISO_MODULE_ID KISO_MODULE_ID_TESTING + +/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ + +/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ + +/*###################### VARIABLES DECLARATION #######################################################################*/ + +/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ + +/* @see Kiso_Testing.h for function description */ +Retcode_T Tests_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown) +{ + TestRegistry_Initialize(eId, setup, teardown); + return TestRunner_Initialize(); +} + +/* @see Kiso_Testing.h for function description */ +Retcode_T Tests_RegisterTestSuite(uint8_t sId, SetupFct_T setup, TearDownFct_T teardown) +{ + return TestRegistry_RegisterTestSuite(sId, setup, teardown); +} + +/* @see Kiso_Testing.h for function description */ +Retcode_T Tests_RegisterTestCase(uint8_t sId, uint8_t cId, SetupFct_T setup, RunFct_T run, TearDownFct_T teardown) +{ + return TestRegistry_RegisterTestCase(sId, cId, setup, run, teardown); +} + +/* @see Kiso_Testing.h for function description */ +void Tests_SendReport(uint8_t result, char *reason) +{ + TestRunner_SendReport(result, reason); +} + +/* @see Kiso_Testing.h for function description */ +Retcode_T Tests_GetTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement) +{ + return CChannel_GetTlvElement(ccmsg, tlvElement); +} + +/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ \ No newline at end of file diff --git a/core/testing/source/protected/CChannel.h b/core/testing/source/protected/CChannel.h new file mode 100644 index 00000000..506ebb13 --- /dev/null +++ b/core/testing/source/protected/CChannel.h @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial contribution + * + ******************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief communication channel handling APIs. + * @details + * This file is part of the test executor software component in the integration testing framework. it provides + * internally used API functions managing the communication protocol between the test executor and the test controller. + * + */ + +#ifndef KISO_CCHANNEL_H_ +#define KISO_CCHANNEL_H_ + +#include "Kiso_Testing.h" + +#define CCMSG_VERSION 1 + +#define CCMSG_TYPE_MASK 0x30 +#define CCMSG_VERSION_MASK 0xC0 +#define CCMSG_VERSION_SHIFT (6) + +#define CCMSG_GET_VERSION(ccmsg) (((ccmsg->header.messageInfo & CCMSG_VERSION_MASK) >> 6) & 0x03) +#define CCMSG_GET_TYPE(ccmsg) (((ccmsg->header.messageInfo & CCMSG_TYPE_MASK) >> 4) & 0x03) + +#define CCMSG_CREATE_TYPE(type) ((((type << 4) & 0x30) + ((CCMSG_VERSION << 6) & CCMSG_VERSION_MASK)) & 0xFF) + +#define CCHANNEL_MSG_TYPE_COMMAND 0 +#define CCHANNEL_MSG_TYPE_REPORT 1 +#define CCHANNEL_MSG_TYPE_ACK 2 + +#define CCHANNEL_MESSAGE_MAX_LENGTH 256 +#define CCHANNEL_HEADER_LENGTH 8 +#define CCHANNEL_PAYLOAD_MAX_SIZE CCHANNEL_MESSAGE_MAX_LENGTH - CCHANNEL_HEADER_LENGTH + +#define NO_REBOOT 0 + +/** + * @brief Initializes the CChannel + * + * @return RETCODE_OK in case of success error code otherwise. + */ +Retcode_T CChannel_Initialize(void); + +/** + * @brief Frees the message after having been processed + * + * @param[in] ccmsg The reference to the message to free + */ +void CChannel_FreeCCMsg(CCMsg_T *ccmsg); + +/** + * @brief Prepares the Ack corresponding to the message + * + * @param[in] ccmsg The reference to the message we are preparing the ack for + */ +void CChannel_PrepareAck(CCMsg_T *ccmsg); + +/** + * @brief Sends the ack previously prepared + * + * @param[in] result error code to set in the ack before sending it + * + * @retcode RETCODE_OK (=0) on success. Any other value means a failure + */ +Retcode_T CChannel_SendAck(uint8_t result); + +/** + * @brief Resends the last ack. This is used in case we receive twice the same command + * + * @return RETCODE_OK on success. Any other value means a failure + */ +Retcode_T CChannel_ResendAck(void); + +/** + * @brief Prepares the report corresponding to the message + * + * @param[in] ccmsg The message we are preparing the report for + */ +void CChannel_PrepareReport(CCMsg_T *ccmsg); + +/** + * @brief Sends a Report. Function to be called within the run function implementation + * + * @param[in] result The test result code (0: success / otherwise: failure) + * @param[in] reason 0-terminating string stating a reason. It can be NULL, if no reason should be sent. + * + * @reurn RETCODE_OK on success. Any other value means a failure + */ +Retcode_T CChannel_SendReport(uint8_t result, char *reason); + +/** + * @brief Resends the last report. This is used in case we did not receive an ack for our report + * + * @return RETCODE_OK on success. Any other value means a failure + */ +Retcode_T CChannel_ResendReport(void); + +/** + * @brief Gets a tlv element using the type of the tlvElement input + * + * @param[in] ccmsg - message in which to look for the element + * @param[in, out] tlvElement - to store the element if found + * tlvElement->type used as input to find the element's value + * + * @return RETCODE_OK if found the TLV element, error code otherwise. + */ +Retcode_T CChannel_GetTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement); + +/** + * @brief Checks if the ack we received corresponds to the report we just sent + * + * @param[in] ccack ack message we received + * + * @return true if match, false if no match + */ +bool CChannel_DoesAckMatchReport(CCMsg_T *ccack); + +/** + * @brief Stops the CChannel + * + * @return RETCODE_OK on success. Any other value means a failure + */ +Retcode_T CChannel_Deinitialize(void); + +/** + * @brief Method triggered when a new message in received + * + * @param[in] buffer pointer to the message as a raw buffer + * @param[in] length length of the received message + */ +void CChannel_ReceiveEventHandler(uint8_t *buffer, uint8_t length); + +/** + * @brief Adds the tlv Element to the message + * + * @param[in] ccmsg Message to add the element to + * @param[in] tlvElement TLV element to add + * + * @return RETCODE_OK if successful error code otherwise + */ +Retcode_T CChannel_AddTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement); + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +#endif /* KISO_CCHANNEL_H_ */ diff --git a/core/testing/source/protected/SerialCChannel.h b/core/testing/source/protected/SerialCChannel.h new file mode 100644 index 00000000..88e1b0eb --- /dev/null +++ b/core/testing/source/protected/SerialCChannel.h @@ -0,0 +1,77 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief Serial Communication Channel Interface + * @details This header provides APIs to control uart communication line if this one is used as a communication + * channel between the Test executor and the test controller. + */ + +#ifndef SERIALCCHANNEL_H_ +#define SERIALCCHANNEL_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Retcode.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +/*###################### TYPE DEFINITIONS ############################################################################*/ + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Initializes the serial interface + * + * @return RETCODE_OK if Initialized successfully error code otherwise. + */ +Retcode_T Serial_Initialize(void); + +/** + * @brief Deinitializes the serial interface + * + * @return RETCODE_OK if Deinitialized successfully error code otherwise. + */ +Retcode_T Serial_Deinitialize(void); + +/** + * @brief Receives data from the serial interface + * + * @param[in] data: A pointer to a data buffer. + * @param[in] len: Number of bytes to be received. + * + * @return RETCODE_OK if Receive successful error code otherwise. + */ +Retcode_T Serial_Receive(void *data, uint32_t len); + +/** + * @brief Sends data to the serial interface + * + * @param[in] data: A pointer to a data buffer. + * @param[in] len: Number of bytes to be sent. + * + * @return RETCODE_OK if Send successful error code otherwise + */ +Retcode_T Serial_Send(void *data, uint32_t len); + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +extern volatile uint32_t serialReceivedCnt; + +#endif /* SERIALCCHANNEL_H_ */ diff --git a/core/testing/source/protected/SerialMsgTransceiver.h b/core/testing/source/protected/SerialMsgTransceiver.h new file mode 100644 index 00000000..f1901bbe --- /dev/null +++ b/core/testing/source/protected/SerialMsgTransceiver.h @@ -0,0 +1,58 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief Provides APIs for sending and receiving over serial line + * + */ + +#ifndef SERIALMSGTRANSCEIVER_H_ +#define SERIALMSGTRANSCEIVER_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Retcode.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +/*###################### TYPE DEFINITIONS ############################################################################*/ + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Receive a message parses it and forwards it to CChannel for further processing + */ +void SerialMsgTransceiver_Receive(void); + +/** + * @brief Sends a message through the serial interface. + * @details This can be also used for building and sending the messages via other interfaces + * + * @param[in] message A reference to the message buffer to be sent. + * @param[in] length The Length of the message to be sent. + * + * @retval RETCODE_OK if the message transmitted successfully, error code otherwise. + */ +Retcode_T SerialMsgTransceiver_Send(uint8_t *message, uint8_t length); + +#endif /* SERIALMSGTRANSCEIVER_H_ */ + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +/** @} */ diff --git a/core/testing/source/protected/TestRegistry.h b/core/testing/source/protected/TestRegistry.h new file mode 100644 index 00000000..bf07f5d5 --- /dev/null +++ b/core/testing/source/protected/TestRegistry.h @@ -0,0 +1,197 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief todo mak write brief here + * + */ + +#ifndef TESTREGISTRY_H_ +#define TESTREGISTRY_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Testing.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +#ifndef TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE +#warning "config TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE not set. The software will build with default value 1" +#define TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE 1 +#endif +/*###################### TYPE DEFINITIONS ############################################################################*/ + +/** + * @brief Structure for the test case which contains the function pointers for setup, run and tear down. + */ +typedef struct TestCase_S +{ + uint8_t id; + SetupFct_T setup; + RunFct_T run; + TearDownFct_T teardown; +} TstCse_T, TstCse_T; + +/** + * @brief Structure for the test suite which contains the function pointers for setup and tear down. + * It also contains the test case structure with the number of test cases. + */ +typedef struct TestSuite_S +{ + uint8_t id; + uint8_t numTestCases; + SetupFct_T setup; + TearDownFct_T teardown; + TstCse_T testCases[TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE]; +} TstSte_T; + +typedef struct TestEntry_S +{ + uint8_t id; + uint8_t numTestSuites; + SetupFct_T setup; + TearDownFct_T teardown; + TstSte_T testSuites[TEST_MAX_NUMBER_OF_TEST_SUITES]; +} TstEnt_T, TstEnt_T; + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Initializes the Test Registry. + * @details This function Initializes the Test Registry by filling it with the Test Entry, Test Suites and Test Case arrays. + * @param[in] eId The Identifier of the Test Entry + * @param[in] setup A reference to the setup function of the Test Entry. + * @param[in] teardown A reference to the tear down function of the Test Entry. + * @note setup and tear down functions pointers can be null if nothing has to be done. + */ +void TestRegistry_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown); + +/** + * @brief Registers a Test Suite + * @param[in] sId The Identifier of the Test Suite to register + * @param[in] setup A reference to the setup function of the Test Suite + * @param[in] teardown A reference to the tear down function of the Test Suite + * @note setup and tear down functions pointers can be null if nothing has to be done. + * @return RETCODE_OK if the Test Suite registered successfully. + * @return RETCODE_OUT_OF_RESOURCES if Maximum number of Test Suites reached. + * @return RETCODE_TESTING_SUITE_ALREADY_REGISTERED if a test suite with this Id has already been registered. + */ +Retcode_T TestRegistry_RegisterTestSuite(uint8_t sId, SetupFct_T setup, TearDownFct_T teardown); + +/** + * @brief Registers a Test Case + * @param[in] sId Id of the Test Suite of the test to register + * @param[in] cId Id of the Test Case to register + * @param[in] setup A reference to the setup function of the Test Suite + * @param[in] run A reference to the run function of the Test Suite + * @param[in] teardown function pointer to the tear down function of the Test Suite + * @note setup and tear down functions pointers can be null if nothing has to be done. + * @note run method MUST NOT be NULL! + * @return RETCODE_OK The Test Case registered successfully + * @return RETCODE_INVALID_PARAM No Test Suite with the given Id has been found + * @return RETCODE_OUT_OF_RESOURCES Maximum number of Test Cases reached + * @return RETCODE_TESTING_CASE_ALREADY_REGISTERED A Test Case with this Id has already been registered + */ +Retcode_T TestRegistry_RegisterTestCase(uint8_t sId, uint8_t cId, SetupFct_T setup, RunFct_T run, TearDownFct_T teardown); + +/** + * @brief Looks up for a Test Entry by Id + * @param[in] eId Id of the Test Entry to look for. + * @return Reference to the Test Entry on SUCCESS NULL if not found + */ +TstEnt_T *TestRegistry_LookupTestEntry(uint8_t eId); + +/** + * @brief Looks up for a Test Suite + * + * @param[in] eId The identifier of the Test Suite the test to register + * @param[in] sId The identifier of the Test Suite the test to register + * @return Test Suite pointer on SUCCESS NULL if not found. + */ +TstSte_T *TestRegistry_LookupTestSuite(uint8_t eId, uint8_t sId); + +/** + * @brief Looks up for a Test Case + * + * @param[in] eId The identifier of the test entry register to look from + * @param[in] sId The identifier of the test suite register to look from + * @param[in] cId Id of the Test Case to look for. + * @return Test Case pointer on SUCCESS, NULL if not found + */ +TstCse_T *TestRegistry_LookupTestCase(uint8_t eId, uint8_t sId, uint8_t cId); + +/** + * @brief Calls the setup function of the Test Entry + * @param[in] testEntry Test Entry which setup function should be called + * @param[in] ccmsg Message to pass to the Test Entry setup function. + * @return Retcode of the Setup function to use for the Acknowledgment message + */ +Retcode_T TestEntry_Setup(TstEnt_T *testEntry, CCMsg_T *ccmsg); + +/** + * @brief Calls the teardown function of the Test Entry + * @param[in] testEntry Test Entry which teardown function should be called + * @param[in] ccmsg Message to pass to the Test Entry teardown function. + * @return Retcode of the Teardown function to use for the Acknowledgment message. + */ +Retcode_T TestEntry_Teardown(TstEnt_T *testEntry, CCMsg_T *ccmsg); + +/** + * @brief Calls the setup function of the Test Suite + * @param[in] testSuite Test Suite which setup function should be called + * @param[in] ccmsg Message to pass to the Test Suite setup function. * + * @return Retcode of the setup function to use for the Acknowledgment message + */ +Retcode_T TestSuite_Setup(TstSte_T *testSuite, CCMsg_T *ccmsg); + +/** + * @brief Calls the teardown function of the Test Suite + * @param[in] testSuite Test Suite which teardown function should be called + * @param[in] ccmsg Message to pass to the Test Suite teardown function. + * @return Retcode of the Teardown function to use for the Acknowledgment message + */ +Retcode_T TestSuite_Teardown(TstSte_T *testSuite, CCMsg_T *ccmsg); + +/** + * @brief Calls the setup function of the Test Case + * @param[in] testCase Test Case which setup function should be called + * @param[in] ccmsg Message to pass to the Test Case setup function. + * @return Retcode of the setup function to use for the Acknowledgment message + */ +Retcode_T TestCase_Setup(TstCse_T *testCase, CCMsg_T *ccmsg); + +/** + * @brief Calls the run function of the Test Case + * @param[in] testCase Test Case which run function should be called + * @param[in] ccmsg Message to pass to the Test Case run function. + */ +void TestCase_Run(TstCse_T *testCase, CCMsg_T *ccmsg); + +/** + * @brief Calls the teardown function of the Test Case + * + * @param[in] testCase Test Case which teardown function should be called + * @param[in] ccmsg Message to pass to the Test Case teardown function. + * @return Retcode of the Teardown function to use for the Acknowledgment message + */ +Retcode_T TestCase_Teardown(TstCse_T *testCase, CCMsg_T *ccmsg); + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +#endif /* TESTREGISTRY_H_ */ diff --git a/core/testing/source/protected/TestRunner.h b/core/testing/source/protected/TestRunner.h new file mode 100644 index 00000000..5b0ec3e1 --- /dev/null +++ b/core/testing/source/protected/TestRunner.h @@ -0,0 +1,62 @@ +/********************************************************************************************************************** + * Copyright (c) 2010#2019 Robert Bosch GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl#2.0. + * + * SPDX#License#Identifier: EPL#2.0 + * + * Contributors: + * Robert Bosch GmbH # initial contribution + * + **********************************************************************************************************************/ + +/** + * @file + * @defgroup + * @ingroup + * @{ + * + * @brief todo mak provide description of the module. + * + */ + +#ifndef TESTRUNNER_H_ +#define TESTRUNNER_H_ + +/*###################### INCLUDED HEADERS ############################################################################*/ + +#include "Kiso_Testing.h" +#include "Kiso_Retcode.h" + +/*###################### MACROS DEFINITION ###########################################################################*/ + +/*###################### TYPE DEFINITIONS ############################################################################*/ + +/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ + +/** + * @brief Initializes the Test Runner + * @retval RETCODE_OK in case of success, error code otherwise. + */ +Retcode_T TestRunner_Initialize(void); + +/** + * @brief Processes an incoming message + * @param[in] ccmsg incoming message to process + */ +void TestRunner_ProcessMessage(CCMsg_T *ccmsg); + +/** + * @brief Sends a Report. + * + * @param[in] result test result code (0: success / otherwise: failure) + * @param[in] reason 0-terminating string stating a reason. It can be NULL, if no reason should be sent. + */ +void TestRunner_SendReport(uint8_t result, char *reason); + +/*###################### GLOBAL VARIABLES ###########################################################################*/ + +#endif /* TESTRUNNER_H_ */ +/** @} */ From 27a58f5cde7c87cdd5e3d1172e7d35e24f071140 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 7 Sep 2020 16:48:53 +0200 Subject: [PATCH 03/29] Neaten up core/testing code Signed-off-by: ChiefGokhlayehBosch --- .../test/integration/source/TestEntry.c | 25 ++++----------- core/testing/source/Serial.c | 27 ++++------------ core/testing/source/SerialMsgTransceiver.c | 16 +++++----- core/testing/source/TestRegistry.c | 32 ++++++------------- core/testing/source/Testing.c | 27 ++++------------ core/testing/source/protected/CChannel.h | 2 -- .../testing/source/protected/SerialCChannel.h | 24 ++++---------- .../source/protected/SerialMsgTransceiver.h | 24 ++++---------- core/testing/source/protected/TestRegistry.h | 25 +++++---------- core/testing/source/protected/TestRunner.h | 26 +++++---------- 10 files changed, 67 insertions(+), 161 deletions(-) diff --git a/core/essentials/test/integration/source/TestEntry.c b/core/essentials/test/integration/source/TestEntry.c index 974a9580..7954f9a5 100644 --- a/core/essentials/test/integration/source/TestEntry.c +++ b/core/essentials/test/integration/source/TestEntry.c @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -18,29 +18,18 @@ * @brief * Implements the following functionalities specified in template.h */ -/*###################### INCLUDED HEADERS ############################################################################*/ #include "Kiso_Testing.h" #include "TestSuiteUart.h" -/*###################### MACROS DEFINITION ###########################################################################*/ #undef KISO_MODULE_ID #define KISO_MODULE_ID KISO_MODULE_ID_TEST_ENTRY #define TEST_ENTRY_ID 1 -/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ - -/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ Retcode_T TestEntry_Initialize(void *param1, uint32_t param2); static Retcode_T TestEntry_Setup(CCMsg_T *ccmsg); static Retcode_T TestEntry_Teardown(CCMsg_T *ccmsg); -/*###################### VARIABLES DECLARATION #######################################################################*/ - -/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ - -/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ - Retcode_T TestEntry_Initialize(void *param1, uint32_t param2) { KISO_UNUSED(param1); @@ -65,4 +54,4 @@ static Retcode_T TestEntry_Teardown(CCMsg_T *ccmsg) { KISO_UNUSED(ccmsg); return RETCODE_OK; -} \ No newline at end of file +} diff --git a/core/testing/source/Serial.c b/core/testing/source/Serial.c index d399bfba..cf742c6b 100644 --- a/core/testing/source/Serial.c +++ b/core/testing/source/Serial.c @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -18,8 +18,6 @@ * @brief * todo add brief description Implements the following functionalities specified in template.h */ -/*###################### INCLUDED HEADERS ###########################################################################*/ - #include "Kiso_Testing.h" #include "Kiso_Testing_Config.h" #include "Kiso_BSP_TestInterface.h" @@ -31,7 +29,6 @@ #include "SerialMsgTransceiver.h" #include "SerialCChannel.h" -/*###################### MACROS DEFINITION ##########################################################################*/ #undef KISO_MODULE_ID #define KISO_MODULE_ID KISO_MODULE_ID_TESTING_SERIAL @@ -47,14 +44,8 @@ #define SERIAL_TASK_STACK_DEPTH (128UL) #endif /* SERIAL_TASK_STACK_DEPTH */ -/*###################### LOCAL_TYPES DEFINITION #####################################################################*/ - -/*###################### LOCAL FUNCTIONS DECLARATION ################################################################*/ - static void uartEventsCallbackFunc(UART_T uart, struct MCU_UART_Event_S event); -/*###################### VARIABLES DECLARATION ######################################################################*/ - static GuardedTask_T serialGuardedTask; volatile uint32_t serialReceivedCnt = 0; static SemaphoreHandle_t TransmitDataSemaphoreHandle = NULL; @@ -66,8 +57,6 @@ static UART_T TestInterfaceUart; volatile uint32_t TestUartErrorCount = 0; /* number of errors post mortem */ #endif /* NDEBUG */ -/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ###########################################################*/ - /* The description is defined at function declaration */ Retcode_T Serial_Initialize(void) { @@ -173,8 +162,6 @@ Retcode_T Serial_Receive(void *data, uint32_t len) return retcode; } -/*###################### LOCAL FUNCTIONS IMPLEMENTATION #############################################################*/ - static void uartEventsCallbackFunc(UART_T uart, struct MCU_UART_Event_S event) { KISO_UNUSED(uart); /* not used in One-Byte-Mode */ @@ -216,4 +203,4 @@ static void uartEventsCallbackFunc(UART_T uart, struct MCU_UART_Event_S event) { Retcode_RaiseErrorFromIsr(retcode); } -} \ No newline at end of file +} diff --git a/core/testing/source/SerialMsgTransceiver.c b/core/testing/source/SerialMsgTransceiver.c index 147b7fce..dbf5e887 100644 --- a/core/testing/source/SerialMsgTransceiver.c +++ b/core/testing/source/SerialMsgTransceiver.c @@ -70,34 +70,34 @@ Retcode_T SerialMsgTransceiver_Send(uint8_t *message, uint8_t length) sendBuffer[j++] = START; - if (((crc >> CHAR_BIT) & 0xFF) == START) + if (((crc >> CHAR_BIT) & CRC_BYTE_MASK) == START) { sendBuffer[j++] = ESC; sendBuffer[j++] = ESC_START; } - else if (((crc >> CHAR_BIT) & 0xFF) == ESC) + else if (((crc >> CHAR_BIT) & CRC_BYTE_MASK) == ESC) { sendBuffer[j++] = ESC; sendBuffer[j++] = ESC_ESC; } else { - sendBuffer[j++] = (crc >> CHAR_BIT) & 0xFF; + sendBuffer[j++] = (crc >> CHAR_BIT) & CRC_BYTE_MASK; } - if ((crc & 0xFF) == START) + if ((crc & CRC_BYTE_MASK) == START) { sendBuffer[j++] = ESC; sendBuffer[j++] = ESC_START; } - else if ((crc & 0xFF & 0xFF) == ESC) + else if ((crc & CRC_BYTE_MASK) == ESC) { sendBuffer[j++] = ESC; sendBuffer[j++] = ESC_ESC; } else { - sendBuffer[j++] = crc & 0xFF; + sendBuffer[j++] = crc & CRC_BYTE_MASK; } for (uint32_t i = 0; i < length; i++) @@ -123,8 +123,8 @@ Retcode_T SerialMsgTransceiver_Send(uint8_t *message, uint8_t length) return (ReturnValue); } -//@todo dei9bue: verify total length, limited to 256! what to do if exceeded? -/* The description is defined at function declaration */ +/** @todo Verify total length, limited to 256! what to do if exceeded? + * The description is defined at function declaration */ void SerialMsgTransceiver_Receive(void) { static uint8_t receivingState = WAITING_FOR_START; diff --git a/core/testing/source/TestRegistry.c b/core/testing/source/TestRegistry.c index 71a32c60..e774bd7b 100644 --- a/core/testing/source/TestRegistry.c +++ b/core/testing/source/TestRegistry.c @@ -1,45 +1,33 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file * * @brief */ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Testing.h" #include "TestRegistry.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - #undef KISO_MODULE_ID #define KISO_MODULE_ID KISO_MODULE_ID_TESTING_TESTREGISTRY -/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ - -/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ - static TstSte_T *lookupTestSuite(uint8_t sId); static TstCse_T *lookupTestCase(TstSte_T *testSuite, uint8_t cId); -/*###################### VARIABLES DECLARATION #######################################################################*/ - static TstEnt_T testEntry; -/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ - /* @see TestRegistry.h for function description */ void TestRegistry_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown) { @@ -168,7 +156,7 @@ Retcode_T TestEntry_Setup(TstEnt_T *theTestEntry, CCMsg_T *ccmsg) return (retcode); } - /* If test section setup pointer are null, it means that there is nothing to be done and that we can just send an + /* If test section setup pointer are null, it means that there is nothing to be done and that we can just send an Acknowledgement with status OK.*/ if (NULL != theTestEntry->setup) { @@ -212,7 +200,7 @@ Retcode_T TestSuite_Setup(TstSte_T *testSuite, CCMsg_T *ccmsg) retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); return (retcode); } - /* If test suite setup pointer are null, it means that there is nothing to be done and that we can just send an + /* If test suite setup pointer are null, it means that there is nothing to be done and that we can just send an Acknowledgement with status OK.*/ if (NULL != testSuite->setup) { @@ -232,7 +220,7 @@ Retcode_T TestSuite_Teardown(TstSte_T *testSuite, CCMsg_T *ccmsg) retcode = RETCODE(RETCODE_SEVERITY_WARNING, (uint32_t)RETCODE_NULL_POINTER); return (retcode); } - /* If test suite teardown pointer are null, it means that there is nothing to be done and that we can just send an + /* If test suite teardown pointer are null, it means that there is nothing to be done and that we can just send an Acknowledgement with status OK.*/ if (NULL != testSuite->teardown) { @@ -298,8 +286,6 @@ Retcode_T TestCase_Teardown(TstCse_T *testCase, CCMsg_T *ccmsg) return retcode; } -/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ - /** * @brief finds a test suite by suite Id in the test suites registry. */ diff --git a/core/testing/source/Testing.c b/core/testing/source/Testing.c index 37f0503a..969e0043 100644 --- a/core/testing/source/Testing.c +++ b/core/testing/source/Testing.c @@ -1,17 +1,16 @@ - -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -20,26 +19,14 @@ * This file implements the promised API functions in Kiso_Testing.h with help of the components TestRunner TestRegistry * and CChannel. */ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Testing.h" #include "TestRegistry.h" #include "TestRunner.h" #include "CChannel.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - #undef KISO_MODULE_ID #define KISO_MODULE_ID KISO_MODULE_ID_TESTING -/*###################### LOCAL_TYPES DEFINITION ######################################################################*/ - -/*###################### LOCAL FUNCTIONS DECLARATION #################################################################*/ - -/*###################### VARIABLES DECLARATION #######################################################################*/ - -/*###################### EXPOSED FUNCTIONS IMPLEMENTATION ############################################################*/ - /* @see Kiso_Testing.h for function description */ Retcode_T Tests_Initialize(uint8_t eId, SetupFct_T setup, TearDownFct_T teardown) { @@ -70,5 +57,3 @@ Retcode_T Tests_GetTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement) { return CChannel_GetTlvElement(ccmsg, tlvElement); } - -/*###################### LOCAL FUNCTIONS IMPLEMENTATION ##############################################################*/ \ No newline at end of file diff --git a/core/testing/source/protected/CChannel.h b/core/testing/source/protected/CChannel.h index 506ebb13..6cf75d09 100644 --- a/core/testing/source/protected/CChannel.h +++ b/core/testing/source/protected/CChannel.h @@ -157,6 +157,4 @@ void CChannel_ReceiveEventHandler(uint8_t *buffer, uint8_t length); */ Retcode_T CChannel_AddTlvElement(CCMsg_T *ccmsg, TlvElt_T *tlvElement); -/*###################### GLOBAL VARIABLES ###########################################################################*/ - #endif /* KISO_CCHANNEL_H_ */ diff --git a/core/testing/source/protected/SerialCChannel.h b/core/testing/source/protected/SerialCChannel.h index 88e1b0eb..f4746e44 100644 --- a/core/testing/source/protected/SerialCChannel.h +++ b/core/testing/source/protected/SerialCChannel.h @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -19,23 +19,15 @@ * @{ * * @brief Serial Communication Channel Interface - * @details This header provides APIs to control uart communication line if this one is used as a communication + * @details This header provides APIs to control uart communication line if this one is used as a communication * channel between the Test executor and the test controller. */ #ifndef SERIALCCHANNEL_H_ #define SERIALCCHANNEL_H_ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Retcode.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - -/*###################### TYPE DEFINITIONS ############################################################################*/ - -/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ - /** * @brief Initializes the serial interface * @@ -70,8 +62,6 @@ Retcode_T Serial_Receive(void *data, uint32_t len); */ Retcode_T Serial_Send(void *data, uint32_t len); -/*###################### GLOBAL VARIABLES ###########################################################################*/ - extern volatile uint32_t serialReceivedCnt; #endif /* SERIALCCHANNEL_H_ */ diff --git a/core/testing/source/protected/SerialMsgTransceiver.h b/core/testing/source/protected/SerialMsgTransceiver.h index f1901bbe..9aa5fe8c 100644 --- a/core/testing/source/protected/SerialMsgTransceiver.h +++ b/core/testing/source/protected/SerialMsgTransceiver.h @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -19,22 +19,14 @@ * @{ * * @brief Provides APIs for sending and receiving over serial line - * + * */ #ifndef SERIALMSGTRANSCEIVER_H_ #define SERIALMSGTRANSCEIVER_H_ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Retcode.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - -/*###################### TYPE DEFINITIONS ############################################################################*/ - -/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ - /** * @brief Receive a message parses it and forwards it to CChannel for further processing */ @@ -53,6 +45,4 @@ Retcode_T SerialMsgTransceiver_Send(uint8_t *message, uint8_t length); #endif /* SERIALMSGTRANSCEIVER_H_ */ -/*###################### GLOBAL VARIABLES ###########################################################################*/ - /** @} */ diff --git a/core/testing/source/protected/TestRegistry.h b/core/testing/source/protected/TestRegistry.h index bf07f5d5..ed6def61 100644 --- a/core/testing/source/protected/TestRegistry.h +++ b/core/testing/source/protected/TestRegistry.h @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -19,23 +19,18 @@ * @{ * * @brief todo mak write brief here - * + * */ #ifndef TESTREGISTRY_H_ #define TESTREGISTRY_H_ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Testing.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - #ifndef TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE #warning "config TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE not set. The software will build with default value 1" #define TEST_MAX_NUMBER_OF_TEST_CASES_PER_TEST_SUITE 1 #endif -/*###################### TYPE DEFINITIONS ############################################################################*/ /** * @brief Structure for the test case which contains the function pointers for setup, run and tear down. @@ -70,9 +65,7 @@ typedef struct TestEntry_S TstSte_T testSuites[TEST_MAX_NUMBER_OF_TEST_SUITES]; } TstEnt_T, TstEnt_T; -/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ - -/** +/** * @brief Initializes the Test Registry. * @details This function Initializes the Test Registry by filling it with the Test Entry, Test Suites and Test Case arrays. * @param[in] eId The Identifier of the Test Entry @@ -192,6 +185,4 @@ void TestCase_Run(TstCse_T *testCase, CCMsg_T *ccmsg); */ Retcode_T TestCase_Teardown(TstCse_T *testCase, CCMsg_T *ccmsg); -/*###################### GLOBAL VARIABLES ###########################################################################*/ - #endif /* TESTREGISTRY_H_ */ diff --git a/core/testing/source/protected/TestRunner.h b/core/testing/source/protected/TestRunner.h index 5b0ec3e1..32544f38 100644 --- a/core/testing/source/protected/TestRunner.h +++ b/core/testing/source/protected/TestRunner.h @@ -1,16 +1,16 @@ -/********************************************************************************************************************** - * Copyright (c) 2010#2019 Robert Bosch GmbH +/******************************************************************************* + * Copyright (c) 2010-2020 Robert Bosch GmbH * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl#2.0. + * http://www.eclipse.org/legal/epl-2.0. * - * SPDX#License#Identifier: EPL#2.0 + * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * Robert Bosch GmbH # initial contribution + * Robert Bosch GmbH - initial contribution * - **********************************************************************************************************************/ + ******************************************************************************/ /** * @file @@ -19,30 +19,22 @@ * @{ * * @brief todo mak provide description of the module. - * + * */ #ifndef TESTRUNNER_H_ #define TESTRUNNER_H_ -/*###################### INCLUDED HEADERS ############################################################################*/ - #include "Kiso_Testing.h" #include "Kiso_Retcode.h" -/*###################### MACROS DEFINITION ###########################################################################*/ - -/*###################### TYPE DEFINITIONS ############################################################################*/ - -/*###################### EXPORTED FUNCTIONS PROTOTYPES ###############################################################*/ - /** * @brief Initializes the Test Runner * @retval RETCODE_OK in case of success, error code otherwise. */ Retcode_T TestRunner_Initialize(void); -/** +/** * @brief Processes an incoming message * @param[in] ccmsg incoming message to process */ @@ -56,7 +48,5 @@ void TestRunner_ProcessMessage(CCMsg_T *ccmsg); */ void TestRunner_SendReport(uint8_t result, char *reason); -/*###################### GLOBAL VARIABLES ###########################################################################*/ - #endif /* TESTRUNNER_H_ */ /** @} */ From e5a5904f5fd9d5e6d655d26b46ef69495d2b727c Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 7 Sep 2020 17:37:31 +0200 Subject: [PATCH 04/29] Properly await echo in UART integration test Signed-off-by: ChiefGokhlayehBosch --- .../test/integration/source/TestSuiteUart.c | 67 ++++++++++++++----- .../test/integration/specs/UART_Test_Spec.md | 4 +- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/core/essentials/test/integration/source/TestSuiteUart.c b/core/essentials/test/integration/source/TestSuiteUart.c index 2ea102f4..26f57c9b 100644 --- a/core/essentials/test/integration/source/TestSuiteUart.c +++ b/core/essentials/test/integration/source/TestSuiteUart.c @@ -31,7 +31,7 @@ #define KISO_MODULE_ID 0 #define UART_BUFFER_LEN (5) -#define DATA_TRANSFER_TIMEOUT_MS UINT32_C(1000) +#define DATA_TRANSFER_TIMEOUT_MS UINT32_C(200) #define UART_DEVICE UINT32_C(1) #define MSG_BUFFER_SIZE (32) @@ -46,7 +46,8 @@ static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg); static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event); static UART_T UartHdl = 0; -static xSemaphoreHandle UartLock = 0; +static xSemaphoreHandle TxSignal = 0; +static xSemaphoreHandle RxSignal = 0; Retcode_T TestSuiteUart_Initialize(uint8_t sId) { @@ -78,8 +79,16 @@ static Retcode_T TestCase_FctTest_Setup(CCMsg_T *ccmsg) } if (RETCODE_OK == retcode) { - UartLock = xSemaphoreCreateBinary(); - if (NULL == UartLock) + TxSignal = xSemaphoreCreateBinary(); + if (NULL == TxSignal) + { + return RETCODE(RETCODE_SEVERITY_FATAL, RETCODE_SEMAPHORE_ERROR); + } + } + if (RETCODE_OK == retcode) + { + RxSignal = xSemaphoreCreateBinary(); + if (NULL == RxSignal) { return RETCODE(RETCODE_SEVERITY_FATAL, RETCODE_SEMAPHORE_ERROR); } @@ -118,7 +127,11 @@ static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg) } if (RETCODE_OK == retcode) { - vSemaphoreDelete(UartLock); + vSemaphoreDelete(TxSignal); + } + if (RETCODE_OK == retcode) + { + vSemaphoreDelete(RxSignal); } return retcode; } @@ -150,22 +163,46 @@ static void TestCase_FctTest_Run(CCMsg_T *ccmsg) } if (RETCODE_OK == retcode) { - if (pdTRUE != xSemaphoreTake(UartLock, DATA_TRANSFER_TIMEOUT_MS)) + if (pdTRUE != xSemaphoreTake(TxSignal, DATA_TRANSFER_TIMEOUT_MS)) { retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_SEMAPHORE_ERROR); strcpy(msg, "FAIL"); } } + if (RETCODE_OK == retcode) { - for (uint8_t i = 0; i < UART_BUFFER_LEN; i++) + if (pdTRUE == xSemaphoreTake(RxSignal, DATA_TRANSFER_TIMEOUT_MS)) { - if (dataIn[i] != dataOut[i]) + uint8_t tries = 1; + // we received something... now wait till we receive EVERYTHING. + while (pdTRUE == xSemaphoreTake(RxSignal, DATA_TRANSFER_TIMEOUT_MS)) { - retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_UNEXPECTED_BEHAVIOR); - strcpy(msg, "FAIL"); + /* Semaphore should signal at most UART_BUFFER_LEN times (also + * counting the first semaphore signal above) */ + tries++; + if (tries > UART_BUFFER_LEN) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_UNEXPECTED_BEHAVIOR); + strcpy(msg, "FAIL"); + break; + } + } + + for (uint8_t i = 0; RETCODE_OK == retcode && i < UART_BUFFER_LEN; i++) + { + if (dataIn[i] != dataOut[i]) + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_UNEXPECTED_BEHAVIOR); + strcpy(msg, "FAIL"); + } } } + else + { + retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_SEMAPHORE_ERROR); + strcpy(msg, "FAIL"); + } } Tests_SendReport(Retcode_GetCode(retcode), msg); } @@ -177,14 +214,13 @@ static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) if (UINT8_C(1) == event.TxComplete) { - if (RETCODE_OK == Rc) { BaseType_t higherPriorityTaskWoken = pdFALSE; - if (NULL != UartLock) + if (NULL != TxSignal) { - if (pdTRUE == xSemaphoreGiveFromISR(UartLock, &higherPriorityTaskWoken)) + if (pdTRUE == xSemaphoreGiveFromISR(TxSignal, &higherPriorityTaskWoken)) { portYIELD_FROM_ISR(higherPriorityTaskWoken); } @@ -202,14 +238,13 @@ static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) if (UINT8_C(1) == event.RxComplete) { - if (RETCODE_OK == Rc) { BaseType_t higherPriorityTaskWoken = pdFALSE; - if (NULL != UartLock) + if (NULL != RxSignal) { - if (pdTRUE == xSemaphoreGiveFromISR(UartLock, &higherPriorityTaskWoken)) + if (pdTRUE == xSemaphoreGiveFromISR(RxSignal, &higherPriorityTaskWoken)) { portYIELD_FROM_ISR(higherPriorityTaskWoken); } diff --git a/core/essentials/test/integration/specs/UART_Test_Spec.md b/core/essentials/test/integration/specs/UART_Test_Spec.md index b2762d3a..9538501b 100644 --- a/core/essentials/test/integration/specs/UART_Test_Spec.md +++ b/core/essentials/test/integration/specs/UART_Test_Spec.md @@ -25,7 +25,7 @@ No special teardown 1. Get device handle of BSP initialized generic UART * UART must be configured in loopback mode, echoing any data sent out -2. Allocate OS signal semaphore used as signal from IRQ +2. Allocate OS signal semaphores used as signal from IRQ 3. Connect the generic UART BSP 4. Initialize UART MCU with UART handle 5. Enable generic UART BSP @@ -43,4 +43,4 @@ No special teardown 1. Deinitialize UART MCU, deactivating IRQs 2. Disable generic UART BSP 3. Disconnect generic UART BSP -4. Free OS signal semaphore +4. Free OS signal semaphores From 04eacbcbb21536384e2353710023d1a379800afd Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 7 Sep 2020 18:04:17 +0200 Subject: [PATCH 05/29] Build essentials integration test in CI/CD Signed-off-by: ChiefGokhlayehBosch --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 36499919..96a1efdc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -85,7 +85,9 @@ pipeline { script { - echo "run integration-tests placeholder" + echo "build integration-tests" + sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_NAME="core/essentials" -DKISO_BOARD_NAME="NucleoF767"' + sh 'cmake --build builddir-integration' } } } From b17d572ecb6dd47edc4009170d73d7775ed6edf0 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Tue, 8 Sep 2020 14:23:11 +0200 Subject: [PATCH 06/29] Change KISO_INTEGRATION_TEST_NAME to ...ENTRY_PATH Signed-off-by: ChiefGokhlayehBosch --- CMakeLists.txt | 4 ++-- Jenkinsfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5f5ec78..f369aadb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ message("---------------------------------- KISO CONFIG ------------------------ message("Building Kiso tests: ${ENABLE_TESTING}") message(" ... with coverage: ${ENABLE_COVERAGE}") message("Building Kiso integration tests: ${ENABLE_INTEGRATION_TESTING}") -message(" ... with entry in: ${KISO_INTEGRATION_TEST_NAME}") +message(" ... with entry in: ${KISO_INTEGRATION_TEST_ENTRY_PATH}") message("Kiso Board Path: ${KISO_BOARD_PATH}") message("Kiso OS: ${KISO_OS_LIB}") message("Kiso Application Path: ${KISO_APPLICATION_PATH}") @@ -94,7 +94,7 @@ endif() ## Add application code if(${ENABLE_INTEGRATION_TESTING}) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/testing/integration/test-executor) - add_subdirectory(${KISO_INTEGRATION_TEST_NAME}/test/integration) + add_subdirectory(${KISO_INTEGRATION_TEST_ENTRY_PATH}) else(${ENABLE_INTEGRATION_TESTING}) add_subdirectory(${KISO_APPLICATION_PATH} ${CMAKE_CURRENT_BINARY_DIR}/applications/${KISO_APPLICATION_NAME}) endif(${ENABLE_INTEGRATION_TESTING}) diff --git a/Jenkinsfile b/Jenkinsfile index 96a1efdc..e5af285a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -86,7 +86,7 @@ pipeline script { echo "build integration-tests" - sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_NAME="core/essentials" -DKISO_BOARD_NAME="NucleoF767"' + sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH="core/essentials/test/integration" -DKISO_BOARD_NAME="NucleoF767"' sh 'cmake --build builddir-integration' } } From f4d2b9794621c786f62b4f6b590e29e746d0410c Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Tue, 8 Sep 2020 14:40:28 +0200 Subject: [PATCH 07/29] Add documentation about integration test framework Signed-off-by: ChiefGokhlayehBosch --- .../content/Concepts/integration_testing.md | 42 ++++++++++++++++++ .../content/images/integration_test.png | Bin 0 -> 28951 bytes .../content/images/integration_test.puml | 35 +++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 docs/website/content/Concepts/integration_testing.md create mode 100644 docs/website/content/images/integration_test.png create mode 100644 docs/website/content/images/integration_test.puml diff --git a/docs/website/content/Concepts/integration_testing.md b/docs/website/content/Concepts/integration_testing.md new file mode 100644 index 00000000..fb95982a --- /dev/null +++ b/docs/website/content/Concepts/integration_testing.md @@ -0,0 +1,42 @@ +--- +title: "Integration Testing Guide" +weight: 2 +--- + +## Definition + +Integration testing is a form of software testing which combines multiple (sub-)modules of a system into one test environment and verifies the compatibility between the selected system subset. + +## Realization + +In Kiso, integration testing is done **on-target**. This is realized through a special test-app which is flashed onto reference or user hardware and listens to the instructions of a test-coordinator (usually a host PC connected via UART, USB, CAN, or the like). Looking at the hierarchy top-to-bottom (see diagram below), the Kiso integration test framework consists of: + +* Test-App - Flashable/Bootable binary for reference and/or user hardware. + * Test-Executor - Application component of the test-app. Handles basic hardware initialization and connects with test-coordinator. + * Test-Entry - Linkable static library usually declared on a package-level (such as Essentials, Cellular, Utils, etc.), combining different test-suites into a single entity. + * Test-Suite - Collection of test-cases that share a common setup and teardown sequence. Can also be used as logical separation of test-cases. + * Test-Case - Individual test to verify one specific aspect of the (sub-)system. Comprised of setup, run and teardown. +* Test-Coordinator - Python based coordinator application running on a host PC. It communicates with the test-executor running on the device-under-test and decides which test-suites/-cases are to be executed. The python framework is realized as `pykiso` and is available as own executable python module. + +![integration test framework](/images/integration_test.png) + +All Kiso packages share a common test-executor, each package also defines one test-entry. Each test-executor/-entry pair compiles into a standalone binary which can be flashed onto hardware. Users can declare their own test-entries and link them with the provided test-executor, that way users can profit form the existing test framework to run integration tests of user written drivers or applications. + +Test-Entry, test-suite and test-case contain optional hooks for setup and teardown work. + +## Test-App Build System + +As with any target binary in Kiso, building is handled by the CMake toolchain. Two special workspace settings need to be provided during workspace configuration to build a integration test app, targeted towards a specific hardware: + +* `ENABLE_INTEGRATION_TESTING = ON` - Marks this workspace as targeted towards building a integration test app. Note that with this option set, `KISO_APPLICATION_NAME` will be ignored, meaning no user application will be built. +* `KISO_INTEGRATION_TEST_ENTRY_PATH = "path/to/test-entry"` - Path to directory containing a `CMakeLists.txt` used to build the `testentry` static-library. This library will be linked with the test-executor to create the test-app. The `CMakeLists.txt` must declare a static-library target called `testentry`, containing source-files for any test-suites that shall be part of the final test-app. + +The static-library target `testentry` can be created as follows: + +```cmake +add_library(testentry STATIC source/test_suite1.c source/test_suite2.c ...) +# List of additional libs needed +target_link_libraries(testentry testing essentials ${KISO_BOARD_LIBS}) +``` + +Please have a look at the existing CMake build files to get a better idea of what to include in your `CMakeLists.txt`. Test-Entry build files are usually located at `core//test/integration/` diff --git a/docs/website/content/images/integration_test.png b/docs/website/content/images/integration_test.png new file mode 100644 index 0000000000000000000000000000000000000000..027d78080a55b8b4c04dbc19b9b5a82c3b3cccc4 GIT binary patch literal 28951 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 LQ>j4F;>rI2WHyep literal 0 HcmV?d00001 diff --git a/docs/website/content/images/integration_test.puml b/docs/website/content/images/integration_test.puml new file mode 100644 index 00000000..d458dc59 --- /dev/null +++ b/docs/website/content/images/integration_test.puml @@ -0,0 +1,35 @@ +@startuml + +title Integration Test Framework + +package "Test App" { + component "Test Executor" as testexecutor + package "Kiso Package (e.g. Essentials) or User App" { + component "Test Entriy" as testentry + component "Test Suite 1" as testsuite1 + component "Test Suite 2" as testsuite2 + component "Test Suite ..." as testsuitecont + component "Test Case 1.1" as testcase11 + component "Test Case 1.2" as testcase12 + component "Test Case 2.1" as testcase21 + component "Test Case 2.2" as testcase22 + component "Test Case 2.3" as testcase23 + component "Test Case ..." as testcasecont + } +} + +testexecutor -> testentry : links with + +testentry --> testsuite1 : contains +testentry --> testsuite2 : contains +testentry --> testsuitecont : contains +testsuite1 --> testcase11 : contains +testsuite1 --> testcase12 : contains +testsuite2 --> testcase21 : contains +testsuite2 --> testcase22 : contains +testsuite2 --> testcase23 : contains +testsuitecont --> testcasecont : contains + +footer © Robert Bosch GmbH 2010-2020 | Licensed under EPL-2.0 + +@enduml From b99cb5fbfd0388abb7bef03236e7b7f5975e4d73 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 10 Sep 2020 14:55:04 +0200 Subject: [PATCH 08/29] Fix test-suite setup and teardown Signed-off-by: ChiefGokhlayehBosch --- core/essentials/test/integration/source/TestSuiteUart.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/essentials/test/integration/source/TestSuiteUart.c b/core/essentials/test/integration/source/TestSuiteUart.c index 26f57c9b..62cf92e5 100644 --- a/core/essentials/test/integration/source/TestSuiteUart.c +++ b/core/essentials/test/integration/source/TestSuiteUart.c @@ -53,7 +53,7 @@ Retcode_T TestSuiteUart_Initialize(uint8_t sId) { Retcode_T retcode = RETCODE_OK; - retcode = Tests_RegisterTestSuite(sId, TestCase_FctTest_Setup, TestCase_FctTest_Teardown); + retcode = Tests_RegisterTestSuite(sId, NULL, NULL); if (RETCODE_OK == retcode) { @@ -115,6 +115,10 @@ static Retcode_T TestCase_FctTest_Teardown(CCMsg_T *ccmsg) { KISO_UNUSED(ccmsg); Retcode_T retcode; + uint8_t dummy[0]; + + /* cancel receive */ + (void)MCU_UART_Receive(UartHdl, dummy, 0); retcode = MCU_UART_Deinitialize(UartHdl); if (RETCODE_OK == retcode) From d33f5e1b7e179415d01350b488e1350efa81ef64 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 10 Sep 2020 14:56:24 +0200 Subject: [PATCH 09/29] Adapt CCMsg sub-type for ACK and REPORT Signed-off-by: ChiefGokhlayehBosch --- core/testing/source/CChannel.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/testing/source/CChannel.c b/core/testing/source/CChannel.c index ba864be6..a419751e 100644 --- a/core/testing/source/CChannel.c +++ b/core/testing/source/CChannel.c @@ -108,6 +108,7 @@ void CChannel_PrepareAck(CCMsg_T *ccmsg) Retcode_T CChannel_SendAck(uint8_t result) { + ackMessage.header.messageType = result == 0 ? 0 : 1; ackMessage.header.errorCode = result; assert(ackMessage.header.messageInfo == CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_ACK)); @@ -127,7 +128,7 @@ void CChannel_PrepareReport(CCMsg_T *ccmsg) (void)memcpy(&reportMessage, ccmsg, CCHANNEL_HEADER_LENGTH); - reportMessage.header.messageType = CCHANNEL_REPORT_TYPE; + reportMessage.header.messageType = 0; reportMessage.header.messageInfo = CCMSG_CREATE_TYPE(CCHANNEL_MSG_TYPE_REPORT); reportMessage.header.payloadLength = 0; } @@ -137,6 +138,7 @@ Retcode_T CChannel_SendReport(uint8_t result, char *reason) Retcode_T retcode = RETCODE_OK; reportMessage.header.errorCode = result; + reportMessage.header.messageType = result == 0 ? 0 : 1; assert(reportMessage.header.payloadLength == 0); From 6b11d2257ead565fb1a226e2a165882c9911785c Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 14 Sep 2020 11:46:15 +0200 Subject: [PATCH 10/29] Remove hugo theme directory before clone Signed-off-by: ChiefGokhlayehBosch --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index e5af285a..50938095 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -109,6 +109,7 @@ pipeline script { echo "Generate Hugo Website" + sh 'rm -rf docs/website/themes/learn/' sh 'git clone --depth 1 --branch 2.5.0 https://github.com/matcornic/hugo-theme-learn.git docs/website/themes/learn/' sh 'hugo -s docs/website' } From d9934f8ee6731c0d3aac8b45d8075c8b6ec53636 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Tue, 15 Sep 2020 09:55:02 +0200 Subject: [PATCH 11/29] Add JLink to Dockerfile Signed-off-by: ChiefGokhlayehBosch --- ci/docker/Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile index 76544dd2..39b76f81 100644 --- a/ci/docker/Dockerfile +++ b/ci/docker/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y \ clang-tidy \ cmake \ cppcheck \ + curl \ doxygen \ g++ \ gcc \ @@ -22,6 +23,7 @@ RUN apt-get update && apt-get install -y \ hugo \ lcov \ libgtest-dev \ + libncurses5 \ libxml2-utils \ ninja-build \ plantuml \ @@ -29,3 +31,9 @@ RUN apt-get update && apt-get install -y \ python3-pip RUN python3 -m pip install pipenv + +RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm')` \ + && TEMP_DEB="$(mktemp)" \ + && curl -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ + && dpkg -i "$TEMP_DEB" \ + && rm -f "$TEMP_DEB" From 456883ea33d9e13f904ac6210ef73c97bfc685ac Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Tue, 15 Sep 2020 10:15:23 +0200 Subject: [PATCH 12/29] Split Dockerfile for dev and agent purposes Signed-off-by: ChiefGokhlayehBosch --- ci/docker/Readme.md | 29 +++++++++++++++++--------- ci/docker/agent/Dockerfile | 28 +++++++++++++++++++++++++ ci/docker/{ => development}/Dockerfile | 0 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 ci/docker/agent/Dockerfile rename ci/docker/{ => development}/Dockerfile (100%) diff --git a/ci/docker/Readme.md b/ci/docker/Readme.md index 0b760cb6..5d4ca065 100644 --- a/ci/docker/Readme.md +++ b/ci/docker/Readme.md @@ -1,23 +1,32 @@ # Docker Image Creation For Jenkins ## Introduction -The Dockerfile file is the source of truth for the docker image used in our continuous integration. -This means, modifying it will affect all builds. + +These `Dockerfile`s are the source of truth for the docker image used in our continuous integration. This means, modifying it will affect all builds. + +* `development` contains the description for the `eclipse/kiso-build-env` image used by developers and CI/CD alike, to build and unit-test Kiso packages. +* `agent` contains the description for the `eclipse/kiso-agent` image used by CI/CD for integration-testing. It adds a Jenkins agent to `eclipse/kiso-build-env`. ## Installation of docker -Please check the following install manuals: + +Please check the following installation manuals: + * [For Windows 10](https://runnable.com/docker/install-docker-on-windows-10) * [For Linux](https://runnable.com/docker/install-docker-on-linux) ## Creation of the image -In this folder, open a Terminal and -* to build the image: `docker build --tag= .` + +In `development` directory, open a terminal and type `docker build . --tag=` to build the image. ## Verification of the image + You can run the image in a container with `docker run -it bash` -## Commit of the image -open a Terminal and -* to commit the image into the registry: `docker tag :` -* to log into the registry: `docker login ` -* to push the image into the registry: `docker push :` +## Deploy an updated image + +To deploy a modified image open a terminal and enter the following: + +```sh +docker login # username and password entered interactively +docker push /: +``` diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile new file mode 100644 index 00000000..3a5fc569 --- /dev/null +++ b/ci/docker/agent/Dockerfile @@ -0,0 +1,28 @@ +FROM eclipse/kiso-build-env:v0.0.7 + +LABEL Description="Eclipse Kiso Jenkins Agent container, used to execute hardware integation tests" + +ARG VERSION=4.3 +ARG user=jenkins +ARG group=jenkins +ARG uid=1000 +ARG gid=1000 +ARG AGENT_WORKDIR=/home/${user}/agent + +RUN groupadd -g ${gid} ${group} +RUN useradd -c "Jenkins Agent user" -d /home/${user} -u ${uid} -g ${gid} -m ${user} + +RUN apt-get update && apt-get install -y --no-install-recommends docker.io git-lfs && apt-get clean +RUN curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar \ + && chmod 755 /usr/share/jenkins \ + && chmod 644 /usr/share/jenkins/agent.jar \ + && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar + +USER ${user} +ENV AGENT_WORKDIR=${AGENT_WORKDIR} +RUN mkdir /home/${user}/.jenkins && mkdir -p ${AGENT_WORKDIR} + +VOLUME /home/${user}/.jenkins +VOLUME ${AGENT_WORKDIR} + +WORKDIR /home/${user} diff --git a/ci/docker/Dockerfile b/ci/docker/development/Dockerfile similarity index 100% rename from ci/docker/Dockerfile rename to ci/docker/development/Dockerfile From 5d725e3d9bf0063eb7c9fe8f4b5c86a2fe6a3e9f Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Tue, 15 Sep 2020 10:21:56 +0200 Subject: [PATCH 13/29] Move JLink install to agent only Signed-off-by: ChiefGokhlayehBosch --- ci/docker/agent/Dockerfile | 13 ++++++++++++- ci/docker/development/Dockerfile | 7 ------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile index 3a5fc569..e131a9f4 100644 --- a/ci/docker/agent/Dockerfile +++ b/ci/docker/agent/Dockerfile @@ -12,11 +12,22 @@ ARG AGENT_WORKDIR=/home/${user}/agent RUN groupadd -g ${gid} ${group} RUN useradd -c "Jenkins Agent user" -d /home/${user} -u ${uid} -g ${gid} -m ${user} -RUN apt-get update && apt-get install -y --no-install-recommends docker.io git-lfs && apt-get clean +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + docker.io \ + git-lfs \ + libncurses5 \ + && apt-get clean RUN curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar \ && chmod 755 /usr/share/jenkins \ && chmod 644 /usr/share/jenkins/agent.jar \ && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar +RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm')` \ + && TEMP_DEB="$(mktemp)" \ + && curl -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ + && dpkg -i "$TEMP_DEB" \ + && rm -f "$TEMP_DEB" USER ${user} ENV AGENT_WORKDIR=${AGENT_WORKDIR} diff --git a/ci/docker/development/Dockerfile b/ci/docker/development/Dockerfile index 39b76f81..a077ba91 100644 --- a/ci/docker/development/Dockerfile +++ b/ci/docker/development/Dockerfile @@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \ clang-tidy \ cmake \ cppcheck \ - curl \ doxygen \ g++ \ gcc \ @@ -31,9 +30,3 @@ RUN apt-get update && apt-get install -y \ python3-pip RUN python3 -m pip install pipenv - -RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm')` \ - && TEMP_DEB="$(mktemp)" \ - && curl -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ - && dpkg -i "$TEMP_DEB" \ - && rm -f "$TEMP_DEB" From 73085ff2d0d05f555ea9fb8a0ed5930fdae92a94 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 14 Sep 2020 11:33:51 +0200 Subject: [PATCH 14/29] Move ITF CI/CD stuff in separate Jenkinsfile Signed-off-by: ChiefGokhlayehBosch --- ci/itf/Jenkinsfile | 29 +++++++++++++++++++++++++++++ ci/itf/readme.md | 10 ++++++++++ 2 files changed, 39 insertions(+) create mode 100644 ci/itf/Jenkinsfile create mode 100644 ci/itf/readme.md diff --git a/ci/itf/Jenkinsfile b/ci/itf/Jenkinsfile new file mode 100644 index 00000000..637a3055 --- /dev/null +++ b/ci/itf/Jenkinsfile @@ -0,0 +1,29 @@ +pipeline +{ + /* NOTE: don't use agent declaration in both global and stage scope: + * https://issues.jenkins-ci.org/browse/JENKINS-63449 + * Follow-up once above issue resolved. */ + agent none + stages + { + stage('Run Integration Tests') + { + agent + { + dockerfile + { + dir 'ci/docker/development' + } + } + steps + { + script + { + echo "build integration-tests" + sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH="core/essentials/test/integration" -DKISO_BOARD_NAME="NucleoF767"' + sh 'cmake --build builddir-integration' + } + } + } // stage('Run Integration Tests') + } // stages +} // pipeline diff --git a/ci/itf/readme.md b/ci/itf/readme.md new file mode 100644 index 00000000..defd3d8f --- /dev/null +++ b/ci/itf/readme.md @@ -0,0 +1,10 @@ +# CI/CD Configuration - Integration Test Framework (ITF) + +This directory contains a special `Jenkinsfile` designed for Jenkins Agents that act as Kiso's Integration Test hosts. These Agents must have physical access to test-hardware on which to flash and execute the integration tests. Additionally, Jenkins Master must be configured to source the job's `Jenkinsfile` from this directory. This can be done in the configuration view of any GitHub or Pipeline job. + +Agents participating in the ITF setup must be marked by labels. + +* General labels: + * `kiso-integration-test` - general ITF agent, applied to all agents participating in ITF tasks +* Hardware labels: + * `nucleo-f767zi` - STM32 NUCLEO-F767ZI board From b07a99edfe95a3f89dda7317a375bca03a018090 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 10:52:57 +0200 Subject: [PATCH 15/29] Raise UART RxErrors too Signed-off-by: ChiefGokhlayehBosch --- .../test/integration/source/TestSuiteUart.c | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/essentials/test/integration/source/TestSuiteUart.c b/core/essentials/test/integration/source/TestSuiteUart.c index 62cf92e5..827369ee 100644 --- a/core/essentials/test/integration/source/TestSuiteUart.c +++ b/core/essentials/test/integration/source/TestSuiteUart.c @@ -214,11 +214,11 @@ static void TestCase_FctTest_Run(CCMsg_T *ccmsg) static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) { KISO_UNUSED(uart); - Retcode_T Rc = RETCODE_OK; + Retcode_T rc = RETCODE_OK; - if (UINT8_C(1) == event.TxComplete) + if (event.TxComplete) { - if (RETCODE_OK == Rc) + if (RETCODE_OK == rc) { BaseType_t higherPriorityTaskWoken = pdFALSE; @@ -235,14 +235,14 @@ static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) } else { - Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); } } } - if (UINT8_C(1) == event.RxComplete) + if (event.RxComplete) { - if (RETCODE_OK == Rc) + if (RETCODE_OK == rc) { BaseType_t higherPriorityTaskWoken = pdFALSE; @@ -259,18 +259,18 @@ static void UartISRCallback(UART_T uart, struct MCU_UART_Event_S event) } else { - Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); + rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER); } } } - if (UINT8_C(1) == event.TxError) + if (event.TxError || event.RxError) { - Rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); + rc = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_FAILURE); } - if (RETCODE_OK != Rc) + if (RETCODE_OK != rc) { - Retcode_RaiseErrorFromIsr(Rc); + Retcode_RaiseErrorFromIsr(rc); } } From 40bd708f5ad23c8e2459c2460a989fe6184dca64 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 12:20:00 +0200 Subject: [PATCH 16/29] Enable use of remote JLink via cmake setting Signed-off-by: ChiefGokhlayehBosch --- cmake/FlashTarget.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/FlashTarget.cmake b/cmake/FlashTarget.cmake index 48f16ad1..c59f6e61 100644 --- a/cmake/FlashTarget.cmake +++ b/cmake/FlashTarget.cmake @@ -67,6 +67,9 @@ function(CREATE_FLASH_TARGET_JLINK ELF_TARGET) # \todo: This can be exported to file and replaced with configure_file file(WRITE ${SCRIPT_PATH} "exitonerror 1\n") + if(JLINK_REMOTE) + file(APPEND ${SCRIPT_PATH} "ip ${JLINK_REMOTE}\n") + endif() file(APPEND ${SCRIPT_PATH} "device ${KISO_MCU_DEVICE}\n") file(APPEND ${SCRIPT_PATH} "if swd\n") file(APPEND ${SCRIPT_PATH} "speed 4000\n") From 813a1c2d664715486ca0507ef6c735221b45c3b3 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 11:12:50 +0200 Subject: [PATCH 17/29] Clone and invoke kiso-testing in CI/CD Signed-off-by: ChiefGokhlayehBosch --- ci/itf/Jenkinsfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ci/itf/Jenkinsfile b/ci/itf/Jenkinsfile index 637a3055..55822af0 100644 --- a/ci/itf/Jenkinsfile +++ b/ci/itf/Jenkinsfile @@ -13,8 +13,14 @@ pipeline dockerfile { dir 'ci/docker/development' + args '--device /dev/ttyACM0' } } + environment + { + WORKON_HOME = "/tmp/.venvs" + PIPENV_CACHE_DIR = "/tmp/.cache" + } steps { script @@ -22,6 +28,15 @@ pipeline echo "build integration-tests" sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH="core/essentials/test/integration" -DKISO_BOARD_NAME="NucleoF767"' sh 'cmake --build builddir-integration' + + sh 'rm -rf ci/itf/kiso-testing' + sh 'git clone --depth 1 --branch prototype/integrate-kiso-demo https://github.com/ChiefGokhlayehBosch/kiso-testing.git ci/itf/kiso-testing' + dir('ci/itf/kiso-testing') + { + sh 'pipenv --python 3' + sh 'pipenv install' + sh 'pipenv run python -m pykiso -c ./examples/demo/demo.yaml' + } } } } // stage('Run Integration Tests') From 012ae5e316204a6599c46d275614d42df62d1c67 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 12:30:17 +0200 Subject: [PATCH 18/29] Run on agent with remote JLink Signed-off-by: ChiefGokhlayehBosch --- ci/itf/Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ci/itf/Jenkinsfile b/ci/itf/Jenkinsfile index 55822af0..26e88703 100644 --- a/ci/itf/Jenkinsfile +++ b/ci/itf/Jenkinsfile @@ -10,10 +10,9 @@ pipeline { agent { - dockerfile + node { - dir 'ci/docker/development' - args '--device /dev/ttyACM0' + label 'kiso/itf/dut/nucleof767 && kiso/itf/connector/uart && (kiso/itf/connector/jlink-via-remote || kiso/itf/connector/jlink-via-local)' } } environment @@ -26,8 +25,9 @@ pipeline script { echo "build integration-tests" - sh 'cmake . -Bbuilddir-integration -G"Ninja" -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH="core/essentials/test/integration" -DKISO_BOARD_NAME="NucleoF767"' - sh 'cmake --build builddir-integration' + echo "${env.JLINK_REMOTE}" + sh "cmake . -Bbuilddir-integration -G'Ninja' -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH='core/essentials/test/integration' -DKISO_BOARD_NAME='NucleoF767' -DJLINK_REMOTE=\"${env.JLINK_REMOTE}\"" + sh 'cmake --build builddir-integration --target flash' sh 'rm -rf ci/itf/kiso-testing' sh 'git clone --depth 1 --branch prototype/integrate-kiso-demo https://github.com/ChiefGokhlayehBosch/kiso-testing.git ci/itf/kiso-testing' From 0cc52eeab288ce197934732cf0cd06cd11174848 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 14:49:49 +0200 Subject: [PATCH 19/29] Export integration testing overview as SVG Signed-off-by: ChiefGokhlayehBosch --- .../content/Concepts/integration_testing.md | 20 ++--- .../content/images/integration_test.png | Bin 28951 -> 0 bytes .../content/images/integration_test.svg | 70 ++++++++++++++++++ 3 files changed, 80 insertions(+), 10 deletions(-) delete mode 100644 docs/website/content/images/integration_test.png create mode 100644 docs/website/content/images/integration_test.svg diff --git a/docs/website/content/Concepts/integration_testing.md b/docs/website/content/Concepts/integration_testing.md index fb95982a..375b3dac 100644 --- a/docs/website/content/Concepts/integration_testing.md +++ b/docs/website/content/Concepts/integration_testing.md @@ -1,5 +1,5 @@ --- -title: "Integration Testing Guide" +title: "Integration Testing" weight: 2 --- @@ -11,14 +11,14 @@ Integration testing is a form of software testing which combines multiple (sub-) In Kiso, integration testing is done **on-target**. This is realized through a special test-app which is flashed onto reference or user hardware and listens to the instructions of a test-coordinator (usually a host PC connected via UART, USB, CAN, or the like). Looking at the hierarchy top-to-bottom (see diagram below), the Kiso integration test framework consists of: -* Test-App - Flashable/Bootable binary for reference and/or user hardware. - * Test-Executor - Application component of the test-app. Handles basic hardware initialization and connects with test-coordinator. - * Test-Entry - Linkable static library usually declared on a package-level (such as Essentials, Cellular, Utils, etc.), combining different test-suites into a single entity. - * Test-Suite - Collection of test-cases that share a common setup and teardown sequence. Can also be used as logical separation of test-cases. - * Test-Case - Individual test to verify one specific aspect of the (sub-)system. Comprised of setup, run and teardown. -* Test-Coordinator - Python based coordinator application running on a host PC. It communicates with the test-executor running on the device-under-test and decides which test-suites/-cases are to be executed. The python framework is realized as `pykiso` and is available as own executable python module. +- Test-App - Flashable/Bootable binary for reference and/or user hardware. + - Test-Executor - Application component of the test-app. Handles basic hardware initialization and connects with test-coordinator. + - Test-Entry - Linkable static library usually declared on a package-level (such as Essentials, Cellular, Utils, etc.), combining different test-suites into a single entity. + - Test-Suite - Collection of test-cases that share a common setup and teardown sequence. Can also be used as logical separation of test-cases. + - Test-Case - Individual test to verify one specific aspect of the (sub-)system. Comprised of setup, run and teardown. +- Test-Coordinator - Python based coordinator application running on a host PC. It communicates with the test-executor running on the device-under-test and decides which test-suites/-cases are to be executed. The python framework is realized as `pykiso` and is available as own executable python module. -![integration test framework](/images/integration_test.png) +![integration test framework](/images/integration_test.svg) All Kiso packages share a common test-executor, each package also defines one test-entry. Each test-executor/-entry pair compiles into a standalone binary which can be flashed onto hardware. Users can declare their own test-entries and link them with the provided test-executor, that way users can profit form the existing test framework to run integration tests of user written drivers or applications. @@ -28,8 +28,8 @@ Test-Entry, test-suite and test-case contain optional hooks for setup and teardo As with any target binary in Kiso, building is handled by the CMake toolchain. Two special workspace settings need to be provided during workspace configuration to build a integration test app, targeted towards a specific hardware: -* `ENABLE_INTEGRATION_TESTING = ON` - Marks this workspace as targeted towards building a integration test app. Note that with this option set, `KISO_APPLICATION_NAME` will be ignored, meaning no user application will be built. -* `KISO_INTEGRATION_TEST_ENTRY_PATH = "path/to/test-entry"` - Path to directory containing a `CMakeLists.txt` used to build the `testentry` static-library. This library will be linked with the test-executor to create the test-app. The `CMakeLists.txt` must declare a static-library target called `testentry`, containing source-files for any test-suites that shall be part of the final test-app. +- `ENABLE_INTEGRATION_TESTING = ON` - Marks this workspace as targeted towards building a integration test app. Note that with this option set, `KISO_APPLICATION_NAME` will be ignored, meaning no user application will be built. +- `KISO_INTEGRATION_TEST_ENTRY_PATH = "path/to/test-entry"` - Path to directory containing a `CMakeLists.txt` used to build the `testentry` static-library. This library will be linked with the test-executor to create the test-app. The `CMakeLists.txt` must declare a static-library target called `testentry`, containing source-files for any test-suites that shall be part of the final test-app. The static-library target `testentry` can be created as follows: diff --git a/docs/website/content/images/integration_test.png b/docs/website/content/images/integration_test.png deleted file mode 100644 index 027d78080a55b8b4c04dbc19b9b5a82c3b3cccc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28951 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 LQ>j4F;>rI2WHyep diff --git a/docs/website/content/images/integration_test.svg b/docs/website/content/images/integration_test.svg new file mode 100644 index 00000000..4127bb57 --- /dev/null +++ b/docs/website/content/images/integration_test.svg @@ -0,0 +1,70 @@ +Integration Test FrameworkTest AppKiso Package (e.g. Essentials) or User AppTest ExecutorTest EntriyTest Suite 1Test Suite 2Test Suite ...Test Case 1.1Test Case 1.2Test Case 2.1Test Case 2.2Test Case 2.3Test Case ...links withcontainscontainscontainscontainscontainscontainscontainscontainscontains© Robert Bosch GmbH 2010-2020 | Licensed under EPL-2.0 \ No newline at end of file From fd4c523136f57ff415f6bb4c950df6c0660072c8 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Wed, 16 Sep 2020 17:07:20 +0200 Subject: [PATCH 20/29] Add integration testing setup guide Signed-off-by: ChiefGokhlayehBosch --- .../integration_testing_setup_guide.md | 130 ++++++++++++++++++ .../images/integration_test_agent_config.png | Bin 0 -> 96663 bytes ...tion_test_infrastructure_architecture.puml | 29 ++++ ...ation_test_infrastructure_architecture.svg | 55 ++++++++ 4 files changed, 214 insertions(+) create mode 100644 docs/website/content/User guide/integration_testing_setup_guide.md create mode 100644 docs/website/content/images/integration_test_agent_config.png create mode 100644 docs/website/content/images/integration_test_infrastructure_architecture.puml create mode 100644 docs/website/content/images/integration_test_infrastructure_architecture.svg diff --git a/docs/website/content/User guide/integration_testing_setup_guide.md b/docs/website/content/User guide/integration_testing_setup_guide.md new file mode 100644 index 00000000..69d8b701 --- /dev/null +++ b/docs/website/content/User guide/integration_testing_setup_guide.md @@ -0,0 +1,130 @@ +--- +title: "Integration Testing - Setup Guide" +weight: 3 +--- + +This advanced guide will teach you how to set up your own integration testing CI/CD infrastructure. The concept behind Kiso's integration tests is explained in [Integration Testing]({{< ref "../Concepts/integration_testing.md" >}}). + +While integration tests can of course be executed manually by each developer, an automated CI/CD setup, in which integration tests are run with every commit, has proved to be of great value. However, the reliance on hardware of Kiso's integration tests present some challenges. For starters, the default [Eclipse/Kiso](https://github.com/eclipse/kiso) CI/CD setup runs in a fully virtualized Kubernetes environment. Thus it can only run non-hardware-reliant checks, such as compilation, static code analysis, format checks, unit-tests, etc. To enable physical access, remote agents need to be created, equipped with hardware to flash and communicate with the Device-under-Test (DuT) and acting as a proxy between Jenkins the DuT. Agents may also be able to physically interact with the DuT during a test (such as actuating a button electronically via GPIO toggle). Each agent identifies its capabilities via labels, following a directory-like naming-scheme `kiso/itf/...` (`itf` stands for "Integration Test Framework"). A separate `Jenkinsfile` located in `/ci/itf` controls the build, flash and execution of integration tests. + +Kiso's Integration Test Infrastructure follows a decentralized approach. You as a developer may set up **your own** test infrastructure, and either run automated tests for your private fork during development, or offer access to it to the community. + +## Setting up Your own Agent + +### Requirements + +- **Device-under-Test** +- **Jenkins master** +- Linux based **Jenkins agent** computer (e.g. Raspberry Pi) +- **Flashing equipment** (if not built into DuT) +- Auxiliaries + - **Communication channel to DuT to control integration test sequence** + - _Additional hardware needed as per test specification (e.g. GSM simulator, shielding box, web-cam, etc.)_ + +(Jenkins master and agent may be combined on the same physical computer) + +### Architecture + +![integration test infrastructure](/images/integration_test_infrastructure_architecture.svg) + +In this guide we will set up a Kiso Integration Test Agent, as depicted in the figure above. The setup consists of a Raspberry Pi 3 and a DuT -- in this case a STM32 Nucleo-F767ZI. We will not cover, how to set up a reverse proxy for your Jenkins setup and make it accessible to the public. In its simplest form, Jenkins just polls the GitHub repository every couple of minutes and starts a new build if new commits are found. Then, once all tests have ran, the build status is posted to GitHub. Internally the Jenkins master spawns a docker-based Jenkins agent with access to the host's devices (via `docker run --device /dev/... ...`). This way we can control the toolchain via `Dockerfiles` and ensure our builds are consistent with Eclipse's Jenkins Master, while at the same time being able to interact with the hardware. Access to JLink flash hardware is provided via a JLink Remote Server, running parallel to the Jenkins Master. The agent then uses an IP-based connection to the JLink Remote Server to flash and reset the hardware. + +### Step by Step + +Start with a fresh Debian-based Raspberry Pi OS (the "Lite" version should suffice). The SD card should be sized adequately to store several GB of tooling. Anything below 32 GB may quickly become a hassle, as different toolchain versions may persist in the form of docker images. Follow the official [Getting Started Guide](https://projects.raspberrypi.org/en/projects/raspberry-pi-getting-started) and set up a SSH connection from your dev-PC to access it remotely. + +#### Installing the Tools + +Once on your Pi you need to install a few tools: + +- Jenkins - Follow [this guide](https://pkg.jenkins.io/debian-stable/) for Debian releases. As Jenkins may not list it as a dependency you may have to manually install a JRE: + ```sh + sudo apt install openjdk-8-jre # use 'apt list openjdk-*-jre' to see what versions are available + ``` +- Docker - Can be installed directly via `apt`: + ```sh + sudo apt install docker.io + ``` +- JLink - Download the `.deb` file for 32-bit ARM systems from [SEGGER](https://www.segger.com/downloads/jlink/#J-LinkSoftwareAndDocumentationPack) and install it via `dpkg`: + ```sh + sudo apt install libncurses5 # requirement for JLink + sudo dpkg -i JLink_Linux_arm.deb + ``` +- Some more recommended tools you maybe want to install are: `vim`, `unattended-upgrades`, and `winbind`. + +#### JLink Remote Service + +We assume your Nucleo's on-chip [ST-Link is flashed with JLink firmware](https://www.segger.com/products/debug-probes/j-link/models/other-j-links/st-link-on-board/). This allows us to use JLink's excellent JLink Remote Server to access the hardware via TCP/IP-connection. To ensure the server is always running we will create a [systemd service](https://www.freedesktop.org/software/systemd/man/systemd.service.html). + +Create a new service unit `/etc/systemd/system/jlinkremote.service`. Paste the following unit description into the newly created file: + +```systemd +[Unit] +Description=JLink Remote Server allowing remote JLink Clients to access local hardware +PartOf=jenkins.service + +[Service] +type=simple +ExecStart=/usr/bin/JLinkRemoteServer + +[Install] +WantedBy=jenkins.service +``` + +Note that we specified this service to be `WantedBy=jenkins.service` and `PartOf=jenkins.service`. Thus if Jenkins is started or stopped JLink Remote will follow in turn. + +Enable and start the service initially via: + +```sh +sudo systemctl enable jlinkremote.service +sudo systemctl start jlinkremote.service +``` + +#### Jenkins Master & Agent + +Make sure Jenkins is running: + +```sh +systemctl status jenkins.service # will report if Jenkins is running or not +# ... +systemctl status jenkins.service # to start Jenkins +``` + +Open Jenkins' web-service in a browser (IP depends on your network, port 8080), and follow the initial setup instructions. We don't need any special plugins so the default selection should be ok. + +Create a GitHub Organization job via "New Item" > give it a name > select "GitHub Organization" > "OK". In the following configuration screen provide your GitHub credentials (use access tokens!) and create a repository filter for Kiso. For token-based authentication make sure the token has the `repo:status` flag set in [GitHub Developer Settings](https://github.com/settings/apps) settings. Under "Project Recognizers" change the "Script Path" to `ci/itf/Jenkinsfile`. Change the scan interval to something reasonable, like every 5 or 10 minutes. All other settings can be left as default or tweaked at a later time. + +The final piece in our setup is the Jenkins agent. As described earlier we use a docker-based agent, spawned by the Jenkins master. We can create such an agent by navigating to the Jenkins dashboard and hitting "Manage Jenkins" > "Manage Nodes and Clouds" > "New Node" > give it a name (e.g. "goofy") > select "Permanent Agent" > "OK". + +![agent configuration](/images/integration_test_agent_config.png) + +In the configuration screen set "Remote root directory" to `/home/jenkins`, set the appropriate labels (more on that below) and select launch method "Launch agent via execution of command on the master". Set "Launch command" to: + +```sh +docker run -i --rm --name jenkins-agent-kiso --device /dev/ttyACM0 -e JLINK_REMOTE="$JLINK_REMOTE" --init eclipse/kiso-integration-test-agent java -jar /usr/share/jenkins/agent.jar`. +``` + +Check "Environment variables" and add `JLINK_REMOTE` with value `172.17.0.1`. This should be the IP address of the docker host as seen from the docker network the agent is part of. + +Note the use of `--device /dev/ttyACM0` in the launch command. This should be the device-file representing the USB serial COM-port of the Nucleo-F767. If this device does not exist, the agent will not be able to start. Connect the Nucleo-F767 and check the presence of the COM-port via `ls -l /dev/ttyACM0`. + +Save the node and hit "Launch agent". If everything is set up properly, the agent should boot and now (provided the correct labels are set) be ready for integration tests. + +### Agent Labeling + +Agents publish their capabilities via labels. The labels follow a directory-like naming-scheme. The following predefined categories exist as of now: + +- `kiso/` - Identifies that label is part of Kiso project + - `itf/` - Identifies that label is part of Kiso's Integration Test Framework + - `connector/` - Available connectors + - `jlink-via-local` - JLink flasher locally available. + - `jlink-via-remote` - JLink flasher accessible via IP, `JLINK_REMOTE` environment variable must contain IP-address pointing to JLink Remote Server instance. + - `uart` - COM-port available. + - `dut/` - Available Device(s)-under-Test + - `nucleof767` - STM32 Nucleo-F767 + +You may add your own labels as needed. A typical agent may have the following labels: + +- `kiso/itf/connector/jlink-via-remote` +- `kiso/itf/connector/uart` +- `kiso/itf/dut/nucleof767` diff --git a/docs/website/content/images/integration_test_agent_config.png b/docs/website/content/images/integration_test_agent_config.png new file mode 100644 index 0000000000000000000000000000000000000000..1cebf1a69b1183a2ba942616a3f3ddc9bc8f367e GIT binary patch literal 96663 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 P4?--=ERU3!oVofR<6{a? literal 0 HcmV?d00001 diff --git a/docs/website/content/images/integration_test_infrastructure_architecture.puml b/docs/website/content/images/integration_test_infrastructure_architecture.puml new file mode 100644 index 00000000..cb8a7daa --- /dev/null +++ b/docs/website/content/images/integration_test_infrastructure_architecture.puml @@ -0,0 +1,29 @@ +@startuml + +title Integration Testing - Example Setup + +node "Nucleo-F767" { + component "Test App" as testapp +} + +node "Raspberry Pi 3" { + component "Jenkins Master" as jenkins_master + component "Jenkins Agent" as jenkins_agent +} + +node "GitHub" { + component "/kiso" as fork +} + +actor "Developer" as dev + +dev --up--> fork : "(1) push commit" +jenkins_master --left--> fork : "(2) poll latest commit" +jenkins_master --left--> fork : "(6) notify build status" +jenkins_master -down-> jenkins_agent : "(3) spawn" +jenkins_master <-down- jenkins_agent : "(5) report test result" +jenkins_agent <-down-> testapp : "(4) interact" + +footer © Robert Bosch GmbH 2010-2020 | Licensed under EPL-2.0 + +@enduml diff --git a/docs/website/content/images/integration_test_infrastructure_architecture.svg b/docs/website/content/images/integration_test_infrastructure_architecture.svg new file mode 100644 index 00000000..fb278936 --- /dev/null +++ b/docs/website/content/images/integration_test_infrastructure_architecture.svg @@ -0,0 +1,55 @@ +Integration Testing - Example SetupNucleo-F767Raspberry Pi 3GitHubTest AppJenkins MasterJenkins Agent<your-fork>/kisoDeveloper(1) push commit(2) poll latest commit(6) notify build status(3) spawn(5) report test result(4) interact© Robert Bosch GmbH 2010-2020 | Licensed under EPL-2.0 \ No newline at end of file From 9f67f2c20213ad683ab58f0683b02c7fd05f4ea3 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 09:40:57 +0200 Subject: [PATCH 21/29] Import Essentials test config from kiso-testing Signed-off-by: ChiefGokhlayehBosch --- .gitignore | 3 +- ci/itf/Jenkinsfile | 2 +- .../integration/coordinator/test_config.yaml | 16 ++++ .../uart_test_suite/uart_test_suite.py | 78 +++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 core/essentials/test/integration/coordinator/test_config.yaml create mode 100644 core/essentials/test/integration/coordinator/uart_test_suite/uart_test_suite.py diff --git a/.gitignore b/.gitignore index 48b7619e..cfb395ac 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /debug build builddir-* -.vscode \ No newline at end of file +.vscode +__pycache__/ diff --git a/ci/itf/Jenkinsfile b/ci/itf/Jenkinsfile index 26e88703..735d12f4 100644 --- a/ci/itf/Jenkinsfile +++ b/ci/itf/Jenkinsfile @@ -35,7 +35,7 @@ pipeline { sh 'pipenv --python 3' sh 'pipenv install' - sh 'pipenv run python -m pykiso -c ./examples/demo/demo.yaml' + sh 'pipenv run python -m pykiso -c ../../../core/essentials/test/integration' } } } diff --git a/core/essentials/test/integration/coordinator/test_config.yaml b/core/essentials/test/integration/coordinator/test_config.yaml new file mode 100644 index 00000000..a358c9f5 --- /dev/null +++ b/core/essentials/test/integration/coordinator/test_config.yaml @@ -0,0 +1,16 @@ +auxiliaries: + dut: + connectors: + com: uart + type: pykiso.lib.auxiliaries.communication_auxiliary:CommunicationAuxiliary +connectors: + uart: + config: + serialPort: /dev/ttyACM0 + baudrate: 115200 + type: pykiso.lib.connectors.cc_uart:CCUart +test_suite_list: + - suite_dir: uart_test_suite + test_filter_pattern: "*.py" + test_suite_id: 1 + test_entry_id: 1 diff --git a/core/essentials/test/integration/coordinator/uart_test_suite/uart_test_suite.py b/core/essentials/test/integration/coordinator/uart_test_suite/uart_test_suite.py new file mode 100644 index 00000000..0f7cd50e --- /dev/null +++ b/core/essentials/test/integration/coordinator/uart_test_suite/uart_test_suite.py @@ -0,0 +1,78 @@ +import pykiso +import logging + +from pykiso.auxiliaries import dut + +from pykiso import message + + +@pykiso.define_test_parameters(entry_id=1, suite_id=1, aux_list=[dut]) +class SuiteSetup(pykiso.BasicTestSuiteSetup): + pass + + +@pykiso.define_test_parameters(entry_id=1, suite_id=1, aux_list=[dut]) +class SuiteTearDown(pykiso.BasicTestSuiteTeardown): + pass + + +@pykiso.define_test_parameters(entry_id=1, suite_id=1, case_id=1, aux_list=[dut]) +class MyTest(pykiso.BasicTest): + def setUp(self): + logging.info("### SETUP") + + msg = message.Message( + msg_type=message.MessageType.COMMAND, + sub_type=message.MessageCommandType.TEST_CASE_SETUP, + test_entry=1, + test_suite=1, + test_case=1, + ) + msg_sent = dut.send_message(msg) + self.assertTrue(msg_sent) + + ack = dut.receive_message() + self.assertEqual(message.MessageType.ACK, ack.msg_type) + self.assertEqual(message.MessageAckType.ACK, ack.sub_type) + + super().setUp() + + def test_run(self): + logging.info("### RUN") + + msg = message.Message( + msg_type=message.MessageType.COMMAND, + sub_type=message.MessageCommandType.TEST_CASE_RUN, + test_entry=1, + test_suite=1, + test_case=1, + ) + msg_sent = dut.send_message(msg) + self.assertTrue(msg_sent) + + ack = dut.receive_message() + self.assertEqual(message.MessageType.ACK, ack.msg_type) + self.assertEqual(message.MessageAckType.ACK, ack.sub_type) + + logging.info("### Executing test case on hardware...") + + super().test_run() + + def tearDown(self): + logging.info("### TEARDOWN") + + msg = message.Message( + msg_type=message.MessageType.COMMAND, + sub_type=message.MessageCommandType.TEST_CASE_TEARDOWN, + test_entry=1, + test_suite=1, + test_case=1, + ) + msg_sent = dut.send_message(msg) + self.assertTrue(msg_sent) + + ack = dut.receive_message() + self.assertEqual(message.MessageType.ACK, ack.msg_type) + self.assertEqual(message.MessageAckType.ACK, ack.sub_type) + + super().tearDown() From 5668e9e162b7ff3fdcb0d017e98c7229efef9635 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 09:56:10 +0200 Subject: [PATCH 22/29] Move contents of ci/itf into testing/integration Signed-off-by: ChiefGokhlayehBosch --- .gitignore | 1 + ci/itf/readme.md | 10 ---------- .../integration_testing_setup_guide.md | 4 ++-- {ci/itf => testing/integration}/Jenkinsfile | 8 ++++---- testing/integration/readme.md | 16 +++++++++++----- .../readme.md | 0 6 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 ci/itf/readme.md rename {ci/itf => testing/integration}/Jenkinsfile (84%) rename testing/integration/{test-auxiliary => test-auxiliaries}/readme.md (100%) diff --git a/.gitignore b/.gitignore index cfb395ac..61325994 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build builddir-* .vscode __pycache__/ +testing/integration/test-coordinator/kiso-testing/ diff --git a/ci/itf/readme.md b/ci/itf/readme.md deleted file mode 100644 index defd3d8f..00000000 --- a/ci/itf/readme.md +++ /dev/null @@ -1,10 +0,0 @@ -# CI/CD Configuration - Integration Test Framework (ITF) - -This directory contains a special `Jenkinsfile` designed for Jenkins Agents that act as Kiso's Integration Test hosts. These Agents must have physical access to test-hardware on which to flash and execute the integration tests. Additionally, Jenkins Master must be configured to source the job's `Jenkinsfile` from this directory. This can be done in the configuration view of any GitHub or Pipeline job. - -Agents participating in the ITF setup must be marked by labels. - -* General labels: - * `kiso-integration-test` - general ITF agent, applied to all agents participating in ITF tasks -* Hardware labels: - * `nucleo-f767zi` - STM32 NUCLEO-F767ZI board diff --git a/docs/website/content/User guide/integration_testing_setup_guide.md b/docs/website/content/User guide/integration_testing_setup_guide.md index 69d8b701..4b369019 100644 --- a/docs/website/content/User guide/integration_testing_setup_guide.md +++ b/docs/website/content/User guide/integration_testing_setup_guide.md @@ -5,7 +5,7 @@ weight: 3 This advanced guide will teach you how to set up your own integration testing CI/CD infrastructure. The concept behind Kiso's integration tests is explained in [Integration Testing]({{< ref "../Concepts/integration_testing.md" >}}). -While integration tests can of course be executed manually by each developer, an automated CI/CD setup, in which integration tests are run with every commit, has proved to be of great value. However, the reliance on hardware of Kiso's integration tests present some challenges. For starters, the default [Eclipse/Kiso](https://github.com/eclipse/kiso) CI/CD setup runs in a fully virtualized Kubernetes environment. Thus it can only run non-hardware-reliant checks, such as compilation, static code analysis, format checks, unit-tests, etc. To enable physical access, remote agents need to be created, equipped with hardware to flash and communicate with the Device-under-Test (DuT) and acting as a proxy between Jenkins the DuT. Agents may also be able to physically interact with the DuT during a test (such as actuating a button electronically via GPIO toggle). Each agent identifies its capabilities via labels, following a directory-like naming-scheme `kiso/itf/...` (`itf` stands for "Integration Test Framework"). A separate `Jenkinsfile` located in `/ci/itf` controls the build, flash and execution of integration tests. +While integration tests can of course be executed manually by each developer, an automated CI/CD setup, in which integration tests are run with every commit, has proved to be of great value. However, the reliance on hardware of Kiso's integration tests present some challenges. For starters, the default [Eclipse/Kiso](https://github.com/eclipse/kiso) CI/CD setup runs in a fully virtualized Kubernetes environment. Thus it can only run non-hardware-reliant checks, such as compilation, static code analysis, format checks, unit-tests, etc. To enable physical access, remote agents need to be created, equipped with hardware to flash and communicate with the Device-under-Test (DuT) and acting as a proxy between Jenkins the DuT. Agents may also be able to physically interact with the DuT during a test (such as actuating a button electronically via GPIO toggle). Each agent identifies its capabilities via labels, following a directory-like naming-scheme `kiso/itf/...` (`itf` stands for "Integration Test Framework"). A separate `Jenkinsfile` located in `/testing/integration` controls the build, flash and execution of integration tests. Kiso's Integration Test Infrastructure follows a decentralized approach. You as a developer may set up **your own** test infrastructure, and either run automated tests for your private fork during development, or offer access to it to the community. @@ -92,7 +92,7 @@ systemctl status jenkins.service # to start Jenkins Open Jenkins' web-service in a browser (IP depends on your network, port 8080), and follow the initial setup instructions. We don't need any special plugins so the default selection should be ok. -Create a GitHub Organization job via "New Item" > give it a name > select "GitHub Organization" > "OK". In the following configuration screen provide your GitHub credentials (use access tokens!) and create a repository filter for Kiso. For token-based authentication make sure the token has the `repo:status` flag set in [GitHub Developer Settings](https://github.com/settings/apps) settings. Under "Project Recognizers" change the "Script Path" to `ci/itf/Jenkinsfile`. Change the scan interval to something reasonable, like every 5 or 10 minutes. All other settings can be left as default or tweaked at a later time. +Create a GitHub Organization job via "New Item" > give it a name > select "GitHub Organization" > "OK". In the following configuration screen provide your GitHub credentials (use access tokens!) and create a repository filter for Kiso. For token-based authentication make sure the token has the `repo:status` flag set in [GitHub Developer Settings](https://github.com/settings/apps) settings. Under "Project Recognizers" change the "Script Path" to `testing/integration/Jenkinsfile`. Change the scan interval to something reasonable, like every 5 or 10 minutes. All other settings can be left as default or tweaked at a later time. The final piece in our setup is the Jenkins agent. As described earlier we use a docker-based agent, spawned by the Jenkins master. We can create such an agent by navigating to the Jenkins dashboard and hitting "Manage Jenkins" > "Manage Nodes and Clouds" > "New Node" > give it a name (e.g. "goofy") > select "Permanent Agent" > "OK". diff --git a/ci/itf/Jenkinsfile b/testing/integration/Jenkinsfile similarity index 84% rename from ci/itf/Jenkinsfile rename to testing/integration/Jenkinsfile index 735d12f4..33030c12 100644 --- a/ci/itf/Jenkinsfile +++ b/testing/integration/Jenkinsfile @@ -29,13 +29,13 @@ pipeline sh "cmake . -Bbuilddir-integration -G'Ninja' -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH='core/essentials/test/integration' -DKISO_BOARD_NAME='NucleoF767' -DJLINK_REMOTE=\"${env.JLINK_REMOTE}\"" sh 'cmake --build builddir-integration --target flash' - sh 'rm -rf ci/itf/kiso-testing' - sh 'git clone --depth 1 --branch prototype/integrate-kiso-demo https://github.com/ChiefGokhlayehBosch/kiso-testing.git ci/itf/kiso-testing' - dir('ci/itf/kiso-testing') + sh 'rm -rf testing/integration/test-coordinator/kiso-testing' + sh 'git clone --depth 1 --branch prototype/integrate-kiso-demo https://github.com/ChiefGokhlayehBosch/kiso-testing.git testing/integration/test-coordinator/kiso-testing' + dir('testing/integration/test-coordinator/kiso-testing') { sh 'pipenv --python 3' sh 'pipenv install' - sh 'pipenv run python -m pykiso -c ../../../core/essentials/test/integration' + sh 'pipenv run python -m pykiso -c ../../../../core/essentials/test/integration/coordinator/test_config.yaml' } } } diff --git a/testing/integration/readme.md b/testing/integration/readme.md index 127ae9a8..30728422 100644 --- a/testing/integration/readme.md +++ b/testing/integration/readme.md @@ -1,8 +1,14 @@ # Integration Test Framework -It is composed of the following packages: +The ITF is composed of the following packages: -* Test-scheduler: Called in continuous integration. Defines where the tests should be run. -* Test-coordinator: Coordinate the build and execution of tests. -* Test-auxiliaries: List of elements that support more complex tests (example: communication tests between a cloud service and a device) -* Test-executor: Software that will be flashed on the device under test. +- Test-auxiliaries: List of elements that support more complex tests (example: utilities to control a bench power supply or a database server the device-under-test can access during the test). +- Test-coordinator: Coordinate the build and execution of tests. +- Test-executor: Software that will be flashed on the device under test. +- Test-scheduler: Called in continuous integration. Defines where the tests should be run. + +## CI/CD Configuration + +This directory contains a special `Jenkinsfile` designed for Jenkins agents that act as Kiso's Integration Test hosts. These Agents must have physical access to test-hardware on which to flash and execute the integration tests. Additionally, Jenkins master must be configured to source the job's `Jenkinsfile` from this directory. This can be done in the configuration view of any GitHub or Pipeline job. + +Agents participating in the ITF setup must be marked by labels. Check out the [integration testing guide](http://docs.eclipsekiso.de:1313/user-guide/integration_testing_setup_guide.html) for more details. diff --git a/testing/integration/test-auxiliary/readme.md b/testing/integration/test-auxiliaries/readme.md similarity index 100% rename from testing/integration/test-auxiliary/readme.md rename to testing/integration/test-auxiliaries/readme.md From bf99b832a087d41801b164064587415f5234234a Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 11:51:36 +0200 Subject: [PATCH 23/29] Upgrade agent base image to multi-arch supported Signed-off-by: ChiefGokhlayehBosch --- ci/docker/agent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile index e131a9f4..ed851bdf 100644 --- a/ci/docker/agent/Dockerfile +++ b/ci/docker/agent/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse/kiso-build-env:v0.0.7 +FROM eclipse/kiso-build-env:v0.0.8 LABEL Description="Eclipse Kiso Jenkins Agent container, used to execute hardware integation tests" From eb52506871369e2e437d9942f4637d6640aeb933 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 12:28:54 +0200 Subject: [PATCH 24/29] Add arm64 bit support to agent image Signed-off-by: ChiefGokhlayehBosch --- ci/docker/agent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile index ed851bdf..890c7b94 100644 --- a/ci/docker/agent/Dockerfile +++ b/ci/docker/agent/Dockerfile @@ -23,7 +23,7 @@ RUN curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar https://repo.jenkins- && chmod 755 /usr/share/jenkins \ && chmod 644 /usr/share/jenkins/agent.jar \ && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar -RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm')` \ +RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm') || ([ $(uname -m) = 'aarch64' ] && echo 'arm64')` \ && TEMP_DEB="$(mktemp)" \ && curl -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ && dpkg -i "$TEMP_DEB" \ From 42535fc779e08ed95dfdea27192916b92661f226 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 11:54:50 +0200 Subject: [PATCH 25/29] Upgrade CI/CD docker image version to v0.0.8 Signed-off-by: ChiefGokhlayehBosch --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 50938095..61876f13 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline containerTemplate { name 'kiso-build-env' - image 'eclipse/kiso-build-env:v0.0.7' + image 'eclipse/kiso-build-env:v0.0.8' ttyEnabled true resourceRequestCpu '2' resourceLimitCpu '2' From ca15ddd970f8565dbe0c8300fcb8870ab0a84d66 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 14:00:05 +0200 Subject: [PATCH 26/29] Add ca-certificate workaround for ARM 32-bit Signed-off-by: ChiefGokhlayehBosch --- ci/docker/agent/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile index 890c7b94..b86bd064 100644 --- a/ci/docker/agent/Dockerfile +++ b/ci/docker/agent/Dockerfile @@ -19,13 +19,18 @@ RUN apt-get update \ git-lfs \ libncurses5 \ && apt-get clean + +# Workaround for new buggy update-ca-certificates in Debian Buster on ARM 32-bit +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923479 +RUN ([ ! $(uname -m) = 'armv7l' ] && true) || ([ $(uname -m) = 'armv7l' ] && c_rehash) + RUN curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/${VERSION}/remoting-${VERSION}.jar \ && chmod 755 /usr/share/jenkins \ && chmod 644 /usr/share/jenkins/agent.jar \ && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm') || ([ $(uname -m) = 'aarch64' ] && echo 'arm64')` \ && TEMP_DEB="$(mktemp)" \ - && curl -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ + && curl -fsSL -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ && dpkg -i "$TEMP_DEB" \ && rm -f "$TEMP_DEB" From 5895511d1a3ef04a115793ff01414d58d161acd2 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Thu, 17 Sep 2020 14:04:16 +0200 Subject: [PATCH 27/29] Remove debug line from int-test Jenkinsfile Signed-off-by: ChiefGokhlayehBosch --- testing/integration/Jenkinsfile | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/integration/Jenkinsfile b/testing/integration/Jenkinsfile index 33030c12..19d51409 100644 --- a/testing/integration/Jenkinsfile +++ b/testing/integration/Jenkinsfile @@ -25,7 +25,6 @@ pipeline script { echo "build integration-tests" - echo "${env.JLINK_REMOTE}" sh "cmake . -Bbuilddir-integration -G'Ninja' -DENABLE_INTEGRATION_TESTING=1 -DKISO_INTEGRATION_TEST_ENTRY_PATH='core/essentials/test/integration' -DKISO_BOARD_NAME='NucleoF767' -DJLINK_REMOTE=\"${env.JLINK_REMOTE}\"" sh 'cmake --build builddir-integration --target flash' From 00d61c1cde94c1658121bdb05a024660031df358 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 21 Sep 2020 14:12:16 +0200 Subject: [PATCH 28/29] Add SEGGER Terms of Use disclaimer to Dockerfile Signed-off-by: ChiefGokhlayehBosch --- ci/docker/Readme.md | 3 ++- ci/docker/agent/Dockerfile | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ci/docker/Readme.md b/ci/docker/Readme.md index 5d4ca065..e601156b 100644 --- a/ci/docker/Readme.md +++ b/ci/docker/Readme.md @@ -5,7 +5,8 @@ These `Dockerfile`s are the source of truth for the docker image used in our continuous integration. This means, modifying it will affect all builds. * `development` contains the description for the `eclipse/kiso-build-env` image used by developers and CI/CD alike, to build and unit-test Kiso packages. -* `agent` contains the description for the `eclipse/kiso-agent` image used by CI/CD for integration-testing. It adds a Jenkins agent to `eclipse/kiso-build-env`. +* `agent` contains the description for the `eclipse/kiso-integration-test-agent` image used by CI/CD for integration-testing. It adds a Jenkins agent to `eclipse/kiso-build-env`. Building it requires a build argument to indicate whether or not to install JLink. Use `docker build ... --build-arg ACCEPT_JLINK_TERMS_OF_USE='accepted'` if you have read and accepted the SEGGER's Terms of Use for this software. Alternatively, set `ACCEPT_JLINK_TERMS_OF_USE='not accepted'` to skip JLink installation. \ +**NOTE: The image must not be re-distributed, if built with JLink enabled! This option is intended for your local setup only.** ## Installation of docker diff --git a/ci/docker/agent/Dockerfile b/ci/docker/agent/Dockerfile index b86bd064..611270cb 100644 --- a/ci/docker/agent/Dockerfile +++ b/ci/docker/agent/Dockerfile @@ -9,6 +9,11 @@ ARG uid=1000 ARG gid=1000 ARG AGENT_WORKDIR=/home/${user}/agent +# Change to 'accepted' if you read and agreed to the SEGGER Terms of Use +# available at https://www.segger.com/downloads/jlink/#J-LinkSoftwareAndDocumentationPack +# Otherwise set to 'not accepted' to skip JLink installation. +ARG ACCEPT_JLINK_TERMS_OF_USE= + RUN groupadd -g ${gid} ${group} RUN useradd -c "Jenkins Agent user" -d /home/${user} -u ${uid} -g ${gid} -m ${user} @@ -20,6 +25,19 @@ RUN apt-get update \ libncurses5 \ && apt-get clean +RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm') || ([ $(uname -m) = 'aarch64' ] && echo 'arm64')` \ + && [ -z "$ACCEPT_JLINK_TERMS_OF_USE" ] \ + && (echo "\n################################################################################\n" \ + "\nBuild argument ACCEPT_JLINK_TERMS_OF_USE not set. Please read the below stated SEGGER Terms of Use and set docker build argument ACCEPT_JLINK_TERMS_OF_USE to 'accepted' (to install JLink) or 'not accepted' (to skip installation).\n" \ + "\n################################################################################\n" \ + ; curl -fsSL "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" \ + | xmllint --html --xpath "//body/div[@id='page']/main/div[@id='productdetails']//textarea/text()" - 2>/dev/null \ + && echo '\n\n' \ + ; exit 123) \ + || [ "$ACCEPT_JLINK_TERMS_OF_USE" = 'accepted' -o "$ACCEPT_JLINK_TERMS_OF_USE" = 'not accepted' ] \ + && true \ + || (echo "Invalid build argument value ACCEPT_JLINK_TERMS_OF_USE='${ACCEPT_JLINK_TERMS_OF_USE}'"; exit 124); + # Workaround for new buggy update-ca-certificates in Debian Buster on ARM 32-bit # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923479 RUN ([ ! $(uname -m) = 'armv7l' ] && true) || ([ $(uname -m) = 'armv7l' ] && c_rehash) @@ -29,10 +47,12 @@ RUN curl --create-dirs -fsSLo /usr/share/jenkins/agent.jar https://repo.jenkins- && chmod 644 /usr/share/jenkins/agent.jar \ && ln -sf /usr/share/jenkins/agent.jar /usr/share/jenkins/slave.jar RUN JLINK_ARCH=`([ $(uname -m) = 'x86_64' ] && echo 'x86_64') || ([ $(uname -m) = 'armv7l' ] && echo 'arm') || ([ $(uname -m) = 'aarch64' ] && echo 'arm64')` \ - && TEMP_DEB="$(mktemp)" \ + && ([ "$ACCEPT_JLINK_TERMS_OF_USE" = 'not accepted' ] \ + && true \ + || (TEMP_DEB="$(mktemp)" \ && curl -fsSL -X POST -F 'accept_license_agreement=accepted' "https://www.segger.com/downloads/jlink/JLink_Linux_${JLINK_ARCH}.deb" -o "$TEMP_DEB" \ && dpkg -i "$TEMP_DEB" \ - && rm -f "$TEMP_DEB" + && rm -f "$TEMP_DEB")) USER ${user} ENV AGENT_WORKDIR=${AGENT_WORKDIR} From 29c78ac00afa31adf1c15d60740e226336516d00 Mon Sep 17 00:00:00 2001 From: ChiefGokhlayehBosch Date: Mon, 21 Sep 2020 14:30:31 +0200 Subject: [PATCH 29/29] Clarify UART setup in Essentials UART test-spec Signed-off-by: ChiefGokhlayehBosch --- core/essentials/test/integration/specs/UART_Test_Spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/essentials/test/integration/specs/UART_Test_Spec.md b/core/essentials/test/integration/specs/UART_Test_Spec.md index 9538501b..c8e06f75 100644 --- a/core/essentials/test/integration/specs/UART_Test_Spec.md +++ b/core/essentials/test/integration/specs/UART_Test_Spec.md @@ -24,7 +24,7 @@ No special teardown ##### Setup 1. Get device handle of BSP initialized generic UART - * UART must be configured in loopback mode, echoing any data sent out + * UART must be physically connected in loopback mode, i.e. jumper wire between UART Tx <-> Rx. 2. Allocate OS signal semaphores used as signal from IRQ 3. Connect the generic UART BSP 4. Initialize UART MCU with UART handle