OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / test / test_uds.cpp

244 lines · cpp ref:
#include "../src/uds_server.h"

#include <cassert>
#include <cstdio>
#include <cstring>
#include <thread>
#include <chrono>

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

static int tests_passed = 0;

#define TEST(name)                             \
	do {                                       \
		printf("  %-40s", #name "...");        \
	} while (0)
#define PASS()                                 \
	do {                                       \
		printf("PASS\n");                      \
		tests_passed++;                        \
	} while (0)

static const char *TEST_SOCK = "/tmp/test_bb_uds.sock";

// Helper: connect a client socket
static int connect_client(const char *path) {
	int fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fd < 0)
		return -1;

	struct sockaddr_un addr = {};
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);

	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		close(fd);
		return -1;
	}
	return fd;
}

// Helper: send a length-prefixed message
static bool send_msg(int fd, const void *data, uint32_t len) {
	uint32_t le_len = len; // already LE on x86
	if (write(fd, &le_len, 4) != 4)
		return false;
	if (write(fd, data, len) != (ssize_t)len)
		return false;
	return true;
}

void test_server_start_stop() {
	TEST(server_start_stop);
	UdsServer server;
	assert(server.start(TEST_SOCK));
	assert(server.listening());
	server.stop();
	assert(!server.listening());
	PASS();
}

void test_client_connect() {
	TEST(client_connect);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	// Connect a client
	int client_fd = connect_client(TEST_SOCK);
	assert(client_fd >= 0);

	// Poll server to accept
	server.poll();
	assert(server.client_count() == 1);

	close(client_fd);
	server.stop();
	PASS();
}

void test_message_roundtrip() {
	TEST(message_roundtrip);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	int client_fd = connect_client(TEST_SOCK);
	assert(client_fd >= 0);
	server.poll(); // accept

	// Client sends a message
	const char *msg = "hello_bridge";
	assert(send_msg(client_fd, msg, strlen(msg)));

	// Small delay for data to arrive
	std::this_thread::sleep_for(std::chrono::milliseconds(10));

	// Poll to read
	server.poll();
	assert(server.has_messages());

	UdsMessage received = server.next_message();
	assert(received.size == strlen(msg));
	assert(memcmp(received.data.data(), msg, strlen(msg)) == 0);

	close(client_fd);
	server.stop();
	PASS();
}

void test_server_send_to_client() {
	TEST(server_send_to_client);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	int client_fd = connect_client(TEST_SOCK);
	assert(client_fd >= 0);
	server.poll(); // accept

	// Server sends to all clients
	const char *msg = "scene_update";
	server.broadcast(msg, strlen(msg));

	// Read on client side
	std::this_thread::sleep_for(std::chrono::milliseconds(10));

	uint32_t len = 0;
	assert(read(client_fd, &len, 4) == 4);
	assert(len == strlen(msg));

	char buf[64] = {};
	assert(read(client_fd, buf, len) == (ssize_t)len);
	assert(memcmp(buf, msg, len) == 0);

	close(client_fd);
	server.stop();
	PASS();
}

void test_client_disconnect_detection() {
	TEST(client_disconnect_detection);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	int client_fd = connect_client(TEST_SOCK);
	assert(client_fd >= 0);
	server.poll(); // accept
	assert(server.client_count() == 1);

	// Client disconnects
	close(client_fd);
	std::this_thread::sleep_for(std::chrono::milliseconds(10));

	// Poll should detect disconnect
	server.poll();
	assert(server.client_count() == 0);

	server.stop();
	PASS();
}

void test_multiple_clients() {
	TEST(multiple_clients);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	int c1 = connect_client(TEST_SOCK);
	int c2 = connect_client(TEST_SOCK);
	assert(c1 >= 0 && c2 >= 0);

	server.poll();
	assert(server.client_count() == 2);

	// Broadcast reaches both
	const char *msg = "sync";
	server.broadcast(msg, strlen(msg));
	std::this_thread::sleep_for(std::chrono::milliseconds(10));

	// Read from both clients
	for (int fd : {c1, c2}) {
		uint32_t len = 0;
		assert(read(fd, &len, 4) == 4);
		assert(len == 4);
		char buf[8] = {};
		assert(read(fd, buf, len) == (ssize_t)len);
		assert(memcmp(buf, "sync", 4) == 0);
	}

	close(c1);
	close(c2);
	server.stop();
	PASS();
}

void test_multiple_messages() {
	TEST(multiple_messages);
	UdsServer server;
	assert(server.start(TEST_SOCK));

	int client_fd = connect_client(TEST_SOCK);
	assert(client_fd >= 0);
	server.poll(); // accept

	// Send 3 messages back-to-back
	for (int i = 0; i < 3; i++) {
		char msg[16];
		int len = snprintf(msg, sizeof(msg), "msg_%d", i);
		assert(send_msg(client_fd, msg, len));
	}

	std::this_thread::sleep_for(std::chrono::milliseconds(20));
	server.poll();

	// Should have all 3
	for (int i = 0; i < 3; i++) {
		assert(server.has_messages());
		UdsMessage m = server.next_message();
		char expected[16];
		int elen = snprintf(expected, sizeof(expected), "msg_%d", i);
		assert(m.size == (uint32_t)elen);
		assert(memcmp(m.data.data(), expected, elen) == 0);
	}
	assert(!server.has_messages());

	close(client_fd);
	server.stop();
	PASS();
}

int main() {
	printf("=== UDS Server Tests ===\n");

	test_server_start_stop();
	test_client_connect();
	test_message_roundtrip();
	test_server_send_to_client();
	test_client_disconnect_detection();
	test_multiple_clients();
	test_multiple_messages();

	printf("\nAll %d tests passed.\n", tests_passed);
	return 0;
}