c++cmakectest

How to CTest an interactive command line utility?


I am learning CTest over the following code:

// A program that echoes lines on stdin back.
#include <iostream>
#include <string>

int main()
{
    std::string line;
    do
    {
        std::getline(std::cin, line);
        std::cout << line << std::endl;
    }while(! line.empty());
}

Now I need to perform a system test. How can my tests.cpp refer to standard input and output of the compiled binary? The test should obviously invoke it, produce some input and verify the output mirrors it. What's the CTest way of doing that?

My current CMakeLists.txt stanza, though probably completely irrelevant:

enable_testing()
add_executable(tests tests.cpp)
add_test(NAME Tests
         COMMAND tests)

Conclusion after reading the linked question(also closed).


Solution

  • You can see if this function works in your case.

    FUNCTION(interactive_command_line_test test_executable input_arguments expected_output
             expected_error)
        EXECUTE_PROCESS(
            COMMAND
                bash -c "echo \"${input_arguments}\" | ${CMAKE_BINARY_DIR}/${test_executable}"
            RESULT_VARIABLE result
            OUTPUT_VARIABLE output
            ERROR_VARIABLE err_output
            COMMAND_ECHO STDOUT
            ECHO_OUTPUT_VARIABLE)
    
        # Find if ${expected_output} is in ${output}; if cannot be found, found =
        # "-1"
        STRING(FIND ${output} ${expected_output} found)
        MESSAGE(found ${found})
        IF((NOT (${result} STREQUAL "0")) OR (${found} STREQUAL "-1"))
            MESSAGE(
                FATAL_ERROR
                    "Test failed with return value '${result}'; output '${output}'; expected output '${expected_output}' and found '${found}'";
            )
        ENDIF()
    ENDFUNCTION()
    

    This is how you could use it, in a test.cmake:

    INTERACTIVE_COMMAND_LINE_TEST(gcd "5 20" "GCD of 5 and 20 is 5" "")
    

    How it works:

    1. It pipes the input arguments "5 20" to a binary located at ~/.../${CMAKE_BINARY_DIR}/gcd using bash.
    2. The OUTPUT VARIABLE output sets the output to stdout to the cmake variable output.
    3. Whatever the ${output} is, cmake tries to find the ${expected_output} substring within ${output}.
    4. If cmake finds it, the cmake variable ${found} will be set to a non-zero integer, else it will be set to "-1".
    5. Subsequently,
    IF((NOT (${result} STREQUAL "0")) OR (${found} STREQUAL "-1"))
            MESSAGE(
                FATAL_ERROR
                    "Test failed with return value '${result}'; output '${output}'; expected output '${expected_output}' and found '${found}'";
            )
    

    is self-explanatory.

    Note:

    1. The function is not perfect since expected_error is unused.
    2. You might need to adjust the path to your built binary depending on how you trigger ctest, e.g. is --test-dir passed in as an argument, etc.