c++boostboost.test

Unable to Compile Parameterized Boost Tests


As a minimal example (this is part of a larger project), I have the following three files in a project with the following structure:

.
├── src
│   ├── binary_search.cpp
└── test
    ├── CMakeLists.txt
    ├── build
    └── test_binary_search.cpp

binary_search.cpp:

#include <cstdint> 

template <typename T> T binary_search(T A[], uint8_t len, T nu) {
  uint8_t low = 0;
  uint8_t high = len - 1;

  while (low <= high) {
    uint8_t mid = low + (high - low) / 2;

    if (nu == A[mid])
      return mid;
    else if (nu > A[mid])
      low = mid + 1;
    else
      high = mid - 1;
  }

  return -1;
}

test_binary_search.cpp:

#include <boost/test/data/test_case.hpp>
#include <boost/test/included/unit_test.hpp>

#define BOOST_TEST_MODULE test_algorithms

BOOST_AUTO_TEST_SUITE(test_search)

int32_t input_arrs[4][8] = {{26, 31, 41, 41, 58, 59, 101, 104},
                            {26, 31, 41, 41, 58, 59, 101, 104},
                            {1, 4, 5, 7, 19, 28, 45, 92},
                            {1, 4, 5, 7, 19, 28, 45, 92}};
int32_t input_vals[] = {31, 32, 92, 101};
int32_t expected_vals[] = {1, -1, 7, -1};

auto test_cases = boost::unit_test::data::make(input_arrs) ^
                  boost::unit_test::data::make(input_vals) ^
                  boost::unit_test::data::make(expected_vals);

BOOST_DATA_TEST_CASE(test_binary_search, test_cases, input_arr, nu, exp) {
  auto obs = binary_search(input_arr, 8, nu);

  BOOST_ASSERT(obs == exp);
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++20")
project(test_algorithms)

set(SOURCES 
    ../src/binary_search.cpp
    test_binary_search.cpp
)

find_package(Boost 1.87.0 REQUIRED COMPONENTS unit_test_framework)
if (Boost_FOUND)
    enable_testing()
    include_directories(${Boost_INCLUDE_DIRS})

    add_executable(test_binary_search ${SOURCES})
    target_include_directories(test_binary_search PRIVATE ${Boost_INCLUDE_DIRS})
    add_test(NAME test_binary_search COMMAND test_binary_search)
endif()

I am attempting to compile this on a MacBook Pro M3 Pro running MacOS Sequoia 15.3.1 using the following commands:

cmake -S . -B build
cmake --build build

after which I receive a very long error message. I suspect that the problem might be

/Users/graham/dev/cpp_experiments/test/test_binary_search.cpp:20:14: error: use of undeclared identifier 'binary_search'
   20 |   auto obs = binary_search(input_arr, 8, nu);

which may in turn be caused by

/opt/homebrew/include/boost/test/data/test_case.hpp:89:16: error: reference to type 'const const int[8]' could not bind to an lvalue of type 'const int'
   89 |         return *value;

I'm not sure where/how I'm casting to type const int in this code; are the Boost macros are performing this conversion? I'm struggling to understand the recommended way to use boost::unit_test::data::make to zip up the parameterized test data. Any help in how to better structure this code to create a parameterized test suite would be greatly appreciated!


Solution

  • Apparently the element types need to be movable, which isn't true for T(&)[N]. You could replace it with std::array.

    Next you will find that you need to make the type printable.

    Aside from this, there are numerous issues with the code:

    1. binary_search.cpp defines a template. Its definition MUST be available at place of invocation, so it has to be an include.

      I'd suggest renaming binary_search.cpp to binary_search.hpp or to split into a header/source where all template definitions reside in the header (Why can templates only be implemented in the header file?)

    2. binary_search collides with a standard library algorithm name. This can (will!) lead to surprises when you least expect it (e.g. if you pass the std::array<> directly, ADL will find std::binary_search in favor of your own.

      I suggest renaming your algorithm to my_binary_search or making it a member of a namespace so you can fully qualify it for clarity

    3. Your interface takes T A[] which is equivalent to T* A. Also, it's an antipattern in C++ to pass raw pointers with separate size. Consider specifying it as std::span<T> instead with no overhead. Now you don't have to do the awkward, error-prone copy to a temporary array either

    4. Your interface hard-codes uint8_t for len. This is a recipe for truncation/conversion errors. Worse, passing -1 will silently behave as if len==255 was passed.

    5. You used BOOST_ASSERT which is not part of the Boost Test library, and compiles away in release build (when NDEBUG is defined). You probably wanted BOOST_CHECK or, indeed, BOOST_CHECK_EQUAL here. If you compile with warnings enabled you will actually be informed of mistakes like these (because obs and exp were never used in release mode).

    6. you were missing BOOST_AUTO_TEST_SUITE_END()

    7. you should probably supply link-dependencies instead of manually specifying boost include-directories in cmake:

      target_link_libraries(test_binary_search Boost::unit_test_framework)
      

      Since you know you don't need runtime link dependencies, you can use the headers target to just get the includes:

      target_link_libraries(test_binary_search Boost::headers)
      
    8. also, using target-specific flags/libraries etc. obviates the need for include_directories

    9. on my Boost version (a develop version after 1.88) there are deprecated header warnings, so I'll define BOOST_ALLOW_DEPRECATED_HEADERS

    Given all these, here's my counter-proposition:

    With a live demo: