0

So I am trying to learn how to use the library check with a simple example on MacOS 11.6.1. For this I copied the code of Merlijn Sebrechts of the following question: Using C unit testing framework Check without Autotools?

#include <check.h>

START_TEST (sanity_check)
{
    fail_unless(5 == 5, "this should succeed");
    fail_unless(6 == 5, "this should fail");
    ck_assert_str_eq("asa", "asasdfasdf");

}
END_TEST

int main(void)
{
    Suite *s1 = suite_create("Core");
    TCase *tc1_1 = tcase_create("Core");
    SRunner *sr = srunner_create(s1);
    int nf;

    suite_add_tcase(s1, tc1_1);
    tcase_add_test(tc1_1, sanity_check);

    srunner_run_all(sr, CK_ENV);
    nf = srunner_ntests_failed(sr);
    srunner_free(sr);

    return nf == 0 ? 0 : 1;
}

When I execute the command in the terminal, I get the following error message:

$ gcc test.c -Wall -o test -lcheck -pthread -lcheck_pic -pthread -lrt -lm -lsubunit
ld: library not found for -lcheck_pic
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I check that the library check is installed by compiling the file following file (test2.c) with gcc test2.c and did not get any error message. From this I assumed that the library is indeed installed


#include <check.h>

int main(){
  int a;
  return 0;
}

What am I doing wrong?

EDIT 1

Ok following the comment, I removed calling most of the libraries and run the following command:

gcc test.c -Wall -o test -lcheck
$ ./test
Running suite(s): Core
0%: Checks: 1, Failures: 1, Errors: 0
test.c:6:F:Core:sanity_check:0: this should fail

Is it correct? And I have to say frankly: I do not understand what the code is doing?

EDIT 2

After the new comment of Jason, I had to serially removed several libraries in order not get an error by build (namely lcheck_pic, lsubunit, lrt:

$ gcc test.c -Wall -o test -lcheck -pthread -lcheck_pic -pthread -lrt -lm -lsubunit
ld: library not found for -lcheck_pic
$ gcc test.c -Wall -o test -lcheck -pthread -pthread -lm -lsubunit
ld: library not found for -lsubunit
clang: error: linker command failed with exit code 1 (use -v to see invocation)
$ gcc test.c -Wall -o test -lcheck -pthread -pthread -lrt -lm -lsubunit
ld: library not found for -lrt
clang: error: linker command failed with exit code 1 (use -v to see invocation)
 -v to see invocation)
$ gcc test.c -Wall -o test -lcheck -pthread -pthread -lm
... build successful ...

EDIT 3

In order to have every failure analyzed individually, I separated the check into difference instances as follows:

#include <check.h>


START_TEST (sanity_check1)
{
    fail_unless(5 == 5, "this should succeed");
}
END_TEST

START_TEST (sanity_check2)
{
    fail_unless(6 == 5, "this should fail");
}
END_TEST

START_TEST (sanity_check3)
{
    ck_assert_str_eq("asa", "asasdfasdf");
}
END_TEST


int main(void)
{
    Suite *s1 = suite_create("Core");
    TCase *tc1_1 = tcase_create("Core");
    SRunner *sr = srunner_create(s1);
    int nf;

    suite_add_tcase(s1, tc1_1);
    tcase_add_test(tc1_1, sanity_check1);
    tcase_add_test(tc1_1, sanity_check2);
    tcase_add_test(tc1_1, sanity_check3);

    srunner_run_all(sr, CK_ENV);
    nf = srunner_ntests_failed(sr);
    srunner_free(sr);

    return nf == 0 ? 0 : 1;
}

and got the following output (displaying every failed test independently):

