Skip to content

Add unit test setup with php_network_connect_socket test #16987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: PHP-8.2
Choose a base branch
from
Open
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
31 changes: 31 additions & 0 deletions tests/unit/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
CC = gcc
CFLAGS = -g -Wall -I../../ -I../../Zend -I../../main -I../../TSRM -I. -I..
COMMON_LDFLAGS = ../../.libs/libphp.a -lcmocka -lpthread -lm -ldl -lresolv -lutil

TESTS = main/test_network
main/test_network_SRC = main/test_network.c
main/test_network_LDFLAGS = $(COMMON_LDFLAGS) -Wl,--wrap=connect,--wrap=poll,--wrap=getsockopt,--wrap=gettimeofday


# Build all tests
all: $(TESTS)

# Build rule for each test
$(TESTS):
$(CC) $(CFLAGS) -o [email protected] $($(basename $@)_SRC) $($(basename $@)_LDFLAGS)

# Run all tests
.PHONY: test
test: $(TESTS)
@echo "Running all tests..."
@for test in $(TESTS); do \
echo "Running $$test..."; \
$$test.out || exit 1; \
done

# Clean tests
.PHONY: clean
clean:
@for test in $(TESTS); do \
rm -f $$test.out; \
done
254 changes: 254 additions & 0 deletions tests/unit/main/test_network.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include "php.h"
#include "php_network.h"
#include <cmocka.h>

// Mocked poll
int __wrap_poll(struct pollfd *ufds, nfds_t nfds, int timeout)
{
function_called();
check_expected(timeout);

int n = mock_type(int);
if (n > 0) {
ufds->revents = 1;
} else if (n < 0) {
errno = -n;
n = -1;
}

return n;
}

// Mocked connect
int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
function_called();
errno = mock_type(int);
return errno != 0 ? -1 : 0;
}

// Mocked getsockopt
int __wrap_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen)
{
function_called();
int *error = (int *) optval;
*error = mock_type(int);
return mock_type(int);
}

// Mocked gettimeofday
int __wrap_gettimeofday(struct timeval *time_Info, struct timezone *timezone_Info)
{
function_called();
struct timeval *now = mock_ptr_type(struct timeval *);
if (now) {
time_Info->tv_sec = now->tv_sec;
time_Info->tv_usec = now->tv_usec;
}
return mock_type(int);
}

// Test successful connection
static void test_php_network_connect_socket_immediate_success(void **state) {
struct timeval timeout = { .tv_sec = 2, .tv_usec = 500000 };
php_socket_t sockfd = 12;
int error_code = 0;

expect_function_call(__wrap_connect);
will_return(__wrap_connect, 0);

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout, NULL, &error_code);

assert_int_equal(result, 0);
assert_int_equal(error_code, 0);
}

// Test successful connection in progress followed by poll
static void test_php_network_connect_socket_progress_success(void **state) {
struct timeval timeout_tv = { .tv_sec = 2, .tv_usec = 500000 };
php_socket_t sockfd = 12;
int error_code = 0;

// Mock connect setting EINPROGRESS errno
expect_function_call(__wrap_connect);
will_return(__wrap_connect, EINPROGRESS);

// Mock time setting - ignored
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, NULL);
will_return(__wrap_gettimeofday, 0);

// Mock poll to return success
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 2500);
will_return(__wrap_poll, 1);

// Mock no socket error
expect_function_call(__wrap_getsockopt);
will_return(__wrap_getsockopt, 0); // optval saved result
will_return(__wrap_getsockopt, 0); // actual return value

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout_tv, NULL, &error_code);

assert_int_equal(result, 0);
assert_int_equal(error_code, 0);
}

static void test_php_network_connect_socket_eintr_t1(void **state) {
struct timeval timeout_tv = { .tv_sec = 2, .tv_usec = 500000 };
struct timeval start_time = { .tv_sec = 1000, .tv_usec = 0 }; // Initial time
struct timeval retry_time = { .tv_sec = 1001, .tv_usec = 200000 }; // Time after EINTR
php_socket_t sockfd = 12;
int error_code = 0;

// Mock connect to set EINPROGRESS
expect_function_call(__wrap_connect);
will_return(__wrap_connect, EINPROGRESS);

// Mock gettimeofday for initial call
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &start_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to return EINTR first
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 2500);
will_return(__wrap_poll, -EINTR);

