Cmocka unit testing with C: mocking nested function calls
Asked Answered
G

1

6

So, toy program that duplicates the issues I am having using cmocka for developing unit tests for existing code. The issue is a nested function call does not mock, which makes the unit test dependent on the nested function call performing properly. Please note, that "mockable_static" define was used as the original code has static functions that exist as "internal function calls", but for purposes of unit tests, these are open to external calls. (See stackoverflow post where this idea came from)

Without further ado, here is the code:

func.h:

#ifndef FUNC_H_
#define FUNC_H_

#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif

char* foo();

#endif // FUNC_H_

func.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef UNIT_TESTING
#define mockable_static static
#else
#define mockable_static
#endif

mockable_static char* bar (){
    printf("This is bar!\n");
    char *str = "This is the result of bar!";
    return str;
}

char* foo(){
    printf("This is foo, and it should return the results of bar()\n");
    char * res;
    res = bar();
    return res;
}

test.c:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>

#include "func.h"

static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);

char* __real_bar();

char* __wrap_bar(){
    printf("This is a wrap and doesn't return bar!\n");
    return (char*)mock();
}

int main(void){
    //bar test
    const struct CMUnitTest bar_tests[] = {
        cmocka_unit_test(test_bar),
        cmocka_unit_test(test_wrap_bar)
    };
    const struct CMUnitTest foo_tests[] = {
        cmocka_unit_test(test_foo)
    };
    //foo test w/ mocking bar

    int status;
    status = cmocka_run_group_tests(bar_tests,NULL,NULL);
    status = cmocka_run_group_tests(foo_tests,NULL,NULL);

    printf("Status = %d\n",status);
    return status;
}

static void test_bar(void **state){
    char expected_res[] = "This is the result of bar!";
    char * actual_res;

    actual_res = __real_bar();
    assert_string_equal(actual_res,expected_res);
}

static void test_wrap_bar(void **state){
    char * this =  "I don't want bar!";
    will_return(__wrap_bar,this);

    char * res = bar();
    assert_string_equal(res,this);
}

static void test_foo(void **state){
    char * this =  "I don't want bar!";
    will_return(__wrap_bar,this);

    char * res = foo();
    assert_string_equal(res,this);
}

gcc compile line:

gcc ./test.c ./func.c -DUNIT_TESTING -g -Wl,--wrap=bar -o test -lcmocka-static

results on test execution:

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- "This is the result of bar!" != "I don't want bar!"
[   LINE   ] --- ./test.c:59: error: Failure!
[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo

 1 FAILED TEST(S)
Status = 1

As you can see, bar() does not get wrapped in foo(), but in the wrap test, bar gets wrapped exactly as foo() calls bar. Bar is tested using __real_bar() which is a part of the cmocka test library (while __real_bar() has a prototype, the function is never defined and returns expected results as per cmocka documentation. Anyone have any experience using unit tests on nested function calls? I haven't found any results of mocking nested function calls with cmocka, but my google-foo might be lacking. If the assert is removed at the end of test_foo(), the test fails due to unused values in the will_return queue.

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- %s() has remaining non-returned values.
./test.c:56: note: remaining item was declared here

[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo

 1 FAILED TEST(S)
Status = 1
Gripsack answered 25/2, 2020 at 18:8 Comment(0)
G
3

Alright, so there are a few different avenues to solve this problem. I am posting the solution so someone else can see this.

Solution #1: separate nested function calls into separate .c files. IE- func.c contains foo() and (newfile)bar.c contains bar(). This allows the GCC --wrap=bar to work within func.c as it needs to link against another file.

Solution #2: build separate tests for testing bar and foo. Use the following line in func.c to make bar "weak"

__attribute__((weak))
mockable_static char* bar ().............(code follows)

In the file which is testing foo, with a mocked bar, we redefine bar() to act as the original char* __wrap_bar() function is defined. With the __attribute__((weak)), this redefined bar overrides the original bar, and we can proceed to force it to give results in the test file as we want.

The resulting test_foo.c file would look like this:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>

#include "func.h"
#include "bar.h"

static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);


char* bar(){
    printf("This is a wrap and doesn't return bar!\n");
    return (char*)mock();
}

int main(void){
    //bar test
    const struct CMUnitTest bar_tests[] = {
        cmocka_unit_test(test_wrap_bar)
    };
    const struct CMUnitTest foo_tests[] = {
        cmocka_unit_test(test_foo)
    };
    //foo test w/ mocking bar

    int status;
    status = cmocka_run_group_tests(bar_tests,NULL,NULL);
    status += cmocka_run_group_tests(foo_tests,NULL,NULL);

    printf("Status = %d\n",status);
    return status;
}

static void test_wrap_bar(void **state){
    char * this =  "I don't want bar!";
    will_return(bar,this);

    char * res = bar();
    assert_string_equal(res,this);
}

static void test_foo(void **state){
    char * this =  "I don't want bar!";
    will_return(bar,this);

    char * res = foo();
    assert_string_equal(res,this);
}

with the func.c file being:

#include <stdio.h>
#include <string.h>

#ifndef UNIT_TEST
#define mockable_static static
#else
#define mockable_static __attribute__((weak))
#endif


mockable_static char* bar (){
    printf("This is bar!\n");
    char *str = "This is the result of bar!";
    //char *str = "This is the resfjkl;dsaj of bar!";
    return str;
}

char* foo(){
    printf("This is foo, and it should return the results of bar()\n");
    char * res;
    res = bar();
    return res;
}

There would be a separate file, test_bar.c which would not redefine bar, and could test bar() within func.c.

Yay first solution is a solution to my own problem! Posting for others to see/comment/yell at me :)

Co-workers, thanks for helping with this!

Gripsack answered 25/2, 2020 at 23:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.