Download test_codex_auto_run.py from yitongl/codex_exec: direct link, hf CLI and curl.
- Browser
- Download file 20 kB
-
https://huggingface.co/yitongl/codex_exec/resolve/main/test_codex_auto_run.py
- Command line
-
hf download hf://yitongl/codex_exec/test_codex_auto_run.py
-
curl -L -o test_codex_auto_run.py https://huggingface.co/yitongl/codex_exec/resolve/main/test_codex_auto_run.py
20 kB
| from __future__ import annotations | |
| import contextlib | |
| import io | |
| import os | |
| import shlex | |
| import stat | |
| import tempfile | |
| import unittest | |
| from pathlib import Path | |
| from unittest import mock | |
| import codex_auto_run as app | |
| COMMAND_APPROVAL = """ | |
| Would you like to run the following command? | |
| Reason: tests need to run | |
| $ python -m unittest | |
| › 1. Yes, proceed (y) | |
| 2. Yes, and don't ask again for commands that start with `python` (p) | |
| 3. No, and tell Codex what to do differently (esc) | |
| Press enter to confirm or esc to cancel | |
| """ | |
| EDIT_APPROVAL = """ | |
| Would you like to make the following edits? | |
| Reason: update the implementation | |
| › 1. Yes, proceed (y) | |
| 2. Yes, and don't ask again for these files (a) | |
| 3. No, and tell Codex what to do differently (esc) | |
| Press enter to confirm or esc to cancel | |
| """ | |
| PERMISSIONS_APPROVAL = """ | |
| Would you like to grant these permissions? | |
| Permission rule: network; write `/tmp/output` | |
| › 1. Yes, grant these permissions for this turn (y) | |
| 2. Yes, grant for this turn with strict auto review (r) | |
| 3. Yes, grant these permissions for this session (a) | |
| 4. No, continue without permissions (d) | |
| Press enter to confirm or esc to cancel | |
| """ | |
| NETWORK_APPROVAL = """ | |
| Do you want to approve network access to "example.com"? | |
| Reason: fetch a dependency | |
| › 1. Yes, just this once (y) | |
| 2. Yes, and allow this host for this conversation (a) | |
| 3. Yes, and allow this host in the future (p) | |
| 4. No, and tell Codex what to do differently (esc) | |
| Press enter to confirm or esc to cancel | |
| """ | |
| MCP_APPROVAL = """ | |
| github needs your approval. | |
| Server: github | |
| Create an issue comment | |
| › 1. Yes, provide the requested info (y) | |
| 2. No, but continue without it (n) | |
| 3. Cancel this request (esc) | |
| Press enter to confirm or esc to cancel | |
| """ | |
| MCP_TOOL_APPROVAL = """ | |
| Field 1/1 | |
| Allow Calendar to create an event | |
| Calendar: primary | |
| Title: Roadmap review | |
| › 1. Allow Run the tool and continue. | |
| 2. Cancel Cancel this tool call | |
| enter to submit | esc to cancel | |
| """ | |
| TRUST_DIRECTORY = """ | |
| > You are in /workspace/project | |
| Do you trust the contents of this directory? Working with untrusted | |
| contents comes with higher risk of prompt injection. | |
| › 1. Yes, continue | |
| 2. No, quit | |
| Press enter to continue | |
| """ | |
| FULL_ACCESS = """ | |
| Enable full access? | |
| When Codex runs with full access, it can edit any file. | |
| › 1. Yes, continue anyway | |
| 2. Yes, and don't ask again | |
| 3. Cancel | |
| Press enter to confirm or esc to cancel | |
| """ | |
| REQUEST_USER_INPUT = """ | |
| Questions 1/1 | |
| Which deployment target should be used? | |
| › 1. Staging | |
| 2. Production | |
| enter to submit | esc to cancel | |
| """ | |
| def rows(value: str) -> list[str]: | |
| return value.strip("\n").splitlines() | |
| def config( | |
| runtime_dir: Path, | |
| *, | |
| approve_mcp: bool = True, | |
| auto_trust_directory: bool = True, | |
| stability_polls: int = 2, | |
| ) -> app.Config: | |
| return app.Config( | |
| session_prefix="codex-auto-test", | |
| runtime_dir=runtime_dir, | |
| poll_interval=0.1, | |
| cooldown=0.5, | |
| stability_polls=stability_polls, | |
| rearm_interval=2.0, | |
| idle_exit_seconds=2.0, | |
| approve_mcp=approve_mcp, | |
| auto_trust_directory=auto_trust_directory, | |
| keep_dead_session=False, | |
| ) | |
| class DetectorTests(unittest.TestCase): | |
| def setUp(self) -> None: | |
| self.temp = tempfile.TemporaryDirectory() | |
| self.runtime = Path(self.temp.name) | |
| def tearDown(self) -> None: | |
| self.temp.cleanup() | |
| def test_standard_approval_types(self) -> None: | |
| cfg = config(self.runtime) | |
| expected = { | |
| COMMAND_APPROVAL: "command", | |
| EDIT_APPROVAL: "edit", | |
| PERMISSIONS_APPROVAL: "permissions", | |
| NETWORK_APPROVAL: "network", | |
| } | |
| for fixture, kind in expected.items(): | |
| with self.subTest(kind=kind): | |
| candidate = app.detect_candidate(rows(fixture), cfg) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual(kind, candidate.kind) | |
| def test_mcp_defaults_to_enabled_but_can_be_disabled(self) -> None: | |
| enabled = config(self.runtime) | |
| disabled = config(self.runtime, approve_mcp=False) | |
| for fixture in (MCP_APPROVAL, MCP_TOOL_APPROVAL): | |
| with self.subTest(fixture=fixture[:30]): | |
| candidate = app.detect_candidate(rows(fixture), enabled) | |
| self.assertIsNotNone(candidate) | |
| self.assertIn(candidate.kind, {"mcp", "mcp_tool"}) | |
| self.assertIsNone(app.detect_candidate(rows(fixture), disabled)) | |
| def test_directory_trust_defaults_to_enabled_but_can_be_disabled(self) -> None: | |
| candidate = app.detect_candidate(rows(TRUST_DIRECTORY), config(self.runtime)) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual("trust_directory", candidate.kind) | |
| self.assertIsNone( | |
| app.detect_candidate( | |
| rows(TRUST_DIRECTORY), | |
| config(self.runtime, auto_trust_directory=False), | |
| ) | |
| ) | |
| def test_generic_interactive_surfaces_are_approved(self) -> None: | |
| cfg = config(self.runtime) | |
| expected = { | |
| FULL_ACCESS: "full_access", | |
| REQUEST_USER_INPUT: "user_input", | |
| } | |
| for fixture, kind in expected.items(): | |
| with self.subTest(kind=kind): | |
| candidate = app.detect_candidate(rows(fixture), cfg) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual(kind, candidate.kind) | |
| def test_historical_approval_text_above_composer_is_not_approved(self) -> None: | |
| transcript = COMMAND_APPROVAL + "\n\n• Command completed\n\n› write a follow-up" | |
| self.assertIsNone(app.detect_candidate(rows(transcript), config(self.runtime))) | |
| def test_persistent_positive_selection_is_approved(self) -> None: | |
| persistent_selection = COMMAND_APPROVAL.replace( | |
| "› 1. Yes, proceed (y)", | |
| " 1. Yes, proceed (y)", | |
| ).replace( | |
| " 2. Yes, and don't ask again", | |
| "› 2. Yes, and don't ask again", | |
| ) | |
| candidate = app.detect_candidate(rows(persistent_selection), config(self.runtime)) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual("command", candidate.kind) | |
| def test_negative_selection_is_not_approved(self) -> None: | |
| negative_selection = COMMAND_APPROVAL.replace( | |
| "› 1. Yes, proceed (y)", | |
| " 1. Yes, proceed (y)", | |
| ).replace( | |
| " 3. No, and tell Codex what to do differently", | |
| "› 3. No, and tell Codex what to do differently", | |
| ) | |
| self.assertIsNone(app.detect_candidate(rows(negative_selection), config(self.runtime))) | |
| def test_soft_wrapped_mcp_description_still_matches(self) -> None: | |
| wrapped = MCP_TOOL_APPROVAL.replace( | |
| "› 1. Allow Run the tool and continue.", | |
| "› 1. Allow\n Run the tool and continue.", | |
| ) | |
| candidate = app.detect_candidate( | |
| rows(wrapped), | |
| config(self.runtime, approve_mcp=True), | |
| ) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual("mcp_tool", candidate.kind) | |
| def test_long_mcp_parameter_block_still_matches(self) -> None: | |
| params = "\n".join(f" Parameter {idx}: value" for idx in range(30)) | |
| expanded = MCP_TOOL_APPROVAL.replace( | |
| " Calendar: primary", | |
| f" Calendar: primary\n{params}", | |
| ) | |
| candidate = app.detect_candidate( | |
| rows(expanded), | |
| config(self.runtime, approve_mcp=True), | |
| ) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual("mcp_tool", candidate.kind) | |
| def test_wrapped_network_title_and_footer_still_match(self) -> None: | |
| wrapped = NETWORK_APPROVAL.replace( | |
| 'network access to "example.com"?', | |
| 'network access to\n "example.com"?', | |
| ).replace( | |
| "Press enter to confirm or esc to cancel", | |
| "Press enter to confirm or esc to\n cancel", | |
| ) | |
| candidate = app.detect_candidate(rows(wrapped), config(self.runtime)) | |
| self.assertIsNotNone(candidate) | |
| self.assertEqual("network", candidate.kind) | |
| class SessionStateTests(unittest.TestCase): | |
| def setUp(self) -> None: | |
| self.temp = tempfile.TemporaryDirectory() | |
| self.cfg = config(Path(self.temp.name)) | |
| self.candidate = app.PromptCandidate("command", "command:abc") | |
| def tearDown(self) -> None: | |
| self.temp.cleanup() | |
| def test_stability_and_rearm(self) -> None: | |
| state = app.SessionState() | |
| self.assertFalse(state.ready(self.candidate, 10.0, self.cfg)) | |
| self.assertTrue(state.ready(self.candidate, 10.1, self.cfg)) | |
| state.mark_approved(self.candidate, 10.1) | |
| self.assertFalse(state.ready(self.candidate, 11.0, self.cfg)) | |
| self.assertFalse(state.ready(self.candidate, 12.2, self.cfg)) | |
| self.assertTrue(state.ready(self.candidate, 12.3, self.cfg)) | |
| def test_screen_clear_rearms_immediately_after_cooldown(self) -> None: | |
| state = app.SessionState() | |
| state.mark_approved(self.candidate, 20.0) | |
| self.assertTrue(state.clear_candidate()) | |
| self.assertFalse(state.ready(self.candidate, 20.6, self.cfg)) | |
| self.assertTrue(state.ready(self.candidate, 20.7, self.cfg)) | |
| class ArgumentTests(unittest.TestCase): | |
| def test_prompt_and_passthrough(self) -> None: | |
| _, args = app.parse_args(["-p", "hello world", "--", "--model", "gpt-5.4"]) | |
| self.assertEqual("hello world", args.prompt) | |
| self.assertEqual(["--model", "gpt-5.4"], args.codex_args) | |
| def test_broad_auto_approval_defaults_can_be_disabled(self) -> None: | |
| _, defaults = app.parse_args([]) | |
| self.assertTrue(defaults.approve_mcp) | |
| self.assertTrue(defaults.auto_trust_directory) | |
| _, disabled = app.parse_args(["--no-approve-mcp", "--no-auto-trust-directory"]) | |
| self.assertFalse(disabled.approve_mcp) | |
| self.assertFalse(disabled.auto_trust_directory) | |
| def test_resume_modes_parse_without_changing_passthrough(self) -> None: | |
| _, exact = app.parse_args( | |
| ["--resume", "019f-test", "-p", "continue", "--", "--search"] | |
| ) | |
| self.assertEqual("019f-test", exact.resume) | |
| self.assertFalse(exact.resume_last) | |
| self.assertEqual(["--search"], exact.codex_args) | |
| _, latest = app.parse_args(["--resume-last", "-C", "/tmp/project"]) | |
| self.assertIsNone(latest.resume) | |
| self.assertTrue(latest.resume_last) | |
| _, picker = app.parse_args(["--resume", "-C", "/tmp/project"]) | |
| self.assertEqual("", picker.resume) | |
| self.assertFalse(picker.resume_last) | |
| def test_resume_picker_rejects_initial_prompt(self) -> None: | |
| with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): | |
| app.parse_args(["--resume", "-p", "continue"]) | |
| def test_resume_selection_passthrough_is_mode_checked(self) -> None: | |
| for argv in ( | |
| ["--resume", "019f-test", "--", "--last"], | |
| ["--", "--all"], | |
| ["--resume", "019f-test", "--", "--include-non-interactive"], | |
| ): | |
| with self.subTest(argv=argv): | |
| with contextlib.redirect_stderr(io.StringIO()), self.assertRaises( | |
| SystemExit | |
| ): | |
| app.parse_args(argv) | |
| _, args = app.parse_args( | |
| ["--resume-last", "--", "--all", "--include-non-interactive"] | |
| ) | |
| self.assertEqual( | |
| ["--all", "--include-non-interactive"], | |
| args.codex_args, | |
| ) | |
| def test_wrapper_rejects_conflicting_codex_options(self) -> None: | |
| with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): | |
| app.parse_args(["-p", "hello", "--", "-a", "never"]) | |
| def test_wrapper_rejects_subcommand_after_global_option(self) -> None: | |
| with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): | |
| app.parse_args( | |
| ["-p", "hello", "--", "--model", "gpt-5.4", "exec"] | |
| ) | |
| def test_build_codex_argv_preserves_prompt_as_one_argument(self) -> None: | |
| prompt = "quoted ' prompt\nwith multiple lines" | |
| argv = app.build_codex_argv( | |
| "/opt/codex", | |
| prompt, | |
| Path("/tmp/project with spaces"), | |
| ["--model", "gpt-5.4"], | |
| "work", | |
| "workspace-write", | |
| False, | |
| ) | |
| self.assertEqual(argv, shlex.split(shlex.join(argv))) | |
| self.assertEqual(["--", prompt], argv[-2:]) | |
| self.assertIn('approvals_reviewer="user"', argv) | |
| self.assertNotIn("exec", argv) | |
| def test_bypass_is_explicit_and_omits_approval_settings(self) -> None: | |
| argv = app.build_codex_argv( | |
| "/opt/codex", | |
| "go", | |
| Path("/tmp/project"), | |
| [], | |
| None, | |
| "workspace-write", | |
| True, | |
| ) | |
| self.assertIn("--dangerously-bypass-approvals-and-sandbox", argv) | |
| self.assertNotIn("on-request", argv) | |
| def test_build_exact_resume_argv(self) -> None: | |
| argv = app.build_codex_argv( | |
| "/opt/codex", | |
| "continue safely", | |
| Path("/tmp/project"), | |
| ["--search"], | |
| None, | |
| "workspace-write", | |
| False, | |
| resume_session="019f-session", | |
| ) | |
| self.assertEqual("resume", argv[1]) | |
| self.assertIn("--search", argv) | |
| self.assertEqual(["--", "019f-session", "continue safely"], argv[-3:]) | |
| def test_build_resume_last_and_picker_argv(self) -> None: | |
| latest = app.build_codex_argv( | |
| "/opt/codex", | |
| "continue", | |
| Path("/tmp/project"), | |
| [], | |
| None, | |
| "workspace-write", | |
| False, | |
| resume_last=True, | |
| ) | |
| self.assertEqual("resume", latest[1]) | |
| self.assertIn("--last", latest) | |
| self.assertEqual(["--", "continue"], latest[-2:]) | |
| picker = app.build_codex_argv( | |
| "/opt/codex", | |
| None, | |
| Path("/tmp/project"), | |
| [], | |
| None, | |
| "workspace-write", | |
| False, | |
| resume_session="", | |
| ) | |
| self.assertEqual("resume", picker[1]) | |
| self.assertNotIn("--last", picker) | |
| self.assertNotIn("--", picker) | |
| def test_build_resume_rejects_ambiguous_internal_combinations(self) -> None: | |
| with self.assertRaises(ValueError): | |
| app.build_codex_argv( | |
| "/opt/codex", | |
| None, | |
| Path("/tmp/project"), | |
| [], | |
| None, | |
| "workspace-write", | |
| False, | |
| resume_session="019f-session", | |
| resume_last=True, | |
| ) | |
| with self.assertRaises(ValueError): | |
| app.build_codex_argv( | |
| "/opt/codex", | |
| "ambiguous prompt", | |
| Path("/tmp/project"), | |
| [], | |
| None, | |
| "workspace-write", | |
| False, | |
| resume_session="", | |
| ) | |
| class BinaryAndTmuxTests(unittest.TestCase): | |
| def test_codex_probe(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| binary = Path(directory) / "codex" | |
| binary.write_text("#!/bin/sh\necho 'codex-cli 9.9.9'\n", encoding="utf-8") | |
| binary.chmod(binary.stat().st_mode | stat.S_IXUSR) | |
| self.assertTrue(app.probe_codex_binary(binary)) | |
| def test_explicit_codex_path_does_not_fall_back(self) -> None: | |
| self.assertIsNone(app.find_codex("/definitely/missing/codex")) | |
| def test_tmux_prefix_filter_is_delimited(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| cfg = config(Path(directory)) | |
| app.register_session(cfg, "codex-auto-test-123") | |
| class FakeTmux(app.TmuxClient): | |
| def run(self, *args: str, timeout: float = 5.0): | |
| del args, timeout | |
| return ( | |
| 0, | |
| "codex-auto-test-123\t%1\t0\n" | |
| "codex-auto-testing\t%2\t0\n" | |
| "other\t%3\t0\n", | |
| "", | |
| ) | |
| panes = FakeTmux(cfg).list_panes() | |
| self.assertEqual(["%1"], [pane.pane_id for pane in panes]) | |
| def test_confirm_selected_choice_uses_rendered_enter_binding(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| cfg = config(Path(directory)) | |
| calls: list[tuple[str, ...]] = [] | |
| class FakeTmux(app.TmuxClient): | |
| def run(self, *args: str, timeout: float = 5.0): | |
| del timeout | |
| calls.append(args) | |
| return 0, "", "" | |
| self.assertTrue(FakeTmux(cfg).confirm_selected_choice("%7")) | |
| self.assertEqual(("send-keys", "-t", "%7", "Enter"), calls[0]) | |
| def test_session_exists_uses_target_session(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| cfg = config(Path(directory)) | |
| calls: list[tuple[str, ...]] = [] | |
| class FakeTmux(app.TmuxClient): | |
| def run(self, *args: str, timeout: float = 5.0): | |
| del timeout | |
| calls.append(args) | |
| return 0, "", "" | |
| self.assertTrue(FakeTmux(cfg).session_exists("test-session")) | |
| self.assertEqual( | |
| ("has-session", "-t", "=test-session"), | |
| calls[0], | |
| ) | |
| def test_new_session_sets_working_directory_and_quotes_argv(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| cfg = config(Path(directory)) | |
| calls: list[tuple[str, ...]] = [] | |
| class FakeTmux(app.TmuxClient): | |
| def run(self, *args: str, timeout: float = 5.0): | |
| del timeout | |
| calls.append(args) | |
| return 0, "", "" | |
| argv = ["/opt/codex", "--", "hello ' world\nsecond prompt line"] | |
| ok, _ = FakeTmux(cfg).new_session( | |
| "codex-auto-test-1", | |
| argv, | |
| Path("/tmp/project with spaces"), | |
| 120, | |
| 40, | |
| ) | |
| self.assertTrue(ok) | |
| new_session = calls[0] | |
| self.assertIn("-c", new_session) | |
| self.assertIn("/tmp/project with spaces", new_session) | |
| self.assertEqual(tuple(argv), new_session[-len(argv) :]) | |
| self.assertEqual("set-window-option", calls[1][0]) | |
| self.assertEqual("=codex-auto-test-1:", calls[1][2]) | |
| class DaemonLifecycleTests(unittest.TestCase): | |
| def test_stop_timeout_preserves_identity_record(self) -> None: | |
| with tempfile.TemporaryDirectory() as directory: | |
| cfg = config(Path(directory)) | |
| record = app.DaemonRecord( | |
| pid=12345, | |
| token="token", | |
| script=str(Path(app.__file__).resolve()), | |
| fingerprint=cfg.daemon_fingerprint(), | |
| ) | |
| app._write_daemon_record(cfg, record) | |
| with ( | |
| mock.patch.object( | |
| app, | |
| "daemon_status", | |
| return_value=(True, record, None), | |
| ), | |
| mock.patch.object(app, "_process_matches", return_value=True), | |
| mock.patch.object(app.os, "kill"), | |
| mock.patch.object(app.time, "monotonic", side_effect=[0.0, 6.0]), | |
| ): | |
| self.assertEqual("timeout", app.stop_daemon(cfg)) | |
| self.assertTrue(cfg.pid_path.exists()) | |
| if __name__ == "__main__": | |
| unittest.main() | |