""" BDD test environment setup for reactive CleverAgents. """ import asyncio import os import shutil import sys import tempfile from pathlib import Path # Increase recursion limit to handle deep import chains in langchain/torch/transformers sys.setrecursionlimit(5000) def before_all(context): """Set up test environment before all tests.""" context.temp_dir = Path(tempfile.mkdtemp()) # Set dummy API keys for testing os.environ["OPENAI_API_KEY"] = "test-key-openai" os.environ["ANTHROPIC_API_KEY"] = "test-key-anthropic" os.environ["GOOGLE_GEMINI_API_KEY"] = "test-key-google" # Create async event loop for tests context.loop = asyncio.new_event_loop() asyncio.set_event_loop(context.loop) def after_all(context): """Clean up test environment after all tests.""" # Clean up temp directory import shutil shutil.rmtree(context.temp_dir, ignore_errors=True) # Close async event loop with proper cleanup if hasattr(context, "loop"): # Cancel all remaining tasks try: pending = asyncio.all_tasks(context.loop) for task in pending: task.cancel() except Exception: pass # Close the loop without waiting for tasks try: context.loop.close() except Exception: pass def before_scenario(context, scenario): """Set up before each scenario.""" context.config_files = [] context.app = None context.result = None context.error = None context.unsafe = False # Create scenario-specific temp directory context.scenario_temp = context.temp_dir / f"scenario_{scenario.name.replace(' ', '_')}" context.scenario_temp.mkdir(exist_ok=True) # Set up test context for InlineYAMLJinja tests - not needed for simplified tests # if "InlineYAMLJinja" in scenario.feature.name or "inline_yaml_jinja" in str(scenario.feature.filename): # from tests.features.steps.inline_yaml_jinja_coverage_steps import TestContext # context.test_context = TestContext() def after_scenario(context, scenario): """Clean up after each scenario.""" # Clean up bridge resources if present if hasattr(context, "bridge") and context.bridge: try: context.bridge.cleanup() except Exception: pass # Dispose of app if created if hasattr(context, "app") and context.app: try: # dispose() is async, so we need to run it in the event loop if hasattr(context, "loop") and context.loop: context.loop.run_until_complete(context.app.dispose()) else: asyncio.run(context.app.dispose()) except Exception: pass # Cancel any remaining tasks without waiting if hasattr(context, "loop"): try: pending = asyncio.all_tasks(context.loop) for task in pending: task.cancel() except Exception: pass # Clean up event loops created by LangGraph tests if hasattr(context, "event_loops"): for loop in context.event_loops: try: if hasattr(loop, "is_closed") and not loop.is_closed(): # Cancel all tasks in this loop tasks = asyncio.all_tasks(loop) for task in tasks: task.cancel() loop.close() except Exception: pass # Clean up temp directories from LangGraph tests if hasattr(context, "temp_dirs"): for temp_dir in context.temp_dirs: try: shutil.rmtree(temp_dir) except: pass # Clean up graphs and their schedulers if hasattr(context, "graphs"): for graph in context.graphs: try: if hasattr(graph, "scheduler") and hasattr(graph.scheduler, "_loop"): loop = graph.scheduler._loop if hasattr(loop, "is_closed") and not loop.is_closed(): # Cancel all tasks tasks = asyncio.all_tasks(loop) for task in tasks: task.cancel() loop.close() except Exception: pass # Clean up template loader test files if "template_loaders_coverage" in str(scenario.feature.filename): # fmt: off from tests.features.steps.template_loaders_coverage_steps import cleanup_test_files # isort: skip # fmt: on cleanup_test_files(context) # Clean up files tracked for cleanup in tool_agent tests if hasattr(context, "__dict__") and "_cleanup_files" in context.__dict__: import os for filepath in context.__dict__["_cleanup_files"]: try: if os.path.exists(filepath): os.remove(filepath) except Exception: pass context.__dict__["_cleanup_files"] = [] # Clean up scenario temp directory if hasattr(context, "scenario_temp") and context.scenario_temp.exists(): import shutil try: shutil.rmtree(context.scenario_temp, ignore_errors=True) except Exception: pass