Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cover a C++ legacy code controlled by preprocessor #ifdefs with unittests?

I have inherited a not too big C++ legacy code which I am currently reengineering. So far I understand most of the code quite well and am able to use it, though the maintenance is hell. I think the main difficulty lies in the MASSIVE use of preprocessor directives to control the behaviour of the program.

Consider the following examples:

void function(){
    ... // lots of code 
    #ifdef PARAMETER == 1
        do_one_thing();

    #elif PARAMETER == 2
        do_another_thing();
    ...//etc
    #endif
    ...//lots of code
}

or

void function(double arg1,
             #ifdef SOME_PP_VAR1 == 5
             double arg2,
             #endif
             )

and stuff like

#ifdef SOME_PP_VAR2 == 2
    typedef myVector std::vector<double>;
#elif SOME_PP_VAR2 == 7
    typedef myVector std::vector<int>;
#endif

in the global scope. Or even

#ifdef SOME_PP_VAR2 == 2
    #include "some_header.hpp"
#elif SOME_PP_VAR2 == 7
    #include "some_other_header.hpp"
#endif

About 30 of such preprocessor variables are set in a configuration file which is passed to the build system and then to the compiler. It basically controls everything and is present in almost any file. By the way, in some places the #ifs are even nested.

Therefore, it is quite difficult to write unit tests. I would have to build all of the possible combinations of the preprocessor variables and test each.

My (poor) ideas so far are:

  • Rewrite the code from scratch, possibly in a different language. (High risk, too time-consuming).
  • Replace the preprocessor variables by constants. (Not always feasible).

Did you ever encounter such a situation and how did you handle it?

like image 774
user92020 Avatar asked Nov 07 '22 06:11

user92020


1 Answers

How to cover a C++ legacy code controlled by preprocessor #ifdefs with unittests?

Just write the unit tests for each needed tested case, compile each test case with different #ifdefs needed to test it, and run them.

Did you ever encounter such a situation

I think I had a similar case. I was (trying to...) writing tests for software that used GuruxDLMS.c library (and here).

and how did you handle it?

Deciding how to compile stuff and running tests is a job for accommodating build system integrated around the project. Assuming for example CMake build system that I am comfortable with, I would just write the tests for each case and compile the library for each case:

function(add_cpp_legacy_code_library name)
     add_library(${name} sources.....cpp)
     target_compile_definitions(${name} PUBLIC ${ARGN})
endfunction()
function(add_cpp_legacy_code_test name sourcefile)
     add_cpp_legacy_code_library(${name}_library ${ARGN})
     add_executable(${name} ${sourcefile})
     target_link_libraries(${name} PRIVATE ${name}_library)
     add_test(NAME ${name} COMMAND ${name})
endfunction()

add_cpp_legacy_code_test(test1 some_test1.cpp
    PARAMETER=1
    SOME_PP_VAR1=4
    SOME_PP_VAR2=4
)
add_cpp_legacy_code_test(test2 some_test2.cpp
    PARAMETER=100
    SOME_PP_VAR1=1234
    SOME_PP_VAR2=7890
)
# etc...

Etc. for each macros combination that you want to test, where some_test1.cpp some_test2.cpp would either have #if for each macro combination or would be separate files.

like image 116
KamilCuk Avatar answered Nov 14 '22 20:11

KamilCuk