Closed LinusLing0910 closed 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();
}
@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).
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')
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?
expect
counts how many times is it called, while Ignore
just ignores everything. So you might be getting the size of the loop wrong?
@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
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
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.
@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)?
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).
ok, thanks
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);
}
@mvandervoord does cmock to support mock the property struct or not ? or is there any solution for above issue?
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).
@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?
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);
@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);
@Letme @mvandervoord @laurensmiers @paulsc Thanks all of you, it works for me now.
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...
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;