Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,4 @@ jobs:
- name: Test
working-directory: ${{github.workspace}}/build
shell: bash
run: if [ "${{ matrix.os }}" == "windows-latest" ] && [ "${{ matrix.compiler }}" != "gcc" ]; then cd ${{ matrix.type }}; fi; ./subprocess_test

- name: Test with Multithreading
working-directory: ${{github.workspace}}/build
shell: bash
if: startsWith(matrix.os, 'windows')
run: if [ "${{ matrix.os }}" == "windows-latest" ] && [ "${{ matrix.compiler }}" != "gcc" ]; then cd ${{ matrix.type }}; fi; ./subprocess_mt_test
run: ctest --rerun-failed --output-on-failure -C ${{ matrix.type }}
282 changes: 202 additions & 80 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,119 +23,241 @@
#
# For more information, please refer to <http://unlicense.org/>

project(subprocess)
cmake_minimum_required(VERSION 3.15)
project(subprocess)

set(SUBPROCESS_USE_SANITIZER "" CACHE STRING "Set which Clang Sanitizer to use")
include(CTest)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../)

add_executable(process_return_zero process_return_zero.c)
add_executable(process_return_fortytwo process_return_fortytwo.c)
add_executable(process_return_argc process_return_argc.c)
add_executable(process_return_argv process_return_argv.c)
add_executable(process_return_special_argv process_return_special_argv.c)
add_executable(process_return_lpcmdline process_return_lpcmdline.c)
add_executable(process_return_stdin process_return_stdin.c)
add_executable(process_return_stdin_count process_return_stdin_count.c)
add_executable(process_stdout_argc process_stdout_argc.c)
add_executable(process_stdout_argv process_stdout_argv.c)
add_executable(process_stderr_argc process_stderr_argc.c)
add_executable(process_stderr_argv process_stderr_argv.c)
add_executable(process_combined_stdout_stderr process_combined_stdout_stderr.c)
add_executable(process_inherit_environment process_inherit_environment.c)
add_executable(process_fail_divzero process_fail_divzero.c)
add_executable(process_fail_stackoverflow process_fail_stackoverflow.c)
add_executable(process_hung process_hung.c)
add_executable(process_stdout_data process_stdout_data.c)
add_executable(process_stdout_poll process_stdout_poll.c)
add_executable(process_stdout_poll_wait_first process_stdout_poll_wait_first.c)
add_executable(process_stderr_poll process_stderr_poll.c)
add_executable(process_stderr_poll_wait_first process_stderr_poll_wait_first.c)
add_executable(process_stdout_large process_stdout_large.c)
add_executable(process_call_return_argc process_call_return_argc.c)
add_executable(process_cwd process_cwd.c)

add_executable(subprocess_test
../subprocess.h
utest.h
main.c
test.c
test.cpp
pre_windows.cpp
post_windows.cpp
)

