Skip to content

Commit 14ee22a

Browse files
authored
Functional tests use GoogleTest now (#26)
This MR makes all LPF functional tests use GoogleTest. The main changes for CMake are: - reorganize CMake to use GTest CMake package for compilation, and gtest_add_tests clause to define Gtests; - remove all use of the run.sh script; - make explicit the list of tests and do not use file(GLOB, ...) clauses to look for all files in test directories, as recommended by CMakesource files: The main changes for source files are: - every single test file needed to be modified to not include internal Test.h but gtest.h, and to use the assert/equal clauses of GoogleTest - all death tests are slightly modified now to expect FAIL() after the expected fail condition - some modernization of C to C++ was needed for a number of files, incl. use of new/delete instead of malloc/free, or string manipulation instead of C char * manipulation A new Python wrapper script test_launcher.py is being employed now for more flexible checking of return codes, which is needed for death tests and normal tests alike. The expected return codes are still parsed from scanning the source files as before. For the complete history, see GitHub PR #26 . This MR also resolves bugs that have appeared on more modern cluster deployments compared to the previous main tag of LPF. This is hence a priority MR to base all further extensions and evolutions of LPF on. This MR was tested successfully for: - x86_64, CentOS Stream 9, GCC 11.5.0, MPICH 4.1.1 (caveat 2) - x86_64, CentOS Stream 9, GCC 11.5.0, OpenMPI 4.1.1 (caveats 1, 2, and 5) - x86_64, Fedora, GCC 9.3.1, MPICH 3.3.2 (caveat 2) - x86_64, Ubuntu 23.04, GCC 13.1.0, OpenMPI 4.1.6 (caveat 5) - x86_64, openEuler 20.03 LTS, GCC 7.3, MPICH 3.2.1 - x86_64, Ubuntu 22.04 LTS, GCC 11.4.0, MPICH 4.3.0b1 (caveat 3, transient) - ARM, Ubuntu 22.04 LTS, GCC 11.4.0, MPICH 4.3.0b1 (caveats 3 & 4) These are under different variations of calls to `bootstrap.sh` to test all the above-described changes. The following caveats apply: 1. with OpenMPI and default arguments to its `mpirun`, oversubscription to larger process counts may be limited, causing some tests to fail. Issue #37 has been raised to pass the appropriate flags to the OpenMPI `mpirun` during testing; 2. `make install` post-install checks fail (as they should) when: 1) tests are disabled, 2) ibverbs dev libraries are present, but 3) no IB card is present. On the machines with IB we tested on, all post-install checks succeed. 3. The hybrid backend on MPICH 4.3.0b1 does not pass the debug layer tests due to a segfault in MPICH's MPI_Abort. Issue #41 has been raised to track this. This error presently only is consistently reproducible on our cluster's ARM nodes and transient on our cluster's x86_64 nodes. They have not been observed outside our cluster. 4. With MPICH 4.3.0b1, we furthermore hit issues #43 and #44. Also these issues are only reproducible on ARM nodes. 5. With OpenMPI 4.1.6 and OpenMPI 4.1.1 on x86_64 we hit issue #45 6. At present, we have no CI flow that can run all build variations relevant to this MR. Issues #40 has been raised to (partially) address as well as to track this issue.
1 parent 251f10e commit 14ee22a

213 files changed

Lines changed: 3716 additions & 3936 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 202 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# limitations under the License.
1616
#
1717

18-
cmake_minimum_required(VERSION 3.1)
18+
cmake_minimum_required(VERSION 3.29.0 FATAL_ERROR)
1919
project(LPF C CXX ASM)
2020

2121
# Version info
@@ -52,12 +52,12 @@ set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}")
5252
set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}")
5353
set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}")
5454
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
55-
"A high performance BSP communications library" )
55+
"A high performance BSP communications library" )
5656

5757
set(CPACK_SOURCE_GENERATOR "TGZ" )
5858
set(CPACK_SOURCE_IGNORE_FILES "/\\\\.git/" "/\\\\.svn/" "\\\\.swp$" "/site/" "/build/" "/pclint/" "/junit/" "/ideas/" )
5959
set(CPACK_SOURCE_PACKAGE_FILE_NAME
60-
"LPF-${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_PACKAGE}")
60+
"LPF-${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_PACKAGE}")
6161

6262
set(CPACK_GENERATOR "RPM")
6363
set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64")
@@ -105,18 +105,6 @@ set( INSTALL_HEADERS "${prefix}/include" CACHE PATH
105105
"Installation path for header files" )
106106
message( STATUS "Installation directory prefix is ${prefix}")
107107

