mirror of https://github.com/sysown/proxysql
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.
224 lines
7.6 KiB
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();
|
|
}
|