if(NOT "${SUBPROCESS_USE_SANITIZER}" STREQUAL "")
target_compile_options(subprocess_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
target_link_options(subprocess_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
# Strict -std=c99/-std=c11/-std=c++XX modes on glibc hide POSIX/GNU
# declarations used by subprocess.h unless feature test macros are enabled.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_compile_definitions(_GNU_SOURCE)
endif()

set(MSVC_FLAGS "/Wall /WX /wd4514 /wd4710 /wd4711 /wd5045")
set(SUBPROCESS_HELPER_SOURCES
process_return_zero.c
process_return_fortytwo.c
process_return_argc.c
process_return_argv.c
process_return_special_argv.c
process_return_lpcmdline.c
process_return_stdin.c
process_return_stdin_count.c
process_stdout_argc.c
process_stdout_argv.c
process_stderr_argc.c
process_stderr_argv.c
process_combined_stdout_stderr.c
process_inherit_environment.c
process_fail_divzero.c
process_fail_stackoverflow.c
process_hung.c
process_stdout_data.c
process_stdout_poll.c
process_stdout_poll_wait_first.c
process_stderr_poll.c
process_stderr_poll_wait_first.c
process_stdout_large.c
process_call_return_argc.c
process_cwd.c
)

foreach(SUBPROCESS_HELPER_SOURCE ${SUBPROCESS_HELPER_SOURCES})
get_filename_component(SUBPROCESS_HELPER_TARGET ${SUBPROCESS_HELPER_SOURCE} NAME_WE)
add_executable(${SUBPROCESS_HELPER_TARGET} ${SUBPROCESS_HELPER_SOURCE})
endforeach()

# Base warning flags and whether -std= flags are supported.
# MSVC and clang-cl use /std: and don't accept -std=, so we skip
# the standard flag for those compilers.
set(SUBPROCESS_MSVC_WARNINGS "/Wall /WX /wd4514 /wd4668 /wd4710 /wd4711 /wd5039 /wd5045")
if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
set_source_files_properties(main.c test.c PROPERTIES
COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89"
)
elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set_source_files_properties(main.c test.c PROPERTIES
COMPILE_FLAGS "${MSVC_FLAGS}"
)
set(SUBPROCESS_C_WARNINGS "-Wall -Wextra -Werror")
set(SUBPROCESS_C_USE_STD_FLAG TRUE)
elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
if(CMAKE_C_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set(SUBPROCESS_C_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}")
set(SUBPROCESS_C_USE_STD_FLAG FALSE)
else()
set_source_files_properties(main.c test.c PROPERTIES
COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89"
)
set(SUBPROCESS_C_WARNINGS "-Wall -Wextra -Weverything -Werror -Wno-poison-system-directories")
set(SUBPROCESS_C_USE_STD_FLAG TRUE)
endif()
elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
set_source_files_properties(main.c test.c PROPERTIES
COMPILE_FLAGS "${MSVC_FLAGS}"
)
set(SUBPROCESS_C_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}")
set(SUBPROCESS_C_USE_STD_FLAG FALSE)
else()
message(WARNING "Unknown compiler '${CMAKE_C_COMPILER_ID}'!")
message(WARNING "Unknown C compiler '${CMAKE_C_COMPILER_ID}'!")
set(SUBPROCESS_C_WARNINGS "")
set(SUBPROCESS_C_USE_STD_FLAG FALSE)
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set_source_files_properties(test.cpp PROPERTIES
COMPILE_FLAGS "-Wall -Wextra -Werror -Wpedantic"
)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(SUBPROCESS_CXX_WARNINGS "-Wall -Wextra -Werror -Wpedantic -Wno-variadic-macros")
set(SUBPROCESS_CXX_USE_STD_FLAG TRUE)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set_source_files_properties(test.cpp PROPERTIES
COMPILE_FLAGS "${MSVC_FLAGS}"
)
set(SUBPROCESS_CXX_WARNINGS "${SUBPROCESS_MSVC_WARNINGS} /clang:-Wno-format /clang:-Wno-old-style-cast /clang:-Wno-zero-as-null-pointer-constant /clang:-Wno-c++98-compat /clang:-Wno-c++98-compat-pedantic /clang:-Wno-c++11-compat-deprecated-writable-strings /clang:-Wno-null-arithmetic /clang:-Wno-writable-strings /clang:-Wno-undefined-func-template /clang:-Wno-gnu-anonymous-struct /clang:-Wno-nested-anon-types")
set(SUBPROCESS_CXX_USE_STD_FLAG FALSE)
else()
set_source_files_properties(test.cpp PROPERTIES
COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -Wpedantic"
)
set(SUBPROCESS_CXX_WARNINGS "-Wall -Wextra -Weverything -Werror -Wpedantic -Wno-poison-system-directories -Wno-format -Wno-old-style-cast -Wno-zero-as-null-pointer-constant -Wno-c++11-compat-deprecated-writable-strings -Wno-null-arithmetic -Wno-writable-strings -Wno-undefined-func-template")
set(SUBPROCESS_CXX_USE_STD_FLAG TRUE)
endif()
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set_source_files_properties(test.cpp PROPERTIES
COMPILE_FLAGS "${MSVC_FLAGS}"
)
set(SUBPROCESS_CXX_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}")
set(SUBPROCESS_CXX_USE_STD_FLAG FALSE)
else()
message(WARNING "Unknown compiler '${CMAKE_C_COMPILER_ID}'!")
message(WARNING "Unknown C++ compiler '${CMAKE_CXX_COMPILER_ID}'!")
set(SUBPROCESS_CXX_WARNINGS "")
set(SUBPROCESS_CXX_USE_STD_FLAG FALSE)
endif()

add_executable(subprocess_mt_test
# Helper: set compile flags for a C source file (warnings + standard).
function(subprocess_set_c_flags FILE STANDARD)
if(SUBPROCESS_C_USE_STD_FLAG)
set_source_files_properties(${FILE} PROPERTIES
COMPILE_FLAGS "${SUBPROCESS_C_WARNINGS} ${STANDARD}")
else()
set_source_files_properties(${FILE} PROPERTIES
COMPILE_FLAGS "${SUBPROCESS_C_WARNINGS}")
endif()
endfunction()

# Helper: set compile flags for a C++ source file (warnings + standard + extras).
function(subprocess_set_cxx_flags FILE STANDARD)
set(EXTRA_FLAGS ${ARGN})
if(SUBPROCESS_CXX_USE_STD_FLAG)
set_source_files_properties(${FILE} PROPERTIES
COMPILE_FLAGS "${SUBPROCESS_CXX_WARNINGS} ${STANDARD} ${EXTRA_FLAGS}")
else()
set_source_files_properties(${FILE} PROPERTIES
COMPILE_FLAGS "${SUBPROCESS_CXX_WARNINGS}")
endif()
endfunction()

# C files — each compiled under a specific C standard.
subprocess_set_c_flags(main.c "-std=gnu89")
subprocess_set_c_flags(test.c "-std=gnu89")
subprocess_set_c_flags(test99.c "-std=c99")

list(FIND CMAKE_C_COMPILE_FEATURES c_std_11 IDX)
if(${IDX} GREATER -1)
subprocess_set_c_flags(test11.c "-std=c11")
endif()

# C++ files — each compiled under a specific C++ standard.
subprocess_set_cxx_flags(test.cpp "" "-Wno-c++98-compat")
subprocess_set_cxx_flags(test98.cpp "-std=gnu++98")
subprocess_set_cxx_flags(test11.cpp "-std=c++11" "-Wno-c++98-compat")
subprocess_set_cxx_flags(test14.cpp "-std=c++14" "-Wno-c++98-compat")
subprocess_set_cxx_flags(test17.cpp "-std=c++17" "-Wno-c++98-compat")
subprocess_set_cxx_flags(test20.cpp "-std=c++20" "-Wno-c++98-compat")
subprocess_set_cxx_flags(pre_windows.cpp "" "-Wno-c++98-compat")
subprocess_set_cxx_flags(post_windows.cpp "" "-Wno-c++98-compat")

set(SUBPROCESS_TEST_SOURCES
../subprocess.h
utest.h
test_shared.h
main.c
test.c
test99.c
test11.c
test.cpp
test98.cpp
test11.cpp
test14.cpp
test17.cpp
test20.cpp
pre_windows.cpp
post_windows.cpp
)

if(NOT "${SUBPROCESS_USE_SANITIZER}" STREQUAL "")
target_compile_options(subprocess_mt_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
target_link_options(subprocess_mt_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
# Helper: add a test executable and register it with CTest.
function(subprocess_add_test NAME)
add_executable(${NAME} ${SUBPROCESS_TEST_SOURCES})
add_test(NAME ${NAME} COMMAND $<TARGET_FILE:${NAME}>)
set_tests_properties(${NAME} PROPERTIES
WORKING_DIRECTORY $<TARGET_FILE_DIR:${NAME}>)
endfunction()

subprocess_add_test(subprocess_test)

if(MSVC)
# Build a second test binary linked against the static CRT (/MT)
# to ensure we work with both static and dynamic libc linkage.
set_property(TARGET subprocess_test PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
subprocess_add_test(subprocess_mt_test)
set_property(TARGET subprocess_mt_test PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_compile_options(subprocess_mt_test PUBLIC "/MT")
# Some sanitizer runtimes can't initialise on every host even when
# the toolchain links them fine — e.g. clang's TSan and MSan on
# aarch64 only ship shadow-memory layouts for 39-, 42- and 48-bit
# user VMAs, but Raspberry Pi OS' kernel is configured with
# CONFIG_ARM64_VA_BITS=47. The binary builds, but aborts before
# main with "FATAL: ThreadSanitizer: unsupported VMA range". Probe
# each candidate sanitizer with try_run and skip the ones whose
# runtime can't start. Result is cached so reconfigure is cheap;
# delete CMakeCache.txt to re-probe (e.g. after changing kernels).
function(subprocess_check_sanitizer_runs SAN OUT_VAR)
set(CACHE_VAR "SUBPROCESS_SAN_${SAN}_RUNS")
if(DEFINED ${CACHE_VAR})
set(${OUT_VAR} ${${CACHE_VAR}} PARENT_SCOPE)
return()
endif()
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options(subprocess_mt_test PUBLIC "/MT")

set(PROBE_SRC "${CMAKE_BINARY_DIR}/subprocess_san_probe_${SAN}.c")
file(WRITE "${PROBE_SRC}" "int main(void) { return 0; }\n")

try_run(RUN_RESULT COMPILE_RESULT
"${CMAKE_BINARY_DIR}/subprocess_san_probe_${SAN}"
"${PROBE_SRC}"
COMPILE_DEFINITIONS "-fsanitize=${SAN}" "-fno-omit-frame-pointer"
LINK_OPTIONS "-fsanitize=${SAN}"
)

if(COMPILE_RESULT AND "${RUN_RESULT}" STREQUAL "0")
set(RESULT TRUE)
message(STATUS "Sanitizer ${SAN}: usable")
else()
set(RESULT FALSE)
message(STATUS "Sanitizer ${SAN}: skipping "
"(compile=${COMPILE_RESULT}, run=${RUN_RESULT})")
endif()

set(${CACHE_VAR} ${RESULT} CACHE INTERNAL
"Whether the ${SAN} sanitizer runtime starts on this host")
set(${OUT_VAR} ${RESULT} PARENT_SCOPE)
endfunction()

if(("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") AND NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
set(SUBPROCESS_SANITIZER_CANDIDATES
address
undefined
thread
)

if(NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
set(SUBPROCESS_SANITIZER_CANDIDATES ${SUBPROCESS_SANITIZER_CANDIDATES} memory)
endif()

set(SUBPROCESS_SANITIZERS)
foreach(SUBPROCESS_SANITIZER ${SUBPROCESS_SANITIZER_CANDIDATES})
if(CMAKE_CROSSCOMPILING)
list(APPEND SUBPROCESS_SANITIZERS ${SUBPROCESS_SANITIZER})
else()
subprocess_check_sanitizer_runs(${SUBPROCESS_SANITIZER} SUBPROCESS_SAN_RUNS)
if(SUBPROCESS_SAN_RUNS)
list(APPEND SUBPROCESS_SANITIZERS ${SUBPROCESS_SANITIZER})
endif()
endif()
endforeach()

foreach(SUBPROCESS_SANITIZER ${SUBPROCESS_SANITIZERS})
subprocess_add_test(subprocess_${SUBPROCESS_SANITIZER}_test)
target_compile_options(subprocess_${SUBPROCESS_SANITIZER}_test PRIVATE -fsanitize=${SUBPROCESS_SANITIZER} -fno-omit-frame-pointer)
target_link_options(subprocess_${SUBPROCESS_SANITIZER}_test PRIVATE -fsanitize=${SUBPROCESS_SANITIZER})
endforeach()
endif()
Loading
Loading