You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/test/tap/tests/unit/server_selection_unit-t.cpp

224 lines
7.6 KiB

/**
* @file server_selection_unit-t.cpp
* @brief Unit tests for the server selection algorithm.
*
* Tests the pure selection functions extracted from get_random_MySrvC():
* - is_candidate_eligible()
* - select_server_from_candidates()
*
* @see Phase 3.4 (GitHub issue #5492)
*/
#include "tap.h"
#include "test_globals.h"
#include "test_init.h"
#include "proxysql.h"
#include "ServerSelection.h"
// ============================================================================
// Helper: create a default ONLINE server candidate
// ============================================================================
static ServerCandidate make_candidate(int idx, int64_t weight = 1,
unsigned int max_conns = 1000)
{
ServerCandidate c {};
c.index = idx;
c.weight = weight;
c.status = SERVER_ONLINE;
c.current_connections = 0;
c.max_connections = max_conns;
c.current_latency_us = 0;
c.max_latency_us = 0;
c.current_repl_lag = 0;
c.max_repl_lag = 0;
return c;
}
// ============================================================================
// 1. is_candidate_eligible
// ============================================================================
static void test_eligibility() {
ServerCandidate online = make_candidate(0);
ok(is_candidate_eligible(online) == true, "eligible: ONLINE server");
ServerCandidate shunned = make_candidate(1);
shunned.status = SERVER_SHUNNED;
ok(is_candidate_eligible(shunned) == false, "ineligible: SHUNNED");
ServerCandidate off_soft = make_candidate(2);
off_soft.status = SERVER_OFFLINE_SOFT;
ok(is_candidate_eligible(off_soft) == false, "ineligible: OFFLINE_SOFT");
ServerCandidate off_hard = make_candidate(3);
off_hard.status = SERVER_OFFLINE_HARD;
ok(is_candidate_eligible(off_hard) == false, "ineligible: OFFLINE_HARD");
ServerCandidate lag_shunned = make_candidate(4);
lag_shunned.status = SERVER_SHUNNED_REPLICATION_LAG;
ok(is_candidate_eligible(lag_shunned) == false, "ineligible: SHUNNED_REPL_LAG");
ServerCandidate at_max = make_candidate(5, 1, 10);
at_max.current_connections = 10;
ok(is_candidate_eligible(at_max) == false, "ineligible: at max_connections");
ServerCandidate below_max = make_candidate(6, 1, 10);
below_max.current_connections = 9;
ok(is_candidate_eligible(below_max) == true, "eligible: below max_connections");
ServerCandidate high_latency = make_candidate(7);
high_latency.max_latency_us = 5000;
high_latency.current_latency_us = 6000;
ok(is_candidate_eligible(high_latency) == false, "ineligible: high latency");
ServerCandidate ok_latency = make_candidate(8);
ok_latency.max_latency_us = 5000;
ok_latency.current_latency_us = 4000;
ok(is_candidate_eligible(ok_latency) == true, "eligible: acceptable latency");
ServerCandidate no_limit = make_candidate(9);
no_limit.max_latency_us = 0;
no_limit.current_latency_us = 999999;
ok(is_candidate_eligible(no_limit) == true, "eligible: latency limit disabled (max=0)");
ServerCandidate high_lag = make_candidate(10);
high_lag.max_repl_lag = 10;
high_lag.current_repl_lag = 15;
ok(is_candidate_eligible(high_lag) == false, "ineligible: high repl lag");
ServerCandidate ok_lag = make_candidate(11);
ok_lag.max_repl_lag = 10;
ok_lag.current_repl_lag = 5;
ok(is_candidate_eligible(ok_lag) == true, "eligible: acceptable repl lag");
}
// ============================================================================
// 2. select_server_from_candidates — basic
// ============================================================================
static void test_select_single() {
ServerCandidate c = make_candidate(42);
int result = select_server_from_candidates(&c, 1, 12345);
ok(result == 42, "single server: always selected (idx=42)");
}
static void test_select_empty() {
ok(select_server_from_candidates(nullptr, 0, 0) == -1,
"empty list: returns -1");
}
static void test_select_all_offline() {
ServerCandidate candidates[3];
candidates[0] = make_candidate(0); candidates[0].status = SERVER_OFFLINE_HARD;
candidates[1] = make_candidate(1); candidates[1].status = SERVER_SHUNNED;
candidates[2] = make_candidate(2); candidates[2].status = SERVER_OFFLINE_SOFT;
ok(select_server_from_candidates(candidates, 3, 999) == -1,
"all offline: returns -1");
}
static void test_select_weight_zero() {
ServerCandidate c = make_candidate(0, 0);
ok(select_server_from_candidates(&c, 1, 12345) == -1,
"weight=0: never selected");
}
// ============================================================================
// 3. Weighted distribution (statistical)
// ============================================================================
static void test_equal_weight_distribution() {
ServerCandidate candidates[2];
candidates[0] = make_candidate(0, 1);
candidates[1] = make_candidate(1, 1);
int count[2] = {0, 0};
const int N = 10000;
for (int seed = 0; seed < N; seed++) {
int result = select_server_from_candidates(candidates, 2, seed);
if (result >= 0 && result <= 1) count[result]++;
}
double pct0 = (double)count[0] / N * 100;
ok(pct0 > 30 && pct0 < 70,
"equal weight: server 0 selected %.1f%% (expect ~50%%)", pct0);
}
static void test_weighted_distribution() {
ServerCandidate candidates[2];
candidates[0] = make_candidate(0, 3); // weight 3
candidates[1] = make_candidate(1, 1); // weight 1
int count[2] = {0, 0};
const int N = 10000;
for (int seed = 0; seed < N; seed++) {
int result = select_server_from_candidates(candidates, 2, seed);
if (result >= 0 && result <= 1) count[result]++;
}
double pct0 = (double)count[0] / N * 100;
ok(pct0 > 60 && pct0 < 90,
"3:1 weight: server 0 selected %.1f%% (expect ~75%%)", pct0);
}
// ============================================================================
// 4. Determinism
// ============================================================================
static void test_determinism() {
ServerCandidate candidates[3];
candidates[0] = make_candidate(0, 2);
candidates[1] = make_candidate(1, 3);
candidates[2] = make_candidate(2, 5);
int r1 = select_server_from_candidates(candidates, 3, 42);
int r2 = select_server_from_candidates(candidates, 3, 42);
ok(r1 == r2, "determinism: same seed → same result");
}
// ============================================================================
// 5. Mixed eligible/ineligible
// ============================================================================
static void test_mixed_eligibility() {
ServerCandidate candidates[4];
candidates[0] = make_candidate(0, 1); candidates[0].status = SERVER_SHUNNED;
candidates[1] = make_candidate(1, 1); candidates[1].status = SERVER_OFFLINE_HARD;
candidates[2] = make_candidate(2, 1); // ONLINE
candidates[3] = make_candidate(3, 1); candidates[3].status = SERVER_OFFLINE_SOFT;
// Only candidate[2] is eligible — must always be selected
int pass = 0;
for (int seed = 0; seed < 100; seed++) {
if (select_server_from_candidates(candidates, 4, seed) == 2) pass++;
}
ok(pass == 100,
"mixed: only eligible server selected 100/100 times");
}
// ============================================================================
// Main
// ============================================================================
int main() {
plan(21);
int rc = test_init_minimal();
ok(rc == 0, "test_init_minimal() succeeds");
test_eligibility(); // 12
test_select_single(); // 1
test_select_empty(); // 1
test_select_all_offline(); // 1
test_select_weight_zero(); // 1
test_equal_weight_distribution(); // 1
test_weighted_distribution(); // 1
test_determinism(); // 1
test_mixed_eligibility(); // 1
// Total: 1+12+1+1+1+1+1+1+1+1 = 21
test_cleanup_minimal();
return exit_status();
}