From 93f1e82cedbf2fcd45ac413cbf2a654e0f65f0b3 Mon Sep 17 00:00:00 2001 From: Aargh Rai Date: Thu, 27 Aug 2026 22:21:14 +0530 Subject: uci tests --- src/uci/response.c | 659 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 638 insertions(+), 21 deletions(-) (limited to 'src/uci/response.c') diff --git a/src/uci/response.c b/src/uci/response.c index 81459d6..f3fc2d9 100644 --- a/src/uci/response.c +++ b/src/uci/response.c @@ -3,6 +3,33 @@ #include #include "uci/internals/response.h" + +// Tokenize a UCI message into a command. Supports double-quoted strings so +// that multi-word values (e.g. FEN positions) survive tokenization. +void load_from_message(uci_cmd_t* cmd, const char* message) { + char token[MAX_TOKEN_SIZE]; + int i = 0, k = 0; + bool quoted = false; + + while (message[i] != 0 && message[i] != '\n' && message[i] != '\r') { + if (message[i] == '"') { + quoted = !quoted; + i++; + continue; + } + if (message[i] == ' ' && !quoted) { + if (k == 0) { i++; continue; } + i++; + token[k] = 0; + uci_cmd_add(cmd, token); + k = 0; + continue; + } + token[k++] = message[i++]; + } + token[k] = 0; + uci_cmd_add(cmd, token); +} #include "uci/state.h" #include "fen.h" @@ -50,23 +77,30 @@ void handle_idle(uci_state_t *state, uci_cmd_t cmd) { char buffer[2048] = {0}; char name[64] = {0}; char *cursor = name; - for (int i = 1; i < cmd.args_count; i++) { - assert((long)cursor - (long)buffer < 2048); + int limit = 64; + int i = 1; + if (i < cmd.args_count && strcmp(uci_cmd_get_arg(cmd, i), "name") == 0) i++; + for (; i < cmd.args_count; i++) { + assert((long)cursor - (long)(limit == 64 ? name : buffer) < limit); char *arg = uci_cmd_get_arg(cmd, i); if (strcmp(arg, "value") == 0) { - *(--cursor) = 0; + if (cursor > name && *(cursor - 1) == ' ') cursor--; + *cursor = 0; cursor = buffer; + limit = 2048; continue; } - cursor = stpncpy(cursor, arg, MAX_TOKEN_SIZE); + while (*arg) *(cursor++) = *(arg++); *(cursor++) = ' '; } - *(--cursor) = 0; + if (cursor > name && *(cursor - 1) == ' ') cursor--; + if (cursor > buffer && *(cursor - 1) == ' ') cursor--; + *cursor = 0; apply_option(state, name, buffer); } else if (strcmp(cmd.root, "ucinewgame") == 0) { } else if (strcmp(cmd.root, "position") == 0) { int k = 0; - char* mode = uci_cmd_get_arg(cmd, 0); + char* mode = uci_cmd_get_arg(cmd, k++); if (strcmp(mode, "startpos") == 0) { struct fen_load fen = load_fen( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" @@ -83,13 +117,8 @@ void handle_idle(uci_state_t *state, uci_cmd_t cmd) { state->position = fen.position; } else { assert(0 && "Invalid argument for position command"); } - if (cmd.args_count >= k) return; + if (cmd.args_count <= k) return; assert(strcmp(uci_cmd_get_arg(cmd, k++), "moves") == 0); - assert(state->moves->count <= cmd.args_count - k); - int old_move_count = state->moves->count; - state->moves->count = cmd.args_count - k; - - k += old_move_count; for (; k < cmd.args_count; k++) { char* move = uci_cmd_get_arg(cmd, k); add_ipc_move( @@ -102,15 +131,6 @@ void handle_idle(uci_state_t *state, uci_cmd_t cmd) { } } else if (strcmp(cmd.root, "stop") == 0) { } else if (strcmp(cmd.root, "quit") == 0) { - free(cmd.args); - - int settings_count = sizeof(state->option_settings)/sizeof(option_setting_t); - for (int i = 0; i < settings_count; i++) { - option_setting_t setting = state->option_settings[i]; - if (OPTION_STRING != setting.type) continue; - free(setting.value.string->data); - } - state->signal = UCI_SIGNAL_QUIT; } else if (strcmp(cmd.root, "isready") == 0) { state->current_state = UCI_STATE_SYNC; @@ -210,3 +230,600 @@ void handle_halt(uci_state_t *state, uci_cmd_t cmd) { // --- state->current_state = UCI_STATE_IDLE; } + +#ifdef TEST_MOD +#include +#include + +static uci_cmd_t __make_cmd(const char *line) { + uci_cmd_t cmd; + uci_cmd_init(&cmd); + load_from_message(&cmd, line); + return cmd; +} + +static void __free_cmd(uci_cmd_t cmd) { uci_cmd_deinit(cmd); } + +static char *__capture_uci( + void (*handler)(uci_state_t *, uci_cmd_t), + uci_state_t *state, + const char *line, + char *out, + int out_size +) { + int pipefd[2]; + if (pipe(pipefd) != 0) { out[0] = 0; return out; } + int saved = dup(STDOUT_FILENO); + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[1]); + + uci_cmd_t cmd = __make_cmd(line); + handler(state, cmd); + __free_cmd(cmd); + + fflush(stdout); + dup2(saved, STDOUT_FILENO); + close(saved); + + int n = read(pipefd[0], out, out_size - 1); + close(pipefd[0]); + out[n > 0 ? n : 0] = 0; + return out; +} + +// ---- UCI handshake ---- + +bool test_uci_handshake_id_and_uciok() { + uci_state_t state; + uci_state_init(&state); + + char out[2048]; + __capture_uci(handle_initial, &state, "uci", out, sizeof(out)); + + bool ok = true; + ok &= strstr(out, "id name Gacrux") != NULL; + ok &= strstr(out, "id author") != NULL; + ok &= strstr(out, "uciok") != NULL; + ok &= state.current_state == UCI_STATE_IDLE; + + uci_state_deinit(&state); + return ok; +} + +bool test_uci_handshake_prints_all_options() { + uci_state_t state; + uci_state_init(&state); + + char out[4096]; + __capture_uci(handle_initial, &state, "uci", out, sizeof(out)); + + bool ok = true; + ok &= strstr(out, "option name Threads type spin") != NULL; + ok &= strstr(out, "option name Hash type spin") != NULL; + ok &= strstr(out, "option name Clear Hash type button") != NULL; + ok &= strstr(out, "option name UCI_ShowCurrLine type check") != NULL; + ok &= strstr(out, "option name UCI_LimitStrength type check") != NULL; + ok &= strstr(out, "option name UCI_Elo type spin") != NULL; + ok &= strstr(out, "option name UCI_AnalyseMode type check") != NULL; + ok &= strstr(out, "option name UCI_Opponent type string") != NULL; + ok &= strstr(out, "option name UCI_EngineAbout type string") != NULL; + + uci_state_deinit(&state); + return ok; +} + +// ---- isready ---- + +bool test_isready_returns_readyok() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_sync, &state, "isready", out, sizeof(out)); + + bool ok = true; + ok &= strstr(out, "readyok") != NULL; + ok &= state.current_state == UCI_STATE_IDLE; + + uci_state_deinit(&state); + return ok; +} + +bool test_isready_transitions_to_idle() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_SYNC; + + char out[256]; + __capture_uci(handle_uci, &state, "isready", out, sizeof(out)); + + bool ok = true; + ok &= strstr(out, "readyok") != NULL; + ok &= state.current_state == UCI_STATE_IDLE; + + uci_state_deinit(&state); + return ok; +} + +// ---- debug ---- + +bool test_debug_on_sets_flag() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "debug on", out, sizeof(out)); + + bool ok = state.debug == true; + uci_state_deinit(&state); + return ok; +} + +bool test_debug_off_clears_flag() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + state.debug = true; + + char out[256]; + __capture_uci(handle_idle, &state, "debug off", out, sizeof(out)); + + bool ok = state.debug == false; + uci_state_deinit(&state); + return ok; +} + +// ---- setoption ---- + +bool test_setoption_spin() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name Threads value 4", out, sizeof(out)); + + bool ok = state.threads == 4; + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_spin_clamps_too_low() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name Threads value 0", out, sizeof(out)); + + bool ok = state.threads == 1; + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_spin_clamps_too_high() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name Threads value 99", out, sizeof(out)); + + bool ok = state.threads == 1; + + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_check_true() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name UCI_AnalyseMode value true", out, sizeof(out)); + + bool ok = state.uci_analysemode == true; + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_check_false() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + state.uci_analysemode = true; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name UCI_AnalyseMode value false", out, sizeof(out)); + + bool ok = state.uci_analysemode == false; + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_button() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name Clear Hash", out, sizeof(out)); + + bool ok = state.clear_hash == true; + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_string() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name UCI_Opponent value Magnus", out, sizeof(out)); + + bool ok = true; + ok &= state.uci_opponent.length == 6; + ok &= memcmp(state.uci_opponent.data, "Magnus", 6) == 0; + + uci_state_deinit(&state); + return ok; +} + +bool test_setoption_hash_changes_value() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "setoption name Hash value 128", out, sizeof(out)); + + bool ok = state.hash == 128; + uci_state_deinit(&state); + return ok; +} + +// ---- position ---- + +bool test_position_startpos_loads() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + ipc_moves_t moves; + ipc_moves_init(&moves); + state.moves = &moves; + + char out[256]; + __capture_uci(handle_idle, &state, + "position startpos", out, sizeof(out)); + + bool ok = true; + ok &= state.position.bitboards[WHITE_PAWN] != 0; + ok &= state.position.turn == WHITE_TURN; + + ipc_moves_deinit(&moves); + uci_state_deinit(&state); + return ok; +} + +bool test_position_fen_loads() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + ipc_moves_t moves; + ipc_moves_init(&moves); + state.moves = &moves; + + char out[256]; + __capture_uci(handle_idle, &state, + "position fen \"k7/8/KQ6/8/8/8/8/8 w - - 0 1\"", out, sizeof(out)); + + bool ok = state.position.bitboards[WHITE_QUEEN] != 0; + + ipc_moves_deinit(&moves); + uci_state_deinit(&state); + return ok; +} + +bool test_position_startpos_moves() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + ipc_moves_t moves; + ipc_moves_init(&moves); + state.moves = &moves; + + char out[256]; + __capture_uci(handle_idle, &state, + "position startpos moves e2e4", out, sizeof(out)); + + bool ok = state.moves->count == 1; + if (ok) { + ok &= state.moves->data[0].from == 33; + ok &= state.moves->data[0].to == 35; + } + + ipc_moves_deinit(&moves); + uci_state_deinit(&state); + return ok; +} + +// ---- go ---- + +bool test_go_depth_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go depth 10", out, sizeof(out)); + + bool ok = true; + ok &= state.go_args.depth == 10; + ok &= state.current_state == UCI_STATE_ACTIVE; + ok &= state.signal == UCI_SIGNAL_GO; + + uci_state_deinit(&state); + return ok; +} + +bool test_go_wtime_btime_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "go wtime 60000 btime 60000 winc 1000 binc 1000", out, sizeof(out)); + + bool ok = true; + ok &= state.go_args.wtime == 60000; + ok &= state.go_args.btime == 60000; + ok &= state.go_args.winc == 1000; + ok &= state.go_args.binc == 1000; + + uci_state_deinit(&state); + return ok; +} + +bool test_go_nodes_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go nodes 50000", out, sizeof(out)); + + bool ok = state.go_args.nodes == 50000; + uci_state_deinit(&state); + return ok; +} + +bool test_go_movetime_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go movetime 5000", out, sizeof(out)); + + bool ok = state.go_args.movetime == 5000; + uci_state_deinit(&state); + return ok; +} + +bool test_go_movestogo_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go movestogo 30", out, sizeof(out)); + + bool ok = state.go_args.movestogo == 30; + uci_state_deinit(&state); + return ok; +} + +bool test_go_mate_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go mate 3", out, sizeof(out)); + + bool ok = state.go_args.mate == 3; + uci_state_deinit(&state); + return ok; +} + +bool test_go_infinite_sets_flag() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go infinite", out, sizeof(out)); + + bool ok = state.go_args.infinite == true; + uci_state_deinit(&state); + return ok; +} + +bool test_go_ponder_sets_flag() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go ponder", out, sizeof(out)); + + bool ok = state.go_args.ponder == true; + uci_state_deinit(&state); + return ok; +} + +bool test_go_combined_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, + "go depth 6 wtime 30000 btime 30000 winc 500 movestogo 20", out, sizeof(out)); + + bool ok = true; + ok &= state.go_args.depth == 6; + ok &= state.go_args.wtime == 30000; + ok &= state.go_args.btime == 30000; + ok &= state.go_args.winc == 500; + ok &= state.go_args.movestogo == 20; + + uci_state_deinit(&state); + return ok; +} + +bool test_go_perft_sets_args() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "go perft 5", out, sizeof(out)); + + bool ok = state.go_args.perft == 5; + uci_state_deinit(&state); + return ok; +} + +// ---- state machine transitions ---- + +bool test_active_stop_transitions_to_idle() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_ACTIVE; + + char out[256]; + __capture_uci(handle_active, &state, "stop", out, sizeof(out)); + + bool ok = true; + ok &= state.current_state == UCI_STATE_HALT; + + uci_state_deinit(&state); + return ok; +} + +bool test_halt_sets_stop_signal() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_HALT; + + char out[256]; + __capture_uci(handle_halt, &state, "", out, sizeof(out)); + + bool ok = true; + ok &= state.signal == UCI_SIGNAL_STOP; + ok &= state.current_state == UCI_STATE_IDLE; + + uci_state_deinit(&state); + return ok; +} + +bool test_active_isready_transitions_to_ping() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_ACTIVE; + + char out[256]; + __capture_uci(handle_active, &state, "isready", out, sizeof(out)); + + bool ok = state.current_state == UCI_STATE_PING; + + uci_state_deinit(&state); + return ok; +} + +bool test_quit_sets_signal() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "quit", out, sizeof(out)); + + bool ok = state.signal == UCI_SIGNAL_QUIT; + uci_state_deinit(&state); + return ok; +} + +bool test_new_game_accepted() { + uci_state_t state; + uci_state_init(&state); + state.current_state = UCI_STATE_IDLE; + + char out[256]; + __capture_uci(handle_idle, &state, "ucinewgame", out, sizeof(out)); + + bool ok = state.current_state == UCI_STATE_IDLE; + uci_state_deinit(&state); + return ok; +} + +bool test_full_handshake_sequence() { + uci_state_t state; + uci_state_init(&state); + + ipc_moves_t moves; + ipc_moves_init(&moves); + state.moves = &moves; + + char out[2048]; + + __capture_uci(handle_uci, &state, "uci", out, sizeof(out)); + bool ok = state.current_state == UCI_STATE_IDLE; + ok &= strstr(out, "uciok") != NULL; + + __capture_uci(handle_idle, &state, "isready", out, sizeof(out)); + ok &= state.current_state == UCI_STATE_SYNC; + + __capture_uci(handle_uci, &state, "isready", out, sizeof(out)); + ok &= state.current_state == UCI_STATE_IDLE; + ok &= strstr(out, "readyok") != NULL; + + __capture_uci(handle_idle, &state, + "setoption name Threads value 2", out, sizeof(out)); + ok &= state.threads == 2; + + __capture_uci(handle_idle, &state, "position startpos", out, sizeof(out)); + ok &= state.position.bitboards[WHITE_PAWN] != 0; + + __capture_uci(handle_idle, &state, "go depth 5", out, sizeof(out)); + ok &= state.current_state == UCI_STATE_ACTIVE; + ok &= state.go_args.depth == 5; + + __capture_uci(handle_active, &state, "stop", out, sizeof(out)); + ok &= state.current_state == UCI_STATE_HALT; + + __capture_uci(handle_halt, &state, "", out, sizeof(out)); + ok &= state.current_state == UCI_STATE_IDLE; + + ipc_moves_deinit(&moves); + uci_state_deinit(&state); + return ok; +} +#endif -- cgit v1.2.3