108-
# C++ standard
109-
find_file(TR1_ARRAY "tr1/array")
110-
if (TR1_ARRAY)
111-
message(STATUS "Governing C++ standard is C++98/TR1")
112-
set(CMAKE_CXX_STANDARD 98)
113-
set(CMAKE_CXX_STANDARD_REQUIRED YES)
114-
else()
115-
message(STATUS "Governing C++ standard is C++11")
116-
set(CMAKE_CXX_STANDARD 11)
117-
set(CMAKE_CXX_STANDARD_REQUIRED YES)
118-
endif()
119-
120108
# Dependencies
121109
set(ENGINES)
122110
find_library( LIB_POSIX_THREADS
@@ -173,9 +161,6 @@ if ( MPI_FOUND )
173161
if ( NOT MPI_IS_THREAD_COMPAT OR NOT MPI_IS_NOT_OPENMPI1 )
174162
message( WARNING "MPI implementation does not tolerate any threading. Hybrid implementation will not be built")
175163
endif()
176-
if ( NOT MPI_OPEN_PORT )
177-
message( WARNING "MPI implementation does not support dynamically connecting separate MPI processes. Hence, lpf_mpi_initialize_over_tcp will always fail.")
178-
endif()
179164
if ( NOT MPI_IBARRIER )
180165
message( WARNING "MPI implementation does not have MPI_Ibarrier, which is required to use the dense all-to-all algorithm on large (> 2 GB) meta-data exchanges")
181166
endif()
@@ -189,18 +174,37 @@ if ( LIB_MATH AND LIB_DL AND MPI_FOUND )
189174
list(APPEND ENGINES "mpirma")
190175
endif()
191176

192-
if (LIB_IBVERBS)
177+
if (ENABLE_IBVERBS)
193178
list(APPEND ENGINES "ibverbs")
194179
endif()
195180

196181
endif()
197182

198183
#enable the hybrid engine
199184
if ( LIB_POSIX_THREADS AND LIB_MATH AND LIB_DL AND MPI_FOUND
200-
AND MPI_IS_THREAD_COMPAT AND MPI_IS_NOT_OPENMPI1
201-
AND LIB_IBVERBS )
202-
list(APPEND ENGINES "hybrid")
203-
set(HYBRID_ENGINE_ENABLED on)
185+
AND MPI_IS_THREAD_COMPAT AND MPI_IS_NOT_OPENMPI1 )
186+
if( ENABLE_IBVERBS )
187+
set(LPFLIB_HYBRID_MPI_ENGINE "ibverbs" CACHE STRING
188+
"Choice of MPI engine to use for inter-process communication")
189+
list(APPEND ENGINES "hybrid")
190+
set(HYBRID_ENGINE_ENABLED on)
191+
elseif( MPI_RMA )
192+
set(LPFLIB_HYBRID_MPI_ENGINE "mpirma" CACHE STRING
193+
"Choice of MPI engine to use for inter-process communication")
194+
list(APPEND ENGINES "hybrid")
195+
set(HYBRID_ENGINE_ENABLED on)
196+
elseif( LIB_MATH AND LIB_DL AND MPI_FOUND )
197+
set(LPFLIB_HYBRID_MPI_ENGINE "mpimsg" CACHE STRING
198+
"Choice of MPI engine to use for inter-process communication")
199+
list(APPEND ENGINES "hybrid")
200+
set(HYBRID_ENGINE_ENABLED on)
201+
endif()
202+
if( HYBRID_ENGINE_ENABLED )
203+
message( "Hybrid engine will be built using the ${LPFLIB_HYBRID_MPI_ENGINE} engine" )
204+
else()
205+
message( "No suitable inter-node communication engine found; "
206+
"hybrid engine will not be built" )
207+
endif()
204208
endif()
205209

206210
message( STATUS "The following engines will be built: ${ENGINES}")
@@ -223,6 +227,7 @@ endif()
223227

224228
# When system is not Linux, enable conditionally compiled blocks
225229
if (APPLE)
230+
message( WARNING "LPF compilation on OS X is not regularly tested" )
226231
add_definitions(-DLPF_ON_MACOS=1)
227232
endif()
228233

