ThrowTheSwitch / CMock

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

Persisting a mock parameter passed to the next function call #315

Closed krish2487 closed 4 years ago

krish2487 commented 4 years ago

Hello, I am not sure if my title makes sense, I ll try and explain it here. Say I have a function of the form funcA( custom_type_t member, char param1, char* param2) in a moduleA. Here the return value of the funcA is stored in param2.

Now I am trying to test moduleB which includes moduleA. Hence I am mocking moduleA to test moduleB. In moduleB, a call to funcA is succeded by trying to check the value of param2, like so

funcA(member, param1, param2); if(strncmp(param2, "True", 4) == 0) ...

Now since I am mocking the moduleA, I am able to simulate the function call like so. funcA_ExpectAndReturn(member, "Key1, "True", SUCCESS) how do I ensure that the value of "True" is being passed to the next strncmp call?

I am not sure if I made any sense, please let me know if I need to provide additional context and/or code samples. Any help or pointers is greatly appreciated!! :-) Thank you and have a nice day!!

Tuc-an commented 4 years ago

I think you’ll want to use IgnoreArg and ReturnThruPtr.

Assuming param2 does not point to “True” prior to calling funcA then the ExpectAndReturn will fail. You can overcome this like:

funcA_ExpectAndReturn(member, "Key1“, NULL, SUCCESS)
funcA_IgnoreArg_param2()

To also populate param2 you can follow it with one of the https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md#returnthruptr options. If param2 is a buffer allocated by the caller then you may want the ReturnMemThruPtr option, however another pattern is for funcA to allocate the buffer in which case you may need to change the type to a char** and use the ReturnThruPtr option.

krish2487 commented 4 years ago

@Tuc-an Thank you for the response. It does make sense. I tried your suggestion like so

funcA_ExpectAndReturn(member, "Key1", NULL, SUCCESS);
funcA_IgnoreArg_param2();
funcA_ReturnThruPtr_param2("True");

which gives me a"Undefined symbols for architecture x86_64" error for the IgnoreArg and ReturnThruPtr functions.

There are two points that I havent mentioned in the earlier post.

  1. The "funcA" is returning an enum EXIT_RESULTwhich can either be SUCCESS or FAILURE.
  2. The param2 is a static member defined in the header which is passed to the funcA function. Presently it is just a pointer to a char array. like so static char *param2; So the buffer is presently not being allocated by the caller. I suppose I should allocate memory for the param2 earlier and then try the steps above??

Thank you for the help so far!!

Letme commented 4 years ago

Did you enable the two plugins in your yml file? Although that error sounds more like you have a compiler issue or maybe includes issue?

  :plugins:
  ....
    - :ignore_arg
    - :return_thru_ptr
krish2487 commented 4 years ago

@Letme You were spot on.. I did not enable those plugins. Facepalm!! The tests not fail with a different error, but that is my fault I am expecting.. I ll work more on in and get back a little later. have to go and earn my upkeep for now..

Thank you again @Tuc-an and @Letme . You guys have been very very helpful and patient!!. I will come back with updates and hopefully a functioning test.

krish2487 commented 4 years ago

Ok.. mindmeltdown... I am still hitting a block.. Please indulge me folks for a little while..

This is a part of my source file, that is under test.

    GetRuntimeSettings(settings, "KW360Enabled", SensorEnabled);
    if (SensorPOST(KW360) == SUCCESS) {
        WriteUSBMessage("KW360 POST Passed");
        SysStatus(KW360Good);
        SetPOSTResults(results, "KW360", "Good");
        SensorGetSerial(KW360, KW360_Serial);
        SetPOSTResults(results, "KW360_Serial", KW360_Serial);
    } else {
        WriteUSBMessage("KW360 POST Failed");
        SysStatus(KW360Bad);
        SetPOSTResults(results, "KW360", "Bad");
        SetRuntimeSettings(settings, "KW360Enabled", "False");
    }

And this is a snippet from the corresponding header file.

static char *ReadBuffer;
static char *SensorEnabled;

And this is one of the headers that I am mocking.

EXIT_STATUS SetPOSTResults(POSTResults_t *postResults, char *parameters, char *value);
EXIT_STATUS SetRuntimeSettings(RunTimeSettings_t *settings, char *parameters, char *value);
EXIT_STATUS GetRuntimeSettings(RunTimeSettings_t *settings, char *parameters, char *Buffer);

and this is another

EXIT_STATUS SensorPOST(Sensor_t *SensorUnderTest);

and another

EXIT_STATUS SysStatus(Status SystemStatus);

and another

EXIT_STATUS WriteUSBMessage(char*);

I think that about covers all the functions used in the above snippet. What I would like to do is to test an alternate path for the GetRuntimeSettings(settings, "KW360Enabled", SensorEnabled); section. settings is a custom struct datatype, KW360Enabled is a key that the function checks the value for - it can be True or 'False. The value True or False is stored in the char* SensorEnabled. Now what I want to test is, that if the value of SensorEnabled after the function call GetRuntimeSettings is True then execute the code that follow it. If the value is False then print a message using WriteUSBMessage() function, bypass the above mentioned codeblock and progress to the next stage.

I hope I explained it a little more clearly.. I am at a loss how to test this specific branch. Thank you in advance for your patience fellows!! I am greatly thankful to you!

PS: I am certain that the above method suggested is the way to go forward.. and I am equally certain I goofed something up in declaration / definition in my code. I am not experienced enough to identify where..

Letme commented 4 years ago

You have a static "global" variable declaration in a header file? What can you change from above? For example those static stuff either go into source file or are to be declared extern and go into test file (for testing case).

That EXIT_STATUS is defined somewhere accessible I would assume? What is the error from build system?

Tuc-an commented 4 years ago

When is SensorEnabled allocated - i.e. before calling GetRuntimeSettings or during that call? If during then you you may need to change the last parameter type for that function to a char**

krish2487 commented 4 years ago

Sorry for the delay folks..

@Letme I am including that header in the function-under-test file. I figured if I am calling the GetRuntimeSettings function then declaring the static global variable in the header would allow the calling file to see the variable. I would assume (I may be wrong though) that the compiler would throw a warning if I declared it in the source file of the GetRuntimeSettings function.

For now, the code is flexible, I can change pretty much anything.

The EXIT_STATUS is a "universal" header file included with all the sub modules. It has a header guard to avoid multiple inclusions.

Right now there is no error. I would like to create a test for the alternate path as I mentioned above. I am not sure how to get around to doing so.

@Tuc-an Presently, no where. The source for the GetRuntimeSettings doesnt exist. I have simply created the header file and am mocking it in the calling function test file. Should I do something like this SensorEnabled = (char*)(malloc(sizeof(char) * 5) ) before calling the GetRuntimeSettings function?

krish2487 commented 4 years ago

Ok. A little more progress..

So this is my calling function presently.

EXIT_STATUS POST(void)
{

    GetRuntimeSettings(settings, "KW360Enabled", SensorEnabled);
    if (strncmp(SensorEnabled, "True", 4) == 0) {
        if (SensorPOST(KW360) == SUCCESS) {
            WriteUSBMessage("KW360 POST Passed");
            SysStatus(KW360Good);
            SetPOSTResults(results, "KW360", "Good");
            SensorGetSerial(KW360, KW360_Serial);
            SetPOSTResults(results, "KW360_Serial", KW360_Serial);
        } else {
            WriteUSBMessage("KW360 POST Failed");
            SysStatus(KW360Bad);
            SetPOSTResults(results, "KW360", "Bad");
            SetRuntimeSettings(settings, "KW360Enabled", "False");
        }
    } else {
        WriteUSBMessage("KW360 Disabled");
    }
}

and this is my test for it.

void test_POST_KW360_Disabled(void)
{
    GetRuntimeSettings_ExpectAndReturn(settings, "KW360Enabled", SensorEnabled, SUCCESS);
    GetRuntimeSettings_IgnoreArg_Buffer();
    GetRuntimeSettings_ReturnThruPtr_Buffer("False");
    WriteUSBMessage_ExpectAndReturn("KW360 Disabled", SUCCESS);

    TEST_ASSERT_EQUAL(SUCCESS, POST());
}

I am assuming my usage of the IgnoreArgs and the ReturnThruPtr is correct. I expect that in the test, the mock would ignore the SensorEnabled / Buffer argument and pass False as the value to the function. I would expect the test to pass. since in the function above the strncmp would not evaluate to 0. Hence I expect a call to Write USBMessage with the parameters "KW360 Disabled". and thats it.

However, running the test gives this output.

-------------------
FAILED TEST SUMMARY
-------------------
[test_states.c]
  Test: test_POST_KW360_Disabled
  At line (296): "Function GetRuntimeSettings Argument parameters.  Pointer is NULL."

which I am assuming is referring to char* SensorEnabled being declared but no memory being allocated?? Is my understanding correct?? But since we are asking the test to ignore this parameter, should'nt it ignore a null pointer?

krish2487 commented 4 years ago

OK. A little more progress and mixed results. I tried a hunch and modified the calling function like so.

EXIT_STATUS POST(void)
{
    SensorEnabled = (char*) malloc(sizeof(char) * 5);

    GetRuntimeSettings(settings, "KW360Enabled", SensorEnabled);
    if (strncmp(SensorEnabled, "True", 4) == 0) {
        if (SensorPOST(KW360) == SUCCESS) {
            WriteUSBMessage("KW360 POST Passed");
            SysStatus(KW360Good);
            SetPOSTResults(results, "KW360", "Good");
            SensorGetSerial(KW360, KW360_Serial);
            SetPOSTResults(results, "KW360_Serial", KW360_Serial);
        } else {
            WriteUSBMessage("KW360 POST Failed");
            SysStatus(KW360Bad);
            SetPOSTResults(results, "KW360", "Bad");
            SetRuntimeSettings(settings, "KW360Enabled", "False");
        }
    } else {
        WriteUSBMessage("KW360 Disabled");
    }
}

I jsut added the SensorEnabled = (char*) malloc(sizeof(char) * 5); part to allocate memory for the SensorEnabled pointer. and reran the tests. and this is what it shows.

Test 'test_states.c'
--------------------
Generating runner for test_states.c...
Compiling test_states_runner.c...
Compiling test_states.c...
Linking test_states.out...
Running test_states.out...

--------------------
IGNORED TEST SUMMARY
--------------------
[test_states.c]
  Test: test_POST_KW360_PMS5003_ADS1015_ECC508_Disabled_NeedToImplement
  At line (291): "Need to Implement the above tests!"

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED:  2
PASSED:  1
FAILED:  0
IGNORED: 1

So not allocating memory for the char* SensorEnabled caused the null pointer error. I decided to try and see if the other path would execute as well if SensorEnabled was True.

So I wrote another test for when the Sensor Enabled is true. and these are my two tests.

void test_POST_KW360_Disabled(void)
{
    GetRuntimeSettings_ExpectAndReturn(settings, "KW360Enabled", SensorEnabled, SUCCESS);
    GetRuntimeSettings_IgnoreArg_Buffer();
    GetRuntimeSettings_ReturnThruPtr_Buffer("False");
    WriteUSBMessage_ExpectAndReturn("KW360 Disabled", SUCCESS);

    TEST_ASSERT_EQUAL(SUCCESS, POST());
}

void test_POST_KW360_Enabled(void)
{
    GetRuntimeSettings_ExpectAndReturn(settings, "KW360Enabled", SensorEnabled, SUCCESS);
    GetRuntimeSettings_IgnoreArg_Buffer();
    GetRuntimeSettings_ReturnThruPtr_Buffer("True");

    SensorPOST_ExpectAndReturn(KW360, SUCCESS);
    WriteUSBMessage_ExpectAndReturn("KW360 POST Passed", SUCCESS);
    SysStatus_ExpectAndReturn(KW360Good, SUCCESS);
    SetPOSTResults_ExpectAndReturn(results, "KW360", "Good", SUCCESS);
    SensorGetSerial_ExpectAndReturn(KW360, KW360_Serial, SUCCESS);
    SetPOSTResults_ExpectAndReturn(results, "KW360_Serial", KW360_Serial,
                                   SUCCESS);

    TEST_ASSERT_EQUAL(SUCCESS, POST());
}

aaand.. I get this failing test.

-------------------
FAILED TEST SUMMARY
-------------------
[test_states.c]
  Test: test_POST_KW360_Enabled
  At line (311): "Function WriteUSBMessage.  Called earlier than expected."

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED:  3
PASSED:  1
FAILED:  1
IGNORED: 1

---------------------
BUILD FAILURE SUMMARY
---------------------
Unit test failures.

I ran ceedling clobber and ran the test again to start from a clean slate and this is still the error I get. I am guessing I goofed up something else.. But the WriteUSBMessage is exactly what I am expecting as seen in the source of the calling function.. What am I doing wrong here??

Edit 1 : On thinking about it, it looks like the path for SensorEnabled = True is not executing even when the value is explicitly set so. i.e the SensorPOST function call is not happening. The test is still executing the WriteUSBMessage ("KW360 Disabled") message and hence the failing test. Any suggestions on what I should try next to fix this folks??

Thank you for the help and handholding!! :-)