// Mock gettimeofday after EINTR
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &retry_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to succeed on retry
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 1300);
will_return(__wrap_poll, 1);

// Mock no socket error
expect_function_call(__wrap_getsockopt);
will_return(__wrap_getsockopt, 0);
will_return(__wrap_getsockopt, 0);

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout_tv, NULL, &error_code);

// Ensure the function succeeds
assert_int_equal(result, 0);
assert_int_equal(error_code, 0);
}

static void test_php_network_connect_socket_eintr_t2(void **state) {
struct timeval timeout_tv = { .tv_sec = 2, .tv_usec = 1500000 };
struct timeval start_time = { .tv_sec = 1000, .tv_usec = 300000 }; // Initial time
struct timeval retry_time = { .tv_sec = 1001, .tv_usec = 200000 }; // Time after EINTR
php_socket_t sockfd = 12;
int error_code = 0;

// Mock connect to set EINPROGRESS
expect_function_call(__wrap_connect);
will_return(__wrap_connect, EINPROGRESS);

// Mock gettimeofday for initial call
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &start_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to return EINTR first
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 3500);
will_return(__wrap_poll, -EINTR);

// Mock gettimeofday after EINTR
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &retry_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to succeed on retry
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 2600);
will_return(__wrap_poll, 1);

// Mock no socket error
expect_function_call(__wrap_getsockopt);
will_return(__wrap_getsockopt, 0); // optval saved result
will_return(__wrap_getsockopt, 0); // actual return value

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout_tv, NULL, &error_code);

// Ensure the function succeeds
assert_int_equal(result, 0);
assert_int_equal(error_code, 0);
}

static void test_php_network_connect_socket_eintr_t3(void **state) {
struct timeval timeout_tv = { .tv_sec = 2, .tv_usec = 500000 };
struct timeval start_time = { .tv_sec = 1002, .tv_usec = 300000 }; // Initial time
struct timeval retry_time = { .tv_sec = 1001, .tv_usec = 2200000 }; // Time after EINTR
php_socket_t sockfd = 12;
int error_code = 0;

// Mock connect to set EINPROGRESS
expect_function_call(__wrap_connect);
will_return(__wrap_connect, EINPROGRESS);

// Mock gettimeofday for initial call
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &start_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to return EINTR first
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 2500);
will_return(__wrap_poll, -EINTR);

// Mock gettimeofday after EINTR
expect_function_call(__wrap_gettimeofday);
will_return(__wrap_gettimeofday, &retry_time);
will_return(__wrap_gettimeofday, 0);

// Mock poll to succeed on retry
expect_function_call(__wrap_poll);
expect_value(__wrap_poll, timeout, 1600);
will_return(__wrap_poll, 1);

// Mock no socket error
expect_function_call(__wrap_getsockopt);
will_return(__wrap_getsockopt, 0); // optval saved result
will_return(__wrap_getsockopt, 0); // actual return value

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout_tv, NULL, &error_code);

// Ensure the function succeeds
assert_int_equal(result, 0);
assert_int_equal(error_code, 0);
}

// Test connection error (ECONNREFUSED)
static void test_php_network_connect_socket_connect_error(void **state) {
struct timeval timeout = { .tv_sec = 2, .tv_usec = 500000 };
php_socket_t sockfd = 12;
int error_code = 0;

// Mock connect to set ECONNREFUSED
expect_function_call(__wrap_connect);
will_return(__wrap_connect, ECONNREFUSED);

int result = php_network_connect_socket(sockfd, NULL, 0, 0, &timeout, NULL, &error_code);

// Ensure the function returns an error
assert_int_equal(result, -1);
assert_int_equal(error_code, ECONNREFUSED);
}


int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_php_network_connect_socket_immediate_success),
cmocka_unit_test(test_php_network_connect_socket_progress_success),
cmocka_unit_test(test_php_network_connect_socket_eintr_t1),
cmocka_unit_test(test_php_network_connect_socket_eintr_t2),
cmocka_unit_test(test_php_network_connect_socket_eintr_t3),
cmocka_unit_test(test_php_network_connect_socket_connect_error),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
Loading