Files
shadowsocks-libev/tests/test_event.c
Max Lv 8773939dce 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
2026-09-12 15:23:43 +08:00

89 lines
3.0 KiB
C

#include "test_helpers.h"
#include <string.h>
#include "ss_event.h"
#include <uv.h>
#ifdef __APPLE__
#include <sys/event.h>
#endif
int verbose = 0;
struct receiver { ss_io io; int count; };
static void receive(struct ss_loop *loop, ss_io *io, int events)
{
struct receiver *r = (struct receiver *)io;
char byte;
assert(events & SS_READ);
assert(recv(io->fd, &byte, 1, 0) == 1 && byte == 'x');
r->count++;
ss_io_stop(loop, io);
}
struct shared { ss_io io; ss_io *other; int *calls; };
static void destroy_peer(struct ss_loop *loop, ss_io *io, int events)
{
(void)events;
struct shared *s = (struct shared *)io;
(*s->calls)++;
ss_io_stop(loop, s->other);
free(s->other);
ss_io_stop(loop, io);
free(s);
}
static void must_not_run(struct ss_loop *loop, ss_io *io, int events)
{
(void)loop; (void)io; (void)events;
assert(!"stopped watcher was invoked");
}
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
assert(strcmp(ss_backend_name(), "iocp") == 0);
assert(ss_native_loop(loop)->iocp != NULL);
#elif defined(__APPLE__)
assert(strcmp(ss_backend_name(), "kqueue") == 0);
struct timespec zero = {0};
assert(kevent(uv_backend_fd(ss_native_loop(loop)), NULL, 0, NULL, 0, &zero) == 0);
#endif
/* 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);
assert(bind(fd, (struct sockaddr *)&address, sizeof(address)) == 0);
socklen_t length = sizeof(address);
assert(getsockname(fd, (struct sockaddr *)&address, &length) == 0);
ss_io_init(&receivers[i].io, receive, fd, SS_READ);
ss_io_start(loop, &receivers[i].io);
assert(sendto(sender, "x", 1, 0, (struct sockaddr *)&address, length) == 1);
}
ss_run(loop, 0);
for (unsigned i = 0; i < 256; i++) {
assert(receivers[i].count == 1);
ss_socket_close(receivers[i].io.fd);
}
/* Both directions share one poll handle; freeing a pending peer is safe. */
int calls = 0;
ss_io *other = calloc(1, sizeof(*other));
struct shared *owner = calloc(1, sizeof(*owner));
assert(other && owner);
ss_io_init(other, must_not_run, (int)sender, SS_WRITE);
ss_io_init(&owner->io, destroy_peer, (int)sender, SS_WRITE);
owner->other = other; owner->calls = &calls;
ss_io_start(loop, other);
ss_io_start(loop, &owner->io);
ss_run(loop, 0);
assert(calls == 1);
ss_socket_close(sender);
assert(ss_run(loop, SS_RUN_NOWAIT) == 0);
ss_loop_destroy(loop);
return 0;
}