Raise the file descriptor limit for descriptor-heavy tests

test_event opens 256 UDP receivers plus a sender on top of the loop's own
descriptors. macOS starts processes with a soft RLIMIT_NOFILE of 256, so
socket() eventually returned -1 and the unchecked descriptor tripped the
bind() assertion when running ctest from a stock terminal (#3058).

Wrap every registered unit test in a small sh launcher that raises the soft
limit to 1024 before exec'ing the binary, and have test_event raise its own
limit as well so it also passes when run directly. Assert on socket()
failures so a limit problem is reported as such instead of as a bind error.

Fixes #3058

Claude-Session: https://claude.ai/code/session_01PSG4XV3jiqitDgdgFSGATx
This commit is contained in:
Max Lv
2026-09-12 15:23:43 +08:00
parent 36a5cc6b8d
commit 8773939dce
3 changed files with 36 additions and 2 deletions
+10 -2
View File
@@ -10,6 +10,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_program(VALGRIND_EXECUTABLE NAMES valgrind)
endif()
# Some hosts (notably macOS, whose default soft limit is 256) start test
# processes with too few file descriptors for descriptor-heavy tests such as
# test_event. Raise the soft limit before exec'ing each test; a failure to
# raise it (hard limit too low) is ignored so the test still runs.
if(NOT WIN32)
set(SS_TEST_LAUNCHER sh -c "ulimit -n 1024 2>/dev/null\; exec \"$0\" \"$@\"")
endif()
# Helper function to add a unit test
function(ss_add_test name sources libs)
if(WIN32 AND "${sources}" MATCHES "utils.c")
@@ -28,12 +36,12 @@ function(ss_add_test name sources libs)
else()
target_compile_options(${name} PRIVATE -UNDEBUG)
endif()
add_test(NAME ${name} COMMAND ${name})
add_test(NAME ${name} COMMAND ${SS_TEST_LAUNCHER} $<TARGET_FILE:${name}>)
set_tests_properties(${name} PROPERTIES LABELS "unit")
if(VALGRIND_EXECUTABLE)
add_test(NAME memcheck_${name}
COMMAND ${VALGRIND_EXECUTABLE}
COMMAND ${SS_TEST_LAUNCHER} ${VALGRIND_EXECUTABLE}
--tool=memcheck
--leak-check=full
--show-leak-kinds=definite,indirect
+4
View File
@@ -35,6 +35,8 @@ static void must_not_run(struct ss_loop *loop, ss_io *io, int events)
int main(void)
{
test_network_init();
/* 256 receivers + sender + loop internals must fit under RLIMIT_NOFILE. */
test_raise_fd_limit(512);
struct ss_loop *loop = ss_loop_new(0);
assert(loop);
#ifdef _WIN32
@@ -48,8 +50,10 @@ int main(void)
/* Exceed Winsock select's default 64 descriptors. */
struct receiver receivers[256] = {0};
ss_socket_t sender = socket(AF_INET, SOCK_DGRAM, 0);
assert((int)sender >= 0);
for (unsigned i = 0; i < 256; i++) {
int fd = (int)socket(AF_INET, SOCK_DGRAM, 0);
assert(fd >= 0);
struct sockaddr_in address = {0};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+22
View File
@@ -4,6 +4,9 @@
#include <stdio.h>
#include <stdlib.h>
#include "platform.h"
#ifndef _WIN32
#include <sys/resource.h>
#endif
static inline void test_network_cleanup(void)
{
@@ -19,6 +22,25 @@ static inline void test_network_init(void)
assert(atexit(test_network_cleanup) == 0);
#endif
}
/* Make sure at least `want` descriptors can be opened; the default soft
* limit is only 256 on macOS, which is below what descriptor-heavy tests
* such as test_event need. Windows sockets are not bounded by RLIMIT_NOFILE. */
static inline void test_raise_fd_limit(unsigned want)
{
#ifndef _WIN32
struct rlimit limit;
assert(getrlimit(RLIMIT_NOFILE, &limit) == 0);
if (limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < want) {
limit.rlim_cur = want;
if (limit.rlim_max != RLIM_INFINITY && limit.rlim_max < limit.rlim_cur) {
limit.rlim_cur = limit.rlim_max;
}
assert(setrlimit(RLIMIT_NOFILE, &limit) == 0);
}
#else
(void)want;
#endif
}
static inline FILE *test_tempfile(char *path, size_t size)
{
#ifdef _WIN32