$ gcc script.c -Wall -o script -lcheck -pthread -pthread -lm
$ ./script
Running suite(s): Core
33%: Checks: 3, Failures: 2, Errors: 0
script.c:12:F:Core:sanity_check2:0: this should fail
script.c:18:F:Core:sanity_check3:0: Assertion '"asa" == "asasdfasdf"' failed: "asa" == "asa", "asasdfasdf" == "asasdfasdf"
ecjb
  • 5,169
  • 12
  • 43
  • 79
  • `` is only a header file for the library, not the whole library. Successfully building a program with it demonstrates only that the compiler is able to find the header file, not that the library file containing the object modules is installed correctly. You will need to either install the library where the linker is already looking or pass a switch to the linker telling it where to look for the library or give the full path to the library. – Eric Postpischil Jan 31 '23 at 12:24
  • Thank you for your comment @EricPostpischil. So I installed the library as recommended in https://libcheck.github.io/check/ with homebrew (also tried tried port). So I assume that the library is installed. No? What would you recommend concretely as next step? – ecjb Jan 31 '23 at 13:08
  • The next step is to find the library you installed. Verify it has the same name as you expect. (That library should be called `libcheck_pic.so`). If the name is correct, then you may need to tell the compiler/linker exactly where to look for it. I have used `libcheck` myself on linux, and I never linked with `-lcheck_pic`. I only ever used `-lcheck`. Maybe that is all you need. Try removing `-lcheck_pic` and see if you get any undefined references. – Jason Jan 31 '23 at 14:34
  • Many thanks for your comment @Jason. So I removed many argument in the compilation with gcc and left `lcheck`. This time it could compile and when running `./test` I got `Running suite(s): Core 0%: Checks: 1, Failures: 1, Errors: 0 test.c:6:F:Core:sanity_check:0: this should fail` Is this correct? And I have to say frankly: I do not understand what the code is doing? I edited the question accordingly – ecjb Jan 31 '23 at 17:15

1 Answers1

1

The build solution was to remove -lcheck_pic.

Just expanding on the comment section...

Expecting 1 success and 1 failure... Is this correct?

Yes, that is correct. This is just an example of how to set up testing. In reality, you would never actually check 5 == 6. You would run portions of your program and check the state of whatever you are testing. Also, you should not need that #include <check.h> in the actual program code.

So think of something simple like an is_even function. You could do:

fail_unless(is_even(6), "failure");

