ThrowTheSwitch / Ceedling

Ruby-based unit testing and build system for C projects
http://throwtheswitch.org
Other
579 stars 243 forks source link

Conflict between header's source and test_source files when declaring RAM-based registers. #685

Open tariksapmaz opened 2 years ago

tariksapmaz commented 2 years ago

I am creating HAL library for my project. I have a very simple project structure. I have shown this project structure in the picture below.

I created a "hal_gpio.h" file containing a GPIO memory map based on the MCU's datasheet. I wrote a few test functions in it, but when I run ceedling test:all I get an undeclared variable error from test_hal_gpio.c. Console output as shown below. I think the problem is 99% not related to the library, but I've been dealing with this for the last 1 week and I'm confused on what to do out of desperation. I am open to all help.

-Project structure resim

-Console output 1

-defs.h

#include <stdint.h>

//--------------------------------------------
//useful test overrides
//--------------------------------------------
#ifdef TEST
#define FOREVER              (0)
#define STATIC
#define INLINE
#else
#define FOREVER              (1)
#define STATIC               static
#define INLINE               inline
#endif

#ifndef NULL
#define NULL 0
#endif

-hal_types.h

#include <stdint.h>
#include <stdbool.h>

typedef enum {
    HAL_OK,
    HAL_ERROR
} HAL_Status_t;

-hal_gpio.h

#include "hal_types.h"

typedef struct
{
    __IO uint32_t DATA;      //<* GPIO Data                      (0x00000000) */
    uint32_t RESERVED[255];
    __IO uint32_t DIR;       //<* GPIO Direction                 (0x00000400) */
} HAL_GPIO_t;

typedef struct
{
    uint32_t port;
    uint32_t id;
    uint8_t mode;
    uint8_t strength;
    uint8_t type;
    uint8_t mux_id;
} HAL_GPIO_Pin_Conf_t;

#define HAL_GPIO_PORT_BASE_ADDRESS   0x40000000
#define HAL_GPIO_PORTA_BASE_ADDRESS     (HAL_GPIO_PORT_BASE_ADDRESS + 0x4000)   // GPIO Port A
#define HAL_GPIO_PORTB_BASE_ADDRESS     (HAL_GPIO_PORT_BASE_ADDRESS + 0x5000)   // GPIO Port B

#ifndef TEST

#define HAL_GPIO_PORT_A     ((HAL_GPIO_t*) HAL_GPIO_PORTA_BASE_ADDRESS)
#define HAL_GPIO_PORT_B     ((HAL_GPIO_t*) HAL_GPIO_PORTB_BASE_ADDRESS)

#endif

#ifdef TEST

#ifdef TEST_INSTANCES

HAL_GPIO_t   HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t   HAL_GPIO_PORT_B_Instance;

HAL_GPIO_t  *HAL_GPIO_PORT_A = &HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t  *HAL_GPIO_PORT_B = &HAL_GPIO_PORT_B_Instance;

#else

extern HAL_GPIO_t  *HAL_GPIO_PORT_A;
extern HAL_GPIO_t  *HAL_GPIO_PORT_B;

#endif

void HAL_GPIO_Init(const HAL_GPIO_Pin_Conf_t *pin, uint32_t length);
bool_t HAL_GPIO_ReadPin(uint32_t port_id, uint32_t pin_id);

-hal_gpio.c

#include "defs.h"
#include "hal_gpio.h"

HAL_GPIO_t *HAL_GPIO_GetPort(uint32_t portAddress)
{
  if (portAddress == HAL_GPIO_PORTA_BASE_ADDRESS) {
    return HAL_GPIO_PORT_A;
  } else if (portAddress == HAL_GPIO_PORTB_BASE_ADDRESS) {
    return HAL_GPIO_PORT_B;
  }
}

void HAL_GPIO_Init(const HAL_GPIO_Pin_Conf_t *pin, uint32_t length)
{
    return;
}

bool_t HAL_GPIO_ReadPin(uint32_t port_id, uint32_t pin_id)
{
    return true;
}

-test_hal_gpio.c

#ifdef TEST

#include "unity.h"

#include "hal_gpio.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_HAL_GPIO_GetPort_should_Return_Port_Pointer(void)
{
    TEST_ASSERT_EQUAL_PTR(HAL_GPIO_PORT_A, HAL_GPIO_GetPort(HAL_GPIO_PORTA_BASE_ADDRESS));
    TEST_ASSERT_EQUAL_PTR(HAL_GPIO_PORT_B, HAL_GPIO_GetPort(HAL_GPIO_PORTB_BASE_ADDRESS));
}

#endif // TEST

-project.yml

---

:project:
  :build_root: build/test
  :test_file_prefix: test_
  :use_exceptions: FALSE
  :which_ceedling: gem

