test(core): add comprehensive test levels for async_cleanup module #10958

Open
HAL9000 wants to merge 9 commits from feature/issue-1923-missing-test-levels-core-module into master
4 changed files with 231 additions and 3 deletions
+1 -3
View File
@@ -3,8 +3,6 @@ name: CI
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
vars:
docker_prefix: "http://harbor.cleverthis.com/docker/"
@@ -92,7 +90,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install asv virtualenv uv=${{ env.UV_VERSION }} nox
python -m pip install asv virtualenv uv==${{ env.UV_VERSION }} nox
- name: Sync prior benchmark results from S3
env:
+32
View File
@@ -0,0 +1,32 @@
Feature: Async Resource Tracker focused coverage
AsyncResourceTracker coverage reuses the existing security_async step
vocabulary so the Behave step registry stays small and fast to load.
Background:
Given I have an async resource tracker
Scenario: Registering and closing a tracked resource
Given I have a mock async resource named "db-pool"
When I register the resource with the tracker
And I close all tracked resources
Then the resource "db-pool" should be closed
And the tracker should have zero open resources
Scenario: Time-bounded shutdown records timed-out resources
Given I have a mock async resource named "slow-resource" that takes 5 seconds to close
And I register the resource with the tracker
When I close all tracked resources with a 0.1 second timeout
Then a warning should be logged about forced termination
And the tracker should report the timed-out resource
Scenario: Registering after close_all is rejected
Given I have a mock async resource named "late-resource"
And I register the resource with the tracker
When I close all tracked resources
And I try to register a resource named "post-close" after close_all
Then a RuntimeError should be raised mentioning "closed"
Scenario: Async context manager closes resources on exit
Given I have a mock async resource named "ctx-resource"
When I use the tracker as an async context manager and register the resource
Then the resource "ctx-resource" should be closed after exiting the context
+124
View File
@@ -0,0 +1,124 @@
*** Settings ***
Documentation Integration tests for AsyncResourceTracker
Library Collections
Library BuiltIn
Library ${CURDIR}/async_cleanup_library.py
*** Test Cases ***
Register And Close Single Resource
[Documentation] Verify basic registration and cleanup of a single resource
${tracker}= Create Async Resource Tracker
${resource}= Create Mock Async Resource test_resource
Register Resource ${tracker} test_resource ${resource}
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 1
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Register Multiple Resources
[Documentation] Verify registration of multiple resources
${tracker}= Create Async Resource Tracker
${res1}= Create Mock Async Resource resource_1
${res2}= Create Mock Async Resource resource_2
${res3}= Create Mock Async Resource resource_3
Register Resource ${tracker} resource_1 ${res1}
Register Resource ${tracker} resource_2 ${res2}
Register Resource ${tracker} resource_3 ${res3}
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 3
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Reject Empty Name
[Documentation] Verify that empty names are rejected
${tracker}= Create Async Resource Tracker
${resource}= Create Mock Async Resource empty_name
Run Keyword And Expect Error ValueError*name must be a non-empty string*
... Register Resource ${tracker} ${EMPTY} ${resource}
Reject None Resource
[Documentation] Verify that None resources are rejected
${tracker}= Create Async Resource Tracker
Run Keyword And Expect Error ValueError*resource must not be None*
... Register Resource ${tracker} test ${None}
Reject Duplicate Registration
[Documentation] Verify that duplicate names are rejected
${tracker}= Create Async Resource Tracker
${resource}= Create Mock Async Resource duplicate
Register Resource ${tracker} duplicate ${resource}
${resource2}= Create Mock Async Resource duplicate2
Run Keyword And Expect Error ValueError*Resource 'duplicate' is already registered*
... Register Resource ${tracker} duplicate ${resource2}
Reject Registration After Close
[Documentation] Verify that registration after close is rejected
${tracker}= Create Async Resource Tracker
Close All Resources ${tracker} timeout=30.0
${resource}= Create Mock Async Resource late
Run Keyword And Expect Error *Cannot register resource after tracker is closed*
... Register Resource ${tracker} late ${resource}
Handle Timeout During Close
[Documentation] Verify timeout handling during resource close
${tracker}= Create Async Resource Tracker
${slow_resource}= Create Slow Async Resource slow_resource 5.0
Register Resource ${tracker} slow_resource ${slow_resource}
Close All Resources ${tracker} timeout=0.1
${timed_out}= Get Timed Out Resources ${tracker}
Should Contain ${timed_out} slow_resource
Handle Exception During Close
[Documentation] Verify exception handling during resource close
${tracker}= Create Async Resource Tracker
${failing_resource}= Create Failing Async Resource failing_resource
Register Resource ${tracker} failing_resource ${failing_resource}
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Close Is Idempotent
[Documentation] Verify that close_all is idempotent
${tracker}= Create Async Resource Tracker
${resource}= Create Mock Async Resource resource
Register Resource ${tracker} resource ${resource}
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Use As Async Context Manager
[Documentation] Verify tracker works as async context manager
${tracker}= Create Async Resource Tracker
${resource}= Create Mock Async Resource ctx_resource
Register Resource ${tracker} ctx_resource ${resource}
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 1
Close All Resources ${tracker} timeout=30.0
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 0
Protocol Compliance
[Documentation] Verify that any object with async close is accepted
${tracker}= Create Async Resource Tracker
${custom}= Create Custom Resource With Close
Register Resource ${tracker} custom ${custom}
${count}= Get Open Count ${tracker}
Should Be Equal As Integers ${count} 1
Close All Resources ${tracker} timeout=30.0
Should Be True ${custom.closed}
+74
View File
@@ -0,0 +1,74 @@
"""Robot Framework library for async_cleanup integration tests."""
import asyncio
from typing import Any
from unittest.mock import AsyncMock
from cleveragents.core.async_cleanup import AsyncResource, AsyncResourceTracker
def create_async_resource_tracker() -> AsyncResourceTracker:
"""Create a new AsyncResourceTracker instance."""
return AsyncResourceTracker()
def create_mock_async_resource(name: str) -> AsyncMock:
"""Create a mock async resource."""
resource = AsyncMock(spec=AsyncResource)
resource.close = AsyncMock()
return resource
def create_slow_async_resource(name: str, delay: float) -> AsyncMock:
"""Create an async resource that delays on close."""
async def slow_close() -> None:
await asyncio.sleep(delay)
resource = AsyncMock(spec=AsyncResource)
resource.close = slow_close
return resource
def create_failing_async_resource(name: str) -> AsyncMock:
"""Create an async resource that raises on close."""
async def failing_close() -> None:
raise RuntimeError(f"Failed to close {name}")
resource = AsyncMock(spec=AsyncResource)
resource.close = failing_close
return resource
def create_custom_resource_with_close() -> Any:
"""Create a custom resource with async close method."""
class CustomResource:
def __init__(self) -> None:
self.closed = False
async def close(self) -> None:
self.closed = True
return CustomResource()
def register_resource(tracker: AsyncResourceTracker, name: str, resource: Any) -> None:
"""Register a resource with the tracker."""
tracker.register(name, resource)
def get_open_count(tracker: AsyncResourceTracker) -> int:
"""Get the number of open resources."""
return tracker.open_count
def get_timed_out_resources(tracker: AsyncResourceTracker) -> list[str]:
"""Get the list of timed out resources."""
return tracker.timed_out_resources
def close_all_resources(tracker: AsyncResourceTracker, timeout: float = 30.0) -> None:
"""Close all resources in the tracker."""
asyncio.run(tracker.close_all(timeout=timeout))