Search code examples
cunit-testingcmakectestunity-test-framework

C Testing - undefined reference with Unity and CMake


I am currently trying to get a project setup with code coverage and testing that is in C. My current stack is CLion for an IDE, Clang for the compiler, gcov and lcov for coverage, Unity for a testing framework, and CMock for mocking/stubbing during tests.

I currently have the following package structure:

app/root
  | build
      | *.*
  |- cmake
     |- modules
        |- CodeCoverage.cmake
  |- coverage
      |- coverage.info
  |- external
      |- Unity
      |- CMock
      |- CMakeLists.txt
  |- src
      |- *.c
      |- *.h
      |- CMakeLists.txt
  |- tests
      |- *.c
      |- *.h
      |- CMakeLists.txt
  |- CMakeLists.txt

My higher level CMakeLists.txt looks like:

cmake_minimum_required(VERSION 3.6)

project(my_c_app)

set(CMAKE_C_COMPILER "/usr/bin/clang")

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake/modules)


set(CMAKE_VERBOSE_MAKEFILE ON)

add_subdirectory(external)
add_subdirectory(src)
add_subdirectory(tests)

My app level CMakeLists.txt looks like:

SET(CMAKE_CXX_FLAGS "-O0")
SET(CMAKE_C_FLAGS "-DLINUX -O0 -Wall -std=c99")

set(SOURCE_FILES
        util.c
        util.h)

add_executable(my_c_app ${SOURCE_FILES})

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(my_c_app Threads::Threads)

target_include_directories(my_c_app PUBLIC ${PROJECT_SOURCE_DIR}/include)

My test level CMakeLists.txt looks like:

enable_testing()

include(CodeCoverage)
include(CTest)

SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS "-DLINUX -O0 -Wall -std=c99 -g -fprofile-arcs -ftest-coverage")

SETUP_TARGET_FOR_COVERAGE(coverage tests ${PROJECT_SOURCE_DIR}/coverage/coverage "'/usr/*';'tests/*';'external/*'")

add_executable(tests util_test.c)

target_link_libraries(tests Unity CMock)

add_test(tests util_test.c)

Currently my issue is I'm not doing something correctly. I am getting an undefined reference while trying to test a function in util.c:

CMakeFiles/tests.dir/util_test.c.o: In function `test_my_method':
/home/patches/my_c_app/tests/util_test.c:6: undefined reference to `my_method'

My util_test.c currently is:

#include <unity.h>
#include "../src/util.h"

void test_my_method(void) {
    uchar result = my_method();
    // assertion and other logic would go here
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_my_method);
    return UNITY_END();
}

I'm a noob at test driven c development and CMake, so what is the proper way I am supposed to setup my tests so that they are dependent on the c files in src?

If I just do a TEST_ASSERT_EQUAL(1,1) instead of placing a call into the util.c function I do see:

1 Tests 0 Failures 0 Ignores
OK

Process finished with exit code 0

so I feel like I'm 100% stuck on a linker issue of some sort.


Solution

  • util.c is not part of tests sources, nor it is compiled in a library linked to tests. So you don't provide any definition to tests for my_method, thus the undefined reference.

    You don't have a main.c file in the sources of your executable my_c_app. I guess your main function is defined in util.c. If I'm right, take it out in a main.c file, and change your app level CMakeLists.txt into:

    SET(CMAKE_CXX_FLAGS "-O0")
    SET(CMAKE_C_FLAGS "-DLINUX -O0 -Wall -std=c99")
    
    set(SOURCE_FILES
            util.c
            util.h)
    
    add_library(my_c_lib STATIC ${SOURCE_FILES})
    
    set(THREADS_PREFER_PTHREAD_FLAG ON)
    find_package(Threads REQUIRED)
    target_link_libraries(my_c_app Threads::Threads)
    
    target_include_directories(my_c_app PUBLIC ${PROJECT_SOURCE_DIR}/include)
    
    add_executable(my_c_app main.c)
    target_link_libraries(my_c_app my_c_lib)
    

    Now your sources are compiled in a static library my_c_lib you can link against. Your app is linked against it, you can link your tests against it in your test level CMakeLists.txt as well:

    enable_testing()
    
    include(CodeCoverage)
    include(CTest)
    
    SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
    SET(CMAKE_C_FLAGS "-DLINUX -O0 -Wall -std=c99 -g -fprofile-arcs -ftest-coverage")
    
    SETUP_TARGET_FOR_COVERAGE(coverage tests ${PROJECT_SOURCE_DIR}/coverage/coverage "'/usr/*';'tests/*';'external/*'")
    
    add_executable(tests util_test.c)
    
    target_link_libraries(tests Unity CMock my_c_lib)
                                            ^^^^^^^^
    
    add_test(tests util_test.c)
    

    About this line:

    add_test(tests util_test.c)
    

    I can't see a signature in the documentation taking a target and its sources. What are you trying to achieve with this?

    Note that you may specify the C version using C_STANDARD instead of CMAKE_C_FLAGS:

    SET(CMAKE_C_STANDARD 99)
    

    Compile definitions may be declared as well with target_compile_definitions to avoid global pollution.