Tuc-an commented 4 years ago

Can you add a printf (or debug print) just before the strncmp? I suspect that it's only copying the first character and not the whole string. If that's the case then try switching to something like:

GetRuntimeSettings_ReturnArrayThruPtr_Buffer("True", sizeof("True"));
krish2487 commented 4 years ago

@Tuc-an Spot on!! I added the printf call like so

    GetRuntimeSettings(settings, "KW360Enabled", SensorEnabled);
    printf("SensorEnabled Value: %s\n", SensorEnabled);
    if (strncmp(SensorEnabled, "True", 4) == 0) {

and it looks like, as you suggested only one character is being copied during the ReturnThruPtr call.

-----------
TEST OUTPUT
-----------
[test_states.c]
  - "SensorEnabled Value: F"
  - "SensorEnabled Value: T"

--------------------
IGNORED TEST SUMMARY
--------------------
[test_states.c]
  Test: test_POST_KW360_PMS5003_ADS1015_ECC508_Disabled_NeedToImplement
  At line (291): "Need to Implement the above tests!"

-------------------
FAILED TEST SUMMARY
-------------------
[test_states.c]
  Test: test_POST_KW360_Enabled
  At line (311): "Function WriteUSBMessage.  Called earlier than expected."

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED:  3
PASSED:  1
FAILED:  1
IGNORED: 1

And similarly, I modified the call to ReturnArrayThruPtr and this is what I get now

-----------
TEST OUTPUT
-----------
[test_states.c]
  - "SensorEnabled Value: False"
  - "SensorEnabled Value: True"

--------------------
IGNORED TEST SUMMARY
--------------------
[test_states.c]
  Test: test_POST_KW360_PMS5003_ADS1015_ECC508_Disabled_NeedToImplement
  At line (291): "Need to Implement the above tests!"

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED:  3
PASSED:  2
FAILED:  0
IGNORED: 1

Thank you !!!! :-) You and @Letme have been very very helpful and patient with me in isolating and addressing the issue!! I can thank you folks enough!! :-)

I can now continue to make more mistakes and learn along the way!!! Sincere thanks to you guys again!!