v/vlib/pool/connection_test.v

293 lines
7.1 KiB
V

// vtest build: !windows // msvc hung, maybe sync/atomic bug, gcc on windows does too, although less frequently
import time
import sync
import pool
import rand
// Mock connection implementation
struct MockConn {
mut:
healthy bool
close_flag bool
reset_flag bool
closed int
id string
}
fn (mut c MockConn) validate() !bool {
return c.healthy
}
fn (mut c MockConn) close() ! {
if c.close_flag {
return error('simulated close error')
}
c.closed++
}
fn (mut c MockConn) reset() ! {
if c.reset_flag {
return error('simulated reset error')
}
c.reset_flag = true
}
// Test utility functions
fn create_mock_factory(mut arr []&pool.ConnectionPoolable, healthy bool, fail_times int) fn () !&pool.ConnectionPoolable {
mut count := 0
return fn [mut arr, healthy, fail_times, mut count] () !&pool.ConnectionPoolable {
if count < fail_times {
count++
return error('connection creation failed')
}
mut conn := &MockConn{
healthy: healthy
id: rand.uuid_v7()
}
arr << conn
return conn
}
}
fn is_same_conn(conn1 &pool.ConnectionPoolable, conn2 &pool.ConnectionPoolable) bool {
c1 := conn1 as MockConn
c2 := conn2 as MockConn
return c1.id == c2.id
}
// Test cases
fn test_basic_usage() {
for _ in 0 .. 1 {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
config := pool.ConnectionPoolConfig{
max_conns: 5
min_idle_conns: 2
idle_timeout: 100 * time.millisecond
get_timeout: 50 * time.millisecond
}
mut p := pool.new_connection_pool(factory, config)!
// Acquire a connection
mut conn1 := p.get()!
assert p.stats().active_conns == 1
// Acquire multiple connections
mut conns := [p.get()!, p.get()!, p.get()!]
assert p.stats().active_conns == 4
// Return connections
for c in conns {
p.put(c)!
}
p.put(conn1)!
assert p.stats().active_conns == 0
assert p.stats().total_conns >= 4
p.close()
}
}
fn test_pool_exhaustion() {
for i in 0 .. 1 {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
config := pool.ConnectionPoolConfig{
max_conns: 2
min_idle_conns: 1
get_timeout: 10 * time.millisecond
}
mut p := pool.new_connection_pool(factory, config)!
// Acquire all connections
c1 := p.get()!
c2 := p.get()!
assert p.stats().active_conns == 2
// Attempt to acquire third connection (should timeout)
p.get() or { assert err.msg().contains('timeout') }
// After returning, should be able to acquire again
p.put(c2)!
c3 := p.get()!
assert is_same_conn(c3, c2)
assert p.stats().active_conns == 2
p.close()
}
}
fn test_connection_validation() {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, false, 0) // Create unhealthy connections
config := pool.ConnectionPoolConfig{
min_idle_conns: 1
}
mut p := pool.new_connection_pool(factory, config) or {
assert err.msg().contains('connection validation failed')
return
}
defer {
p.close()
}
}
fn test_eviction() {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
config := pool.ConnectionPoolConfig{
max_lifetime: 10 * time.millisecond
idle_timeout: 10 * time.millisecond
min_idle_conns: 0
}
mut p := pool.new_connection_pool(factory, config)!
defer {
p.close()
}
// Acquire and return a connection
c1 := p.get()!
p.put(c1)!
// Wait longer than timeout thresholds
time.sleep(100 * time.millisecond)
p.send_eviction(.urgent)
time.sleep(10 * time.millisecond)
stats := p.stats()
assert stats.evicted_count > 0
assert stats.total_conns == 0
assert stats.idle_conns == 0
c2 := p.get()!
assert !is_same_conn(c1, c2)
assert p.stats().total_conns == 1
assert p.stats().evicted_count > 0
}
fn test_retry_mechanism() {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 3) // First 3 attempts fail
config := pool.ConnectionPoolConfig{
max_retry_attempts: 5
retry_base_delay: 10 * time.millisecond
min_idle_conns: 0 // No idle connections
}
mut p := pool.new_connection_pool(factory, config)!
defer {
p.close()
}
// Should successfully create connection after retries
conn := p.get()!
assert test_conns.len == 1
assert p.stats().creation_errors == 3
}
fn test_concurrent_access() {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
config := pool.ConnectionPoolConfig{
max_conns: 10
}
mut p := pool.new_connection_pool(factory, config)!
defer {
p.close()
}
mut wg := sync.new_waitgroup()
for _ in 0 .. 20 {
wg.add(1)
spawn fn (mut p pool.ConnectionPool, mut wg sync.WaitGroup) ! {
defer { wg.done() }
conn := p.get()!
time.sleep(5 * time.millisecond)
p.put(conn)!
}(mut p, mut wg)
}
wg.wait()
stats := p.stats()
assert stats.total_conns <= 10
assert stats.active_conns == 0
}
fn test_pool_close() {
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
c := p.get()!
p.put(c)!
assert p.stats().active_conns == 0
assert p.stats().idle_conns >= 5
p.close()
// Attempt to acquire connection after close
p.get() or { assert err.msg().contains('closed') }
assert p.stats().active_conns == 0
assert p.stats().idle_conns == 0
// Verify connection was closed
mock_conn := test_conns[0] as MockConn
assert mock_conn.closed == 1
}
fn test_config_update() {
mut dummy := []&pool.ConnectionPoolable{}
mut p := pool.new_connection_pool(create_mock_factory(mut dummy, true, 0), pool.ConnectionPoolConfig{
max_conns: 2
min_idle_conns: 1
})!
defer {
p.close()
}
assert p.stats().idle_conns == 1
// Modify configuration
new_config := pool.ConnectionPoolConfig{
max_conns: 5
min_idle_conns: 3
}
p.update_config(new_config)!
// Trigger idle connection replenishment
time.sleep(100 * time.millisecond)
assert p.stats().idle_conns >= 3
}
fn test_error_handling() {
// Test close error handling
mut test_conns := []&pool.ConnectionPoolable{}
factory := create_mock_factory(mut test_conns, true, 0)
mut p := pool.new_connection_pool(factory, pool.ConnectionPoolConfig{})!
defer {
p.close()
}
// default configuration has 5 idle_conns
assert p.stats().idle_conns == 5
// Bug Fix Needed! msvc generated wrong code for `mut conn := p.get()! as MockConn`
mut connx := p.get()!
mut conn := connx as MockConn
assert p.stats().active_conns == 1
// it depend on `background_maintenance` thread to keep 5 idle_conns
assert p.stats().idle_conns >= 4
conn.close_flag = true
p.put(conn) or { assert err.msg().contains('simulated close error') }
assert p.stats().active_conns == 0
// it depend on `background_maintenance` thread to keep 5 idle_conns
assert p.stats().idle_conns >= 4
// Test reset error handling
conn.reset_flag = true
p.put(conn) or { assert err.msg().contains('reset') }
assert p.stats().active_conns == 0
// it depend on `background_maintenance` thread to keep 5 idle_conns
assert p.stats().idle_conns >= 4
}