2

I am trying to implement unit testing in C using Ceedling.

struct Person *list;
/* TASK_FIND_BY */
void test_main_task_find_by_normal(void)
{
    char *string = (char *)malloc(sizeof(char));
    struct Person new_list_instance;
    struct Person *new_list = &new_list_instance;
    ask_input_ExpectAndReturn(string);
    llist_find_by_ExpectAndReturn(list, string, new_list);
    llist_print_Expect(new_list);
    llist_remove_all_Expect(&new_list);

    printf("\nOutside single: %p\n", new_list);
    printf("Outside double: %p\n", &new_list);

    task_find_by(list);
}
void task_find_by(struct Person *list)
{
    char *input = NULL;
    struct Person *new = NULL;

    printf("Input exact name/surname/email/phone: ");
    input = ask_input();
    if (input == NULL) {
        printf("Error!");
        return;
    }
    new = llist_find_by(list, input);
    if (new != NULL)
        llist_print(new);
    else 
        printf("Address not found!\n");
    
    printf("\nInside single: %p\n", new);
    printf("Inside double: %p\n", &new);
    
    free(input);
    llist_remove_all(&new);
}

I expect the function inside task_find_by to be called with the same argument as here: llist_remove_all_Expect(&new_list). But the actual function is called with the address of new pointer, where new holds the new_list. Since new_list and new are different pointers their addresses are different. Is there any way to test the called argument without changing the implementation of task_find_by function?

  - "Outside single: 0x7ffc2e3c03d0"
  - "Outside double: 0x7ffc2e3c03c0"
  - "Inside single: 0x7ffc2e3c03d0"
  - "Inside double: 0x7ffc2e3c0398"
ryyker
  • 22,849
  • 3
  • 43
  • 87
Mykolas D.
  • 43
  • 5
  • 6
    Please note that `malloc(sizeof(char))` allocate space for a single character. That can only hold the empty string, the string null-terminator character `'\0'`, nothing more. – Some programmer dude May 09 '23 at 11:51
  • And please try to create a [mre] to show us. What is this `llist_find_by_ExpectAndReturn` "function"? Is it really a macro, that defines the `list` variable? What does it then initialize it as? Are you sure it won't do anything like `list = new_list` or similar? – Some programmer dude May 09 '23 at 11:57
  • 1
    @Someprogrammerdude The malloc(sizeof(char)) is just for getting the address (single byte is enough) on the heap because of how CMock and my function expects to get the address and that is completely besides the point here and it does not do anything more. And to answer your second comment, it is a macro that is generated by CMock which is a mocking interface that is included inside Ceedling unit testing framework and all of the code inside test_main_task_find_by_normal(void) is relevant to this question. Edit: struct Person *list; is defined as a global variable that holds nothing. – Mykolas D. May 09 '23 at 12:35
  • If the result of that macro or the functions it calls result in something like `list = new_list` then the result you get is what's to be expected. Have you tried to run the code through the preprocessor to see what it expands to? How about using a debugger to see what really happens, and `llist_find_by_ExpectAndReturn` really does and what the result of it might be? – Some programmer dude May 09 '23 at 12:53
  • 1
    @ryyker I am referring to the `llist_remove_all_Expect(&new_list)` where it expects `&new_list` call and in the actual function it gets `&new` call where `new = new_list` – Mykolas D. May 09 '23 at 12:59
  • @Someprogrammerdude Yes, this is indeed the expected result and that's why I am asking is there any way to actually test this functionality without changing the implementation of function that is being unit tested. (I might be missing some functionality of Ceedling or there is a workaroud to test the address) – Mykolas D. May 09 '23 at 13:01
  • 1
    You might just have to go with `llist_remove_all_ExpectAnyArgs();` since there's no way to know the address of the automatic variable `new`'. Either that or make a complete callback mock for that function that checks the contents of the pointer. – pmacfarlane May 09 '23 at 13:56
  • I'm sorry, but I'm confused. The question seems to say that the behavior is different than you expected, but in comments you seem to say that it is as expected. Furthermore, it's unclear to me what you mean by "the called argument", or what kind of test you want to perform on it. – John Bollinger May 09 '23 at 14:04
  • My bad, I meant to say that I want to instead of expect and since it does not work how I want I am searching for a solution. And I will probably just go with @pmacfarlane solution – Mykolas D. May 09 '23 at 14:12

2 Answers2

2

Since you cannot know the address of the automatic variable new, you basically have two options.

Ignore the parameter that gets passed to llist_remove_all() (but still check that it gets called):

llist_remove_all_ExpectAnyArgs();

Or write a stub function that can check the contents of the pointer that is passed to the function. Something like:

static struct Person *expect_person;

static void llist_remove_all_my_stub(struct Person **p, int NumCalls)
{
    // Original suggestion
    //if (*p != expect_person)
    //    TEST_FAIL();

    // Better suggestion, from someone in comments
    TEST_ASSERT_EQUAL_PTR(expect_person, *p);
}

void test_main_task_find_by_normal(void)
{
    struct Person new_list_instance;
    ...
    expect_person = &new_list_instance;
    llist_remove_all_Stub(llist_remove_all_my_stub);
    llist_remove_all_Expect(&new_list);
    task_find_by(list);
}
pmacfarlane
  • 3,057
  • 1
  • 7
  • 24
  • 1
    In Unity from Ceedling framework there also exists TEST_ASSERT_EQUAL_PTR(expected_pointer, actual_pointer), which should improve clarity in testing workspace if anyone else has this issue. And thanks for a nice workaround! – Mykolas D. May 10 '23 at 08:09
0

I [want] the function inside task_find_by to be called with the same argument as here: llist_remove_all_Expect(&new_list).

The argument you are passing to the latter has a different type than does the parameter of the former. These types are not compatible, and if the values compared equal then that would almost surely indicate a flaw in your test suite or in the code under test.

But the actual function is called with the address of new pointer, where new holds the new_list.

If the function receives a parameter that compares equal to (struct Person *) &new then something is dreadfully wrong with either the tests or the code under test. In task_find_by(), new designates a local variable. The only way its address can be passed into that function as an argument is if either the caller uses a pointer after its referrent's lifetime ends, or if an altogether wild pointer is passed.

Perhaps you meant that, having passed &new_list as the argument to llist_remove_all_Expect(), you want to pass new_list to task_find_by(). That is within your direct control.

Or perhaps you meant that you want llist_find_by_ExpectAndReturn(list, string, new_list) to set list equal to new_list, with the effect that passing list to task_find_by() is equivalent to the behavior described in the previous paragraph. That would be a matter of the implementation of llist_find_by_ExpectAndReturn(), and if it's not happening, then that might be a sign that either the test suite or the code under test is flawed.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157