You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_code_executor_agent.py 24 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. import asyncio
  2. from typing import List
  3. import pytest
  4. from autogen_agentchat.agents import CodeExecutorAgent
  5. from autogen_agentchat.agents._code_executor_agent import ApprovalFuncType, ApprovalRequest, ApprovalResponse
  6. from autogen_agentchat.base import Response
  7. from autogen_agentchat.messages import (
  8. CodeExecutionEvent,
  9. CodeGenerationEvent,
  10. TextMessage,
  11. )
  12. from autogen_core import CancellationToken
  13. from autogen_core.code_executor import CodeBlock
  14. from autogen_core.models import ModelFamily, ModelInfo
  15. from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
  16. from autogen_ext.models.replay import ReplayChatCompletionClient
  17. @pytest.mark.asyncio
  18. async def test_basic_code_execution() -> None:
  19. """Test basic code execution"""
  20. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  21. messages = [
  22. TextMessage(
  23. content="""
  24. ```python
  25. import math
  26. number = 42
  27. square_root = math.sqrt(number)
  28. print("%0.3f" % (square_root,))
  29. ```
  30. """.strip(),
  31. source="assistant",
  32. )
  33. ]
  34. response = await agent.on_messages(messages, CancellationToken())
  35. assert isinstance(response, Response)
  36. assert isinstance(response.chat_message, TextMessage)
  37. assert response.chat_message.content.strip() == "6.481"
  38. assert response.chat_message.source == "code_executor"
  39. @pytest.mark.asyncio
  40. async def test_code_generation_and_execution_with_model_client() -> None:
  41. """
  42. Tests the code generation, execution and reflection pipeline using a model client.
  43. """
  44. language = "python"
  45. code = 'import math\n\nnumber = 42\nsquare_root = math.sqrt(number)\nprint("%0.3f" % (square_root,))'
  46. model_client = ReplayChatCompletionClient(
  47. [f"Here is the code to calculate the square root of 42:\n```{language}\n{code}```".strip(), "TERMINATE"]
  48. )
  49. agent = CodeExecutorAgent(
  50. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  51. )
  52. messages = [
  53. TextMessage(
  54. content="Generate python code to calculate the square root of 42",
  55. source="assistant",
  56. )
  57. ]
  58. code_generation_event: CodeGenerationEvent | None = None
  59. code_execution_event: CodeExecutionEvent | None = None
  60. response: Response | None = None
  61. async for message in agent.on_messages_stream(messages, CancellationToken()):
  62. if isinstance(message, CodeGenerationEvent):
  63. code_block = message.code_blocks[0]
  64. assert code_block.code == code, "Code block does not match"
  65. assert code_block.language == language, "Language does not match"
  66. code_generation_event = message
  67. elif isinstance(message, CodeExecutionEvent):
  68. assert message.to_text().strip() == "6.481", f"Expected '6.481', got: {message.to_text().strip()}"
  69. code_execution_event = message
  70. elif isinstance(message, Response):
  71. assert isinstance(
  72. message.chat_message, TextMessage
  73. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  74. assert (
  75. message.chat_message.source == "code_executor_agent"
  76. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  77. response = message
  78. else:
  79. raise AssertionError(f"Unexpected message type: {type(message)}")
  80. assert code_generation_event is not None, "Code generation event was not received"
  81. assert code_execution_event is not None, "Code execution event was not received"
  82. assert response is not None, "Response was not received"
  83. @pytest.mark.asyncio
  84. async def test_no_code_response_with_model_client() -> None:
  85. """
  86. Tests agent behavior when the model client responds with non-code content.
  87. """
  88. model_client = ReplayChatCompletionClient(["The capital of France is Paris.", "TERMINATE"])
  89. agent = CodeExecutorAgent(
  90. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  91. )
  92. messages = [
  93. TextMessage(
  94. content="What is the capital of France?",
  95. source="assistant",
  96. )
  97. ]
  98. response: Response | None = None
  99. async for message in agent.on_messages_stream(messages, CancellationToken()):
  100. if isinstance(message, Response):
  101. assert isinstance(
  102. message.chat_message, TextMessage
  103. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  104. assert (
  105. message.chat_message.source == "code_executor_agent"
  106. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  107. assert (
  108. message.chat_message.content.strip() == "The capital of France is Paris."
  109. ), f"Expected 'The capital of France is Paris.', got: {message.chat_message.content.strip()}"
  110. response = message
  111. else:
  112. raise AssertionError(f"Unexpected message type: {type(message)}")
  113. assert response is not None, "Response was not received"
  114. @pytest.mark.asyncio
  115. async def test_self_debugging_loop() -> None:
  116. """
  117. Tests self debugging loop when the model client responds with incorrect code.
  118. """
  119. language = "python"
  120. incorrect_code_block = """
  121. numbers = [10, 20, 30, 40, 50]
  122. mean = sum(numbers) / len(numbers
  123. print("The mean is:", mean)
  124. """.strip()
  125. incorrect_code_result = """
  126. mean = sum(numbers) / len(numbers
  127. ^
  128. SyntaxError: '(' was never closed
  129. """.strip()
  130. correct_code_block = """
  131. numbers = [10, 20, 30, 40, 50]
  132. mean = sum(numbers) / len(numbers)
  133. print("The mean is:", mean)
  134. """.strip()
  135. correct_code_result = """
  136. The mean is: 30.0
  137. """.strip()
  138. model_client = ReplayChatCompletionClient(
  139. [
  140. f"""
  141. Here is the code to calculate the mean of 10, 20, 30, 40, 50
  142. ```{language}
  143. {incorrect_code_block}
  144. ```
  145. """,
  146. """{"retry": "true", "reason": "Retry 1: It is a test environment"}""",
  147. f"""
  148. Here is the updated code to calculate the mean of 10, 20, 30, 40, 50
  149. ```{language}
  150. {correct_code_block}
  151. ```""",
  152. "Final Response",
  153. "TERMINATE",
  154. ],
  155. model_info=ModelInfo(
  156. vision=False,
  157. function_calling=False,
  158. json_output=True,
  159. family=ModelFamily.UNKNOWN,
  160. structured_output=True,
  161. ),
  162. )
  163. agent = CodeExecutorAgent(
  164. name="code_executor_agent",
  165. code_executor=LocalCommandLineCodeExecutor(),
  166. model_client=model_client,
  167. max_retries_on_error=1,
  168. )
  169. messages = [
  170. TextMessage(
  171. content="Calculate the mean of 10, 20, 30, 40, 50.",
  172. source="assistant",
  173. )
  174. ]
  175. incorrect_code_generation_event: CodeGenerationEvent | None = None
  176. correct_code_generation_event: CodeGenerationEvent | None = None
  177. retry_decision_event: CodeGenerationEvent | None = None
  178. incorrect_code_execution_event: CodeExecutionEvent | None = None
  179. correct_code_execution_event: CodeExecutionEvent | None = None
  180. response: Response | None = None
  181. message_id: int = 0
  182. async for message in agent.on_messages_stream(messages, CancellationToken()):
  183. if isinstance(message, CodeGenerationEvent) and message_id == 0:
  184. # Step 1: First code generation
  185. code_block = message.code_blocks[0]
  186. assert code_block.code.strip() == incorrect_code_block, "Incorrect code block does not match"
  187. assert code_block.language == language, "Language does not match"
  188. incorrect_code_generation_event = message
  189. elif isinstance(message, CodeExecutionEvent) and message_id == 1:
  190. # Step 2: First code execution
  191. assert (
  192. incorrect_code_result in message.to_text().strip()
  193. ), f"Expected {incorrect_code_result} in execution result, got: {message.to_text().strip()}"
  194. incorrect_code_execution_event = message
  195. elif isinstance(message, CodeGenerationEvent) and message_id == 2:
  196. # Step 3: Retry generation with proposed correction
  197. retry_response = "Attempt number: 1\nProposed correction: Retry 1: It is a test environment"
  198. assert (
  199. message.to_text().strip() == retry_response
  200. ), f"Expected {retry_response}, got: {message.to_text().strip()}"
  201. retry_decision_event = message
  202. elif isinstance(message, CodeGenerationEvent) and message_id == 3:
  203. # Step 4: Second retry code generation
  204. code_block = message.code_blocks[0]
  205. assert code_block.code.strip() == correct_code_block, "Correct code block does not match"
  206. assert code_block.language == language, "Language does not match"
  207. correct_code_generation_event = message
  208. elif isinstance(message, CodeExecutionEvent) and message_id == 4:
  209. # Step 5: Second retry code execution
  210. assert (
  211. message.to_text().strip() == correct_code_result
  212. ), f"Expected {correct_code_result} in execution result, got: {message.to_text().strip()}"
  213. correct_code_execution_event = message
  214. elif isinstance(message, Response) and message_id == 5:
  215. # Step 6: Final response
  216. assert isinstance(
  217. message.chat_message, TextMessage
  218. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  219. assert (
  220. message.chat_message.source == "code_executor_agent"
  221. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  222. response = message
  223. else:
  224. raise AssertionError(f"Unexpected message type: {type(message)}")
  225. message_id += 1
  226. assert incorrect_code_generation_event is not None, "Incorrect code generation event was not received"
  227. assert incorrect_code_execution_event is not None, "Incorrect code execution event was not received"
  228. assert retry_decision_event is not None, "Retry decision event was not received"
  229. assert correct_code_generation_event is not None, "Correct code generation event was not received"
  230. assert correct_code_execution_event is not None, "Correct code execution event was not received"
  231. assert response is not None, "Response was not received"
  232. @pytest.mark.asyncio
  233. async def test_code_execution_error() -> None:
  234. """Test basic code execution"""
  235. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  236. messages = [
  237. TextMessage(
  238. content="""
  239. ```python
  240. import math
  241. number = -1.0
  242. square_root = math.sqrt(number)
  243. print("%0.3f" % (square_root,))
  244. ```
  245. """.strip(),
  246. source="assistant",
  247. )
  248. ]
  249. response = await agent.on_messages(messages, CancellationToken())
  250. assert isinstance(response, Response)
  251. assert isinstance(response.chat_message, TextMessage)
  252. assert "The script ran, then exited with an error (POSIX exit code: 1)" in response.chat_message.content
  253. assert "ValueError: math domain error" in response.chat_message.content
  254. @pytest.mark.asyncio
  255. async def test_code_execution_no_output() -> None:
  256. """Test basic code execution"""
  257. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  258. messages = [
  259. TextMessage(
  260. content="""
  261. ```python
  262. import math
  263. number = 42
  264. square_root = math.sqrt(number)
  265. ```
  266. """.strip(),
  267. source="assistant",
  268. )
  269. ]
  270. response = await agent.on_messages(messages, CancellationToken())
  271. assert isinstance(response, Response)
  272. assert isinstance(response.chat_message, TextMessage)
  273. assert (
  274. "The script ran but produced no output to console. The POSIX exit code was: 0. If you were expecting output, consider revising the script to ensure content is printed to stdout."
  275. in response.chat_message.content
  276. )
  277. @pytest.mark.asyncio
  278. async def test_code_execution_no_block() -> None:
  279. """Test basic code execution"""
  280. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  281. messages = [
  282. TextMessage(
  283. content="""
  284. import math
  285. number = 42
  286. square_root = math.sqrt(number)
  287. """.strip(),
  288. source="assistant",
  289. )
  290. ]
  291. response = await agent.on_messages(messages, CancellationToken())
  292. assert isinstance(response, Response)
  293. assert isinstance(response.chat_message, TextMessage)
  294. assert (
  295. "No code blocks found in the thread. Please provide at least one markdown-encoded code block"
  296. in response.chat_message.content
  297. )
  298. @pytest.mark.asyncio
  299. async def test_code_execution_multiple_blocks() -> None:
  300. """Test basic code execution"""
  301. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  302. messages = [
  303. TextMessage(
  304. content="""
  305. ```python
  306. import math
  307. number = 42
  308. square_root = math.sqrt(number)
  309. print("%0.3f" % (square_root,))
  310. ```
  311. And also:
  312. ```python
  313. import time
  314. print(f"The current time is: {time.time()}")
  315. ```
  316. And this should result in an error:
  317. ```python
  318. import math
  319. number = -1.0
  320. square_root = math.sqrt(number)
  321. print("%0.3f" % (square_root,))
  322. ```
  323. """.strip(),
  324. source="assistant",
  325. )
  326. ]
  327. response = await agent.on_messages(messages, CancellationToken())
  328. assert isinstance(response, Response)
  329. assert isinstance(response.chat_message, TextMessage)
  330. assert "6.481" in response.chat_message.content
  331. assert "The current time is:" in response.chat_message.content
  332. assert "The script ran, then exited with an error (POSIX exit code: 1)" in response.chat_message.content
  333. assert "ValueError: math domain error" in response.chat_message.content
  334. @pytest.mark.asyncio
  335. async def test_code_execution_agent_serialization() -> None:
  336. """Test agent config serialization"""
  337. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  338. # Serialize and deserialize the agent
  339. serialized_agent = agent.dump_component()
  340. deserialized_agent = CodeExecutorAgent.load_component(serialized_agent)
  341. assert isinstance(deserialized_agent, CodeExecutorAgent)
  342. assert deserialized_agent.name == "code_executor"
  343. @pytest.mark.asyncio
  344. async def test_code_execution_agent_serialization_with_model_client() -> None:
  345. """Test agent config serialization"""
  346. model_client = ReplayChatCompletionClient(["The capital of France is Paris.", "TERMINATE"])
  347. agent = CodeExecutorAgent(
  348. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  349. )
  350. # Serialize and deserialize the agent
  351. serialized_agent = agent.dump_component()
  352. deserialized_agent = CodeExecutorAgent.load_component(serialized_agent)
  353. assert isinstance(deserialized_agent, CodeExecutorAgent)
  354. assert deserialized_agent.name == "code_executor_agent"
  355. assert deserialized_agent._model_client is not None # type: ignore
  356. # Approval function test helpers
  357. def approval_function_allow_all(request: ApprovalRequest) -> ApprovalResponse:
  358. """Approval function that allows all code execution."""
  359. return ApprovalResponse(approved=True, reason="All code is approved")
  360. def approval_function_deny_dangerous(request: ApprovalRequest) -> ApprovalResponse:
  361. """Approval function that denies potentially dangerous code."""
  362. dangerous_keywords = ["rm ", "del ", "format", "delete", "DROP TABLE"]
  363. for keyword in dangerous_keywords:
  364. if keyword in request.code:
  365. return ApprovalResponse(approved=False, reason=f"Code contains potentially dangerous keyword: {keyword}")
  366. return ApprovalResponse(approved=True, reason="Code appears safe")
  367. def approval_function_deny_all(request: ApprovalRequest) -> ApprovalResponse:
  368. """Approval function that denies all code execution."""
  369. return ApprovalResponse(approved=False, reason="All code execution is denied")
  370. # Async approval function test helpers
  371. async def async_approval_function_allow_all(request: ApprovalRequest) -> ApprovalResponse:
  372. """Async approval function that allows all code execution."""
  373. await asyncio.sleep(0.01) # Simulate async operation
  374. return ApprovalResponse(approved=True, reason="All code is approved (async)")
  375. async def async_approval_function_deny_dangerous(request: ApprovalRequest) -> ApprovalResponse:
  376. """Async approval function that denies potentially dangerous code."""
  377. await asyncio.sleep(0.01) # Simulate async operation
  378. dangerous_keywords = ["rm ", "del ", "format", "delete", "DROP TABLE"]
  379. for keyword in dangerous_keywords:
  380. if keyword in request.code:
  381. return ApprovalResponse(
  382. approved=False, reason=f"Code contains potentially dangerous keyword: {keyword} (async)"
  383. )
  384. return ApprovalResponse(approved=True, reason="Code appears safe (async)")
  385. async def async_approval_function_deny_all(request: ApprovalRequest) -> ApprovalResponse:
  386. """Async approval function that denies all code execution."""
  387. await asyncio.sleep(0.01) # Simulate async operation
  388. return ApprovalResponse(approved=False, reason="All code execution is denied (async)")
  389. @pytest.mark.asyncio
  390. async def test_approval_functionality_no_approval() -> None:
  391. """Test that CodeExecutorAgent works without approval function (default behavior)."""
  392. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor())
  393. code_blocks = [CodeBlock(code="print('Hello World!')", language="python")]
  394. result = await agent.execute_code_block(code_blocks, CancellationToken())
  395. # Should execute successfully
  396. assert result.exit_code == 0
  397. assert "Hello World!" in result.output
  398. @pytest.mark.asyncio
  399. @pytest.mark.parametrize(
  400. "approval_func,code,language,expected_exit_code,expected_in_output",
  401. [
  402. (approval_function_allow_all, "print('Approved code')", "python", 0, "Approved code"),
  403. (approval_function_deny_dangerous, "print('Safe code')", "python", 0, "Safe code"),
  404. (approval_function_deny_dangerous, "rm somefile.txt", "sh", 1, "dangerous keyword"),
  405. (approval_function_deny_all, "print('This should be denied')", "python", 1, "All code execution is denied"),
  406. ],
  407. )
  408. async def test_approval_functionality_sync(
  409. approval_func: ApprovalFuncType, code: str, language: str, expected_exit_code: int, expected_in_output: str
  410. ) -> None:
  411. """Test sync approval functionality with various approval functions and code samples."""
  412. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=approval_func)
  413. code_blocks = [CodeBlock(code=code, language=language)]
  414. result = await agent.execute_code_block(code_blocks, CancellationToken())
  415. assert result.exit_code == expected_exit_code
  416. assert expected_in_output in result.output
  417. @pytest.mark.asyncio
  418. @pytest.mark.parametrize("is_async", [False, True])
  419. async def test_approval_functionality_context_passed(is_async: bool) -> None:
  420. """Test that approval functions receive the correct context."""
  421. received_requests: List[ApprovalRequest] = []
  422. if is_async:
  423. async def capture_context_async(request: ApprovalRequest) -> ApprovalResponse:
  424. await asyncio.sleep(0.01)
  425. received_requests.append(request)
  426. return ApprovalResponse(approved=True, reason="Captured for testing (async)")
  427. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=capture_context_async)
  428. else:
  429. def capture_context_sync(request: ApprovalRequest) -> ApprovalResponse:
  430. received_requests.append(request)
  431. return ApprovalResponse(approved=True, reason="Captured for testing")
  432. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=capture_context_sync)
  433. code_blocks = [CodeBlock(code="print('Test context')", language="python")]
  434. await agent.execute_code_block(code_blocks, CancellationToken())
  435. # Verify the approval function was called and received the correct data
  436. assert len(received_requests) == 1
  437. request = received_requests[0]
  438. assert isinstance(request, ApprovalRequest)
  439. assert "print('Test context')" in request.code
  440. assert "```python" in request.code
  441. assert isinstance(request.context, list)
  442. @pytest.mark.parametrize(
  443. "approval_func",
  444. [approval_function_allow_all, async_approval_function_allow_all],
  445. )
  446. def test_approval_functionality_serialization_fails(approval_func: ApprovalFuncType) -> None:
  447. """Test that serialization fails when approval function is set."""
  448. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=approval_func)
  449. # Should raise ValueError when trying to serialize
  450. with pytest.raises(ValueError, match="Cannot serialize CodeExecutorAgent with approval_func set"):
  451. agent.dump_component()
  452. def test_approval_functionality_serialization_succeeds() -> None:
  453. """Test that serialization succeeds when no approval function is set."""
  454. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor())
  455. # Should serialize successfully
  456. config = agent.dump_component()
  457. assert config.config["name"] == "test_agent"
  458. @pytest.mark.asyncio
  459. @pytest.mark.parametrize(
  460. "approval_func,async_marker",
  461. [
  462. (approval_function_deny_dangerous, ""),
  463. (async_approval_function_deny_dangerous, "(async)"),
  464. ],
  465. )
  466. async def test_approval_functionality_with_on_messages(approval_func: ApprovalFuncType, async_marker: str) -> None:
  467. """Test approval functionality works with the on_messages interface."""
  468. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=approval_func)
  469. # Test with safe code
  470. safe_message = TextMessage(content="```python\nprint('Safe message')\n```", source="user")
  471. response = await agent.on_messages([safe_message], CancellationToken())
  472. assert isinstance(response.chat_message, TextMessage)
  473. assert "Safe message" in response.chat_message.content
  474. # Test with dangerous code
  475. dangerous_message = TextMessage(content="```sh\nrm -rf /\n```", source="user")
  476. response = await agent.on_messages([dangerous_message], CancellationToken())
  477. assert isinstance(response.chat_message, TextMessage)
  478. assert "Code execution was not approved" in response.chat_message.content
  479. if async_marker:
  480. assert async_marker in response.chat_message.content
  481. @pytest.mark.asyncio
  482. @pytest.mark.parametrize(
  483. "approval_func,code,language,expected_exit_code,expected_in_output",
  484. [
  485. (async_approval_function_allow_all, "print('Approved async code')", "python", 0, "Approved async code"),
  486. (async_approval_function_deny_dangerous, "print('Safe async code')", "python", 0, "Safe async code"),
  487. (async_approval_function_deny_dangerous, "rm somefile.txt", "sh", 1, "dangerous keyword"),
  488. (
  489. async_approval_function_deny_all,
  490. "print('This should be denied async')",
  491. "python",
  492. 1,
  493. "All code execution is denied (async)",
  494. ),
  495. ],
  496. )
  497. async def test_approval_functionality_async(
  498. approval_func: ApprovalFuncType, code: str, language: str, expected_exit_code: int, expected_in_output: str
  499. ) -> None:
  500. """Test async approval functionality with various approval functions and code samples."""
  501. agent = CodeExecutorAgent("test_agent", LocalCommandLineCodeExecutor(), approval_func=approval_func)
  502. code_blocks = [CodeBlock(code=code, language=language)]
  503. result = await agent.execute_code_block(code_blocks, CancellationToken())
  504. assert result.exit_code == expected_exit_code
  505. assert expected_in_output in result.output