Upload files to "tests"
This commit is contained in:
411
tests/autoscaling_test.lua
Normal file
411
tests/autoscaling_test.lua
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
-- tests/autoscaling_test.lua
|
||||||
|
-- Тестирование автоматического масштабирования кластера
|
||||||
|
|
||||||
|
local function setup_test_environment()
|
||||||
|
print("=== Setup Test Environment ===")
|
||||||
|
|
||||||
|
-- Создаём тестовую базу данных
|
||||||
|
local result = db.execute("CREATE DATABASE IF NOT EXISTS autoscale_test")
|
||||||
|
assert(result, "Failed to create database")
|
||||||
|
|
||||||
|
result = db.execute("USE autoscale_test")
|
||||||
|
assert(result, "Failed to use database")
|
||||||
|
|
||||||
|
-- Создаём тестовую коллекцию
|
||||||
|
result = db.execute([[
|
||||||
|
CREATE COLLECTION IF NOT EXISTS metrics_collection
|
||||||
|
WITH SETTINGS {
|
||||||
|
max_documents = 1000000,
|
||||||
|
validate_schema = true
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
assert(result, "Failed to create collection")
|
||||||
|
|
||||||
|
-- Получаем конфигурацию автомасштабирования
|
||||||
|
local config = db.execute("SHOW AUTOSCALING CONFIG")
|
||||||
|
print("Current autoscaling config:")
|
||||||
|
print(config)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function generate_test_load(duration_seconds, target_qps)
|
||||||
|
print(string.format("Generating %.2f QPS load for %d seconds", target_qps, duration_seconds))
|
||||||
|
|
||||||
|
local start_time = os.time()
|
||||||
|
local requests = 0
|
||||||
|
local errors = 0
|
||||||
|
local latencies = {}
|
||||||
|
|
||||||
|
local interval = 1.0 / target_qps
|
||||||
|
|
||||||
|
while os.time() - start_time < duration_seconds do
|
||||||
|
local req_start = os.clock()
|
||||||
|
|
||||||
|
-- Симуляция запроса на запись
|
||||||
|
local success, err = pcall(function()
|
||||||
|
local doc = {
|
||||||
|
_id = string.format("doc_%d_%d", os.time(), math.random(1, 10000)),
|
||||||
|
timestamp = os.time() * 1000,
|
||||||
|
value = math.random(1, 1000),
|
||||||
|
type = "test_metric"
|
||||||
|
}
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO metrics_collection VALUES (%s)",
|
||||||
|
db.json_encode(doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
local req_end = os.clock()
|
||||||
|
local latency = (req_end - req_start) * 1000 -- в миллисекундах
|
||||||
|
|
||||||
|
table.insert(latencies, latency)
|
||||||
|
requests = requests + 1
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
errors = errors + 1
|
||||||
|
print("Request failed: " .. tostring(err))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Контроль скорости
|
||||||
|
local elapsed = os.clock() - req_start
|
||||||
|
if elapsed < interval then
|
||||||
|
local sleep_time = interval - elapsed
|
||||||
|
db.sleep(sleep_time)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Вычисляем статистику
|
||||||
|
local total_latency = 0
|
||||||
|
for _, lat in ipairs(latencies) do
|
||||||
|
total_latency = total_latency + lat
|
||||||
|
end
|
||||||
|
local avg_latency = total_latency / #latencies
|
||||||
|
|
||||||
|
-- Сортируем для перцентилей
|
||||||
|
table.sort(latencies)
|
||||||
|
local p95_latency = latencies[math.floor(#latencies * 0.95)]
|
||||||
|
local p99_latency = latencies[math.floor(#latencies * 0.99)]
|
||||||
|
|
||||||
|
print(string.format([[
|
||||||
|
=== Load Test Results ===
|
||||||
|
Duration: %d seconds
|
||||||
|
Total Requests: %d
|
||||||
|
Successful: %d
|
||||||
|
Errors: %d
|
||||||
|
Actual QPS: %.2f
|
||||||
|
Avg Latency: %.2f ms
|
||||||
|
P95 Latency: %.2f ms
|
||||||
|
P99 Latency: %.2f ms
|
||||||
|
]], duration_seconds, requests, requests - errors, errors,
|
||||||
|
requests / duration_seconds, avg_latency, p95_latency, p99_latency))
|
||||||
|
|
||||||
|
return {
|
||||||
|
requests = requests,
|
||||||
|
errors = errors,
|
||||||
|
avg_latency = avg_latency,
|
||||||
|
p95_latency = p95_latency,
|
||||||
|
p99_latency = p99_latency,
|
||||||
|
actual_qps = requests / duration_seconds
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function monitor_cluster_size(interval_seconds, duration_seconds)
|
||||||
|
print(string.format("Monitoring cluster size every %d seconds for %d seconds",
|
||||||
|
interval_seconds, duration_seconds))
|
||||||
|
|
||||||
|
local history = {}
|
||||||
|
local start_time = os.time()
|
||||||
|
|
||||||
|
while os.time() - start_time < duration_seconds do
|
||||||
|
local status = db.execute("SHOW CLUSTER STATUS")
|
||||||
|
local cluster_info = db.json_decode(status)
|
||||||
|
|
||||||
|
table.insert(history, {
|
||||||
|
timestamp = os.time(),
|
||||||
|
nodes = cluster_info.total_nodes,
|
||||||
|
active_nodes = cluster_info.active_nodes,
|
||||||
|
leader = cluster_info.leader_id,
|
||||||
|
health = cluster_info.health
|
||||||
|
})
|
||||||
|
|
||||||
|
print(string.format("Time %ds: Nodes=%d, Active=%d, Health=%s",
|
||||||
|
os.time() - start_time,
|
||||||
|
cluster_info.total_nodes,
|
||||||
|
cluster_info.active_nodes,
|
||||||
|
cluster_info.health))
|
||||||
|
|
||||||
|
db.sleep(interval_seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
return history
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_scale_up_on_high_load()
|
||||||
|
print("\n=== Test 1: Scale Up on High Load ===")
|
||||||
|
|
||||||
|
-- Проверяем начальное состояние
|
||||||
|
local initial_status = db.execute("SHOW CLUSTER STATUS")
|
||||||
|
local initial_info = db.json_decode(initial_status)
|
||||||
|
local initial_nodes = initial_info.total_nodes
|
||||||
|
print(string.format("Initial cluster size: %d nodes", initial_nodes))
|
||||||
|
|
||||||
|
-- Запускаем мониторинг в фоне
|
||||||
|
local monitor_thread = coroutine.create(function()
|
||||||
|
return monitor_cluster_size(5, 180) -- мониторим 3 минуты
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Генерируем высокую нагрузку
|
||||||
|
local load_results = generate_test_load(60, 500) -- 60 секунд, 500 QPS
|
||||||
|
|
||||||
|
-- Ожидаем возможного масштабирования
|
||||||
|
print("Waiting for potential scale up...")
|
||||||
|
db.sleep(120) -- ждём 2 минуты
|
||||||
|
|
||||||
|
-- Проверяем результат
|
||||||
|
local final_status = db.execute("SHOW CLUSTER STATUS")
|
||||||
|
local final_info = db.json_decode(final_status)
|
||||||
|
local final_nodes = final_info.total_nodes
|
||||||
|
|
||||||
|
print(string.format("Final cluster size: %d nodes", final_nodes))
|
||||||
|
|
||||||
|
if final_nodes > initial_nodes then
|
||||||
|
print("✅ Scale Up successful: cluster expanded from " ..
|
||||||
|
initial_nodes .. " to " .. final_nodes .. " nodes")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ No scale up detected (may be at max capacity or cooldown)")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_scale_down_on_low_load()
|
||||||
|
print("\n=== Test 2: Scale Down on Low Load ===")
|
||||||
|
|
||||||
|
local initial_status = db.execute("SHOW CLUSTER STATUS")
|
||||||
|
local initial_info = db.json_decode(initial_status)
|
||||||
|
local initial_nodes = initial_info.total_nodes
|
||||||
|
|
||||||
|
if initial_nodes <= 1 then
|
||||||
|
print("⚠️ Cannot test scale down: only one node in cluster")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Initial cluster size: %d nodes", initial_nodes))
|
||||||
|
|
||||||
|
-- Ждём снижения нагрузки
|
||||||
|
print("Waiting for load to decrease and scale down...")
|
||||||
|
db.sleep(300) -- ждём 5 минут
|
||||||
|
|
||||||
|
local final_status = db.execute("SHOW CLUSTER STATUS")
|
||||||
|
local final_info = db.json_decode(final_status)
|
||||||
|
local final_nodes = final_info.total_nodes
|
||||||
|
|
||||||
|
print(string.format("Final cluster size: %d nodes", final_nodes))
|
||||||
|
|
||||||
|
if final_nodes < initial_nodes then
|
||||||
|
print("✅ Scale Down successful: cluster reduced from " ..
|
||||||
|
initial_nodes .. " to " .. final_nodes .. " nodes")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ No scale down detected (may be at min capacity or cooldown)")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_predictive_scaling()
|
||||||
|
print("\n=== Test 3: Predictive Scaling ===")
|
||||||
|
|
||||||
|
-- Включаем прогнозирование
|
||||||
|
db.execute("SET AUTOSCALING PREDICTIVE true")
|
||||||
|
|
||||||
|
-- Создаём растущую нагрузку
|
||||||
|
print("Generating increasing load pattern...")
|
||||||
|
|
||||||
|
local function generate_ramp_load(initial_qps, final_qps, duration_seconds)
|
||||||
|
local step_duration = duration_seconds / 10
|
||||||
|
for step = 0, 9 do
|
||||||
|
local target_qps = initial_qps + (final_qps - initial_qps) * step / 10
|
||||||
|
print(string.format("Step %d/10: %.2f QPS", step + 1, target_qps))
|
||||||
|
generate_test_load(step_duration, target_qps)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
generate_ramp_load(50, 600, 120)
|
||||||
|
|
||||||
|
-- Проверяем, произошло ли масштабирование до достижения пика
|
||||||
|
local scaling_history = db.execute("SHOW AUTOSCALING HISTORY")
|
||||||
|
local history = db.json_decode(scaling_history)
|
||||||
|
|
||||||
|
local scaled_before_peak = false
|
||||||
|
for _, event in ipairs(history) do
|
||||||
|
if event.decision == "scale_up" then
|
||||||
|
local load_at_scale = event.avg_load
|
||||||
|
if load_at_scale < 0.85 then -- Масштабирование до достижения 85% нагрузки
|
||||||
|
scaled_before_peak = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if scaled_before_peak then
|
||||||
|
print("✅ Predictive scaling successful: scaled before reaching peak load")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ Predictive scaling may not be working optimally")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_autoscaling_metrics()
|
||||||
|
print("\n=== Test 4: Autoscaling Metrics ===")
|
||||||
|
|
||||||
|
local metrics = db.execute("SHOW AUTOSCALING METRICS")
|
||||||
|
local metrics_data = db.json_decode(metrics)
|
||||||
|
|
||||||
|
print("Current autoscaling metrics:")
|
||||||
|
print(string.format(" Current nodes: %d", metrics_data.current_nodes or 0))
|
||||||
|
print(string.format(" Min nodes: %d", metrics_data.min_nodes or 0))
|
||||||
|
print(string.format(" Max nodes: %d", metrics_data.max_nodes or 0))
|
||||||
|
print(string.format(" Last scale up: %s", metrics_data.last_scale_up or "never"))
|
||||||
|
print(string.format(" Last scale down: %s", metrics_data.last_scale_down or "never"))
|
||||||
|
print(string.format(" Evaluation count: %d", metrics_data.evaluation_count or 0))
|
||||||
|
print(string.format(" Predictive enabled: %s", tostring(metrics_data.predictive_enabled)))
|
||||||
|
|
||||||
|
-- Проверяем наличие критических метрик
|
||||||
|
local required_fields = {
|
||||||
|
"current_nodes", "min_nodes", "max_nodes",
|
||||||
|
"evaluation_count", "predictive_enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field in ipairs(required_fields) do
|
||||||
|
if metrics_data[field] == nil then
|
||||||
|
print("❌ Missing metric: " .. field)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✅ All required metrics present")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_cooldown_behavior()
|
||||||
|
print("\n=== Test 5: Cooldown Behavior ===")
|
||||||
|
|
||||||
|
-- Быстро генерируем две всплески нагрузки
|
||||||
|
for i = 1, 2 do
|
||||||
|
print(string.format("Load spike %d/2", i))
|
||||||
|
generate_test_load(30, 800)
|
||||||
|
db.sleep(10) -- Короткая пауза между всплесками
|
||||||
|
end
|
||||||
|
|
||||||
|
local history = db.execute("SHOW AUTOSCALING HISTORY")
|
||||||
|
local history_data = db.json_decode(history)
|
||||||
|
|
||||||
|
-- Проверяем, что масштабирование не происходило слишком часто
|
||||||
|
local scale_ups = {}
|
||||||
|
for _, event in ipairs(history_data) do
|
||||||
|
if event.decision == "scale_up" then
|
||||||
|
table.insert(scale_ups, event.timestamp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local cooldown_ok = true
|
||||||
|
for i = 2, #scale_ups do
|
||||||
|
local time_diff = scale_ups[i] - scale_ups[i-1]
|
||||||
|
if time_diff < 300 then -- 5 минут cooldown
|
||||||
|
cooldown_ok = false
|
||||||
|
print(string.format("⚠️ Scale ups too close: %d seconds apart", time_diff))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cooldown_ok then
|
||||||
|
print("✅ Cooldown mechanism working properly")
|
||||||
|
else
|
||||||
|
print("❌ Cooldown mechanism may be misconfigured")
|
||||||
|
end
|
||||||
|
|
||||||
|
return cooldown_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_node_selection_for_scale_down()
|
||||||
|
print("\n=== Test 6: Node Selection for Scale Down ===")
|
||||||
|
|
||||||
|
-- Получаем нагрузку на узлах
|
||||||
|
local nodes_load = db.execute("SHOW NODES LOAD")
|
||||||
|
local load_data = db.json_decode(nodes_load)
|
||||||
|
|
||||||
|
print("Node load distribution:")
|
||||||
|
local loads = {}
|
||||||
|
for _, node in ipairs(load_data) do
|
||||||
|
table.insert(loads, node.load)
|
||||||
|
print(string.format(" %s: %.2f%%", node.id, node.load * 100))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Сортируем узлы по нагрузке
|
||||||
|
table.sort(load_data, function(a, b) return a.load < b.load end)
|
||||||
|
|
||||||
|
-- Наименее загруженный узел должен быть кандидатом на удаление
|
||||||
|
local lowest_load_node = load_data[1]
|
||||||
|
print(string.format("Lowest load node: %s (%.2f%%)",
|
||||||
|
lowest_load_node.id, lowest_load_node.load * 100))
|
||||||
|
|
||||||
|
-- Запрашиваем кандидата на удаление у системы
|
||||||
|
local scale_down_candidate = db.execute("SHOW SCALE_DOWN_CANDIDATE")
|
||||||
|
local candidate = db.json_decode(scale_down_candidate)
|
||||||
|
|
||||||
|
if candidate.id == lowest_load_node.id then
|
||||||
|
print("✅ Correct node selected for scale down")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print(string.format("❌ Wrong node selected. Expected: %s, Got: %s",
|
||||||
|
lowest_load_node.id, candidate.id))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Функция очистки
|
||||||
|
local function cleanup()
|
||||||
|
print("\n=== Cleanup ===")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS autoscale_test")
|
||||||
|
print("Test database removed")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запуск всех тестов
|
||||||
|
local function run_all_autoscaling_tests()
|
||||||
|
print("╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ AUTOSCALING TESTS SUITE ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
setup_test_environment()
|
||||||
|
|
||||||
|
local results = {
|
||||||
|
test_scale_up_on_high_load(),
|
||||||
|
test_scale_down_on_low_load(),
|
||||||
|
test_predictive_scaling(),
|
||||||
|
test_autoscaling_metrics(),
|
||||||
|
test_cooldown_behavior(),
|
||||||
|
test_node_selection_for_scale_down()
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Подведение итогов
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ TEST RESULTS ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local passed = 0
|
||||||
|
for i, result in ipairs(results) do
|
||||||
|
local status = result and "✅ PASSED" or "⚠️ SKIPPED/FAILED"
|
||||||
|
print(string.format("Test %d: %s", i, status))
|
||||||
|
if result then passed = passed + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("\nTotal: %d/%d tests passed", passed, #results))
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
return passed == #results
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запуск
|
||||||
|
return run_all_autoscaling_tests()
|
||||||
342
tests/backpressure_test.lua
Normal file
342
tests/backpressure_test.lua
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
-- tests/backpressure_test.lua
|
||||||
|
-- Тестирование механизма backpressure при перегрузке
|
||||||
|
|
||||||
|
local function setup_backpressure_test()
|
||||||
|
print("=== Setup Backpressure Test ===")
|
||||||
|
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS backpressure_test")
|
||||||
|
db.execute("USE backpressure_test")
|
||||||
|
|
||||||
|
db.execute([[
|
||||||
|
CREATE COLLECTION IF NOT EXISTS test_collection
|
||||||
|
WITH SETTINGS {
|
||||||
|
max_documents = 10000000
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Включаем backpressure с агрессивными настройками для теста
|
||||||
|
db.execute([[
|
||||||
|
SET BACKPRESSURE CONFIG {
|
||||||
|
enabled = true,
|
||||||
|
cpu_threshold = 0.50,
|
||||||
|
memory_threshold = 0.60,
|
||||||
|
queue_size_threshold = 100,
|
||||||
|
connection_threshold = 50,
|
||||||
|
low_delay_ms = 50,
|
||||||
|
medium_reject_prob = 30,
|
||||||
|
high_reject_prob = 70
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_level_none()
|
||||||
|
print("\n=== Test 1: Backpressure Level NONE ===")
|
||||||
|
|
||||||
|
-- При нормальной нагрузке
|
||||||
|
local start_time = os.clock()
|
||||||
|
local success_count = 0
|
||||||
|
local reject_count = 0
|
||||||
|
|
||||||
|
for i = 1, 100 do
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format([[
|
||||||
|
INSERT INTO test_collection VALUES {
|
||||||
|
_id = "doc_%d",
|
||||||
|
value = %d,
|
||||||
|
timestamp = %d
|
||||||
|
}
|
||||||
|
]], i, i, os.time() * 1000))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
success_count = success_count + 1
|
||||||
|
else
|
||||||
|
reject_count = reject_count + 1
|
||||||
|
if string.find(err, "backpressure") then
|
||||||
|
print("Unexpected rejection: " .. err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local duration = os.clock() - start_time
|
||||||
|
|
||||||
|
print(string.format("Success: %d, Rejected: %d, Duration: %.3fs",
|
||||||
|
success_count, reject_count, duration))
|
||||||
|
|
||||||
|
if reject_count == 0 then
|
||||||
|
print("✅ No rejections under normal load")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Unexpected rejections: " .. reject_count)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function simulate_backpressure_condition()
|
||||||
|
print("Simulating high load condition...")
|
||||||
|
|
||||||
|
-- Симулируем высокую нагрузку
|
||||||
|
local threads = {}
|
||||||
|
for i = 1, 100 do
|
||||||
|
table.insert(threads, coroutine.create(function()
|
||||||
|
for j = 1, 100 do
|
||||||
|
local success = pcall(function()
|
||||||
|
db.execute(string.format([[
|
||||||
|
SELECT * FROM test_collection WHERE value > %d
|
||||||
|
]], math.random(1, 50)))
|
||||||
|
end)
|
||||||
|
if not success then
|
||||||
|
coroutine.yield(false)
|
||||||
|
end
|
||||||
|
db.sleep(0.001)
|
||||||
|
end
|
||||||
|
coroutine.yield(true)
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запускаем все потоки
|
||||||
|
for _, thread in ipairs(threads) do
|
||||||
|
coroutine.resume(thread)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ждём, пока сработает backpressure
|
||||||
|
db.sleep(5)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_level_medium()
|
||||||
|
print("\n=== Test 2: Backpressure Level MEDIUM ===")
|
||||||
|
|
||||||
|
simulate_backpressure_condition()
|
||||||
|
|
||||||
|
local status = db.execute("SHOW BACKPRESSURE STATUS")
|
||||||
|
local bp_status = db.json_decode(status)
|
||||||
|
|
||||||
|
print(string.format("Current backpressure level: %s", bp_status.current_level))
|
||||||
|
print(string.format("Write allowed: %s", tostring(bp_status.write_allowed)))
|
||||||
|
print(string.format("Read allowed: %s", tostring(bp_status.read_allowed)))
|
||||||
|
print(string.format("Reject probability: %d%%", bp_status.reject_probability or 0))
|
||||||
|
|
||||||
|
-- Тестируем процент отказов
|
||||||
|
local total_requests = 500
|
||||||
|
local rejected = 0
|
||||||
|
|
||||||
|
for i = 1, total_requests do
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute("INSERT INTO test_collection VALUES {_id = 'test_" .. i .. "', value = " .. i .. "}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not success and string.find(err or "", "rejected") then
|
||||||
|
rejected = rejected + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local reject_rate = (rejected / total_requests) * 100
|
||||||
|
print(string.format("Actual reject rate: %.1f%% (expected: ~%d%%)",
|
||||||
|
reject_rate, bp_status.reject_probability or 30))
|
||||||
|
|
||||||
|
-- Проверяем, что reject_rate в разумных пределах
|
||||||
|
local expected_reject = bp_status.reject_probability or 30
|
||||||
|
if math.abs(reject_rate - expected_reject) < 20 then
|
||||||
|
print("✅ Rejection rate within acceptable range")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Rejection rate outside expected range")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_with_delay()
|
||||||
|
print("\n=== Test 3: Backpressure with Delay ===")
|
||||||
|
|
||||||
|
-- Замеряем время выполнения при backpressure
|
||||||
|
local start_time = os.clock()
|
||||||
|
local delayed_requests = 0
|
||||||
|
|
||||||
|
for i = 1, 50 do
|
||||||
|
local req_start = os.clock()
|
||||||
|
db.execute("SELECT * FROM test_collection LIMIT 10")
|
||||||
|
local req_end = os.clock()
|
||||||
|
|
||||||
|
if req_end - req_start > 0.05 then -- больше 50ms задержки
|
||||||
|
delayed_requests = delayed_requests + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local total_duration = os.clock() - start_time
|
||||||
|
local avg_delay = total_duration / 50 * 1000 -- в миллисекундах
|
||||||
|
|
||||||
|
print(string.format("Avg request time: %.2f ms", avg_delay))
|
||||||
|
print(string.format("Requests with >50ms delay: %d/%d", delayed_requests, 50))
|
||||||
|
|
||||||
|
if avg_delay > 40 then
|
||||||
|
print("✅ Backpressure delays detected as expected")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ Lower than expected delays")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_recovery()
|
||||||
|
print("\n=== Test 4: Backpressure Recovery ===")
|
||||||
|
|
||||||
|
-- Даём системе восстановиться
|
||||||
|
print("Waiting for system recovery...")
|
||||||
|
db.sleep(30)
|
||||||
|
|
||||||
|
local status = db.execute("SHOW BACKPRESSURE STATUS")
|
||||||
|
local bp_status = db.json_decode(status)
|
||||||
|
|
||||||
|
print(string.format("Recovered backpressure level: %s", bp_status.current_level))
|
||||||
|
|
||||||
|
-- Проверяем, что запросы снова проходят
|
||||||
|
local success_count = 0
|
||||||
|
for i = 1, 100 do
|
||||||
|
local success = pcall(function()
|
||||||
|
return db.execute("INSERT INTO test_collection VALUES {_id = 'recovery_" .. i .. "', value = " .. i .. "}")
|
||||||
|
end)
|
||||||
|
if success then success_count = success_count + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Successful writes after recovery: %d/100", success_count))
|
||||||
|
|
||||||
|
if success_count > 95 then
|
||||||
|
print("✅ System recovered successfully")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Recovery incomplete: " .. success_count .. " successes")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_metrics()
|
||||||
|
print("\n=== Test 5: Backpressure Metrics ===")
|
||||||
|
|
||||||
|
local metrics = db.execute("SHOW BACKPRESSURE METRICS")
|
||||||
|
local metrics_data = db.json_decode(metrics)
|
||||||
|
|
||||||
|
print("Backpressure metrics:")
|
||||||
|
for key, value in pairs(metrics_data) do
|
||||||
|
print(string.format(" %s: %s", key, tostring(value)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local required_fields = {
|
||||||
|
"current_level", "write_allowed", "read_allowed",
|
||||||
|
"rejected_count", "delayed_count", "cpu_threshold"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field in ipairs(required_fields) do
|
||||||
|
if metrics_data[field] == nil then
|
||||||
|
print("❌ Missing metric: " .. field)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✅ All required metrics present")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backpressure_burst_handling()
|
||||||
|
print("\n=== Test 6: Burst Handling ===")
|
||||||
|
|
||||||
|
-- Генерируем burst запросов
|
||||||
|
local burst_size = 1000
|
||||||
|
local start_time = os.clock()
|
||||||
|
local results = {}
|
||||||
|
|
||||||
|
-- Используем coroutines для параллельных запросов
|
||||||
|
local handlers = {}
|
||||||
|
for i = 1, burst_size do
|
||||||
|
table.insert(handlers, coroutine.create(function()
|
||||||
|
local success = pcall(function()
|
||||||
|
return db.execute(string.format([[
|
||||||
|
INSERT INTO test_collection VALUES {
|
||||||
|
_id = "burst_%d_%d",
|
||||||
|
value = %d,
|
||||||
|
timestamp = %d
|
||||||
|
}
|
||||||
|
]], os.time(), i, i, os.time() * 1000)
|
||||||
|
end)
|
||||||
|
coroutine.yield(success)
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запускаем все handlers
|
||||||
|
for _, handler in ipairs(handlers) do
|
||||||
|
coroutine.resume(handler)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Собираем результаты
|
||||||
|
local success_count = 0
|
||||||
|
for _, handler in ipairs(handlers) do
|
||||||
|
local success = coroutine.resume(handler)
|
||||||
|
if success then
|
||||||
|
local result = coroutine.status(handler) == "dead" and true or false
|
||||||
|
if result then success_count = success_count + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local duration = os.clock() - start_time
|
||||||
|
local qps = burst_size / duration
|
||||||
|
|
||||||
|
print(string.format("Burst: %d requests in %.2fs (%.0f QPS)",
|
||||||
|
burst_size, duration, qps))
|
||||||
|
print(string.format("Successful: %d, Failed: %d",
|
||||||
|
success_count, burst_size - success_count))
|
||||||
|
|
||||||
|
if qps > 100 then
|
||||||
|
print("✅ Backpressure handling burst correctly")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ Lower than expected QPS")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanup_backpressure()
|
||||||
|
print("\n=== Cleanup ===")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS backpressure_test")
|
||||||
|
-- Сбрасываем backpressure настройки
|
||||||
|
db.execute("SET BACKPRESSURE CONFIG DEFAULT")
|
||||||
|
print("Cleanup completed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_all_backpressure_tests()
|
||||||
|
print("╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ BACKPRESSURE TESTS SUITE ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
setup_backpressure_test()
|
||||||
|
|
||||||
|
local results = {
|
||||||
|
test_backpressure_level_none(),
|
||||||
|
test_backpressure_level_medium(),
|
||||||
|
test_backpressure_with_delay(),
|
||||||
|
test_backpressure_recovery(),
|
||||||
|
test_backpressure_metrics(),
|
||||||
|
test_backpressure_burst_handling()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ TEST RESULTS ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local passed = 0
|
||||||
|
for i, result in ipairs(results) do
|
||||||
|
local status = result and "✅ PASSED" or "⚠️ SKIPPED/FAILED"
|
||||||
|
print(string.format("Test %d: %s", i, status))
|
||||||
|
if result then passed = passed + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("\nTotal: %d/%d tests passed", passed, #results))
|
||||||
|
|
||||||
|
cleanup_backpressure()
|
||||||
|
|
||||||
|
return passed == #results
|
||||||
|
end
|
||||||
|
|
||||||
|
return run_all_backpressure_tests()
|
||||||
349
tests/backup_test.lua
Normal file
349
tests/backup_test.lua
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
-- tests/backup_test.lua
|
||||||
|
-- Тестирование бэкапов и восстановления
|
||||||
|
|
||||||
|
local function setup_backup_test()
|
||||||
|
print("=== Setup Backup Test ===")
|
||||||
|
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS backup_test")
|
||||||
|
db.execute("USE backup_test")
|
||||||
|
|
||||||
|
db.execute([[
|
||||||
|
CREATE COLLECTION IF NOT EXISTS users
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Вставляем тестовые данные
|
||||||
|
for i = 1, 100 do
|
||||||
|
db.execute(string.format([[
|
||||||
|
INSERT INTO users VALUES {
|
||||||
|
_id = "user_%d",
|
||||||
|
name = "User %d",
|
||||||
|
email = "user%d@test.com",
|
||||||
|
score = %d,
|
||||||
|
created_at = %d
|
||||||
|
}
|
||||||
|
]], i, i, i, math.random(1, 1000), os.time() * 1000))
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Inserted 100 test documents")
|
||||||
|
|
||||||
|
-- Включаем планировщик бэкапов
|
||||||
|
db.execute([[
|
||||||
|
SET BACKUP SCHEDULE daily_backup
|
||||||
|
WITH CRON "0 0 * * *"
|
||||||
|
TYPE full
|
||||||
|
RETENTION 7
|
||||||
|
]])
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_full_backup()
|
||||||
|
print("\n=== Test 1: Full Backup ===")
|
||||||
|
|
||||||
|
-- Запускаем полный бэкап
|
||||||
|
local result = db.execute("BACKUP DATABASE backup_test TO full_backup.msgpack")
|
||||||
|
print("Backup result: " .. tostring(result))
|
||||||
|
|
||||||
|
-- Проверяем, что файл бэкапа создан
|
||||||
|
local backup_info = db.execute("SHOW BACKUP INFO")
|
||||||
|
local info = db.json_decode(backup_info)
|
||||||
|
|
||||||
|
print(string.format("Backup ID: %s", info.id or "unknown"))
|
||||||
|
print(string.format("Backup size: %d bytes", info.size_bytes or 0))
|
||||||
|
print(string.format("Backup time: %s", info.end_time or "unknown"))
|
||||||
|
|
||||||
|
if info.id and info.size_bytes > 0 then
|
||||||
|
print("✅ Full backup completed successfully")
|
||||||
|
return info.id
|
||||||
|
else
|
||||||
|
print("❌ Full backup failed")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_incremental_backup()
|
||||||
|
print("\n=== Test 2: Incremental Backup ===")
|
||||||
|
|
||||||
|
-- Добавляем новые документы после полного бэкапа
|
||||||
|
for i = 101, 150 do
|
||||||
|
db.execute(string.format([[
|
||||||
|
INSERT INTO users VALUES {
|
||||||
|
_id = "user_%d",
|
||||||
|
name = "New User %d",
|
||||||
|
email = "newuser%d@test.com",
|
||||||
|
score = %d,
|
||||||
|
created_at = %d
|
||||||
|
}
|
||||||
|
]], i, i, i, math.random(1, 1000), os.time() * 1000))
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Added 50 new documents")
|
||||||
|
|
||||||
|
-- Запускаем инкрементальный бэкап
|
||||||
|
local result = db.execute("BACKUP DATABASE backup_test INCREMENTAL TO inc_backup.msgpack")
|
||||||
|
print("Incremental backup result: " .. tostring(result))
|
||||||
|
|
||||||
|
local backup_info = db.execute("SHOW BACKUP INFO inc_backup")
|
||||||
|
local info = db.json_decode(backup_info)
|
||||||
|
|
||||||
|
if info and info.type == "incremental" then
|
||||||
|
print(string.format("Incremental backup: %d entries", info.entries_count or 0))
|
||||||
|
print("✅ Incremental backup completed successfully")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Incremental backup failed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_restore()
|
||||||
|
print("\n=== Test 3: Backup Restore ===")
|
||||||
|
|
||||||
|
-- Создаём новую БД для восстановления
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS restore_test")
|
||||||
|
|
||||||
|
-- Восстанавливаем из полного бэкапа
|
||||||
|
local result = db.execute("RESTORE DATABASE restore_test FROM full_backup.msgpack")
|
||||||
|
print("Restore result: " .. tostring(result))
|
||||||
|
|
||||||
|
-- Проверяем количество документов
|
||||||
|
db.execute("USE restore_test")
|
||||||
|
local count = db.execute("SELECT COUNT(*) FROM users")
|
||||||
|
local count_data = db.json_decode(count)
|
||||||
|
|
||||||
|
print(string.format("Restored documents: %d", count_data[1].count or 0))
|
||||||
|
|
||||||
|
if count_data[1].count == 100 then
|
||||||
|
print("✅ Full restore successful")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Restore failed: expected 100 documents")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_incremental_restore()
|
||||||
|
print("\n=== Test 4: Incremental Restore ===")
|
||||||
|
|
||||||
|
-- Создаём другую БД для теста инкрементального восстановления
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS inc_restore_test")
|
||||||
|
|
||||||
|
-- Восстанавливаем сначала полный бэкап
|
||||||
|
db.execute("USE inc_restore_test")
|
||||||
|
db.execute("RESTORE DATABASE inc_restore_test FROM full_backup.msgpack")
|
||||||
|
|
||||||
|
local before_count = db.execute("SELECT COUNT(*) FROM users")
|
||||||
|
local before_data = db.json_decode(before_count)
|
||||||
|
print(string.format("After full restore: %d documents", before_data[1].count or 0))
|
||||||
|
|
||||||
|
-- Затем применяем инкрементальный
|
||||||
|
db.execute("RESTORE DATABASE inc_restore_test INCREMENTAL FROM inc_backup.msgpack")
|
||||||
|
|
||||||
|
local after_count = db.execute("SELECT COUNT(*) FROM users")
|
||||||
|
local after_data = db.json_decode(after_count)
|
||||||
|
print(string.format("After incremental restore: %d documents", after_data[1].count or 0))
|
||||||
|
|
||||||
|
if after_data[1].count == 150 then
|
||||||
|
print("✅ Incremental restore successful")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print(string.format("❌ Expected 150 documents, got %d", after_data[1].count or 0))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_schedule()
|
||||||
|
print("\n=== Test 5: Backup Schedule ===")
|
||||||
|
|
||||||
|
local schedules = db.execute("SHOW BACKUP SCHEDULES")
|
||||||
|
local schedules_data = db.json_decode(schedules)
|
||||||
|
|
||||||
|
print("Current backup schedules:")
|
||||||
|
for _, schedule in ipairs(schedules_data) do
|
||||||
|
print(string.format(" %s: cron='%s', type='%s', enabled=%s",
|
||||||
|
schedule.name, schedule.cron_expr, schedule.type, tostring(schedule.enabled)))
|
||||||
|
end
|
||||||
|
|
||||||
|
if #schedules_data > 0 then
|
||||||
|
print("✅ Backup schedules configured correctly")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ No backup schedules found")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_compression()
|
||||||
|
print("\n=== Test 6: Backup Compression ===")
|
||||||
|
|
||||||
|
-- Включаем сжатие
|
||||||
|
db.execute("SET BACKUP COMPRESSION true")
|
||||||
|
|
||||||
|
-- Создаём много данных для бэкапа
|
||||||
|
for i = 1, 1000 do
|
||||||
|
db.execute(string.format([[
|
||||||
|
INSERT INTO users VALUES {
|
||||||
|
_id = "compress_%d",
|
||||||
|
data = "%s"
|
||||||
|
}
|
||||||
|
]], i, string.rep("x", 1000)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local start_time = os.clock()
|
||||||
|
db.execute("BACKUP DATABASE backup_test TO compressed_backup.msgpack WITH COMPRESSION")
|
||||||
|
local duration = os.clock() - start_time
|
||||||
|
|
||||||
|
local backup_info = db.execute("SHOW BACKUP INFO compressed_backup")
|
||||||
|
local info = db.json_decode(backup_info)
|
||||||
|
|
||||||
|
print(string.format("Compressed backup size: %d bytes", info.size_bytes or 0))
|
||||||
|
print(string.format("Compression time: %.2fs", duration))
|
||||||
|
|
||||||
|
if info.size_bytes and info.size_bytes > 0 then
|
||||||
|
print("✅ Backup compression working")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Backup compression failed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_retention()
|
||||||
|
print("\n=== Test 7: Backup Retention ===")
|
||||||
|
|
||||||
|
-- Создаём несколько старых бэкапов (симуляция)
|
||||||
|
for i = 1, 10 do
|
||||||
|
db.execute(string.format("BACKUP DATABASE backup_test TO old_backup_%d.msgpack", i))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Устанавливаем retention на 5 дней
|
||||||
|
db.execute("SET BACKUP RETENTION 5")
|
||||||
|
|
||||||
|
-- Запускаем очистку
|
||||||
|
db.execute("CLEANUP BACKUPS")
|
||||||
|
|
||||||
|
local backups = db.execute("SHOW BACKUPS")
|
||||||
|
local backups_data = db.json_decode(backups)
|
||||||
|
|
||||||
|
print(string.format("Remaining backups after cleanup: %d", #backups_data))
|
||||||
|
|
||||||
|
-- Проверяем, что не все бэкапы удалены
|
||||||
|
if #backups_data > 0 then
|
||||||
|
print("✅ Backup retention working")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ All backups cleaned up, retention may be too aggressive")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_verify()
|
||||||
|
print("\n=== Test 8: Backup Verification ===")
|
||||||
|
|
||||||
|
-- Проверяем целостность бэкапа
|
||||||
|
local result = db.execute("VERIFY BACKUP full_backup.msgpack")
|
||||||
|
local verify_result = db.json_decode(result)
|
||||||
|
|
||||||
|
print(string.format("Verification result: %s", verify_result.valid and "VALID" or "INVALID"))
|
||||||
|
print(string.format("Checksum match: %s", tostring(verify_result.checksum_match)))
|
||||||
|
print(string.format("Corruption detected: %s", tostring(verify_result.corrupted)))
|
||||||
|
|
||||||
|
if verify_result.valid then
|
||||||
|
print("✅ Backup verification passed")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Backup verification failed")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_backup_parallel()
|
||||||
|
print("\n=== Test 9: Parallel Backups ===")
|
||||||
|
|
||||||
|
-- Проверяем, что параллельные бэкапы блокируются
|
||||||
|
local results = {}
|
||||||
|
|
||||||
|
local function run_backup(name)
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format("BACKUP DATABASE backup_test TO parallel_%s.msgpack", name))
|
||||||
|
end)
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запускаем 3 параллельных бэкапа
|
||||||
|
local threads = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
table.insert(threads, coroutine.create(function()
|
||||||
|
local result = run_backup(tostring(i))
|
||||||
|
coroutine.yield(result)
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, thread in ipairs(threads) do
|
||||||
|
coroutine.resume(thread)
|
||||||
|
end
|
||||||
|
|
||||||
|
local success_count = 0
|
||||||
|
for _, thread in ipairs(threads) do
|
||||||
|
local success = coroutine.resume(thread)
|
||||||
|
if success then success_count = success_count + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Parallel backups completed: %d/3 succeeded", success_count))
|
||||||
|
|
||||||
|
if success_count == 1 then
|
||||||
|
print("✅ Parallel backups correctly limited to 1")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("⚠️ Parallel backup behavior may need tuning")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanup_backup()
|
||||||
|
print("\n=== Cleanup ===")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS backup_test")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS restore_test")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS inc_restore_test")
|
||||||
|
print("Cleanup completed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_all_backup_tests()
|
||||||
|
print("╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ BACKUP TESTS SUITE ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
setup_backup_test()
|
||||||
|
|
||||||
|
local full_backup_id = test_full_backup()
|
||||||
|
local results = {
|
||||||
|
full_backup_id ~= nil,
|
||||||
|
test_incremental_backup(),
|
||||||
|
test_backup_restore(),
|
||||||
|
test_incremental_restore(),
|
||||||
|
test_backup_schedule(),
|
||||||
|
test_backup_compression(),
|
||||||
|
test_backup_retention(),
|
||||||
|
test_backup_verify(),
|
||||||
|
test_backup_parallel()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ TEST RESULTS ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local passed = 0
|
||||||
|
for i, result in ipairs(results) do
|
||||||
|
local status = result and "✅ PASSED" or "⚠️ SKIPPED/FAILED"
|
||||||
|
print(string.format("Test %d: %s", i, status))
|
||||||
|
if result then passed = passed + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("\nTotal: %d/%d tests passed", passed, #results))
|
||||||
|
|
||||||
|
cleanup_backup()
|
||||||
|
|
||||||
|
return passed == #results
|
||||||
|
end
|
||||||
|
|
||||||
|
return run_all_backup_tests()
|
||||||
243
tests/load_test.lua
Normal file
243
tests/load_test.lua
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
-- tests/load_test.lua
|
||||||
|
-- Комплексное нагрузочное тестирование всех компонентов
|
||||||
|
|
||||||
|
local function run_load_test()
|
||||||
|
print("╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ LOAD TEST SUITE ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local test_results = {}
|
||||||
|
|
||||||
|
-- Функция для замера производительности
|
||||||
|
local function measure_performance(name, fn, iterations)
|
||||||
|
print(string.format("\n📊 Measuring: %s (%d iterations)", name, iterations))
|
||||||
|
|
||||||
|
local times = {}
|
||||||
|
local errors = 0
|
||||||
|
|
||||||
|
for i = 1, iterations do
|
||||||
|
local start = os.clock()
|
||||||
|
local success, err = pcall(fn)
|
||||||
|
local duration = os.clock() - start
|
||||||
|
|
||||||
|
if success then
|
||||||
|
table.insert(times, duration)
|
||||||
|
else
|
||||||
|
errors = errors + 1
|
||||||
|
print(string.format(" Error #%d: %s", i, tostring(err)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #times == 0 then
|
||||||
|
print(string.format(" ❌ All %d iterations failed", iterations))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Вычисляем статистику
|
||||||
|
table.sort(times)
|
||||||
|
local min = times[1]
|
||||||
|
local max = times[#times]
|
||||||
|
local sum = 0
|
||||||
|
for _, t in ipairs(times) do sum = sum + t end
|
||||||
|
local avg = sum / #times
|
||||||
|
local p50 = times[math.floor(#times * 0.5)]
|
||||||
|
local p95 = times[math.floor(#times * 0.95)]
|
||||||
|
local p99 = times[math.floor(#times * 0.99)]
|
||||||
|
|
||||||
|
print(string.format([[
|
||||||
|
Results:
|
||||||
|
Success rate: %.1f%% (%d/%d)
|
||||||
|
Min: %.3f ms
|
||||||
|
Max: %.3f ms
|
||||||
|
Avg: %.3f ms
|
||||||
|
P50: %.3f ms
|
||||||
|
P95: %.3f ms
|
||||||
|
P99: %.3f ms
|
||||||
|
]], (iterations - errors) / iterations * 100, iterations - errors, iterations,
|
||||||
|
min * 1000, max * 1000, avg * 1000, p50 * 1000, p95 * 1000, p99 * 1000))
|
||||||
|
|
||||||
|
return {
|
||||||
|
success_rate = (iterations - errors) / iterations,
|
||||||
|
min_ms = min * 1000,
|
||||||
|
max_ms = max * 1000,
|
||||||
|
avg_ms = avg * 1000,
|
||||||
|
p50_ms = p50 * 1000,
|
||||||
|
p95_ms = p95 * 1000,
|
||||||
|
p99_ms = p99 * 1000
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Подготовка
|
||||||
|
print("\n🔧 Preparing test environment...")
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS load_test")
|
||||||
|
db.execute("USE load_test")
|
||||||
|
|
||||||
|
db.execute([[
|
||||||
|
CREATE COLLECTION IF NOT EXISTS load_collection
|
||||||
|
WITH SETTINGS {
|
||||||
|
max_documents = 1000000,
|
||||||
|
auto_index_id = true
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Тест 1: Вставка документов
|
||||||
|
local insert_results = measure_performance("INSERT", function()
|
||||||
|
local doc = {
|
||||||
|
_id = string.format("doc_%d_%d", os.time(), math.random(1, 1000000)),
|
||||||
|
timestamp = os.time() * 1000,
|
||||||
|
value = math.random(1, 10000),
|
||||||
|
data = string.rep("x", 100)
|
||||||
|
}
|
||||||
|
db.execute(string.format(
|
||||||
|
"INSERT INTO load_collection VALUES (%s)",
|
||||||
|
db.json_encode(doc)
|
||||||
|
))
|
||||||
|
end, 1000)
|
||||||
|
|
||||||
|
-- Тест 2: Чтение по ID
|
||||||
|
local read_results = measure_performance("SELECT BY ID", function()
|
||||||
|
db.execute(string.format("SELECT * FROM load_collection WHERE _id = 'doc_%d'",
|
||||||
|
math.random(1, 1000)))
|
||||||
|
end, 1000)
|
||||||
|
|
||||||
|
-- Тест 3: Обновление
|
||||||
|
local update_results = measure_performance("UPDATE", function()
|
||||||
|
db.execute(string.format([[
|
||||||
|
UPDATE load_collection
|
||||||
|
SET value = %d, updated_at = %d
|
||||||
|
WHERE _id = 'doc_%d'
|
||||||
|
]], math.random(1, 10000), os.time() * 1000, math.random(1, 1000)))
|
||||||
|
end, 1000)
|
||||||
|
|
||||||
|
-- Тест 4: Удаление
|
||||||
|
local delete_results = measure_performance("DELETE", function()
|
||||||
|
db.execute(string.format("DELETE FROM load_collection WHERE _id = 'temp_%d'",
|
||||||
|
math.random(1, 100)))
|
||||||
|
end, 500)
|
||||||
|
|
||||||
|
-- Тест 5: Сложный запрос с фильтром
|
||||||
|
local query_results = measure_performance("COMPLEX QUERY", function()
|
||||||
|
db.execute([[
|
||||||
|
SELECT * FROM load_collection
|
||||||
|
WHERE value > 5000
|
||||||
|
AND value < 8000
|
||||||
|
LIMIT 50
|
||||||
|
]])
|
||||||
|
end, 500)
|
||||||
|
|
||||||
|
-- Тест 6: Агрегация
|
||||||
|
local agg_results = measure_performance("AGGREGATION", function()
|
||||||
|
db.execute([[
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total,
|
||||||
|
AVG(value) as avg_value,
|
||||||
|
MIN(value) as min_value,
|
||||||
|
MAX(value) as max_value
|
||||||
|
FROM load_collection
|
||||||
|
WHERE value > 1000
|
||||||
|
]])
|
||||||
|
end, 200)
|
||||||
|
|
||||||
|
-- Тест 7: Пакетная вставка
|
||||||
|
local batch_results = measure_performance("BATCH INSERT", function()
|
||||||
|
local docs = {}
|
||||||
|
for i = 1, 10 do
|
||||||
|
table.insert(docs, {
|
||||||
|
_id = string.format("batch_%d_%d", os.time(), i),
|
||||||
|
value = i
|
||||||
|
})
|
||||||
|
end
|
||||||
|
db.execute(string.format(
|
||||||
|
"INSERT INTO load_collection VALUES %s",
|
||||||
|
db.json_encode(docs)
|
||||||
|
))
|
||||||
|
end, 100)
|
||||||
|
|
||||||
|
-- Тест 8: Транзакция
|
||||||
|
local tx_results = measure_performance("TRANSACTION", function()
|
||||||
|
db.execute("BEGIN TRANSACTION")
|
||||||
|
db.execute("INSERT INTO load_collection VALUES {_id = 'tx1', value = 1}")
|
||||||
|
db.execute("INSERT INTO load_collection VALUES {_id = 'tx2', value = 2}")
|
||||||
|
db.execute("COMMIT")
|
||||||
|
end, 100)
|
||||||
|
|
||||||
|
-- Тест 9: Поиск по индексу
|
||||||
|
db.execute("CREATE INDEX idx_value ON load_collection(value)")
|
||||||
|
|
||||||
|
local index_results = measure_performance("INDEX SEARCH", function()
|
||||||
|
db.execute("SELECT * FROM load_collection WHERE value = 5000")
|
||||||
|
end, 500)
|
||||||
|
|
||||||
|
-- Тест 10: Сканирование с лимитом
|
||||||
|
local scan_results = measure_performance("SCAN WITH LIMIT", function()
|
||||||
|
db.execute("SELECT * FROM load_collection LIMIT 1000")
|
||||||
|
end, 100)
|
||||||
|
|
||||||
|
-- Сводка
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ PERFORMANCE SUMMARY ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local function print_summary(name, results)
|
||||||
|
if results then
|
||||||
|
local status = results.success_rate > 0.99 and "✅" or "⚠️"
|
||||||
|
print(string.format("%s %-15s: avg=%.2fms, p95=%.2fms, success=%.1f%%",
|
||||||
|
status, name, results.avg_ms, results.p95_ms, results.success_rate * 100))
|
||||||
|
else
|
||||||
|
print(string.format("❌ %-15s: FAILED", name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print_summary("INSERT", insert_results)
|
||||||
|
print_summary("SELECT", read_results)
|
||||||
|
print_summary("UPDATE", update_results)
|
||||||
|
print_summary("DELETE", delete_results)
|
||||||
|
print_summary("QUERY", query_results)
|
||||||
|
print_summary("AGGREGATE", agg_results)
|
||||||
|
print_summary("BATCH", batch_results)
|
||||||
|
print_summary("TRANSACTION", tx_results)
|
||||||
|
print_summary("INDEX SEARCH", index_results)
|
||||||
|
print_summary("SCAN", scan_results)
|
||||||
|
|
||||||
|
-- Общий вердикт
|
||||||
|
local success_rate = (insert_results and insert_results.success_rate or 0) +
|
||||||
|
(read_results and read_results.success_rate or 0) +
|
||||||
|
(update_results and update_results.success_rate or 0) +
|
||||||
|
(delete_results and delete_results.success_rate or 0) +
|
||||||
|
(query_results and query_results.success_rate or 0) +
|
||||||
|
(agg_results and agg_results.success_rate or 0) +
|
||||||
|
(batch_results and batch_results.success_rate or 0) +
|
||||||
|
(tx_results and tx_results.success_rate or 0) +
|
||||||
|
(index_results and index_results.success_rate or 0) +
|
||||||
|
(scan_results and scan_results.success_rate or 0)
|
||||||
|
|
||||||
|
success_rate = success_rate / 10 * 100
|
||||||
|
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ FINAL VERDICT ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
if success_rate > 99 then
|
||||||
|
print("✅ EXCELLENT: All operations performing optimally")
|
||||||
|
elseif success_rate > 95 then
|
||||||
|
print("✅ GOOD: Most operations performing well")
|
||||||
|
elseif success_rate > 90 then
|
||||||
|
print("⚠️ ACCEPTABLE: Some operations need optimization")
|
||||||
|
else
|
||||||
|
print("❌ POOR: Significant performance issues detected")
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Overall success rate: %.1f%%", success_rate))
|
||||||
|
|
||||||
|
-- Очистка
|
||||||
|
print("\n🧹 Cleaning up...")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS load_test")
|
||||||
|
|
||||||
|
return success_rate > 90
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Запуск всех тестов
|
||||||
|
local function run_all_tests()
|
||||||
|
print("🚀 STARTING FULL TEST SUITE\n")
|
||||||
|
|
||||||
|
|
||||||
318
tests/runtime_limits_test.lua
Normal file
318
tests/runtime_limits_test.lua
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
-- tests/runtime_limits_test.lua
|
||||||
|
-- Тестирование runtime-ограничений на размер коллекции и документа
|
||||||
|
|
||||||
|
local function setup_limits_test()
|
||||||
|
print("=== Setup Runtime Limits Test ===")
|
||||||
|
|
||||||
|
db.execute("CREATE DATABASE IF NOT EXISTS limits_test")
|
||||||
|
db.execute("USE limits_test")
|
||||||
|
|
||||||
|
-- Устанавливаем тестовые ограничения
|
||||||
|
db.execute([[
|
||||||
|
SET RUNTIME_LIMITS {
|
||||||
|
global_max_doc_size_mb = 1,
|
||||||
|
global_max_coll_size_mb = 10,
|
||||||
|
global_max_docs_per_coll = 1000
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
db.execute([[
|
||||||
|
CREATE COLLECTION IF NOT EXISTS test_collection
|
||||||
|
]])
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_max_document_size()
|
||||||
|
print("\n=== Test 1: Max Document Size ===")
|
||||||
|
|
||||||
|
-- Создаём документ меньше лимита (0.5 MB)
|
||||||
|
local small_doc = {
|
||||||
|
_id = "small_doc",
|
||||||
|
data = string.rep("x", 500 * 1024) -- 500KB
|
||||||
|
}
|
||||||
|
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO test_collection VALUES (%s)",
|
||||||
|
db.json_encode(small_doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
print("✅ Small document (500KB) inserted successfully")
|
||||||
|
else
|
||||||
|
print("❌ Small document rejected: " .. tostring(err))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Создаём документ больше лимита (2 MB)
|
||||||
|
local large_doc = {
|
||||||
|
_id = "large_doc",
|
||||||
|
data = string.rep("x", 2 * 1024 * 1024) -- 2MB
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err = pcall(function()
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO test_collection VALUES (%s)",
|
||||||
|
db.json_encode(large_doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not success and string.find(err or "", "exceeds limit") then
|
||||||
|
print("✅ Large document (2MB) correctly rejected")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("❌ Large document should have been rejected: " .. tostring(err))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_max_collection_size()
|
||||||
|
print("\n=== Test 2: Max Collection Size ===")
|
||||||
|
|
||||||
|
local inserted = 0
|
||||||
|
local rejected = 0
|
||||||
|
|
||||||
|
for i = 1, 20 do
|
||||||
|
-- Каждый документ ~1MB
|
||||||
|
local doc = {
|
||||||
|
_id = string.format("doc_%d", i),
|
||||||
|
data = string.rep("x", 900 * 1024), -- 900KB
|
||||||
|
index = i
|
||||||
|
}
|
||||||
|
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO test_collection VALUES (%s)",
|
||||||
|
db.json_encode(doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
inserted = inserted + 1
|
||||||
|
print(string.format(" Inserted doc %d, total inserted: %d", i, inserted))
|
||||||
|
else
|
||||||
|
rejected = rejected + 1
|
||||||
|
if string.find(err or "", "exceeds limit") then
|
||||||
|
print(string.format(" Doc %d rejected: collection size limit reached", i))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Final: %d inserted, %d rejected", inserted, rejected))
|
||||||
|
|
||||||
|
-- Должно быть вставлено не более 10 документов (~10MB)
|
||||||
|
if inserted <= 11 and inserted >= 8 then
|
||||||
|
print("✅ Collection size limit working correctly")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print(string.format("❌ Unexpected number of inserts: %d", inserted))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_max_document_count()
|
||||||
|
print("\n=== Test 3: Max Document Count ===")
|
||||||
|
|
||||||
|
-- Создаём новую коллекцию для этого теста
|
||||||
|
db.execute("CREATE COLLECTION IF NOT EXISTS count_test")
|
||||||
|
|
||||||
|
-- Устанавливаем лимит в 50 документов
|
||||||
|
db.execute([[
|
||||||
|
ALTER COLLECTION count_test SET LIMITS {
|
||||||
|
max_documents = 50
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
local inserted = 0
|
||||||
|
local rejected = 0
|
||||||
|
|
||||||
|
for i = 1, 60 do
|
||||||
|
local doc = {
|
||||||
|
_id = string.format("count_doc_%d", i),
|
||||||
|
value = i
|
||||||
|
}
|
||||||
|
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO count_test VALUES (%s)",
|
||||||
|
db.json_encode(doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
inserted = inserted + 1
|
||||||
|
else
|
||||||
|
rejected = rejected + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("Inserted: %d, Rejected: %d", inserted, rejected))
|
||||||
|
|
||||||
|
if inserted == 50 and rejected == 10 then
|
||||||
|
print("✅ Document count limit working correctly")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print(string.format("❌ Expected 50 inserts, got %d", inserted))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
db.execute("DROP COLLECTION count_test")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_collection_override_limits()
|
||||||
|
print("\n=== Test 4: Collection Override Limits ===")
|
||||||
|
|
||||||
|
-- Создаём коллекцию с собственными лимитами
|
||||||
|
db.execute([[
|
||||||
|
CREATE COLLECTION override_test
|
||||||
|
WITH LIMITS {
|
||||||
|
max_doc_size_mb = 5,
|
||||||
|
max_docs = 200
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Проверяем, что можно вставить 4MB документ
|
||||||
|
local large_doc = {
|
||||||
|
_id = "large_override",
|
||||||
|
data = string.rep("y", 4 * 1024 * 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute(string.format(
|
||||||
|
"INSERT INTO override_test VALUES (%s)",
|
||||||
|
db.json_encode(large_doc)
|
||||||
|
))
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
print("❌ Failed to insert 4MB document with 5MB limit: " .. tostring(err))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✅ Collection override limits working correctly")
|
||||||
|
|
||||||
|
db.execute("DROP COLLECTION override_test")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_runtime_limits_metrics()
|
||||||
|
print("\n=== Test 5: Runtime Limits Metrics ===")
|
||||||
|
|
||||||
|
local metrics = db.execute("SHOW RUNTIME_LIMITS METRICS")
|
||||||
|
local metrics_data = db.json_decode(metrics)
|
||||||
|
|
||||||
|
print("Runtime limits metrics:")
|
||||||
|
for key, value in pairs(metrics_data) do
|
||||||
|
print(string.format(" %s: %s", key, tostring(value)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local required_fields = {
|
||||||
|
"rejected_by_size",
|
||||||
|
"rejected_by_doc_count",
|
||||||
|
"rejected_by_coll_size",
|
||||||
|
"global_max_doc_size_mb",
|
||||||
|
"global_max_coll_size_mb",
|
||||||
|
"global_max_docs_per_coll",
|
||||||
|
"enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field in ipairs(required_fields) do
|
||||||
|
if metrics_data[field] == nil then
|
||||||
|
print("❌ Missing metric: " .. field)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✅ All required metrics present")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_dynamic_limit_change()
|
||||||
|
print("\n=== Test 6: Dynamic Limit Change ===")
|
||||||
|
|
||||||
|
-- Создаём коллекцию
|
||||||
|
db.execute("CREATE COLLECTION dynamic_test")
|
||||||
|
|
||||||
|
-- Меняем лимиты динамически
|
||||||
|
db.execute("ALTER COLLECTION dynamic_test SET LIMITS { max_docs = 10 }")
|
||||||
|
|
||||||
|
-- Вставляем 10 документов
|
||||||
|
for i = 1, 10 do
|
||||||
|
db.execute(string.format(
|
||||||
|
"INSERT INTO dynamic_test VALUES {_id = 'dyn_%d'}", i
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Пытаемся вставить 11-й
|
||||||
|
local success, err = pcall(function()
|
||||||
|
return db.execute("INSERT INTO dynamic_test VALUES {_id = 'dyn_11'}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
print("❌ Should have rejected after dynamic limit change")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Увеличиваем лимит
|
||||||
|
db.execute("ALTER COLLECTION dynamic_test SET LIMITS { max_docs = 20 }")
|
||||||
|
|
||||||
|
-- Теперь 11-й должен пройти
|
||||||
|
success, err = pcall(function()
|
||||||
|
return db.execute("INSERT INTO dynamic_test VALUES {_id = 'dyn_11'}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
print("❌ Should have accepted after limit increase")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
print("✅ Dynamic limit change working correctly")
|
||||||
|
|
||||||
|
db.execute("DROP COLLECTION dynamic_test")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanup_limits()
|
||||||
|
print("\n=== Cleanup ===")
|
||||||
|
db.execute("DROP DATABASE IF EXISTS limits_test")
|
||||||
|
db.execute("SET RUNTIME_LIMITS DEFAULT")
|
||||||
|
print("Cleanup completed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_all_runtime_limits_tests()
|
||||||
|
print("╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ RUNTIME LIMITS TESTS SUITE ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
setup_limits_test()
|
||||||
|
|
||||||
|
local results = {
|
||||||
|
test_max_document_size(),
|
||||||
|
test_max_collection_size(),
|
||||||
|
test_max_document_count(),
|
||||||
|
test_collection_override_limits(),
|
||||||
|
test_runtime_limits_metrics(),
|
||||||
|
test_dynamic_limit_change()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n╔══════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ TEST RESULTS ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════╝")
|
||||||
|
|
||||||
|
local passed = 0
|
||||||
|
for i, result in ipairs(results) do
|
||||||
|
local status = result and "✅ PASSED" or "⚠️ SKIPPED/FAILED"
|
||||||
|
print(string.format("Test %d: %s", i, status))
|
||||||
|
if result then passed = passed + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(string.format("\nTotal: %d/%d tests passed", passed, #results))
|
||||||
|
|
||||||
|
cleanup_limits()
|
||||||
|
|
||||||
|
return passed == #results
|
||||||
|
end
|
||||||
|
|
||||||
|
return run_all_runtime_limits_tests()
|
||||||
Reference in New Issue
Block a user