Files
battleship/test/host/test_sync_service.c
T

133 lines
6.0 KiB
C

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "sync_service.h"
typedef struct { uint32_t value; } test_random_t;
typedef struct { int fail_client; uint8_t sends[kSessionCapacity]; char payloads[kSessionCapacity][kStateMessageCapacity]; } capture_t;
static uint32_t next_random(void *context) {
test_random_t *random = context;
random->value = random->value * 1664525U + 1013904223U;
return random->value;
}
static void token_text(const uint8_t token[kSessionTokenBytes], char output[33]) {
static const char hex[] = "0123456789abcdef";
for (uint8_t index = 0; index < kSessionTokenBytes; ++index) {
output[index * 2U] = hex[token[index] >> 4U];
output[index * 2U + 1U] = hex[token[index] & 15U];
}
output[32] = '\0';
}
static bool capture_send(void *context, int client_id, const char *payload, size_t length) {
capture_t *capture = context;
if (client_id == capture->fail_client) return false;
assert(client_id >= 0 && client_id < kSessionCapacity);
assert(length < kStateMessageCapacity);
++capture->sends[client_id];
memcpy(capture->payloads[client_id], payload, length);
capture->payloads[client_id][length] = '\0';
return true;
}
static const char *board_start(const char *json, uint8_t board) {
const char *start = strstr(json, "\"boards\":[\"");
assert(start != NULL);
start += strlen("\"boards\":[\"");
return board == 0U ? start : start + kBoardCellCount + 3U;
}
static void hello(sync_service_t *service, int client_id, const uint8_t token[kSessionTokenBytes], uint32_t version) {
char token_value[33];
char frame[96];
char output[kStateMessageCapacity];
size_t output_length = 0U;
bool changed = false;
bool close = false;
token_text(token, token_value);
snprintf(frame, sizeof(frame), "{\"type\":\"hello\",\"token\":\"%s\",\"version\":%u}", token_value, version);
assert(sync_service_receive(service, client_id, frame, strlen(frame), 10U, output, &output_length, &changed, &close));
assert(!changed && !close && output_length > 0U && strstr(output, "\"type\":\"state\"") != NULL);
}
static void test_authentication_visibility_and_backpressure(void) {
test_random_t random = {.value = 1U};
application_t application;
application_init(&application, (random_source_t){.next_u32 = next_random, .context = &random});
uint8_t player_1 = 0;
uint8_t player_2 = 0;
uint8_t spectator = 0;
assert(application_join(&application, ROLE_PLAYER_1, "Alice", &player_1) == LIFECYCLE_RESULT_OK);
assert(application_join(&application, ROLE_PLAYER_2, "Bob", &player_2) == LIFECYCLE_RESULT_OK);
assert(application_join(&application, ROLE_SPECTATOR, "Watch", &spectator) == LIFECYCLE_RESULT_OK);
sync_service_t service;
sync_service_init(&service, &application);
assert(sync_service_open(&service, 0, 0U));
assert(sync_service_open(&service, 1, 0U));
assert(sync_service_open(&service, 2, 0U));
for (int client_id = 3; client_id < kSessionCapacity; ++client_id) assert(sync_service_open(&service, client_id, 0U));
assert(!sync_service_open(&service, kSessionCapacity, 0U));
hello(&service, 0, application.lifecycle.sessions.entries[player_1].token, 0U);
hello(&service, 1, application.lifecycle.sessions.entries[player_2].token, 999U);
hello(&service, 2, application.lifecycle.sessions.entries[spectator].token, 0U);
application.lifecycle.game.state.phase = PHASE_IN_PROGRESS;
application.lifecycle.game.state.boards[0].cells[0] = CELL_SHIP;
application.lifecycle.game.state.boards[1].cells[0] = CELL_SHIP;
application.lifecycle.game.state.boards[0].cells[1] = CELL_HIT;
application.lifecycle.game.state.boards[1].cells[1] = CELL_MISS;
capture_t capture = {.fail_client = -1};
sync_service_broadcast(&service, capture_send, &capture);
assert(board_start(capture.payloads[0], 0)[0] == '1' && board_start(capture.payloads[0], 1)[0] == '0');
assert(board_start(capture.payloads[1], 0)[0] == '0' && board_start(capture.payloads[1], 1)[0] == '1');
assert(board_start(capture.payloads[2], 0)[0] == '0' && board_start(capture.payloads[2], 1)[0] == '0');
assert(board_start(capture.payloads[2], 0)[1] == '3' && board_start(capture.payloads[2], 1)[1] == '2');
capture.fail_client = 2;
const uint8_t previous_sends = capture.sends[2];
sync_service_broadcast(&service, capture_send, &capture);
sync_service_broadcast(&service, capture_send, &capture);
assert(capture.sends[2] == previous_sends);
}
static void test_timeout_ping_and_command(void) {
test_random_t random = {.value = 9U};
application_t application;
application_init(&application, (random_source_t){.next_u32 = next_random, .context = &random});
uint8_t player_1 = 0;
assert(application_join(&application, ROLE_PLAYER_1, "Alice", &player_1) == LIFECYCLE_RESULT_OK);
sync_service_t service;
sync_service_init(&service, &application);
assert(sync_service_open(&service, 3, 0U));
int expired[kSessionCapacity] = {0};
size_t expired_count = 0U;
sync_service_expire(&service, kWebSocketHelloTimeoutMs + 1U, expired, &expired_count);
assert(expired_count == 1U && expired[0] == 3);
assert(sync_service_open(&service, 0, 0U));
hello(&service, 0, application.lifecycle.sessions.entries[player_1].token, 0U);
char output[kStateMessageCapacity];
size_t output_length = 0U;
bool changed = false;
bool close = false;
assert(sync_service_receive(&service, 0, "{\"type\":\"ping\"}", 15U, 20U, output, &output_length, &changed, &close));
assert(!changed && !close && strcmp(output, "{\"type\":\"pong\"}") == 0);
char token[33];
char frame[128];
token_text(application.lifecycle.sessions.entries[player_1].token, token);
snprintf(frame, sizeof(frame), "{\"type\":\"config\",\"token\":\"%s\",\"gameId\":1,\"mode\":\"bot\"}", token);
assert(sync_service_receive(&service, 0, frame, strlen(frame), 30U, output, &output_length, &changed, &close));
assert(changed && !close && output_length == 0U && application.lifecycle.game.state.mode == MODE_BOT);
}
int main(void) {
test_authentication_visibility_and_backpressure();
test_timeout_ping_and_command();
puts("sync service tests passed");
return 0;
}