SIENTIAPDE-1205
Refactor tests for async compatibility and enhance IngestorManager functionality - Updated test cases in `test_app.py`, `test_ingestor.py`, and `test_ingestor_manager.py` to use async/await syntax for improved concurrency. - Refactored methods in IngestorManager and related classes to support asynchronous operations, ensuring non-blocking behavior during execution. - Enhanced mock setups in tests to accommodate async methods, improving test reliability and performance.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
import pytest_asyncio
|
||||
from unittest.mock import patch, MagicMock, call, AsyncMock
|
||||
from threading import Event
|
||||
import signal as signal_module # To avoid conflict with mock names
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
# Import the 'app' module to be tested
|
||||
from ingestor import app
|
||||
@@ -30,9 +32,9 @@ def mock_app_env(monkeypatch):
|
||||
"metrics_APP_LOOP_COUNT_labels_inc": MagicMock(),
|
||||
"metrics_APP_LOOP_DURATION_labels_observe": MagicMock(),
|
||||
"metrics_APP_ERRORS_TOTAL_labels_inc": MagicMock(),
|
||||
"os_exit": MagicMock(side_effect=raise_os_exit_with_code), # CORRECTED
|
||||
"os_exit": MagicMock(side_effect=raise_os_exit_with_code),
|
||||
"time_time": MagicMock(),
|
||||
"time_sleep": MagicMock(),
|
||||
"asyncio_sleep": AsyncMock(),
|
||||
"signal_signal": MagicMock(),
|
||||
"traceback_print_exc": MagicMock(),
|
||||
"mock_exit_signal": MagicMock(spec=Event),
|
||||
@@ -40,6 +42,7 @@ def mock_app_env(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(app, "start_http_server", mocks["start_http_server"])
|
||||
monkeypatch.setattr(app, "Ingestor", mocks["Ingestor"])
|
||||
monkeypatch.setattr(asyncio, "sleep", mocks["asyncio_sleep"])
|
||||
|
||||
monkeypatch.setattr(
|
||||
app.metrics.APP_UP,
|
||||
@@ -75,7 +78,6 @@ def mock_app_env(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(app.os, "_exit", mocks["os_exit"])
|
||||
monkeypatch.setattr(app, "time", mocks["time_time"])
|
||||
monkeypatch.setattr(app, "sleep", mocks["time_sleep"])
|
||||
monkeypatch.setattr(app.signal, "signal", mocks["signal_signal"])
|
||||
monkeypatch.setattr(app.traceback, "print_exc",
|
||||
mocks["traceback_print_exc"])
|
||||
@@ -87,10 +89,16 @@ def mock_app_env(monkeypatch):
|
||||
mock_ingestor_instance.poll_interval = 0.01
|
||||
mock_ingestor_instance.logger = MagicMock()
|
||||
|
||||
# Make async methods async mocks
|
||||
mock_ingestor_instance.prepare_ingestor = AsyncMock()
|
||||
mock_ingestor_instance.loop = AsyncMock()
|
||||
mock_ingestor_instance.shutdown = AsyncMock()
|
||||
|
||||
return mocks
|
||||
|
||||
|
||||
def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
"""Test a successful run where the loop executes once and then exits gracefully."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
@@ -99,7 +107,7 @@ def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
mock_app_env["time_time"].side_effect = [10.0, 11.5]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.main()
|
||||
await app.main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
mock_app_env["start_http_server"].assert_called_once_with(9090)
|
||||
@@ -119,7 +127,7 @@ def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
app.metrics.APP_LOOP_COUNT.labels.assert_called_with(pod_id="test_pod")
|
||||
mock_app_env["metrics_APP_LOOP_COUNT_labels_inc"].assert_called_once()
|
||||
|
||||
mock_exit_signal.wait.assert_called_once_with(
|
||||
mock_app_env["asyncio_sleep"].assert_called_once_with(
|
||||
mock_ingestor_instance.poll_interval)
|
||||
|
||||
app.metrics.APP_LOOP_DURATION.labels.assert_called_with(pod_id="test_pod")
|
||||
@@ -128,8 +136,7 @@ def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
)
|
||||
|
||||
mock_ingestor_instance.shutdown.assert_called_once()
|
||||
mock_app_env["time_sleep"].assert_called_once_with(5)
|
||||
# CORREÇÃO APLICADA ABAIXO:
|
||||
mock_app_env["asyncio_sleep"].assert_any_call(5)
|
||||
mock_ingestor_instance.logger.info.assert_any_call(
|
||||
"Main loop exit_signaled.")
|
||||
|
||||
@@ -137,22 +144,16 @@ def test_main_successful_run_one_loop(mock_app_env, capsys):
|
||||
assert "Prometheus server started on port 9090." in captured.out
|
||||
|
||||
|
||||
def test_main_prometheus_server_fails_to_start(mock_app_env, capsys):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_prometheus_server_fails_to_start(mock_app_env, capsys):
|
||||
"""Test the scenario where starting the Prometheus server fails."""
|
||||
mock_app_env["start_http_server"].side_effect = OSError(
|
||||
"Port already in use")
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.main()
|
||||
await app.main()
|
||||
assert excinfo.value.code == 1
|
||||
|
||||
# Verify that APP_UP.labels(...).set(1) was NOT called.
|
||||
# The mock for .set is mock_app_env['metrics_APP_UP_labels_set']
|
||||
# We need to check if it was called with 1.
|
||||
# A more robust way is to check if the specific .labels(pod_id="test_pod") mock was ever called
|
||||
# and then its .set(1) method.
|
||||
# For simplicity here, we check if metrics_APP_UP_labels_set was ever called with 1.
|
||||
|
||||
# Check that .set(1) was not called. .set(0) definitely not called.
|
||||
called_with_1 = False
|
||||
for call_args in mock_app_env["metrics_APP_UP_labels_set"].call_args_list:
|
||||
@@ -168,7 +169,8 @@ def test_main_prometheus_server_fails_to_start(mock_app_env, capsys):
|
||||
assert "Failed to start Prometheus server: Port already in use" in captured.out
|
||||
|
||||
|
||||
def test_main_loop_exception_handling(mock_app_env, capsys):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_loop_exception_handling(mock_app_env, capsys):
|
||||
"""Test that an exception in ingestor.loop() is handled gracefully."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
@@ -178,7 +180,7 @@ def test_main_loop_exception_handling(mock_app_env, capsys):
|
||||
mock_app_env["time_time"].side_effect = [10.0, 10.1]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.main()
|
||||
await app.main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
mock_ingestor_instance.loop.assert_called_once()
|
||||
@@ -201,7 +203,8 @@ def test_main_loop_exception_handling(mock_app_env, capsys):
|
||||
assert "Exception in main loop. Setting exit_signal flag." in captured.out
|
||||
|
||||
|
||||
def test_main_keyboard_interrupt_handling(mock_app_env, capsys):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_keyboard_interrupt_handling(mock_app_env, capsys):
|
||||
"""Test that KeyboardInterrupt in ingestor.loop() is handled."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
@@ -211,7 +214,7 @@ def test_main_keyboard_interrupt_handling(mock_app_env, capsys):
|
||||
mock_app_env["time_time"].side_effect = [10.0, 10.1]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.main()
|
||||
await app.main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
mock_ingestor_instance.loop.assert_called_once()
|
||||
@@ -230,7 +233,8 @@ def test_signal_handler_sets_exit_signal(mock_app_env):
|
||||
mock_exit_signal_set.assert_called_once()
|
||||
|
||||
|
||||
def test_main_multiple_loop_iterations(mock_app_env):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_multiple_loop_iterations(mock_app_env):
|
||||
"""Test the main loop runs for a few iterations."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
@@ -240,7 +244,7 @@ def test_main_multiple_loop_iterations(mock_app_env):
|
||||
10.0, 10.1, 10.2, 10.3, 10.4, 10.5]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.main()
|
||||
await app.main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
assert mock_ingestor_instance.loop.call_count == 3
|
||||
@@ -251,7 +255,7 @@ def test_main_multiple_loop_iterations(mock_app_env):
|
||||
) # Checks last call or any call
|
||||
assert mock_app_env["metrics_APP_LOOP_COUNT_labels_inc"].call_count == 3
|
||||
|
||||
assert mock_exit_signal.wait.call_count == 3
|
||||
assert mock_app_env["asyncio_sleep"].call_count == 3
|
||||
|
||||
assert app.metrics.APP_LOOP_DURATION.labels.call_count == 3
|
||||
app.metrics.APP_LOOP_DURATION.labels.assert_called_with(pod_id="test_pod")
|
||||
@@ -265,14 +269,15 @@ def test_main_multiple_loop_iterations(mock_app_env):
|
||||
mock_ingestor_instance.shutdown.assert_called_once()
|
||||
|
||||
|
||||
def test_main_pod_id_used_in_metrics(mock_app_env):
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_pod_id_used_in_metrics(mock_app_env):
|
||||
"""Test that the POD_ID from app module is used in metric labels."""
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
mock_exit_signal.is_set.side_effect = [False, True]
|
||||
mock_app_env["time_time"].side_effect = [10.0, 11.0]
|
||||
|
||||
with pytest.raises(OsExitCalled):
|
||||
app.main()
|
||||
await app.main()
|
||||
|
||||
app.metrics.APP_UP.labels.assert_any_call(pod_id="test_pod")
|
||||
app.metrics.APP_LOOP_COUNT.labels.assert_any_call(pod_id="test_pod")
|
||||
@@ -283,3 +288,53 @@ def test_main_pod_id_used_in_metrics(mock_app_env):
|
||||
mock_app_env["metrics_APP_UP_labels_set"].assert_any_call(1)
|
||||
mock_app_env["metrics_APP_UP_labels_set"].assert_any_call(0)
|
||||
mock_app_env["metrics_APP_LOOP_COUNT_labels_inc"].assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_async_main(mock_app_env, capsys):
|
||||
"""Test the run_async_main function that sets up the event loop."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
|
||||
mock_exit_signal.is_set.side_effect = [False, True]
|
||||
mock_app_env["time_time"].side_effect = [10.0, 11.0]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
app.run_async_main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
# Verify the main function was called through the event loop
|
||||
mock_app_env["start_http_server"].assert_called_once_with(9090)
|
||||
mock_ingestor_instance.prepare_ingestor.assert_called_once()
|
||||
mock_ingestor_instance.loop.assert_called_once()
|
||||
mock_ingestor_instance.shutdown.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_prepare_ingestor_failure(mock_app_env, capsys):
|
||||
"""Test that prepare_ingestor failure is handled correctly."""
|
||||
mock_ingestor_instance = mock_app_env["Ingestor"].return_value
|
||||
mock_exit_signal = mock_app_env["mock_exit_signal"]
|
||||
|
||||
mock_ingestor_instance.prepare_ingestor.side_effect = Exception(
|
||||
"Preparation failed")
|
||||
mock_exit_signal.is_set.side_effect = [False, True]
|
||||
|
||||
with pytest.raises(OsExitCalled) as excinfo:
|
||||
await app.main()
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
# Verify error metrics were incremented
|
||||
app.metrics.APP_ERRORS_TOTAL.labels.assert_called_with(pod_id="test_pod")
|
||||
mock_app_env["metrics_APP_ERRORS_TOTAL_labels_inc"].assert_called_once()
|
||||
|
||||
# Verify exit signal was set
|
||||
mock_exit_signal.set.assert_called_once()
|
||||
|
||||
# Verify shutdown was called
|
||||
mock_ingestor_instance.shutdown.assert_called_once()
|
||||
|
||||
# Verify error was logged
|
||||
mock_ingestor_instance.logger.error.assert_called_once_with(
|
||||
"Failed to prepare ingestor: Preparation failed"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user