ThrowTheSwitch / CMock

CMock - Mock/stub generator for C
http://throwtheswitch.org
MIT License
653 stars 269 forks source link

How to mock the struct pointer function in loop? #388

Closed LinusLing0910 closed 2 years ago

LinusLing0910 commented 2 years ago

Is there any way to mock the struct pointer function?

ifndef __HAL_IF_NVM_H__

define __HAL_IF_NVM_H__

include

include

typedef enum { kHalNvm_Success = 0, kHalNvm_Failed = -1, kHalNvm_NotSupported = -2, kHalNvm_AddressOutOfRange = -3, }HalNvmRet;

typedef struct HalNvmProperty { uint32_t total_size; uint32_t erase_size; uint32_t page_size; uint32_t sector_size; uint32_t block_size; }HalNvmProperty;

struct HalNvm; typedef struct HalNvmFunc { HalNvmRet (const read) (const struct HalNvm this, uint32_t offset, uint32_t byte_length, uint8_t data); HalNvmRet (const write) (const struct HalNvm this, uint32_t offset, uint32_t byte_length, const uint8_t data); HalNvmRet (const erase) (const struct HalNvm this, uint32_t offset, uint32_t byte_length); HalNvmRet (const get_property) (const struct HalNvm this, HalNvmProperty property); HalNvmRet (const write_protect) (const struct HalNvm* this, uint32_t offset, uint32_t byte_length, bool enabled); }HalNvmFunc;

typedef struct HalNvm { HalNvmFunc func; }constHalNvm;

laurensmiers commented 2 years ago

If I understand correctly, you want to mock one of the functions in HalNvmFunc? I did this something similar some time ago, don't have the code anymore but it was something like this:

Create a new helper.h file (name is just an example): helper.h:

HalNvmRet hal_nvm_read(onst struct HalNvm this, uint32_t offset, uint32_t byte_length, uint8_t* data);
HalNvmRet hal_nvm_write(onst struct HalNvm this, uint32_t offset, uint32_t byte_length, uint8_t* data);

These contain the functions you will use in the HalNvmFunc struct and which will be called by your code-under-test. You don't implement these functions, cmock will do that for you.

Then, your test file looks something like this: test.c :

#include "mock_helper.h"

static struct HalNvm nvm = {
    .read = hal_nvm_read,
    .write = hal_nvm_write,
};

void my_test(void)
{
     hal_nvm_read_ExpectAnyArgs(kHalNvm_Failed);
     function_under_test_calling_hal_nvm_read(&nvm);
}

Assuming the code-under-test is something like this: code.c

void function_under_test_calling_hal_nvm_read(const struct HalNvm *this)
{
    this->read();
}
mvandervoord commented 2 years ago

@laurensmiers is right. CMock needs a header of functions in order to create mocks, so you need to give it matching prototypes. In your test you assign the struct pointers themselves to point at the mock functions created by CMock (which are whatever you named them in your helper header file).

LinusLing0910 commented 2 years ago

Thanks @laurensmiers and @mvandervoord I had define the method in the hal_if_nvm.h as below, do it need to separate to helper.h file or can keep in the same file?

#ifdef TEST
HalNvmRet hal_nvm_read(const struct HalNvm* this, uint32_t offset, uint32_t byte_length, uint8_t* data);
HalNvmRet hal_nvm_write(const struct HalNvm* this, uint32_t offset, uint32_t byte_length, const uint8_t* data);
HalNvmRet hal_nvm_erase(const struct HalNvm* this, uint32_t offset, uint32_t byte_length);
HalNvmRet hal_nvm_get_property(const struct HalNvm* this, HalNvmProperty* property);
HalNvmRet hal_nvm_write_protect(const struct HalNvm* this, uint32_t offset, uint32_t byte_length, bool enabled);
#endif

and also add the static struct in the test_*.c file, as below

