mirror of https://github.com/sysown/proxysql
14-task testing plan covering three tiers: - Tier 1 (pure unit): protocol frames, auth, hex, stats counters, config store logic, routing strategies - Tier 2 (integration): plugin lifecycle, admin LOAD/SAVE with aliases, DDL validation, listener lifecycle, concurrent reads/writes - Tier 3 (E2E): Docker MySQL 8.x handshake, query routing Target: 112 -> 307+ assertions across 14 test files.chore/retire-dead-mysqlx-worker
parent
cf867e46fd
commit
30477a3ea5
@ -0,0 +1,634 @@
|
||||
# MySQLX Plugin Comprehensive Testing Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Achieve thorough test coverage of the mysqlx plugin across all layers — pure unit tests for protocol/config/stats, integration tests for admin commands and plugin lifecycle, and end-to-end tests with a real MySQL X Protocol backend.
|
||||
|
||||
**Architecture:** Tests are organized in three tiers. Tier 1 (pure unit) has zero external dependencies. Tier 2 (integration) uses in-memory SQLite3 and the plugin `.so`. Tier 3 (end-to-end) requires Docker MySQL 8.x. Each test file is self-contained and runnable independently.
|
||||
|
||||
**Tech Stack:** TAP framework (`tap.h`), in-memory SQLite3, `socketpair()` for protocol tests, Docker Compose for E2E
|
||||
|
||||
**Current state:** 112 assertions across 10 test files. Target: 400+ assertions.
|
||||
|
||||
---
|
||||
|
||||
## Testing Matrix
|
||||
|
||||
### Tier 1: Pure Unit Tests (no DB, no globals, no I/O)
|
||||
|
||||
| Test File | Module | Tests |
|
||||
|-----------|--------|-------|
|
||||
| `mysqlx_protocol_unit-t.cpp` (expand) | Protocol frame, auth | 10 → 40+ |
|
||||
| `mysqlx_stats_unit-t.cpp` (expand) | Stats counters + flush | 7 → 20+ |
|
||||
| NEW: `mysqlx_config_store_pure_unit-t.cpp` | Config store logic | 0 → 25+ |
|
||||
|
||||
### Tier 2: Integration Tests (in-memory SQLite, plugin .so)
|
||||
|
||||
| Test File | Module | Tests |
|
||||
|-----------|--------|-------|
|
||||
| `plugin_manager_unit-t.cpp` (expand) | Plugin lifecycle | 7 → 20+ |
|
||||
| `plugin_registry_unit-t.cpp` (expand) | Table/command registry | 15 → 25+ |
|
||||
| `plugin_config_unit-t.cpp` (expand) | Config parsing, free functions | 12 → 20+ |
|
||||
| `mysqlx_config_store_unit-t.cpp` (expand) | Runtime loading, identity | 16 → 30+ |
|
||||
| `mysqlx_route_store_unit-t.cpp` (expand) | Routing strategies | 8 → 25+ |
|
||||
| `test_mysqlx_admin_tables-t.cpp` (expand) | LOAD/SAVE commands | 23 → 40+ |
|
||||
| NEW: `mysqlx_admin_schema_unit-t.cpp` | DDL validation, command names | 0 → 15+ |
|
||||
| NEW: `test_mysqlx_listener_smoke-t.cpp` (expand) | Listener lifecycle | 8 → 15+ |
|
||||
|
||||
### Tier 3: End-to-End Tests (requires Docker MySQL 8.x)
|
||||
|
||||
| Test File | Module | Tests |
|
||||
|-----------|--------|-------|
|
||||
| NEW: `test_mysqlx_e2e_handshake-t.cpp` | Full X Protocol handshake | 0 → 10+ |
|
||||
| NEW: `test_mysqlx_e2e_routing-t.cpp` | Query routing via X Protocol | 0 → 10+ |
|
||||
| NEW: `test_mysqlx_e2e_failover-t.cpp` | Backend failover | 0 → 8+ |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Test Specifications
|
||||
|
||||
### Task 1: Expand `mysqlx_protocol_unit-t.cpp` (10 → 42 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/mysqlx_protocol_unit-t.cpp`
|
||||
|
||||
Add these test groups as static functions, called from `main()`:
|
||||
|
||||
#### 1a. Frame header encode/decode (current: 4 → 10)
|
||||
|
||||
- [ ] **Existing assertions** (keep all 4): encode size, decode success, payload preserved, decode rejects short buffer
|
||||
- [ ] Test: `mysqlx_decode_frame_header` with `nullptr` data returns `nullopt`
|
||||
- [ ] Test: `mysqlx_decode_frame_header` with exactly 5 bytes containing zero payload_size (boundary)
|
||||
- [ ] Test: `mysqlx_encode_frame_header` roundtrip — encode then decode, all fields preserved
|
||||
- [ ] Test: `mysqlx_encode_frame_header` with `payload_size = MYSQLX_MAX_PAYLOAD_SIZE` (16MB boundary)
|
||||
- [ ] Test: `mysqlx_decode_frame_header` with `payload_size = 0` and `message_type = 0`
|
||||
|
||||
#### 1b. Frame building (current: 1 → 4)
|
||||
|
||||
- [ ] **Keep existing**: `build_frame` header correct
|
||||
- [ ] Test: `mysqlx_build_frame` payload bytes match input after 5-byte header
|
||||
- [ ] Test: `mysqlx_build_frame` with empty payload produces 5-byte output
|
||||
- [ ] Test: `mysqlx_build_frame` total size = 5 + payload.size()
|
||||
|
||||
#### 1c. Auth method validation (current: 2 → 5)
|
||||
|
||||
- [ ] **Keep existing**: MYSQL41 supported, SHA256_MEMORY not supported
|
||||
- [ ] Test: `mysqlx_is_supported_auth_method("PLAIN")` returns true
|
||||
- [ ] Test: `mysqlx_is_supported_auth_method("")` returns false
|
||||
- [ ] Test: Case insensitivity: `mysqlx_is_supported_auth_method("mysql41")` returns true
|
||||
|
||||
#### 1d. MYSQL41 auth (current: 3 → 10)
|
||||
|
||||
- [ ] **Keep existing**: hash is 20 bytes, scramble is 20 bytes, verify succeeds
|
||||
- [ ] Test: `mysqlx_mysql41_verify` returns false with wrong password
|
||||
- [ ] Test: `mysqlx_mysql41_hash("")` returns 20-byte result (SHA1 of empty string)
|
||||
- [ ] Test: `mysqlx_mysql41_scramble` with same inputs produces same output (deterministic)
|
||||
- [ ] Test: `mysqlx_mysql41_scramble` with different challenges produces different scrambles
|
||||
- [ ] Test: `mysqlx_mysql41_verify` with empty challenge and empty response returns false
|
||||
- [ ] Test: `mysqlx_mysql41_verify` with truncated response (10 bytes instead of 20) returns false
|
||||
- [ ] Test: `mysqlx_mysql41_scramble` with 20-byte challenge and long password works correctly
|
||||
|
||||
#### 1e. Hex encode/decode (current: 0 → 8)
|
||||
|
||||
- [ ] Test: `mysqlx_hex_encode({0x00})` returns `"00"`
|
||||
- [ ] Test: `mysqlx_hex_encode({0xFF})` returns `"ff"` (lowercase)
|
||||
- [ ] Test: `mysqlx_hex_encode({0xAB, 0xCD})` returns `"abcd"`
|
||||
- [ ] Test: `mysqlx_hex_encode({})` (empty) returns `""`
|
||||
- [ ] Test: `mysqlx_hex_decode("00", out)` → out[0] == 0x00
|
||||
- [ ] Test: `mysqlx_hex_decode("aBcD", out)` → out = {0xAB, 0xCD} (case insensitive)
|
||||
- [ ] Test: `mysqlx_hex_decode("", out)` → out is empty, returns true
|
||||
- [ ] Test: `mysqlx_hex_decode("ZZZ", out)` returns false (invalid hex chars)
|
||||
- [ ] Test: `mysqlx_hex_decode("A", out)` returns false (odd length)
|
||||
|
||||
#### 1f. Error/OK frame building (current: 0 → 5)
|
||||
|
||||
- [ ] Test: `mysqlx_send_error` on a `socketpair` fd — read frame, verify type is Error, parse code and message
|
||||
- [ ] Test: `mysqlx_send_error` with custom SQL state — verify state in payload
|
||||
- [ ] Test: `mysqlx_send_ok` on a `socketpair` fd — read frame, verify type is Ok
|
||||
- [ ] Test: `mysqlx_send_ok` with custom message — verify message in payload
|
||||
- [ ] Test: `mysqlx_send_error` with empty message — does not crash
|
||||
|
||||
Note: For send_error/send_ok tests, use `socketpair(AF_UNIX, SOCK_STREAM, 0, fds)` to get a connected pair of fds without needing real network.
|
||||
|
||||
Plan: `plan(42)`
|
||||
|
||||
- [ ] **Commit**: `test: expand mysqlx protocol unit tests to 42 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Expand `mysqlx_stats_unit-t.cpp` (7 → 22 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/mysqlx_stats_unit-t.cpp`
|
||||
|
||||
#### 2a. Counter operations (current: 3 → 8)
|
||||
|
||||
- [ ] **Keep existing 3**: record_conn_ok/err, multi-route
|
||||
- [ ] Test: `get_conn_ok("nonexistent")` returns 0
|
||||
- [ ] Test: `get_conn_err("nonexistent")` returns 0
|
||||
- [ ] Test: Multiple `record_conn_ok` on same route accumulates correctly (3 calls → value 3)
|
||||
- [ ] Test: `record_conn_ok` and `record_conn_err` on same route are independent
|
||||
- [ ] Test: Route name with special characters (e.g. `"route-with-dashes"`) works
|
||||
|
||||
#### 2b. SQLite flush (current: 4 → 9)
|
||||
|
||||
- [ ] **Keep existing 4**: single route flush, values correct, re-flush updates
|
||||
- [ ] Test: Flush with empty stats store — `stats_mysqlx_routes` has 0 rows
|
||||
- [ ] Test: Flush replaces previous rows (seed 1 row manually, flush with 2 routes → 2 rows)
|
||||
- [ ] Test: Flush with route name containing single quote (e.g. `"route'name"`) — no SQL injection
|
||||
- [ ] Test: Flush preserves values across multiple routes — verify each route's ConnOK independently
|
||||
- [ ] Test: Flush with stats at large values (1,000,000+) — no truncation
|
||||
|
||||
#### 2c. Concurrent increment (0 → 5)
|
||||
|
||||
- [ ] Test: Spawn 4 threads, each doing 1000 `record_conn_ok("route")` — final value is 4000
|
||||
- [ ] Test: Spawn 4 threads, each doing 500 `record_conn_ok` + 500 `record_conn_err` on same route — final conn_ok=2000, conn_err=2000
|
||||
- [ ] Test: Two threads recording on different routes — each route's counters are independent
|
||||
- [ ] Test: Concurrent `record_conn_ok` while `flush_to_sqlite` is running — no crash, no data corruption
|
||||
- [ ] Test: `flush_to_sqlite` while counters are being incremented — flushed values are self-consistent (may be slightly stale)
|
||||
|
||||
Plan: `plan(22)`
|
||||
|
||||
- [ ] **Commit**: `test: expand mysqlx stats unit tests to 22 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 3: NEW `mysqlx_config_store_pure_unit-t.cpp` (25 assertions)
|
||||
|
||||
**Files:**
|
||||
- Create: `test/tap/tests/unit/mysqlx_config_store_pure_unit-t.cpp`
|
||||
- Modify: `test/tap/tests/unit/Makefile` (add build target)
|
||||
|
||||
This tests `MysqlxConfigStore` methods that don't need SQLite — identity resolution, endpoint picking, topology generation — by calling `load_from_runtime` from a separate test that creates the DB, or by directly testing the public API with data already loaded.
|
||||
|
||||
Actually, since `load_from_runtime` is the only way to populate the store, and it needs SQLite3DB, this file should use in-memory SQLite3DB (like `mysqlx_config_store_unit-t.cpp` does).
|
||||
|
||||
#### 3a. Backend auth mode parsing (0 → 5)
|
||||
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("mapped")` returns `mapped`
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("MAPPED")` returns `mapped` (case insensitive)
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("service_account")` returns `service_account`
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("pass_through")` returns `pass_through`
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("unknown_value")` returns `mapped` (default)
|
||||
- [ ] Test: `mysqlx_backend_auth_mode_from_string("")` returns `mapped` (default)
|
||||
|
||||
#### 3b. Identity resolution edge cases (0 → 8)
|
||||
|
||||
- [ ] Test: `resolve_identity("nonexistent_user")` returns `nullopt`
|
||||
- [ ] Test: `resolve_identity` for user in mysql_users but NOT in mysqlx_users — returns identity with `x_enabled = false`
|
||||
- [ ] Test: `resolve_identity` for user in mysqlx_users but NOT in mysql_users — returns `nullopt` (canonical wins)
|
||||
- [ ] Test: `resolve_identity` after `load_from_runtime` with empty mysql_users — returns `nullopt`
|
||||
- [ ] Test: `resolve_identity` after `load_from_runtime` with empty mysqlx_users — returns identity with defaults
|
||||
- [ ] Test: Multiple users — `resolve_identity` returns correct identity for each
|
||||
- [ ] Test: Inactive user (`active=0`) in mysql_users is excluded
|
||||
- [ ] Test: `resolve_identity` with `backend=1` users excluded (only `frontend=1`)
|
||||
|
||||
#### 3c. Endpoint picking edge cases (0 → 7)
|
||||
|
||||
- [ ] Test: `pick_endpoint("nonexistent_route")` returns empty endpoint
|
||||
- [ ] Test: `pick_endpoint` for route whose hostgroup has no online servers — returns empty
|
||||
- [ ] Test: `pick_endpoint` for route with fallback_hostgroup, primary empty — uses fallback
|
||||
- [ ] Test: `pick_endpoint` for route with fallback_hostgroup = -1, primary empty — returns empty
|
||||
- [ ] Test: `pick_endpoint` returns endpoint with mysqlx_port from mysqlx_backend_endpoints override
|
||||
- [ ] Test: `pick_endpoint` returns endpoint with default mysqlx_port (33060) when no override exists
|
||||
- [ ] Test: `load_from_runtime` called twice — second call replaces all data (no stale entries)
|
||||
|
||||
#### 3d. Topology generation (0 → 4)
|
||||
|
||||
- [ ] Test: `topology_generation()` starts at 0
|
||||
- [ ] Test: `bump_topology_generation()` increments by 1
|
||||
- [ ] Test: Multiple bumps — generation reaches correct value
|
||||
- [ ] Test: Topology generation survives `load_from_runtime` (is NOT reset)
|
||||
|
||||
Plan: `plan(25)`
|
||||
|
||||
Build: Same pattern as `mysqlx_config_store_unit-t` — compiles `mysqlx_config_store.cpp` directly with the test harness.
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx config store pure unit tests (25 assertions)`
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Expand `mysqlx_route_store_unit-t.cpp` (8 → 26 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/mysqlx_route_store_unit-t.cpp`
|
||||
|
||||
#### 4a. Existing tests (keep all 8)
|
||||
|
||||
#### 4b. Round-robin thorough (0 → 6)
|
||||
|
||||
- [ ] Test: Round-robin with 3 endpoints cycles through all 3 in order
|
||||
- [ ] Test: Round-robin wraps back to first after visiting all endpoints
|
||||
- [ ] Test: Round-robin counter is per-hostgroup (two hostgroups have independent counters)
|
||||
- [ ] Test: Round-robin with 1 endpoint always returns same endpoint
|
||||
- [ ] Test: `first_available` with 3 endpoints always returns first (never rotates)
|
||||
- [ ] Test: `round_robin` followed by `first_available` on same hostgroup — first_available always returns first
|
||||
|
||||
#### 4c. Fallback (0 → 4)
|
||||
|
||||
- [ ] Test: Primary hostgroup has servers, fallback not used — returns from primary
|
||||
- [ ] Test: Primary empty, fallback has servers — returns from fallback
|
||||
- [ ] Test: Primary empty, fallback empty — returns empty endpoint
|
||||
- [ ] Test: `round_robin_with_fallback` primary has servers — does NOT use fallback
|
||||
|
||||
#### 4d. Inactive routes (0 → 3)
|
||||
|
||||
- [ ] Test: Route with `active=0` is not loaded — `pick_endpoint` returns empty
|
||||
- [ ] Test: Only active routes are loaded — verify count
|
||||
- [ ] Test: All routes inactive — `pick_endpoint` returns empty for all
|
||||
|
||||
#### 4e. Endpoint overrides (0 → 3)
|
||||
|
||||
- [ ] Test: Endpoint with custom mysqlx_port override — picked endpoint uses override
|
||||
- [ ] Test: Endpoint with `use_ssl=1` override — picked endpoint has use_ssl=true
|
||||
- [ ] Test: Endpoint with no override — defaults to mysqlx_port=33060, use_ssl=false
|
||||
|
||||
#### 4f. Multiple routes (0 → 2)
|
||||
|
||||
- [ ] Test: Two routes pointing to different hostgroups — each picks from correct hostgroup
|
||||
- [ ] Test: Two routes pointing to same hostgroup but different strategies — strategies work independently
|
||||
|
||||
Plan: `plan(26)`
|
||||
|
||||
- [ ] **Commit**: `test: expand mysqlx route store unit tests to 26 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Expand `plugin_manager_unit-t.cpp` (7 → 20 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/plugin_manager_unit-t.cpp`
|
||||
|
||||
#### 5a. Existing (keep all 7)
|
||||
|
||||
#### 5b. Load error cases (0 → 7)
|
||||
|
||||
- [ ] Test: `load("")` with empty path — returns false with error message
|
||||
- [ ] Test: `load` with path to regular text file (not a .so) — returns false with dlopen error
|
||||
- [ ] Test: `load` with path to .so missing `proxysql_plugin_descriptor_v1` symbol — returns false
|
||||
- [ ] Test: `load` same plugin twice — both succeed, `size()` == 2
|
||||
- [ ] Test: `init_all` with no plugins — returns true, no error
|
||||
- [ ] Test: `start_all` with no plugins — returns true
|
||||
- [ ] Test: `stop_all` with no plugins — returns true
|
||||
|
||||
#### 5c. Lifecycle edge cases (0 → 6)
|
||||
|
||||
- [ ] Test: `stop_all` before `start_all` — returns true (idempotent)
|
||||
- [ ] Test: `init_all` called twice — returns true (idempotent)
|
||||
- [ ] Test: Plugin with null `init` callback — `init_all` succeeds, plugin marked initialized
|
||||
- [ ] Test: Plugin with null `start` callback — `start_all` succeeds, plugin marked started
|
||||
- [ ] Test: Plugin with null `stop` callback — `stop_all` succeeds
|
||||
- [ ] Test: Destructor with started plugin — calls stop then dlclose, no crash
|
||||
|
||||
Plan: `plan(20)`
|
||||
|
||||
- [ ] **Commit**: `test: expand plugin manager unit tests to 20 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Expand `plugin_registry_unit-t.cpp` (15 → 25 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/plugin_registry_unit-t.cpp`
|
||||
|
||||
#### 6a. Existing (keep all 15)
|
||||
|
||||
#### 6b. Command registration edge cases (0 → 5)
|
||||
|
||||
- [ ] Test: `register_command` with empty string — returns false
|
||||
- [ ] Test: `register_command` with null callback — returns false
|
||||
- [ ] Test: `register_command` with whitespace-only string — canonicalizes to empty, returns false
|
||||
- [ ] Test: `dispatch_admin_command` with empty SQL — returns false
|
||||
- [ ] Test: `dispatch_admin_command` with unregistered command — returns false
|
||||
|
||||
#### 6c. Table registration edge cases (0 → 3)
|
||||
|
||||
- [ ] Test: `register_table` with null table_name — returns false
|
||||
- [ ] Test: `register_table` with empty table_def — returns false
|
||||
- [ ] Test: `register_table` with same table name but different db_kind — both succeed (namespaced)
|
||||
|
||||
#### 6d. Canonicalization (0 → 2)
|
||||
|
||||
- [ ] Test: Command with leading/trailing spaces canonicalizes and matches
|
||||
- [ ] Test: Command with multiple internal spaces and trailing semicolon canonicalizes correctly
|
||||
|
||||
Plan: `plan(25)`
|
||||
|
||||
- [ ] **Commit**: `test: expand plugin registry unit tests to 25 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Expand `test_mysqlx_admin_tables-t.cpp` (23 → 42 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/test_mysqlx_admin_tables-t.cpp`
|
||||
|
||||
#### 7a. Existing (keep all 23)
|
||||
|
||||
#### 7b. LOAD edge cases (0 → 7)
|
||||
|
||||
- [ ] Test: `LOAD MYSQLX USERS TO RUNTIME` with empty config table — runtime has 0 rows, success
|
||||
- [ ] Test: `LOAD MYSQLX ROUTES TO RUNTIME` with empty config table — runtime has 0 rows, success
|
||||
- [ ] Test: `LOAD MYSQLX BACKEND ENDPOINTS TO RUNTIME` with empty config table — runtime has 0 rows, success
|
||||
- [ ] Test: LOAD twice — second LOAD replaces runtime data (not accumulates)
|
||||
- [ ] Test: LOAD after modifying config data — runtime reflects new data
|
||||
- [ ] Test: LOAD with null admindb — returns error
|
||||
- [ ] Test: LOAD with multiple rows — all rows copied
|
||||
|
||||
#### 7c. SAVE data integrity (0 → 4)
|
||||
|
||||
- [ ] Test: LOAD users, modify a runtime row (UPDATE), SAVE — config table reflects modified data
|
||||
- [ ] Test: LOAD routes, add a runtime row (INSERT), SAVE — config table has the new row
|
||||
- [ ] Test: SAVE with empty runtime — config table becomes empty
|
||||
- [ ] Test: SAVE routes then LOAD routes — roundtrip preserves all fields
|
||||
|
||||
#### 7d. Alias dispatch (0 → 8)
|
||||
|
||||
- [ ] Test: `"LOAD MYSQLX USERS FROM MEMORY"` dispatches correctly (alias 1)
|
||||
- [ ] Test: `"LOAD MYSQLX USERS FROM MEM"` dispatches correctly (alias 2)
|
||||
- [ ] Test: `"LOAD MYSQLX USERS TO RUN"` dispatches correctly (alias 3)
|
||||
- [ ] Test: `"SAVE MYSQLX USERS TO MEM"` dispatches correctly (save alias 1)
|
||||
- [ ] Test: `"SAVE MYSQLX USERS FROM RUNTIME"` dispatches correctly (save alias 2)
|
||||
- [ ] Test: `"SAVE MYSQLX USERS FROM RUN"` dispatches correctly (save alias 3)
|
||||
- [ ] Test: `"LOAD MYSQLX ROUTES FROM MEMORY"` dispatches correctly
|
||||
- [ ] Test: `"LOAD MYSQLX BACKEND ENDPOINTS FROM MEMORY"` dispatches correctly
|
||||
|
||||
Plan: `plan(42)`
|
||||
|
||||
- [ ] **Commit**: `test: expand mysqlx admin table tests to 42 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 8: NEW `mysqlx_admin_schema_unit-t.cpp` (15 assertions)
|
||||
|
||||
**Files:**
|
||||
- Create: `test/tap/tests/unit/mysqlx_admin_schema_unit-t.cpp`
|
||||
- Modify: `test/tap/tests/unit/Makefile`
|
||||
|
||||
Tests the DDL schema definitions and command registration in isolation, without loading the plugin `.so`.
|
||||
|
||||
#### 8a. DDL validation (0 → 8)
|
||||
|
||||
- [ ] Test: `mysqlx_users` DDL contains `username`, `active`, `require_tls`, `allowed_auth_methods`, `backend_auth_mode`
|
||||
- [ ] Test: `mysqlx_routes` DDL contains `name`, `bind`, `destination_hostgroup`, `fallback_hostgroup`, `strategy`
|
||||
- [ ] Test: `mysqlx_backend_endpoints` DDL contains `hostname`, `mysql_port`, `mysqlx_port`, `use_ssl`
|
||||
- [ ] Test: All runtime table DDLs match their config counterparts (column-for-column)
|
||||
- [ ] Test: `stats_mysqlx_routes` DDL contains `name`, `ConnOK`, `ConnERR`, `Bytes_data_sent`, `Bytes_data_recv`
|
||||
- [ ] Test: `stats_mysqlx_processlist` DDL contains `username`, `route`, `worker_id`, `backend_host`
|
||||
- [ ] Test: Each config table DDL is valid SQLite (CREATE TABLE succeeds on in-memory DB)
|
||||
- [ ] Test: Each runtime table DDL is valid SQLite (CREATE TABLE succeeds on in-memory DB)
|
||||
|
||||
#### 8b. Command registration (0 → 7)
|
||||
|
||||
- [ ] Test: `mysqlx_register_admin_schema` with null `register_table` callback — returns false
|
||||
- [ ] Test: `mysqlx_register_admin_schema` with null `register_command` callback — returns false
|
||||
- [ ] Test: `mysqlx_register_admin_schema` with valid services — returns true
|
||||
- [ ] Test: After registration, exactly 6 commands are registered (3 LOAD + 3 SAVE)
|
||||
- [ ] Test: After registration, admin_db has tables for all config+runtime pairs
|
||||
- [ ] Test: After registration, config_db has tables for all config pairs
|
||||
- [ ] Test: After registration, stats_db has tables for stats_mysqlx_routes + stats_mysqlx_processlist
|
||||
|
||||
Plan: `plan(15)`
|
||||
|
||||
Build: Compiles `mysqlx_admin_schema.cpp` directly with a mock `ProxySQL_PluginServices`.
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx admin schema unit tests (15 assertions)`
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Expand `test_mysqlx_listener_smoke-t.cpp` (8 → 15 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/test_mysqlx_listener_smoke-t.cpp`
|
||||
|
||||
#### 9a. Existing (keep all 8)
|
||||
|
||||
#### 9b. Listener lifecycle (0 → 7)
|
||||
|
||||
- [ ] Test: `mysqlx_listener_count()` is 0 before any listeners start
|
||||
- [ ] Test: Start listeners with empty routes table — count stays 0, returns true
|
||||
- [ ] Test: Start listeners with 2 routes on different ports — count is 2
|
||||
- [ ] Test: `mysqlx_stop_listeners()` — count drops to 0
|
||||
- [ ] Test: Start listeners after stop — count goes back up
|
||||
- [ ] Test: Listener on `0.0.0.0:PORT` — TCP connect succeeds
|
||||
- [ ] Test: Start listeners with duplicate bind address — only one listener created (or returns false)
|
||||
|
||||
Plan: `plan(15)`
|
||||
|
||||
Note: Use high ports (46000-46999 range) to avoid conflicts. Clean up listeners between sub-tests.
|
||||
|
||||
- [ ] **Commit**: `test: expand mysqlx listener smoke tests to 15 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 10: NEW `mysqlx_protocol_socket_unit-t.cpp` (20 assertions)
|
||||
|
||||
**Files:**
|
||||
- Create: `test/tap/tests/unit/mysqlx_protocol_socket_unit-t.cpp`
|
||||
- Modify: `test/tap/tests/unit/Makefile`
|
||||
|
||||
Tests `mysqlx_read_frame`, `mysqlx_write_all`, `mysqlx_read_exact` using `socketpair()`.
|
||||
|
||||
#### 10a. Frame I/O roundtrip (0 → 8)
|
||||
|
||||
- [ ] Test: `mysqlx_write_all` + `mysqlx_read_exact` roundtrip — bytes match
|
||||
- [ ] Test: `mysqlx_build_frame` + `mysqlx_write_all` on one fd, `mysqlx_read_frame` on other — header and payload match
|
||||
- [ ] Test: Write two frames, read two frames — FIFO order preserved
|
||||
- [ ] Test: `mysqlx_read_frame` with 0 bytes available — blocks (test with non-blocking + immediate EAGAIN)
|
||||
- [ ] Test: `mysqlx_read_exact` with partial data available — blocks until all bytes arrive (write rest in another thread)
|
||||
- [ ] Test: Close one fd, `mysqlx_read_frame` — returns false (EOF)
|
||||
- [ ] Test: Close one fd, `mysqlx_write_all` — returns false (EPIPE)
|
||||
- [ ] Test: `mysqlx_read_exact` with `len=0` — returns true immediately
|
||||
|
||||
#### 10b. Error/OK send+receive (0 → 6)
|
||||
|
||||
- [ ] Test: `mysqlx_send_error` → `mysqlx_read_frame` — verify frame type is server Error
|
||||
- [ ] Test: `mysqlx_send_ok` → `mysqlx_read_frame` — verify frame type is server Ok
|
||||
- [ ] Test: Send error with code 1045 and message "Access denied" — verify payload contains both
|
||||
- [ ] Test: Send ok with long message (256 chars) — roundtrip succeeds
|
||||
- [ ] Test: `mysqlx_send_error` with 2-char SQL state — roundtrip preserves state
|
||||
- [ ] Test: Multiple send_error + send_ok interleaved — each read_frame gets correct type
|
||||
|
||||
#### 10c. Large frames (0 → 6)
|
||||
|
||||
- [ ] Test: 64KB frame roundtrip — succeeds
|
||||
- [ ] Test: 1MB frame roundtrip — succeeds
|
||||
- [ ] Test: Frame at `MYSQLX_MAX_PAYLOAD_SIZE - 1` — succeeds
|
||||
- [ ] Test: `mysqlx_read_frame` with payload_size exceeding `MYSQLX_MAX_PAYLOAD_SIZE` — returns false
|
||||
- [ ] Test: `mysqlx_read_frame` with payload_size == 1 (just message_type, no body) — succeeds with empty payload
|
||||
- [ ] Test: Write 10 small frames rapidly, read all 10 — no data loss, correct order
|
||||
|
||||
Plan: `plan(20)`
|
||||
|
||||
Build: Same pattern as `mysqlx_protocol_unit-t` but also links `protobuf` objects for frame building.
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx protocol socket unit tests (20 assertions)`
|
||||
|
||||
---
|
||||
|
||||
### Task 11: NEW `mysqlx_config_store_concurrent_unit-t.cpp` (15 assertions)
|
||||
|
||||
**Files:**
|
||||
- Create: `test/tap/tests/unit/mysqlx_config_store_concurrent_unit-t.cpp`
|
||||
- Modify: `test/tap/tests/unit/Makefile`
|
||||
|
||||
Tests thread safety of `MysqlxConfigStore`.
|
||||
|
||||
#### 11a. Concurrent reads during load (0 → 5)
|
||||
|
||||
- [ ] Test: Thread A calls `load_from_runtime` while Thread B calls `resolve_identity` — no crash
|
||||
- [ ] Test: Thread A calls `load_from_runtime` while Thread B calls `pick_endpoint` — no crash
|
||||
- [ ] Test: `load_from_runtime` with data while readers are active — readers eventually see new data or old data (never partial)
|
||||
- [ ] Test: Multiple concurrent `resolve_identity` calls — all return valid results (no crash)
|
||||
- [ ] Test: `load_from_runtime` called from 2 threads sequentially (serialized by external lock) — both succeed
|
||||
|
||||
#### 11b. Concurrent endpoint picking (0 → 5)
|
||||
|
||||
- [ ] Test: 4 threads calling `pick_endpoint` on same route — no crash, all get valid endpoints
|
||||
- [ ] Test: 4 threads calling `pick_endpoint` with round_robin strategy — no crash, counter advances correctly
|
||||
- [ ] Test: `pick_endpoint` while `load_from_runtime` replaces data — no crash
|
||||
- [ ] Test: `topology_generation` read while `bump_topology_generation` in another thread — no crash, value is monotonic
|
||||
- [ ] Test: Stress: 10 threads, 10000 `pick_endpoint` calls each — no crash, no hang
|
||||
|
||||
#### 11c. Data consistency after reload (0 → 5)
|
||||
|
||||
- [ ] Test: Load config A, read identity X, load config B (no X), read returns nullopt — data is replaced atomically
|
||||
- [ ] Test: Load config A, pick endpoint, load config B (different endpoints), pick returns new data — complete swap
|
||||
- [ ] Test: Load with routes R1,R2, load with routes R2,R3 — R1 no longer resolvable, R3 is
|
||||
- [ ] Test: Load with hostgroup HG1 having servers, load with HG1 empty — pick_endpoint returns empty
|
||||
- [ ] Test: Round-robin counter resets sense: load, pick 2 (advances counter), reload, pick 2 more — counter behavior is defined
|
||||
|
||||
Plan: `plan(15)`
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx config store concurrent unit tests (15 assertions)`
|
||||
|
||||
---
|
||||
|
||||
### Task 12: Expand `plugin_config_unit-t.cpp` (12 → 20 assertions)
|
||||
|
||||
**Files:**
|
||||
- Modify: `test/tap/tests/unit/plugin_config_unit-t.cpp`
|
||||
|
||||
#### 12a. Existing (keep all 12)
|
||||
|
||||
#### 12b. Config parsing edge cases (0 → 8)
|
||||
|
||||
- [ ] Test: Config with empty `plugins=()` — `plugin_modules` is empty
|
||||
- [ ] Test: Config with `plugins=("path/a.so","path/b.so")` — two entries stored
|
||||
- [ ] Test: Config with `plugins=("path with spaces.so")` — path preserved with spaces
|
||||
- [ ] Test: Config with no `plugins` key — `plugin_modules` is empty (not an error)
|
||||
- [ ] Test: `proxysql_load_plugin_modules_from_config` called twice — second call replaces first
|
||||
- [ ] Test: `proxysql_stop_configured_plugins` with null manager — returns true
|
||||
- [ ] Test: `proxysql_start_configured_plugins` with null manager — returns true
|
||||
- [ ] Test: `proxysql_load_configured_plugins` with empty list — returns true, manager is null
|
||||
|
||||
Plan: `plan(20)`
|
||||
|
||||
- [ ] **Commit**: `test: expand plugin config unit tests to 20 assertions`
|
||||
|
||||
---
|
||||
|
||||
### Task 13: End-to-End Test Infrastructure
|
||||
|
||||
**Files:**
|
||||
- Create: `test/infra/docker-compose-mysqlx.yml`
|
||||
- Create: `test/tap/tests/test_mysqlx_e2e_handshake-t.cpp`
|
||||
|
||||
This task requires Docker MySQL 8.x with X Protocol enabled (port 33060).
|
||||
|
||||
#### 13a. Docker infrastructure
|
||||
|
||||
- [ ] Create `test/infra/docker-compose-mysqlx.yml` based on existing mysql84 compose, exposing port 33060
|
||||
- [ ] MySQL 8.x config must enable `mysqlx` plugin and create test user with MYSQL41 auth
|
||||
|
||||
#### 13b. E2E handshake test (0 → 10+)
|
||||
|
||||
- [ ] Test: TCP connect to ProxySQL X port — succeeds
|
||||
- [ ] Test: Server sends CapabilitiesGet — verify frame type
|
||||
- [ ] Test: Client sends CapabilitiesSet with MYSQL41 — server responds with Ok
|
||||
- [ ] Test: Server sends AuthenticateStart with MYSQL41 method
|
||||
- [ ] Test: Client sends AuthContinue with scramble — server responds with Ok
|
||||
- [ ] Test: Full handshake succeeds — session is authenticated
|
||||
- [ ] Test: Handshake with wrong password — server sends Error
|
||||
- [ ] Test: Handshake with nonexistent user — server sends Error
|
||||
- [ ] Test: Handshake with user not in mysqlx_users — server sends Error
|
||||
- [ ] Test: Handshake with user whose `x_enabled=0` — server sends Error
|
||||
|
||||
Plan: `plan(10)`
|
||||
|
||||
Note: This task depends on ProxySQL being built and running with the mysqlx plugin loaded, connected to a Docker MySQL 8.x backend. The test connects to ProxySQL's X port, not directly to MySQL.
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx E2E handshake test infrastructure`
|
||||
|
||||
---
|
||||
|
||||
### Task 14: End-to-End Routing Test
|
||||
|
||||
**Files:**
|
||||
- Create: `test/tap/tests/test_mysqlx_e2e_routing-t.cpp`
|
||||
|
||||
#### 14a. Query routing (0 → 10+)
|
||||
|
||||
- [ ] Test: X Protocol SQL statement roundtrip — send `SELECT 1`, receive resultset
|
||||
- [ ] Test: `SELECT @@hostname` returns backend hostname (confirms routing through proxy)
|
||||
- [ ] Test: Multiple queries on same session — all succeed
|
||||
- [ ] Test: Route to specific hostgroup — query reaches correct backend
|
||||
- [ ] Test: Round-robin routing — successive connections hit different backends
|
||||
- [ ] Test: Session close — clean disconnect, no crash
|
||||
- [ ] Test: Multiple concurrent sessions — all succeed independently
|
||||
- [ ] Test: Large result set (1000 rows) — proxied correctly
|
||||
- [ ] Test: Backend disconnect during query — error propagated to client
|
||||
- [ ] Test: Stats counters increment after successful connection
|
||||
|
||||
Plan: `plan(10)`
|
||||
|
||||
Note: Requires same Docker infrastructure as Task 13.
|
||||
|
||||
- [ ] **Commit**: `test: add mysqlx E2E routing test`
|
||||
|
||||
---
|
||||
|
||||
## Build System Integration
|
||||
|
||||
Each new test file needs a corresponding build target in `test/tap/tests/unit/Makefile`. The pattern follows existing mysqlx tests:
|
||||
|
||||
- Tests that compile plugin source directly: link `$(PROXYSQL_PATH)/plugins/mysqlx/src/<file>.cpp` with `-I$(PROXYSQL_PATH)/plugins/mysqlx/include`
|
||||
- Tests that need protobuf: add `$(MYSQLX_PROTO_OBJS)` and `-lprotobuf -lssl -lcrypto`
|
||||
- Tests that need the plugin `.so`: depend on `mysqlx_plugin_build` phony target
|
||||
- Socket-based tests: no extra deps beyond what `mysqlx_protocol_unit-t` already uses
|
||||
|
||||
New test files to register in `UNIT_TESTS` list and `groups.json`:
|
||||
|
||||
```
|
||||
mysqlx_config_store_pure_unit-t → unit-tests-g1
|
||||
mysqlx_admin_schema_unit-t → unit-tests-g1
|
||||
mysqlx_protocol_socket_unit-t → unit-tests-g1
|
||||
mysqlx_config_store_concurrent_unit-t → unit-tests-g1
|
||||
```
|
||||
|
||||
E2E tests to register:
|
||||
|
||||
```
|
||||
test_mysqlx_e2e_handshake-t → new group: mysqlx-e2e-g1 (requires Docker)
|
||||
test_mysqlx_e2e_routing-t → new group: mysqlx-e2e-g1
|
||||
```
|
||||
|
||||
## Assertion Count Summary
|
||||
|
||||
| File | Before | After | Delta |
|
||||
|------|--------|-------|-------|
|
||||
| mysqlx_protocol_unit-t | 10 | 42 | +32 |
|
||||
| mysqlx_stats_unit-t | 7 | 22 | +15 |
|
||||
| mysqlx_config_store_pure_unit-t (NEW) | 0 | 25 | +25 |
|
||||
| mysqlx_route_store_unit-t | 8 | 26 | +18 |
|
||||
| plugin_manager_unit-t | 7 | 20 | +13 |
|
||||
| plugin_registry_unit-t | 15 | 25 | +10 |
|
||||
| test_mysqlx_admin_tables-t | 23 | 42 | +19 |
|
||||
| mysqlx_admin_schema_unit-t (NEW) | 0 | 15 | +15 |
|
||||
| test_mysqlx_listener_smoke-t | 8 | 15 | +7 |
|
||||
| mysqlx_protocol_socket_unit-t (NEW) | 0 | 20 | +20 |
|
||||
| mysqlx_config_store_concurrent_unit-t (NEW) | 0 | 15 | +15 |
|
||||
| plugin_config_unit-t | 12 | 20 | +8 |
|
||||
| test_mysqlx_e2e_handshake-t (NEW) | 0 | 10 | +10 |
|
||||
| test_mysqlx_e2e_routing-t (NEW) | 0 | 10 | +10 |
|
||||
| **Total** | **112** | **307** | **+237** |
|
||||
Loading…
Reference in new issue