claude-multiacc 2.0.64 → 2.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claude-accounts +15 -201
- package/bin/codex +25 -7
- package/bin/codex-accounts +16 -3
- package/docs/AUTORESUME.md +10 -1
- package/docs/CODEX.md +39 -0
- package/lib/__pycache__/audit.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-314.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-314.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-314.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-314.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_mcp_oauth.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_resume_cwd.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_speed.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_trust.cpython-314.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.cpython-314.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-314.pyc +0 -0
- package/lib/__pycache__/mcp_mirror.cpython-314.pyc +0 -0
- package/lib/__pycache__/mcp_oauth.cpython-314.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-314.pyc +0 -0
- package/lib/__pycache__/model_catalog.cpython-314.pyc +0 -0
- package/lib/__pycache__/model_match.cpython-314.pyc +0 -0
- package/lib/__pycache__/report.cpython-314.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-314.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-314.pyc +0 -0
- package/lib/__pycache__/selector_funding.cpython-314.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-314.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-314.pyc +0 -0
- package/lib/__pycache__/session_settings.cpython-314.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-314.pyc +0 -0
- package/lib/autoresume.py +28 -8
- package/lib/claude_oauth_lock.py +172 -0
- package/lib/claude_oauth_refresh.py +225 -0
- package/lib/codex_speed.py +351 -0
- package/lib/codex_speed.sh +39 -0
- package/lib/common.sh +13 -3
- package/lib/model_match.py +4 -1
- package/lib/session_settings.py +25 -6
- package/lib/shim_path.py +6 -0
- package/package.json +4 -3
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-314.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-314.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-314.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-314.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-314.pyc +0 -0
- package/tests/__pycache__/packaged_command_support.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog_cli.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_records.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_trust.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_origin.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_reader.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_origins.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_settings.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_autoresume_trust_tmux.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_claude_oauth_lock.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_exec_stdin.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_mcp_oauth.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_mcp_mirror.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_mcp_mirror_args.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_mcp_oauth.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_mcp_registry.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-314.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-314.pyc +0 -0
- package/tests/claude_oauth_fixture.py +100 -0
- package/tests/run-tests.sh +23 -7
- package/tests/test_claude_oauth_lock.py +138 -0
- package/tests/test_claude_oauth_refresh.py +106 -0
- package/tests/test_claude_oauth_refresh_failures.py +112 -0
- package/tests/test_codex_speed.py +236 -0
- package/tests/test_mutate_lock.py +107 -0
- package/tests/test_shim_path.py +44 -4
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_resume_cwd.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_mirror.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
- package/lib/__pycache__/report.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_reader.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_exec_stdin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_mcp_oauth.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_mirror.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_mirror_args.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_oauth.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_registry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Account-specific speed, native argv, and quota-resume regression coverage."""
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
sys.path.insert(0, str(ROOT / "lib"))
|
|
15
|
+
sys.path.insert(0, str(ROOT / "tests"))
|
|
16
|
+
import codex_speed as cs
|
|
17
|
+
import session_settings
|
|
18
|
+
import autoresume as ar
|
|
19
|
+
import test_codex_exec_stdin as stdin_fixtures
|
|
20
|
+
from test_autoresume_relaunch_settings import shell_gate
|
|
21
|
+
|
|
22
|
+
MODEL = "gpt-6.1-sol"
|
|
23
|
+
NODE = stdin_fixtures.NODE
|
|
24
|
+
SID = "01a135db-f14d-7a60-b656-d5ff72abd71d"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def catalog(directory, ultra=False, model=MODEL, **changes):
|
|
28
|
+
document = {"fetched_at": datetime.now(timezone.utc).isoformat(), "identity": "native-account-identity",
|
|
29
|
+
"models": [{"slug": model, "visibility": "list", "priority": 0,
|
|
30
|
+
"service_tiers": [{"id": "priority"}] + ([{"id": "ultrafast"}] if ultra else [])}]}
|
|
31
|
+
document.update(changes)
|
|
32
|
+
(Path(directory) / "models_cache.json").write_text(json.dumps(document))
|
|
33
|
+
return document
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SpeedResolutionTests(unittest.TestCase):
|
|
37
|
+
def setUp(self):
|
|
38
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
39
|
+
self.addCleanup(self.tmp.cleanup)
|
|
40
|
+
self.home = Path(self.tmp.name)
|
|
41
|
+
|
|
42
|
+
def test_capability_is_per_account_and_per_model(self):
|
|
43
|
+
catalog(self.home, ultra=True)
|
|
44
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", MODEL)["effective"], "ultrafast")
|
|
45
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", "other-model")["effective"], "fast")
|
|
46
|
+
catalog(self.home)
|
|
47
|
+
result = cs.resolve(self.home, "ultrafast", MODEL)
|
|
48
|
+
self.assertEqual((result["effective"], result["service_tier"], result["fallback"]),
|
|
49
|
+
("fast", "priority", True))
|
|
50
|
+
|
|
51
|
+
def test_stale_malformed_or_reauthenticated_catalog_cannot_grant_ultrafast(self):
|
|
52
|
+
for changes in ({"fetched_at": "2000-01-01T00:00:00Z"}, {"fetched_at": None}, {"models": None}):
|
|
53
|
+
catalog(self.home, ultra=True, **changes)
|
|
54
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", MODEL)["effective"], "fast")
|
|
55
|
+
catalog(self.home, ultra=True)
|
|
56
|
+
(self.home / "auth.json").write_text("{}")
|
|
57
|
+
future = (self.home / "models_cache.json").stat().st_mtime + 2
|
|
58
|
+
os.utime(self.home / "auth.json", (future, future))
|
|
59
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", MODEL)["effective"], "fast")
|
|
60
|
+
|
|
61
|
+
def test_service_tiers_are_authoritative_and_legacy_cache_is_supported(self):
|
|
62
|
+
doc = catalog(self.home)
|
|
63
|
+
row = doc["models"][0]
|
|
64
|
+
row.update(service_tiers=[], additional_speed_tiers=["fast", "ultrafast"])
|
|
65
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", MODEL, doc)["effective"], "fast")
|
|
66
|
+
del row["service_tiers"]
|
|
67
|
+
self.assertEqual(cs.resolve(self.home, "ultrafast", MODEL, doc)["effective"], "ultrafast")
|
|
68
|
+
|
|
69
|
+
def test_env_override_cli_precedence_and_prompt_fidelity(self):
|
|
70
|
+
catalog(self.home, ultra=True)
|
|
71
|
+
(self.home / "config.toml").write_text('model = "gpt-6.1-sol"\nservice_tier = "ultrafast"\n')
|
|
72
|
+
result, args = cs.launch_argv(self.home, ["exec", "--", "-c service_tier=ultrafast"],
|
|
73
|
+
{"CODEX_SPEED": "normal"})
|
|
74
|
+
self.assertEqual(result["effective"], "normal")
|
|
75
|
+
self.assertEqual(args, ["-c", "service_tier=default", "exec", "--", "-c service_tier=ultrafast"])
|
|
76
|
+
result, args = cs.launch_argv(self.home, ["-c", 'service_tier="fast"', "continue"],
|
|
77
|
+
{"CODEX_SPEED": "ultrafast"})
|
|
78
|
+
self.assertEqual(result["effective"], "fast")
|
|
79
|
+
self.assertNotIn('service_tier="fast"', args)
|
|
80
|
+
self.assertEqual(args[-1], "continue")
|
|
81
|
+
|
|
82
|
+
def test_app_server_model_hint_and_profile_defaults(self):
|
|
83
|
+
catalog(self.home, ultra=True)
|
|
84
|
+
(self.home / "config.toml").write_text('model="other-model"\n')
|
|
85
|
+
result, _ = cs.launch_argv(self.home, ["app-server"], {"CODEX_SPEED": "ultrafast", "CODEX_SPEED_MODEL": MODEL})
|
|
86
|
+
self.assertEqual(result["effective"], "ultrafast")
|
|
87
|
+
(self.home / "quick.config.toml").write_text('model="gpt-6.1-sol"\nservice_tier="fast"\n')
|
|
88
|
+
result, _ = cs.launch_argv(self.home, ["--profile", "quick", "continue"], {})
|
|
89
|
+
self.assertEqual(result["effective"], "fast")
|
|
90
|
+
|
|
91
|
+
def test_ignore_user_config_does_not_reintroduce_account_speed(self):
|
|
92
|
+
(self.home / "config.toml").write_text('service_tier="ultrafast"\n')
|
|
93
|
+
argv = ["exec", "--ignore-user-config", "do the work"]
|
|
94
|
+
result, args = cs.launch_argv(self.home, argv, {})
|
|
95
|
+
self.assertIsNone(result)
|
|
96
|
+
self.assertEqual(args, argv)
|
|
97
|
+
result, _ = cs.launch_argv(self.home, argv, {"CODEX_SPEED": "normal"})
|
|
98
|
+
self.assertEqual(result["effective"], "normal")
|
|
99
|
+
|
|
100
|
+
def test_normal_and_fast_never_request_catalog_refresh(self):
|
|
101
|
+
for choice in ("normal", "fast"):
|
|
102
|
+
result, args = cs.launch_argv(self.home, ["app-server"], {"CODEX_SPEED": choice}, executable="/missing")
|
|
103
|
+
self.assertEqual(result["effective"], choice)
|
|
104
|
+
self.assertIn("service_tier=" + ("default" if choice == "normal" else "fast"), args)
|
|
105
|
+
|
|
106
|
+
def test_cold_native_catalog_refresh_and_failure_fallback(self):
|
|
107
|
+
native = self.home / "native"
|
|
108
|
+
native.write_text(f'#!{sys.executable}\n' + '''import json,sys
|
|
109
|
+
for line in sys.stdin:
|
|
110
|
+
request=json.loads(line)
|
|
111
|
+
if 'id' not in request: continue
|
|
112
|
+
result={'data':[{'id':'gpt-6.1-sol','serviceTiers':[{'id':'priority'},{'id':'ultrafast'}]}]} if request['method']=='model/list' else {}
|
|
113
|
+
print(json.dumps({'id':request['id'],'result':result}),flush=True)
|
|
114
|
+
''')
|
|
115
|
+
native.chmod(0o755)
|
|
116
|
+
result, _ = cs.launch_argv(self.home, ["app-server"], {"CODEX_SPEED": "ultrafast", "CODEX_SPEED_MODEL": MODEL}, str(native))
|
|
117
|
+
self.assertEqual(result["effective"], "ultrafast")
|
|
118
|
+
catalog(self.home, ultra=True, fetched_at="2000-01-01T00:00:00Z")
|
|
119
|
+
result, _ = cs.launch_argv(self.home, ["app-server"], {"CODEX_SPEED": "ultrafast", "CODEX_SPEED_MODEL": MODEL}, "/missing")
|
|
120
|
+
self.assertEqual(result["effective"], "fast")
|
|
121
|
+
|
|
122
|
+
def test_native_resume_preparation_saves_only_speed_and_refuses_failed_writes(self):
|
|
123
|
+
native, requests = self.home / "native", self.home / "requests.jsonl"
|
|
124
|
+
native.write_text(f'#!{sys.executable}\n' + '''import json,sys,pathlib
|
|
125
|
+
for line in sys.stdin:
|
|
126
|
+
request=json.loads(line)
|
|
127
|
+
if 'id' not in request: continue
|
|
128
|
+
with pathlib.Path(__file__).with_name('requests.jsonl').open('a') as log: log.write(json.dumps(request)+'\\n')
|
|
129
|
+
result={'serviceTier':request['params']['serviceTier']} if request['method']=='thread/resume' else {}
|
|
130
|
+
print(json.dumps({'id':request['id'],'result':result}),flush=True)
|
|
131
|
+
''')
|
|
132
|
+
native.chmod(0o755)
|
|
133
|
+
result, args = cs.launch_argv(self.home, ["resume", SID, "continue now"], {"CODEX_SPEED": "normal"}, str(native))
|
|
134
|
+
calls = [json.loads(line) for line in requests.read_text().splitlines()]
|
|
135
|
+
self.assertEqual([call["method"] for call in calls], ["initialize", "thread/resume"])
|
|
136
|
+
self.assertEqual(calls[-1]["params"], {"threadId": SID, "serviceTier": "default"})
|
|
137
|
+
self.assertIn("service_tier=default", args)
|
|
138
|
+
with self.assertRaisesRegex(ValueError, "could not preserve speed"):
|
|
139
|
+
cs.launch_argv(self.home, ["resume", SID], {"CODEX_SPEED": "normal"}, "/missing")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@unittest.skipUnless(NODE, "Node is required for the actual child-process transport")
|
|
143
|
+
class SpeedShimTests(unittest.TestCase):
|
|
144
|
+
setUp = stdin_fixtures.CodexExecStdinTests.setUp
|
|
145
|
+
run_node = stdin_fixtures.CodexExecStdinTests.run_node
|
|
146
|
+
|
|
147
|
+
def prepare(self):
|
|
148
|
+
self.env["CLAUDE_MULTIACC_PYTHON"] = sys.executable
|
|
149
|
+
for number in (1, 2):
|
|
150
|
+
catalog(self.pool / f"acct-{number:02d}", ultra=number == 1)
|
|
151
|
+
native = self.root / "bin" / "codex"
|
|
152
|
+
source = native.read_text().split("\n", 1)[1]
|
|
153
|
+
native.write_text(f'#!{sys.executable}\n' + '''import sys,os,json,pathlib
|
|
154
|
+
if sys.argv[1:2] == ['app-server']:
|
|
155
|
+
for line in sys.stdin:
|
|
156
|
+
request=json.loads(line)
|
|
157
|
+
if 'id' not in request: continue
|
|
158
|
+
result=json.loads((pathlib.Path(os.environ['CODEX_HOME'])/'models_cache.json').read_text()) if request['method']=='model/list' else {}
|
|
159
|
+
print(json.dumps({'id':request['id'],'result':result}),flush=True)
|
|
160
|
+
sys.exit(0)
|
|
161
|
+
''' + source)
|
|
162
|
+
|
|
163
|
+
def test_ultrafast_quota_rotation_recomputes_target_capability(self):
|
|
164
|
+
self.prepare()
|
|
165
|
+
result, calls = self.run_node(CODEX_SPEED="ultrafast", CODEX_SPEED_MODEL=MODEL)
|
|
166
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
167
|
+
self.assertEqual([row["account"] for row in calls], ["acct-01", "acct-02"])
|
|
168
|
+
self.assertIn("service_tier=ultrafast", calls[0]["argv"])
|
|
169
|
+
self.assertIn("service_tier=priority", calls[1]["argv"])
|
|
170
|
+
self.assertNotIn("service_tier=ultrafast", calls[1]["argv"])
|
|
171
|
+
|
|
172
|
+
def test_normal_overrides_pinned_home_fast_and_single_word_prompts(self):
|
|
173
|
+
self.prepare()
|
|
174
|
+
home = self.pool / "acct-01"
|
|
175
|
+
(home / "config.toml").write_text('service_tier="fast"\n')
|
|
176
|
+
for pin in ({"CODEX_HOME": str(home)}, {"CODEX_ACCOUNT": "acct-01"}):
|
|
177
|
+
(self.root / "calls.jsonl").unlink(missing_ok=True)
|
|
178
|
+
result, calls = self.run_node(["continue"], FAKE_MODE="success", CODEX_SPEED="normal", **pin)
|
|
179
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
180
|
+
self.assertIn("service_tier=default", calls[0]["argv"])
|
|
181
|
+
|
|
182
|
+
def test_administrative_commands_and_disabled_shim_remain_unchanged(self):
|
|
183
|
+
self.prepare()
|
|
184
|
+
for args, extra in [(["login"], {}), (["exec", "--help"], {}), (["continue"], {"CODEX_MULTIACC_DISABLE": "1"})]:
|
|
185
|
+
(self.root / "calls.jsonl").unlink(missing_ok=True)
|
|
186
|
+
result, calls = self.run_node(args, CODEX_HOME=str(self.pool / "acct-01"),
|
|
187
|
+
FAKE_MODE="success", CODEX_SPEED="ultrafast", **extra)
|
|
188
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
189
|
+
self.assertEqual(calls[0]["argv"], args)
|
|
190
|
+
|
|
191
|
+
def test_invalid_explicit_speed_or_unavailable_helper_never_silently_launches(self):
|
|
192
|
+
self.prepare()
|
|
193
|
+
for extra in ({"CODEX_SPEED": "turbo"}, {"CODEX_SPEED": "normal", "CLAUDE_MULTIACC_PYTHON": "/missing"}):
|
|
194
|
+
result, calls = self.run_node(["continue"], CODEX_HOME=str(self.pool / "acct-01"), **extra)
|
|
195
|
+
self.assertEqual(result.returncode, 2, result.stderr)
|
|
196
|
+
self.assertFalse(calls)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ResumeSpeedTests(unittest.TestCase):
|
|
200
|
+
def test_history_restores_latest_own_tier_even_when_turn_context_omits_it(self):
|
|
201
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
202
|
+
path = Path(directory) / "history.jsonl"
|
|
203
|
+
records = [
|
|
204
|
+
{"type": "session_meta", "payload": {"id": SID}},
|
|
205
|
+
{"type": "event_msg", "payload": {"type": "thread_settings_applied", "thread_id": SID,
|
|
206
|
+
"thread_settings": {"model": MODEL, "reasoning_effort": "high", "service_tier": "ultrafast"}}},
|
|
207
|
+
{"type": "turn_context", "payload": {"model": MODEL, "effort": "low"}},
|
|
208
|
+
{"type": "event_msg", "payload": {"type": "thread_settings_applied", "thread_id": "other",
|
|
209
|
+
"thread_settings": {"model": MODEL, "service_tier": "default"}}},
|
|
210
|
+
]
|
|
211
|
+
path.write_text("".join(json.dumps(row) + "\n" for row in records))
|
|
212
|
+
self.assertEqual(session_settings.session_settings("codex", path),
|
|
213
|
+
{"model": MODEL, "effort": "low", "speed": "ultrafast"})
|
|
214
|
+
isolated = subprocess.run([sys.executable, "-I", str(ROOT / "lib/session_settings.py"), "codex", str(path)],
|
|
215
|
+
capture_output=True, text=True)
|
|
216
|
+
self.assertEqual(isolated.returncode, 0, isolated.stderr)
|
|
217
|
+
self.assertEqual(json.loads(isolated.stdout), {"model": MODEL, "effort": "low", "speed": "ultrafast"})
|
|
218
|
+
records.append({"type": "turn_context", "payload": {"model": MODEL, "service_tier": None}})
|
|
219
|
+
path.write_text("".join(json.dumps(row) + "\n" for row in records))
|
|
220
|
+
self.assertEqual(session_settings.session_settings("codex", path)["speed"], "normal")
|
|
221
|
+
|
|
222
|
+
def test_relaunch_replaces_speed_without_losing_effort_and_both_gates_agree(self):
|
|
223
|
+
original = ["-m", MODEL, "-c", "model_reasoning_effort=low", "-c", 'service_tier="ultrafast"']
|
|
224
|
+
resumed = ar.relaunch_argv("codex", original, SID, "quota", settings={"speed": "normal"})
|
|
225
|
+
self.assertIn("model_reasoning_effort=low", resumed)
|
|
226
|
+
self.assertIn("service_tier=default", resumed)
|
|
227
|
+
self.assertNotIn('service_tier="ultrafast"', resumed)
|
|
228
|
+
for args in (original, resumed):
|
|
229
|
+
self.assertTrue(ar.argv_ok("codex", args))
|
|
230
|
+
gate = subprocess.run(["bash", "-c", shell_gate(), "gate", *args], capture_output=True)
|
|
231
|
+
self.assertEqual(gate.returncode, 0, gate.stderr)
|
|
232
|
+
self.assertIsNone(ar.relaunch_argv("claude", original, SID, "quota"))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
unittest.main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Exercise real mutation-lock ownership when a failed mkdir races with release."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MutationLockTests(unittest.TestCase):
|
|
14
|
+
def run_shell(self, body):
|
|
15
|
+
with tempfile.TemporaryDirectory(prefix="multiacc-mutation-lock-") as directory:
|
|
16
|
+
root = Path(directory)
|
|
17
|
+
env = {"PATH": os.environ.get("PATH", "/usr/bin:/bin"), "HOME": str(root),
|
|
18
|
+
"CLAUDE_ACCOUNTS_ROOT": str(root / "pool"), "MULTIACC_PROVIDER": "claude"}
|
|
19
|
+
script = '''set -u
|
|
20
|
+
. "$1/lib/common.sh"
|
|
21
|
+
lock="$ACC_ROOT/.locks/mutate"
|
|
22
|
+
mkdir -p "$lock"
|
|
23
|
+
''' + body
|
|
24
|
+
result = subprocess.run(["bash", "-c", script, "mutation-lock-test", str(ROOT)],
|
|
25
|
+
env=env, capture_output=True, text=True, timeout=10)
|
|
26
|
+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
27
|
+
|
|
28
|
+
def test_acquisition_does_not_trust_nonexclusive_system_mkdir(self):
|
|
29
|
+
self.run_shell('''
|
|
30
|
+
: > "$lock/competing-owner"
|
|
31
|
+
mkdir() {
|
|
32
|
+
# uutils 0.10 can report success when a competing creator won. Model that
|
|
33
|
+
# behavior deterministically: mkdir -p accepts the other holder's directory.
|
|
34
|
+
command mkdir -p "$@"
|
|
35
|
+
}
|
|
36
|
+
sleep() {
|
|
37
|
+
[ -f "$lock/competing-owner" ] || exit 10
|
|
38
|
+
: > "$ACC_ROOT/waited-for-owner"
|
|
39
|
+
rm "$lock/competing-owner"
|
|
40
|
+
rmdir "$lock"
|
|
41
|
+
}
|
|
42
|
+
mutate_lock || exit 11
|
|
43
|
+
[ -f "$ACC_ROOT/waited-for-owner" ] || exit 12
|
|
44
|
+
[ -d "$lock" ] || exit 13
|
|
45
|
+
mutate_unlock
|
|
46
|
+
''')
|
|
47
|
+
|
|
48
|
+
def test_missing_stat_never_removes_a_replacement_owner(self):
|
|
49
|
+
self.run_shell('''
|
|
50
|
+
file_mtime() {
|
|
51
|
+
# The first holder releases after our failed mkdir, before stat. A second
|
|
52
|
+
# holder acquires after that failed stat and before its zero reaches us.
|
|
53
|
+
rmdir "$1" || return 1
|
|
54
|
+
local observed
|
|
55
|
+
if [ "$(uname -s)" = Darwin ]; then
|
|
56
|
+
observed="$(stat -f %m "$1" 2>/dev/null || echo 0)"
|
|
57
|
+
else
|
|
58
|
+
observed="$(stat -c %Y "$1" 2>/dev/null || echo 0)"
|
|
59
|
+
fi
|
|
60
|
+
mkdir "$1" || return 1
|
|
61
|
+
: > "$1/replacement-owner"
|
|
62
|
+
printf '%s\\n' "$observed"
|
|
63
|
+
}
|
|
64
|
+
sleep() {
|
|
65
|
+
# Only the replacement owner's own release permits the waiter to acquire.
|
|
66
|
+
[ -f "$lock/replacement-owner" ] || exit 20
|
|
67
|
+
: > "$ACC_ROOT/owner-preserved"
|
|
68
|
+
rm "$lock/replacement-owner"
|
|
69
|
+
rmdir "$lock"
|
|
70
|
+
}
|
|
71
|
+
mutate_lock || exit 21
|
|
72
|
+
[ -f "$ACC_ROOT/owner-preserved" ] || exit 22
|
|
73
|
+
[ "$MUTATE_LOCK_HELD" = 1 ] || exit 23
|
|
74
|
+
mutate_unlock
|
|
75
|
+
[ ! -e "$lock" ]
|
|
76
|
+
''')
|
|
77
|
+
|
|
78
|
+
def test_unreadable_mtime_uses_the_existing_backoff(self):
|
|
79
|
+
self.run_shell('''
|
|
80
|
+
: > "$lock/owner"
|
|
81
|
+
file_mtime() { printf 'unreadable\\n'; }
|
|
82
|
+
waits=0
|
|
83
|
+
sleep() {
|
|
84
|
+
[ -f "$lock/owner" ] || exit 30
|
|
85
|
+
waits=$((waits + 1))
|
|
86
|
+
if [ "$waits" = 2 ]; then rm "$lock/owner"; rmdir "$lock"; fi
|
|
87
|
+
}
|
|
88
|
+
mutate_lock || exit 31
|
|
89
|
+
[ "$waits" = 2 ] || exit 32
|
|
90
|
+
mutate_unlock
|
|
91
|
+
''')
|
|
92
|
+
|
|
93
|
+
def test_observed_old_lock_remains_reclaimable(self):
|
|
94
|
+
self.run_shell('''
|
|
95
|
+
: > "$lock/abandoned"
|
|
96
|
+
file_mtime() { printf '1\\n'; }
|
|
97
|
+
epoch_now() { printf '100\\n'; }
|
|
98
|
+
sleep() { exit 40; }
|
|
99
|
+
mutate_lock || exit 41
|
|
100
|
+
[ "$MUTATE_LOCK_HELD" = 1 ] || exit 42
|
|
101
|
+
[ ! -e "$lock/abandoned" ] || exit 43
|
|
102
|
+
mutate_unlock
|
|
103
|
+
''')
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
unittest.main()
|
package/tests/test_shim_path.py
CHANGED
|
@@ -40,6 +40,39 @@ def legacy_block(repo_dir):
|
|
|
40
40
|
return '\n'.join(lines[:3] + [MARK_END]) + '\n'
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
class ShimPathParserTests(unittest.TestCase):
|
|
44
|
+
def test_complete_osc_shell_integration_wraps_answers(self):
|
|
45
|
+
marker = shim_path.SENTINEL
|
|
46
|
+
for terminator in ('\x07', '\x1b\\'):
|
|
47
|
+
with self.subTest(terminator=repr(terminator)):
|
|
48
|
+
start = '\x1b]3008;start=fixture' + terminator
|
|
49
|
+
end = '\x1b]3008;end=fixture' + terminator
|
|
50
|
+
text = (f'{start}{start}{marker} claude=/pool/claude{end}\n'
|
|
51
|
+
f'{start}{marker} codex=/pool/codex{end}{end}\n')
|
|
52
|
+
self.assertEqual(shim_path._parse(text, shim_path.NAMES),
|
|
53
|
+
{'claude': '/pool/claude', 'codex': '/pool/codex'})
|
|
54
|
+
|
|
55
|
+
def test_osc_removal_keeps_strict_framing_and_first_answer(self):
|
|
56
|
+
marker = shim_path.SENTINEL
|
|
57
|
+
text = (f'\x1b]3008;{marker} claude=/inside-control\x07\n'
|
|
58
|
+
f'echo "{marker} claude=/echoed"\n'
|
|
59
|
+
f'prompt {marker} claude=/prompt\n'
|
|
60
|
+
f'\x1b]3008;start=x\x07{marker} claude=/first\n'
|
|
61
|
+
f'{marker} claude=/later\n'
|
|
62
|
+
f'{marker} codex=/plain\n')
|
|
63
|
+
self.assertEqual(shim_path._parse(text, shim_path.NAMES),
|
|
64
|
+
{'claude': '/first', 'codex': '/plain'})
|
|
65
|
+
|
|
66
|
+
def test_incomplete_osc_cannot_consume_another_line(self):
|
|
67
|
+
marker = shim_path.SENTINEL
|
|
68
|
+
text = (f'\x1b]3008;partial {marker} claude=/unframed\n'
|
|
69
|
+
f'{marker} claude=/complete\n'
|
|
70
|
+
f'\x1b]broken\x1b[31m{marker} codex=/malformed\n'
|
|
71
|
+
f'\x1b]3008;start=x\x1b\\{marker} codex=/complete\n')
|
|
72
|
+
self.assertEqual(shim_path._parse(text, shim_path.NAMES),
|
|
73
|
+
{'claude': '/complete', 'codex': '/complete'})
|
|
74
|
+
|
|
75
|
+
|
|
43
76
|
class ShimPathTests(unittest.TestCase):
|
|
44
77
|
def setUp(self):
|
|
45
78
|
self.tmp = Path(tempfile.mkdtemp(prefix='multiacc-shim-path-'))
|
|
@@ -212,12 +245,19 @@ class ShimPathTests(unittest.TestCase):
|
|
|
212
245
|
block = installed_block(self.repo)
|
|
213
246
|
for rc in ('.zshenv', '.zprofile', '.zshrc', '.profile'):
|
|
214
247
|
self.write(rc, block)
|
|
215
|
-
before = sorted(p.name for p in self.home.iterdir())
|
|
216
248
|
self.probe()
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
for hist in ('.zsh_history', '.bash_history', '.sh_history'):
|
|
249
|
+
histories = ('.zsh_history', '.bash_history', '.sh_history')
|
|
250
|
+
for hist in histories:
|
|
220
251
|
self.assertFalse((self.home / hist).exists(), hist)
|
|
252
|
+
# System rc files may create unrelated completion caches such as
|
|
253
|
+
# .zcompdump. The guarantee is that probe commands neither create history
|
|
254
|
+
# nor change history the operator already has.
|
|
255
|
+
prior = b'echo existing-user-history\n'
|
|
256
|
+
for hist in histories:
|
|
257
|
+
(self.home / hist).write_bytes(prior)
|
|
258
|
+
self.probe()
|
|
259
|
+
for hist in histories:
|
|
260
|
+
self.assertEqual((self.home / hist).read_bytes(), prior, hist)
|
|
221
261
|
|
|
222
262
|
def test_rc_that_reads_stdin_costs_an_answer_not_a_verdict(self):
|
|
223
263
|
"""A prompt or `read` in an rc file eats one line of the fed script. The lost
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|