static struct HalNvmFunc nvm_func = 
{
    .read = hal_nvm_read,
    .write = hal_nvm_write,
    .erase = hal_nvm_erase,
    .get_property = hal_nvm_get_property,
    .write_protect = hal_nvm_write_protect,
};

static struct HalNvm nvm ={
    .func = nvm_func,
};

void test_core_retain_init_ParameterError(void)
{
    PlcCoreCb* core_cb; 
    uint32_t nvm_start_offset = 0;
    uint32_t nvm_total_size = 20;
    uint32_t partition_num = 1;
    uint32_t total_size = 4*1024*1024;
    uint32_t erase_size = 256;
    uint32_t page_size = 256;
    uint32_t sector_size = 4*1024;
    uint32_t block_size = 64*1024;
    HalNvmProperty* property = {total_size, erase_size, page_size, sector_size, block_size};
    hal_nvm_get_property_ExpectAndReturn(&nvm, property, kHalNvm_Success);
    PlcRet ret = core_retain_init(core_cb, &nvm, 0, 20 ,1);
    TEST_ASSERT_EQUAL(kPlcRet_ParameterError, ret);
}

using the cmock, it was generate the related method for the mock, but it failed with below error

Compiling test_plc_core_retain.c... test/test_plc_core_retain.c:26:13: error: incompatible types when initializing type 'HalNvmFunc ' {aka 'struct HalNvmFunc '} using type 'struct HalNvmFunc' .func = nvm_func, ^~~~ test/test_plc_core_retain.c: In function 'test_core_retain_init_ParameterError': test/test_plc_core_retain.c:40:33: warning: initialization of 'HalNvmProperty ' {aka 'struct HalNvmProperty '} from 'uint32_t' {aka 'unsigned int'} makes pointer from integer without a cast [-Wint-conversion] HalNvmProperty* property = {total_size, erase_size, page_size, sector_size, block_size}; ^~~~~~ test/test_plc_core_retain.c:40:33: note: (near initialization for 'property')

LinusLing0910 commented 2 years ago

The issue was resolved, it worked with hal_nvm_get_property_IgnoreAndReturn(kHalNvm_Success);, but it failed with hal_nvm_get_property_ExpectAndReturn(&halNvm, &property, kHalNvm_Success);

with the error message, "Function hal_nvm_get_property. Called fewer times than expected.", I have tried to search information in the document, but cannot find related?

Letme commented 2 years ago

expect counts how many times is it called, while Ignore just ignores everything. So you might be getting the size of the loop wrong?

LinusLing0910 commented 2 years ago

@Letme I'm sorry for that I cannot understand "expect counts how many times is it called"? The method hal_nvm_get_property_ExpectAndReturn only call one time in the test code, and origin code also only call one time

LinusLing0910 commented 2 years ago

I debug the code, and it thrown out the error message "called fewer times than expected" after CMock_Verify() method to call mock_hal_if_nvm_Verify() failed image

Letme commented 2 years ago

hal_nvm_get_property_ExpectAndReturn method counts number of times it is called and based on the error message you declared that you expect it too many times (you expected it will be called 4 times, but it was only called 3 times). The hal_nvm_get_property_IgnoreAndReturn does not matter how many times you call it -> It will ignore the calls and just always return the value.

LinusLing0910 commented 2 years ago

@Letme thanks for your comments, I know the different with expect and ingore method, but how do you get the expected method called time(4 times)?

Letme commented 2 years ago

It was just an example to show you what the error message means since I assume that is what you are struggling (in a loop).

LinusLing0910 commented 2 years ago

ok, thanks

LinusLing0910 commented 2 years ago

the code which need to be tested like this