:paths:
  :test:
    - +:src/test/*
    - +:src/lib/test/*
    - -:src/test/support
  :source:
    - src/lib/core/*
    - src/lib/hal/*
  :support:
    - src/test/support

:libraries:
  :system:
    - m

:defines:
  :common: &common_defines []
  :test:
    - *common_defines
    - TEST
    - UNITY_OUTPUT_COLOR 
    - MCU_PART_NR=MCU_TM4C123GH6PM

:unity:
  :defines:
    - UNITY_EXCLUDE_STDINT_H
    - UNITY_EXCLUDE_LIMITS_H
    - UNITY_EXCLUDE_SIZEOF
    - UNITY_INCLUDE_DOUBLE
    - UNITY_SUPPORT_TEST_CASES
    - UNITY_INT_WIDTH=32
    - UNITY_LONG_WIDTH=32
    - UNITY_INCLUDE_CONFIG_H

:cmock:
  :mock_prefix: mock_
  :when_no_prototypes: :warn
  :mock_path: 'build/test/mocks/'
  :includes:
    - stdint.h
    - math.h
    - defs.h
  :includes_h_pre_orig_header:
    - stdint.h
    - defs.h
  :enforce_strict_ordering: FALSE
  :verbosity: 1
  :plugins:
    - :expect
    - :ignore
    #- :array
    #- :expect_any_args
    #- :return_thru_ptr
    #- :ignore_arg
    #- :callback
  :defines:
    - CMOCK_MEM_STATIC
    # Enough RAM for mocking but not too much to max out emulated RAM usage
    - CMOCK_MEM_SIZE=20480
    - CMOCK_MEM_ALIGN=2

:test_runner:
  :vendor_path: '..'
  :defines:
    - TEST_INSTANCES
  :includes:
    - stdint.h
    - defs.h

:module_generator:
  :naming: "snake"
  :project_root: ./
  :source_root: ./
  :test_root: ../test/
  :inc_root: ./inc/
  :source:
    :includes:
      - defs.h

:plugins:
  :load_paths:
    - "#{Ceedling.load_path}"
  :enabled:
    #- xml_tests_report
    #- junit_tests_report
    #- stdout_pretty_tests_report
    - stdout_gtestlike_tests_report #<------ Enable This If Using Eclipse
    - module_generator
    - gcov
    - command_hooks
...
SilverBulletEngineering commented 2 years ago

Hi, I would start by ensuring the proper flags are set when compiling and linking on the PC with gcc. What are your compiler and linker flags on the target build? Check the product code build log. Step 1 when using a HAL or 3rd party software is to ensure the compiler and linker have the proper MCU and other build flags so the proper 3rd party code will be included in the unit test build. Most HALs and 3rd party software have flags to select a particular HAL implementation for your MCU. Sometimes you run into compiler/linker issues with flags that the PC gcc compiler will not support, and then you have to get creative to get the unit test build to work. The yml file :defines: section must have the proper build flags for the HAL.

Letme commented 2 years ago

Your header files are missing include guards (provided you copied them in full).

and test_hal_gpio.c does not need #ifdef TEST guarding it.

and hal_gpio.h has a strange way,but you are missing in test_hal_gpio.c a definition for the variable HAL_GPIO_t *HAL_GPIO_PORT_A (and PORT_B). Otherwise it does not know which memory to point to...

tariksapmaz commented 2 years ago

Hi, I would start by ensuring the proper flags are set when compiling and linking on the PC with gcc. What are your compiler and linker flags on the target build? Check the product code build log. Step 1 when using a HAL or 3rd party software is to ensure the compiler and linker have the proper MCU and other build flags so the proper 3rd party code will be included in the unit test build. Most HALs and 3rd party software have flags to select a particular HAL implementation for your MCU. Sometimes you run into compiler/linker issues with flags that the PC gcc compiler will not support, and then you have to get creative to get the unit test build to work. The yml file :defines: section must have the proper build flags for the HAL.

I'm using arm-none-eabi-gcc (gcc version 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] (15:9-2019-q4-0ubuntu1)) compiler and my mcu is tm4c123gh6pm. Also my linker is very simple

MEMORY
{
    FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}

SECTIONS
{
    .text :
    {
        _text = .;
        KEEP(*(.isr_vector))
        *(.text*)
        *(.rodata*)
        _etext = .;
    } > FLASH

    .data : AT(ADDR(.text) + SIZEOF(.text))
    {
        _data = .;
        _ldata = LOADADDR (.data);
        *(vtable)
        *(.data*)
        _edata = .;
    } > SRAM

    .bss :
    {
        _bss = .;
        *(.bss*)
        *(COMMON)
        _ebss = .;
    } > SRAM

    /DISCARD/ :
    {
        libc.a ( * )
        libm.a ( * )
        libgcc.a ( * )
    }
    .ARM.attributes 0 : { *(.ARM.attributes) }
}

I think I explained my problem wrong. I can compile and run my code in my MCU, no problem (I don't use ceedling for this, I have a custom makefile and startup_gcc.c files). I also run some code and it works. However, when I compiled my test source with ceedling, I got this error(multiple definition, first defined here etc...).

Your header files are missing include guards (provided you copied them in full).

and test_hal_gpio.c does not need #ifdef TEST guarding it.

and hal_gpio.h has a strange way,but you are missing in test_hal_gpio.c a definition for the variable HAL_GPIO_t *HAL_GPIO_PORT_A (and PORT_B). Otherwise it does not know which memory to point to...

When I cut out some fields, I forgot to add them. I'm sorry for them. I have #ifndef, #define and #endif guards in the header file.

#ifdef TEST

#ifdef TEST_INSTANCES

HAL_GPIO_t   HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t   HAL_GPIO_PORT_B_Instance;

HAL_GPIO_t  *HAL_GPIO_PORT_A = &HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t  *HAL_GPIO_PORT_B = &HAL_GPIO_PORT_B_Instance;

#else

extern HAL_GPIO_t  *HAL_GPIO_PORT_A;
extern HAL_GPIO_t  *HAL_GPIO_PORT_B;

#endif

With this way, did i declare variables (HAL_GPIO_t HAL_GPIO_PORT_A, HAL_GPIO_t HAL_GPIO_PORT_B) in test source ?

Also i found this solution in another forum at last night. I moved declarations to source files and i add extern keywords to those variables in test source. Is this a good approach?

Final files -hal_gpio.h

#ifndef HAL_GPIO_H
#define HAL_GPIO_H
#include "hal_types.h"

typedef struct
{
    __IO uint32_t DATA;      //<* GPIO Data                      (0x00000000) */
    uint32_t RESERVED[255];
    __IO uint32_t DIR;       //<* GPIO Direction                 (0x00000400) */
} HAL_GPIO_t;

