Skip to content

Commit 8c56081

Browse files
Merge pull request #63 from itsnegaar/feature/upgrade_behave
feature: update to behave 1.3.0 and handle async with behave
2 parents 1ca9b75 + c559985 commit 8c56081

9 files changed

Lines changed: 404 additions & 748 deletions

features/environment.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,6 @@ def before_scenario(context: Context, scenario: Scenario):
6565
except Exception as e:
6666
logger.exception(f"Error setting test config: {e}")
6767

68-
# Set up async test environment if needed
69-
if "async" in scenario.name.lower() or any("async" in tag.lower() for tag in scenario.tags):
70-
logger.info("Setting up async test environment")
71-
try:
72-
# Create a new event loop for this scenario
73-
loop = asyncio.new_event_loop()
74-
asyncio.set_event_loop(loop)
75-
scenario_context.store("_async_test_loop", loop)
76-
except Exception as e:
77-
logger.exception(f"Error setting up async environment: {e}")
7868

7969

8070
def after_scenario(context: Context, scenario: Scenario):

features/scenario_context.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,14 @@ def cleanup(self):
4343
# Clean up async adapter
4444
if self.async_adapter:
4545
try:
46-
if hasattr(self.async_adapter, "session_manager") and hasattr(
47-
self.async_adapter.session_manager,
48-
"engine",
49-
):
50-
# For async, we need to get a loop and run the coroutine
51-
loop = asyncio.get_event_loop()
52-
if not loop.is_closed():
53-
try:
54-
# Run the session removal coroutine
55-
loop.run_until_complete(self.async_adapter.session_manager.remove_session())
56-
57-
# Run the engine disposal coroutine
58-
loop.run_until_complete(self.async_adapter.session_manager.engine.dispose())
59-
except Exception as e:
60-
print(f"Error in async cleanup: {e}")
61-
else:
62-
# If the loop is closed, create a new one temporarily
63-
temp_loop = asyncio.new_event_loop()
64-
try:
65-
asyncio.set_event_loop(temp_loop)
66-
temp_loop.run_until_complete(self.async_adapter.session_manager.remove_session())
67-
temp_loop.run_until_complete(self.async_adapter.session_manager.engine.dispose())
68-
finally:
69-
temp_loop.close()
46+
# Try to run async cleanup if we're in an async context
47+
try:
48+
loop = asyncio.get_running_loop()
49+
# If we have a running loop, create a task
50+
asyncio.create_task(self.async_cleanup())
51+
except RuntimeError:
52+
# No running loop, run in new loop
53+
asyncio.run(self.async_cleanup())
7054
except Exception as e:
7155
print(f"Error in async cleanup: {e}")
7256

@@ -80,3 +64,16 @@ def cleanup(self):
8064
os.remove(self.db_file)
8165
except Exception as e:
8266
print(f"Error removing database file: {e}")
67+
68+
async def async_cleanup(self):
69+
"""Clean up async resources associated with this scenario."""
70+
if self.async_adapter:
71+
try:
72+
if hasattr(self.async_adapter, "session_manager") and hasattr(
73+
self.async_adapter.session_manager, "engine"
74+
):
75+
# Clean up async sessions and engine
76+
await self.async_adapter.session_manager.remove_session()
77+
await self.async_adapter.session_manager.engine.dispose()
78+
except Exception as e:
79+
print(f"Error in async cleanup: {e}")

features/steps/atomic_transaction_steps.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This module contains step definitions for both synchronous and asynchronous
44
atomic transaction scenarios.
55
"""
6-
6+
import asyncio
77
import logging
88
import os
99
import tempfile
@@ -16,12 +16,10 @@
1616
from features.test_entity import RelatedTestEntity, TestAdminEntity, TestEntity, TestManagerEntity
1717
from features.test_entity_factory import TestEntityFactory
1818
from features.test_helpers import (
19-
SafeAsyncContextManager,
2019
async_schema_setup,
2120
get_adapter,
2221
get_async_adapter,
2322
get_current_scenario_context,
24-
safe_run_async,
2523
)
2624
from sqlalchemy import select
2725

@@ -127,8 +125,7 @@ def step_given_database_initialized(context):
127125

128126
# Create schema with async adapter
129127
logger.info("Creating database schema with async adapter")
130-
with SafeAsyncContextManager(context) as ctx:
131-
ctx.run(async_schema_setup(async_adapter))
128+
asyncio.run(async_schema_setup(async_adapter))
132129

133130
logger.info("Async adapter and schema setup completed")
134131
except Exception as e:
@@ -965,7 +962,6 @@ def verify_consistency():
965962

966963

967964
@when("a new entity is created in an async atomic transaction")
968-
@safe_run_async
969965
async def step_when_entity_created_in_async_atomic(context):
970966
"""Create a new entity within an async atomic transaction."""
971967
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -993,7 +989,6 @@ async def create_entity_async_atomic():
993989

994990

995991
@then("the async entity should be retrievable")
996-
@safe_run_async
997992
async def step_then_async_entity_should_be_retrievable(context):
998993
"""Verify the entity exists after async atomic transaction."""
999994
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1016,7 +1011,6 @@ async def get_entity():
10161011

10171012

10181013
@when("a new async entity creation fails within an atomic transaction")
1019-
@safe_run_async
10201014
async def step_when_async_entity_creation_fails(context):
10211015
"""Attempt to create an async entity with a failure that causes rollback."""
10221016
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1053,7 +1047,6 @@ async def create_entity_with_failure():
10531047

10541048

10551049
@then("no async entity should exist in the database")
1056-
@safe_run_async
10571050
async def step_then_no_async_entity_should_exist(context):
10581051
"""Verify the entity doesn't exist after failed async atomic transaction."""
10591052
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1075,7 +1068,6 @@ async def check_entity_absence():
10751068

10761069

10771070
@then("the async database session should remain usable")
1078-
@safe_run_async
10791071
async def step_then_async_session_should_remain_usable(context):
10801072
"""Verify the async session is still usable after a failed transaction."""
10811073
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1107,7 +1099,6 @@ async def verify_session_usable():
11071099

11081100

11091101
@when("multiple entities are created in an async atomic transaction")
1110-
@safe_run_async
11111102
async def step_when_multiple_async_entities_created(context):
11121103
"""Create multiple entities in a single async atomic transaction."""
11131104
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1141,7 +1132,6 @@ async def create_multiple_entities():
11411132

11421133

11431134
@then("all async entities should be retrievable")
1144-
@safe_run_async
11451135
async def step_then_all_async_entities_retrievable(context):
11461136
"""Verify all entities exist after async atomic transaction."""
11471137
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1177,7 +1167,6 @@ async def verify_entities():
11771167

11781168

11791169
@when("complex async operations are performed in a transaction")
1180-
@safe_run_async
11811170
async def step_when_complex_async_operations(context):
11821171
"""Demonstrate more complex async operations with proper session management."""
11831172
logger = getattr(context, "logger", logging.getLogger("behave.steps"))
@@ -1227,7 +1216,6 @@ async def create_entity_with_relations():
12271216

12281217

12291218
@then("all related entities should be accessible")
1230-
@safe_run_async
12311219
async def step_then_related_entities_accessible(context):
12321220
"""Verify that related entities can be accessed through relationships."""
12331221
logger = getattr(context, "logger", logging.getLogger("behave.steps"))

0 commit comments

Comments
 (0)