static PlcRet core_retain_init(PlcCoreCb* core_cb, HalNvm nvm_hal, uint32_t nvm_start_offset, uint32_t nvm_total_size, uint32_t partition_num)
{
    core_cb->retain.nvm_hal = nvm_hal;
    core_cb->retain.nvm_start_offset = nvm_start_offset;
    core_cb->retain.nvm_size = nvm_total_size;
    struct HalNvmProperty property;
    nvm_hal->func->get_property(nvm_hal, &property);

    // calc and check size of paratition
    if(partition_num<2)
        return kPlcRet_ParameterError;
    uint16_t partition_size = nvm_total_size/partition_num;
    if((partition_size%property.erase_size)!=0)
    {
        partition_size += partition_size%property.erase_size;
    }

I mock the fun get_property first, then call the method again, but the property was changed , how to use the previous mock property struct , because the second time the propery.erase_size is 0 and cause the function failed.

void test_core_retain_init_with_invalid_partition_num_02(void)
{
    struct HalNvm halNvm = {
        .func = &nvm_func,
    };
    struct HalNvmProperty property;
    property.erase_size = 256;
    PlcCoreCb* core_cb; 
    uint32_t nvm_start_offset = 0;
    uint32_t nvm_total_size = 500;
    uint32_t partition_num = 2;

    hal_nvm_get_property_ExpectAndReturn(&halNvm, &property, kHalNvm_Success);
    printf("%d\n", property.erase_size);
    hal_nvm_get_property_IgnoreAndReturn(kHalNvm_Success);
    printf("%d\n", property.erase_size);
    property.erase_size = 256;
    PlcRet ret = core_retain_init(&core_cb, &halNvm, 0, 20 ,2);
    TEST_ASSERT_EQUAL(kPlcRet_ParameterError, ret);
}
LinusLing0910 commented 2 years ago

@mvandervoord does cmock to support mock the property struct or not ? or is there any solution for above issue?

Letme commented 2 years ago

quick glance over your example, the second time you Ignore and Return, so the value of the property is kinda undefined (points to whatever is currently on the stack at some location - so kinda random jibberish, but could also be the data from last called). However why do you expect call get_property twice, it is only called once?

In your test your ExpectAndReturn points it to your test local property but it doesn't act as copy data of the test property to function local property. That means function local property remains unchanged when that function is called. You could use ReturnThruPtr functionality if you wanted to write to some pointer location (I assume this is what you want here).

LinusLing0910 commented 2 years ago

@Letme I also tries the ReturnThruPtr, the value of property.erase_size is 256 before call core_retain_init()method, but debug into the method, the value of propery.erase_size is 0, but I didn't find which one change the value? image image

Letme commented 2 years ago

You should never use IgnoreAndReturn should be the cardinal rule everywhere. You can use IgnoreArg if you do not care for certain arguments as in example below. I am quite a bit rusty (has been a long time that I had to mock something through pointers) but what you want is roughly:

hal_nvm_get_property_ExpectAndReturn(&halNvm, &property, kHalNvm_Success);
hal_nvm_get_property_ReturnThruPtr_property(&property);
hal_nvm_get_property_IgnoreArg_halnvm(); //not sure of the name of this argument -> no prototype of the get_property function

PlcRet ret = core_retain_init(&core_cb, &halNvm, 0, 20 ,2);
TEST_ASSERT_EQUAL(kPlcRet_ParameterError, ret);
LinusLing0910 commented 2 years ago

@Letme Thanks so much, you are right. It works with below code

    hal_nvm_get_property_ExpectAndReturn(&halNvm, &property, kHalNvm_Success);
    hal_nvm_get_property_ReturnThruPtr_property(&property);
    hal_nvm_get_property_IgnoreArg_property();
    PlcRet ret = core_retain_init(&core_cb, &halNvm, 0, 20 ,2);
    TEST_ASSERT_EQUAL(kPlcRet_ParameterError, ret);
LinusLing0910 commented 2 years ago

@Letme @mvandervoord @laurensmiers @paulsc Thanks all of you, it works for me now.

Letme commented 2 years ago

Your last comment you again tell it to ingoreArg_property - and that you do not want, I wanted to ignore the first argument (since you return through property). Not sure about precedense in your case...