@@ -246,59 +251,19 @@ add_definitions(-DBSPLIB_DLL=1)
246251
option(LPF_ENABLE_TESTS
247252
"Enable unit and API tests. This uses Google Testing and Mocking Framework"
248253
OFF)
254+
option(GTEST_AGREE_TO_LICENSE
255+
"Does the user agree to the GoogleTest license"
256+
OFF)
257+
258+
# C++ standard -- Google tests require newer C++ standard than C++11
249259
if (LPF_ENABLE_TESTS)
250-
message(STATUS "Unit and API tests will be built")
251-
252-
# set testing timeout to 60 seconds
253-
set(CMAKE_TESTING_TIMEOUT 60)
254-
255-
# import Google Testing Framework
256-
include(cmake/googletest.cmake)
257-
258-
# Have directory to gather all the tests results
259-
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/junit)
260-
set(test_output "${CMAKE_BINARY_DIR}/junit")
261-
262-
# Have a macro to add a unit test
263-
function(add_gtest testName)
264-
add_executable(${testName} ${ARGN})
265-
target_link_libraries(${testName} gtest_main)
266-
add_test(${testName} ${testName} --gtest_output=xml:${test_output}/ )
267-
endfunction(add_gtest)
268-
269-
# Have a macro to add a unit test that should run with MPI
270-
if (MPI_FOUND)
271-
function(add_gtest_mpi testName nprocs)
272-
add_executable(${testName} ${ARGN})
273-
target_link_libraries(${testName} ${MPI_C_LIBRARIES} gtest_main)
274-
foreach( p ${nprocs})
275-
set(mpmd)
276-
foreach( i RANGE 1 ${p})
277-
if (i GREATER 1)
278-
set(mpmd ${mpmd} ":")
279-
endif()
280-
set(mpmd ${mpmd} ${MPIEXEC_NUMPROC_FLAG} 1 ${MPIEXEC_PREFLAGS}
281-
./${testName} --gtest_output=xml:${test_output}/${testName}_${i}of${p}.xml)
282-
endforeach(i)
283-
add_test(NAME ${testName}_${p}
284-
COMMAND ${MPIRUN} ${mpmd}
285-
)
286-
endforeach(p)
287-
endfunction(add_gtest_mpi)
288-
endif(MPI_FOUND)
289-
290-
# Enable testing in CMake
291-
enable_testing()
292-
else(LPF_ENABLE_TESTS)
293-
message(STATUS "Unit and API tests will *not* be built")
294-
function(add_gtest testName)
295-
# Do nothing because tests are disabled
296-
endfunction(add_gtest)
260+
set(CMAKE_CXX_STANDARD 17)
261+
set(CMAKE_CXX_STANDARD_REQUIRED YES)
262+
else()
263+
set(CMAKE_CXX_STANDARD 11)
264+
set(CMAKE_CXX_STANDARD_REQUIRED YES)
265+
endif()
297266

298-
function(add_gtest_mpi testName nprocs)
299-
# DO nothing because tests are disabled
300-
endfunction(add_gtest_mpi)
301-
endif(LPF_ENABLE_TESTS)
302267

303268
# Handling of compiler flags
304269
function(target_add_compilation_flags target visibility)
@@ -367,31 +332,185 @@ endfunction(target_compile_flags)
367332
set(lpf_cflags)
368333
set(lpf_lib_link_flags)
369334
set(lpf_exe_link_flags)
370-
include_directories(include)
371-
include_directories(src/common)
372-
add_subdirectory(src)
335+
336+
# Populate lpf_cflags, lpf_lib_link_flags, lpf_exe_link_flags according to
337+
# (enabled) engine requirements
338+
# - 0) PThreads engine needs nothing special
339+
# - 1) MPI-based engines:
340+
if ( LIB_MATH AND LIB_DL AND MPI_FOUND )
341+
# -fPIC and -rdynamic are necessary to ensure that symbols can be
342+
# looked up by dlsym which is the mechanism lpf_exec uses to broadcast the
343+
# function that should be executed
344+
set(rdyn_lflag "-rdynamic")
345+
if (APPLE)
346+
# OS X does not support -rdynamic
347+
set(rdyn_lflag "")
348+
endif ()
349+
350+
# include flags:
351+
set( mpi_include_flags )
352+
string( REPLACE ";" " -I" mpi_include_flags "${MPI_C_INCLUDE_PATH}" )
353+
set(lpf_cflags "${lpf_cflags} -I${mpi_include_flags} -fPIC")
354+
355+
# linker flags:
356+
set(lib_lflags "${MPI_C_LINK_FLAGS}") #Note: the core library is already linked with MPI_C_LIBRARIES.
357+
string(REPLACE ";" " " lib_lflags "${lib_lflags}") # So, no need to also link executables with it.
358+
set(lpf_lib_link_flags "${lpf_lib_link_flags} ${lib_lflags} ${rdyn_lflag}")
359+
360+
# executable linker flags:
361+
set(lpf_exe_link_flags "${lpf_exe_link_flags} ${rdyn_lflag}")
362+
endif ()
363+
# ...add requirements from other engines here...
373364