This should not fail if your is_even function is correct. That may seem silly, but if later down the road you change the is_even function (or maybe a dependency of that function, this test may fail. That will let you know you need to review your last set of changes. You are essentially setting up automatic testing so you know right away if you broke something in your program.

It should also be noted that fail_unless has been deprecated. You should be using the functions provided here.

For more examples, here is a link to the test directory of a project of mine that uses libcheck.

Expansion on comment discussion

I linked to the tests directory in hopes that you would look at the C files in there... an example of how and why you would use check. For example, one of the more simple functions of the library is called sgetline which just means "safe getline". It will retrieve a single line from a file that can have either Linux (\n) or Windows (\r\n) line terminators and allocate a line buffer for you. Allow me to walk you through one of the tests for that function (from check_sgetline.c):

START_TEST(test_safegetline_long)
{
        _file = fopen("test_long.txt", "r");
        if (!_file) {
                perror("test_long.txt");
                exit(EXIT_FAILURE);
        }
        int ret = sgetline(_file, &buf, &buflen, &linelen);
        ck_assert_ptr_nonnull(buf);
        ck_assert_str_eq(buf, "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
        ck_assert_uint_eq(linelen, strlen("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"));
        ck_assert_int_eq(ret, 0);
        ck_assert_uint_gt(buflen, BUFFER_FACTOR);

        ret = sgetline(_file, &buf, &buflen, &linelen);
        ck_assert_int_eq(ret, EOF);
}
END_TEST

First, I open the file (test_long.txt is in that tests directory). Then, that FILE* is sent to sgetline along with a buffer, a buffer length and a line length (globals). sgetline can (and in this case will) modify these variables. The whole point of testing (and check) is to prove that my program does exactly what I "expect." In order to prove to myself that sgetline works as expected, I use the ck_* functions provided by check. So, here is what the checks are doing in English:

I would not EXPECT the buffer to be NULL as sgetline should be allocating it for me:

ck_assert_ptr_nonnull(buf);

I EXPECT this silly long string to be placed into buf:

ck_assert_str_eq(buf, "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");

I EXPECT linelen to be equal to the length of the string that was placed in buf:

ck_assert_uint_eq(linelen, strlen("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"));

I EXPECT the return value of sgetline to be 0 (meaning success in this case):

ck_assert_int_eq(ret, 0);

I EXPECT the buflen to increase beyond a constant in the library BUFFER_FACTOR because the line is quite long (NOTE the _gt means greater than):

ck_assert_uint_gt(buflen, BUFFER_FACTOR);

Run sgetline again which should read the next line out of the file. I know that the file only has one line to begin with, so I EXPECT sgetline to return EOF.

ret = sgetline(_file, &buf, &buflen, &linelen);
ck_assert_int_eq(ret, EOF);

If any of these checks fail, I know I have a bug in sgetline.

Jason
  • 2,493
  • 2
  • 27
  • 27
  • Many thanks for your answer which I upvoted @Jason . I had to remove other libraries on top of `lcheck_pic` namely `lsubunit` and `lrt` to have a successful build. Do you have an idea why? – ecjb Jan 31 '23 at 18:26
  • @ecjb I have no idea. It really depends what they were being used for. In your examples, however, they are not being used. In general, you only need to ever link what you are using. That is sort the idea behind headers + library files. The header just tells the compiler what is available in the library and how to interact with it. Even though you `#include `, you aren't compiling it. If you aren't using any functions from those libraries, you don't need to ever link them. – Jason Jan 31 '23 at 18:45
  • Also, you will know if you forgot to link a library you need when you get an "undefined reference" error. This is a linking error. If you don't get an error like that, then you are linked to everything you need. – Jason Jan 31 '23 at 18:47
  • I had a look at your repo and ... Wouah! the complexity of the program is kind of well over my head unfortunately... For example the `.configure` file (15000 lines...): I saw that it's written by the FSF but how does it work? and the Makefiles (before the `./configure` command) have those weird extensions (.am and .in). My current level in C is that I am finishing to read "C Programming: A Modern Approach, 2nd Edition" by King (and wrote some small apps) and I think that my question is: what would you recommend as next step to do/read to understand your repo? Many thanks in advance! :) – ecjb Feb 01 '23 at 08:30
  • I realized I went beyond the scope of the question in my previous comment (allthough I would still be interested in having some info with this respect ...:). To refocus on the question: do you know if an example using `check` slightly more complex than the example given in the question (where the program actually does pretty much nothing) and the example in your repo. I am thinking of for example a simple calculator (which implements + - * and /) with some tests implemented better understandable for beginners like me? – ecjb Feb 01 '23 at 10:09
  • @ecjb lol. Just for reference, your original link was titled "Using C unit testing framework Check without Autotools?" The complex things you are asking about (`configure`, `*.in` files) are part of Autotools. As the name suggests, they are automatically generated. I didn't write them =]. Autotools is nice for making something that can be packaged. But for learning purposes, it will not serve much purpose. I was trying to point out the tests directory for simple examples. I've added an update to the answer that might help explain what I was trying to say. – Jason Feb 01 '23 at 14:41
  • many thanks @Jason for very detailed answer. I will try to reduce it to a more minimal reproducible example (with only `check_sgetline.c` without other files) and would maybe ask a second question. – ecjb Feb 02 '23 at 06:49
  • ok @Jason if I dare, one last question: in the first dummy code in the question, I added the line `ck_assert_str_eq("asa", "asasdfasdf");` which should fail. However the output of the test does not change. Now if comment out the first 2 tests`//fail_unless(5 == 5, "this should succeed"); //fail_unless(6 == 5, "this should fail");` I get `0%: Checks: 1, Failures: 1, Errors: 0 script.c:7:F:Core:sanity_check:0: Assertion '"asa" == "asasdfasdf"' failed: "asa" == "asa", "asasdfasdf" == "asasdfasdf"`. So I conclude that in the current written version of main(), only one test is run, correct? – ecjb Feb 02 '23 at 08:20
  • As output of the `./test` I would actually expect `Checks: 3, Failures: 2, Errors: 0` instead of `Checks: 1, Failures: 1, Errors: 0`, no? – ecjb Feb 02 '23 at 08:24
  • @ecjb That tripped me up when I started using it for the first time. I *think* the number of tests is based on the number of instances of `Suite`. For the repo I linked, according to check, I only have 4 tests. In `check_sgetline.c` alone, there are 5 instances `TCase` and many many individual "tests". So check's definition of "test" appears to be per `Suite`. It's not very intuitive at first. Also, I believe the whole suite will exit on the first failure, so if you fail the first one, I don't *think* it will continue. – Jason Feb 02 '23 at 15:16
  • Thank you for your comment @Jason. I tried a last thing: I separated every test in different instances and could get a different message for every failed test and got the number of performed test right. I am not sure if this is the most elegant way though... – ecjb Feb 02 '23 at 16:24