typedef struct
{
    uint32_t port;
    uint32_t id;
    uint8_t mode;
    uint8_t strength;
    uint8_t type;
    uint8_t mux_id;
} HAL_GPIO_Pin_Conf_t;

#define HAL_GPIO_PORT_BASE_ADDRESS   0x40000000
#define HAL_GPIO_PORTA_BASE_ADDRESS     (HAL_GPIO_PORT_BASE_ADDRESS + 0x4000)   // GPIO Port A
#define HAL_GPIO_PORTB_BASE_ADDRESS     (HAL_GPIO_PORT_BASE_ADDRESS + 0x5000)   // GPIO Port B

void HAL_GPIO_Init(const HAL_GPIO_Pin_Conf_t *pin, uint32_t length);
bool_t HAL_GPIO_ReadPin(uint32_t port_id, uint32_t pin_id);
#endif

-hal_gpio.c

#include "defs.h"
#include "hal_gpio.h"

#ifndef TEST

#define HAL_GPIO_PORT_A     ((HAL_GPIO_t*) HAL_GPIO_PORTA_BASE_ADDRESS)
#define HAL_GPIO_PORT_B     ((HAL_GPIO_t*) HAL_GPIO_PORTB_BASE_ADDRESS)

#else

HAL_GPIO_t   HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t   HAL_GPIO_PORT_B_Instance;

HAL_GPIO_t  *HAL_GPIO_PORT_A = &HAL_GPIO_PORT_A_Instance;
HAL_GPIO_t  *HAL_GPIO_PORT_B = &HAL_GPIO_PORT_B_Instance;

#endif

HAL_GPIO_t *HAL_GPIO_GetPort(uint32_t portAddress)
{
  if (portAddress == HAL_GPIO_PORTA_BASE_ADDRESS) {
    return HAL_GPIO_PORT_A;
  } else if (portAddress == HAL_GPIO_PORTB_BASE_ADDRESS) {
    return HAL_GPIO_PORT_B;
  }
}

void HAL_GPIO_Init(const HAL_GPIO_Pin_Conf_t *pin, uint32_t length)
{
    return;
}

bool_t HAL_GPIO_ReadPin(uint32_t port_id, uint32_t pin_id)
{
    return true;
}

-test_hal_gpio.c

#ifdef TEST

#include "unity.h"

#include "hal_gpio.h"

extern HAL_GPIO_t  *HAL_GPIO_PORT_A;
extern HAL_GPIO_t  *HAL_GPIO_PORT_B;

void setUp(void)
{
}

void tearDown(void)
{
}

void test_HAL_GPIO_GetPort_should_Return_Port_Pointer(void)
{
    TEST_ASSERT_EQUAL_PTR(HAL_GPIO_PORT_A, HAL_GPIO_GetPort(HAL_GPIO_PORTA_BASE_ADDRESS));
    TEST_ASSERT_EQUAL_PTR(HAL_GPIO_PORT_B, HAL_GPIO_GetPort(HAL_GPIO_PORTB_BASE_ADDRESS));
}

#endif // TEST

And i dont know why it's work...