374365
# Collating all compile & link flags
375366
set(LPF_CORE_COMPILE_FLAGS "${lpf_cflags}" CACHE STRING "Compilation flags for all user code" )
376367
set(LPF_CORE_LIB_LINK_FLAGS "${lpf_lib_link_flags}" CACHE STRING "Flags to link user libraries" )
377368
set(LPF_CORE_EXE_LINK_FLAGS "${lpf_exe_link_flags}" CACHE STRING "Flags to link user executables" )
378369

379-
# Compiling LPF programmes in the build dir
370+
# Compiling LPF programs in the build dir
380371
function( target_link_exe_with_core target )
381372
set(engine "imp")
382373
if (ARGV1)
383374
set(engine "${ARGV1}")
384375
endif()
385376
set(corelib "lpf_core_univ_${engine}_${LPFLIB_CONFIG_NAME}")
386377

387-
target_link_libraries(${target} ${corelib})
378+
if ("${engine}" STREQUAL "pthread")
379+
target_link_libraries(${target} ${corelib})
380+
else()
381+
target_link_libraries(${target} ${corelib} ${MPI_C_LIBRARIES})
382+
endif()
388383
target_compile_flags(${target} PRIVATE ${LPF_CORE_COMPILE_FLAGS})
389384
set_target_properties(${target} PROPERTIES
390-
LINK_FLAGS "${LPF_CORE_LIB_LINK_FLAGS} ${LPF_CORE_EXE_LINK_FLAGS}"
391-
LINKER_LANGUAGE CXX
392-
)
385+
LINK_FLAGS "${LPF_CORE_LIB_LINK_FLAGS} ${LPF_CORE_EXE_LINK_FLAGS}"
386+
LINKER_LANGUAGE CXX
387+
)
393388
endfunction()
394389

