test(core): add comprehensive test levels for async_cleanup module #10958
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user