390+
if (LPF_ENABLE_TESTS)
391+
message(STATUS "Unit and API tests will be built. This requires CMake version 3.29 or higher, since we use recent features of the GoogleTest package in CMake.")
392+
393+
if (NOT GTEST_AGREE_TO_LICENSE)
394+
message(FATAL_ERROR "The user needs to agree with the GoogleTest license to use tests (option GTEST_AGREE_TO_LICENSE=TRUE)")
395+
endif()
396+
# Enable testing in CMake
397+
enable_testing()
398+
include(ProcessorCount)
399+
ProcessorCount(processorCount)
400+
find_package(GTest)
401+
include(GoogleTest)
402+
if(NOT GTest_FOUND) # if not found, download it and pull it in
403+
include(FetchContent)
404+
FetchContent_Declare(
405+
googletest
406+
GIT_REPOSITORY https://github.com/google/googletest.git
407+
# This tag corresponds to GoogleTest 1.15.0 release
408+
GIT_TAG e397860
409+
)
410+
FetchContent_MakeAvailable(googletest)
411+
412+
endif()
413+
# set testing timeout to 60 seconds
414+
set(CMAKE_TESTING_TIMEOUT 60)
415+
416+
# Have directory to gather all the tests results
417+
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/junit)
418+
set(test_output "${CMAKE_BINARY_DIR}/junit")
419+
420+
find_package( Python3 REQUIRED COMPONENTS Interpreter)
421+
set(MY_TEST_LAUNCHER ${Python3_EXECUTABLE} ${CMAKE_BINARY_DIR}/test_launcher.py)
422+
configure_file( ${CMAKE_SOURCE_DIR}/test_launcher.py.in ${CMAKE_BINARY_DIR}/test_launcher.py @ONLY FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ)
423+
424+
# Macro for adding a new GoogleTest test
425+
function(add_gtest testName ENGINE debug testSource )
426+
if ("{$ENGINE}" STREQUAL "")
427+
message(FATAL_ERROR "engine cannot be empty, ever!")
428+
endif()
429+
add_executable(${testName} ${testSource} ${ARGN})
430+
target_compile_definitions(${testName} PUBLIC LPF_CORE_IMPL_ID=${ENGINE})
431+
target_compile_definitions(${testName} PUBLIC LPF_CORE_MPI_USES_${ENGINE})
432+
if (debug)
433+
target_include_directories( ${testName} BEFORE PRIVATE ${CMAKE_SOURCE_DIR}/include/debug )
434+
target_link_libraries(${testName} lpf_debug lpf_hl_debug GTest::gtest GTest::gtest_main)
435+
else(debug)
436+
target_link_libraries(${testName} GTest::gtest GTest::gtest_main)
437+
endif(debug)
438+
439+
440+
# Extract test-specific information from comments of tests
441+
file(READ ${testSource} fileContents)
442+
string(REGEX MATCH "Exit code: ([0-9]+)" _ ${fileContents})
443+
set(retCode ${CMAKE_MATCH_1})
444+
string(REGEX MATCH "pre P >= ([0-9]+)" _ ${fileContents})
445+
set(minProcs ${CMAKE_MATCH_1})
446+
string(REGEX MATCH "pre P <= ([0-9]+)" _ ${fileContents})
447+
set(maxProcs ${CMAKE_MATCH_1})
448+
string(REGEX MATCH "-probe ([0-9]+.[0-9]+)" _ ${fileContents})
449+
set(lpfProbeSecs ${CMAKE_MATCH_1})
450+
451+
target_link_exe_with_core(${testName} ${ENGINE})
452+
453+
# The "\pre P <= max" comment in a test indicates the desired number of
454+
# maximum LPF processes. If the test does not define a desired number of
455+
# maximum LPF processes, it will be set to 5.
456+
#
457+
# The "\pre P >= min" comment in a test indicates the desired number of
458+
# minimum LPF processes. If the test does not define a desired minimum
459+
# number of LPF processes, it will be set to 1.
460+
#
461+
# Let 'processorCount' be the detected number of processors by the system.
462+
# If this number is smaller than the desider minimum and/or maximum number
463+
# of processes, it overwrites these
464+
#
465+
# Most tests only define a mininum number of desired processes, such as
466+
# "\pre P >= 1". In those cases, the test will execute for the range 1,..,5
467+
# (including)
468+
469+
if ("${minProcs}" STREQUAL "")
470+
set(minProcs "1")
471+
endif()
472+
if ("${maxProcs}" STREQUAL "")
473+
set(maxProcs "5")
474+
endif()
475+
# cap min with processorCount, if needed
476+
if ("${minProcs}" GREATER "${processorCount}")
477+
set(minProcs ${processorCount})
478+
endif()
479+
# cap max with processorCount, if needed
480+
if ("${maxProcs}" GREATER "${processorCount}")
481+
set(maxProcs ${processorCount})
482+
endif()
483+
if ("${lpfProbeSecs}" STREQUAL "")
484+
set(lpfProbeSecs "0.0")
485+
endif()
486+
if ("${retCode}" STREQUAL "")
487+
set(retCode "0")
488+
endif()
489+
490+
# Most recent approach to Gtests, recommended!
491+
set_property(TARGET ${testName} PROPERTY TEST_LAUNCHER ${MY_TEST_LAUNCHER};--engine;${ENGINE};--parallel_launcher;${CMAKE_BINARY_DIR}/lpfrun_build;--min_process_count;${minProcs};--max_process_count;${maxProcs};--lpf_probe_timer;${lpfProbeSecs};--expected_return_code;${retCode})
492+
gtest_discover_tests(${testName}
493+
TEST_PREFIX ${ENGINE}_
494+
EXTRA_ARGS --gtest_output=xml:${test_output}/${ENGINE}_${testName}
495+
DISCOVERY_MODE POST_BUILD
496+
DISCOVERY_TIMEOUT 15
497+
)
498+
499+
endfunction(add_gtest)
500+
501+
else(LPF_ENABLE_TESTS)
502+
message(STATUS "Unit and API tests will *not* be built")
503+
function(add_gtest testName ENGINE debug testSource )
504+
# Do nothing because tests are disabled
505+
endfunction(add_gtest)
506+
507+
endif(LPF_ENABLE_TESTS)
508+
509+
# Main LPF library includes and sources
510+
include_directories(include)
511+
include_directories(src/common)
512+
add_subdirectory(src)
513+
395514
# Apps
396515
add_subdirectory(src/utils)
397516

@@ -432,7 +551,7 @@ set( lpf_proxy_dummy ${CMAKE_CURRENT_BINARY_DIR}/src/MPI/lpf_proxy_dummy)
432551
set( lpf_probe ${CMAKE_CURRENT_BINARY_DIR}/src/utils/lpfprobe)
433552
set( lpfrun ${CMAKE_CURRENT_BINARY_DIR}/lpfrun_build)
434553
set( lpfcore ${CMAKE_CURRENT_BINARY_DIR}/src/*/liblpf_core_univ_ENGINE_${LPFLIB_CONFIG_NAME}${SOSUFFIX} )
435-
configure_file( lpfrun.in lpfrun_build @ONLY)
554+
configure_file( lpfrun.in lpfrun_build FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE @ONLY)
436555
configure_file( lpfproxy.in lpfproxy_build @ONLY)
437556
configure_file( lpfprobe.in lpfprobe_build @ONLY)
438557

0 commit comments

Comments
 (0)