77import plistlib
88import shutil
99import subprocess
10+ import sys
1011import tempfile
1112from pathlib import Path
1213
@@ -54,7 +55,17 @@ def log_rotation_prelude(plist: Path) -> str:
5455 return prelude
5556
5657
57- def check_log_rotation (home : Path , plist : Path , basename : str , limit : int ) -> None :
58+ def run_rotation_prelude (fake_bin : Path , prelude : str ) -> subprocess .CompletedProcess [str ]:
59+ return subprocess .run (
60+ ["zsh" , "-c" , prelude ],
61+ env = {** os .environ , "PATH" : f"{ fake_bin } :{ os .environ .get ('PATH' , '' )} " },
62+ check = True ,
63+ capture_output = True ,
64+ text = True ,
65+ )
66+
67+
68+ def check_log_rotation (fake_bin : Path , home : Path , plist : Path , basename : str , limit : int ) -> None :
5869 logs_dir = home / "Library" / "Logs" / "loopx"
5970 prelude = log_rotation_prelude (plist )
6071 for stream in ("out" , "err" ):
@@ -69,7 +80,7 @@ def check_log_rotation(home: Path, plist: Path, basename: str, limit: int) -> No
6980 live .write_bytes (b"O" * (limit + 1 ))
7081 descriptor = os .open (live , os .O_WRONLY | os .O_APPEND )
7182 try :
72- subprocess . run ([ "zsh" , "-c" , prelude ], check = True )
83+ run_rotation_prelude ( fake_bin , prelude )
7384 os .write (descriptor , b"after-rotation\n " )
7485 finally :
7586 os .close (descriptor )
@@ -85,12 +96,12 @@ def check_log_rotation(home: Path, plist: Path, basename: str, limit: int) -> No
8596 # reset on every service start.
8697 small = logs_dir / f"{ basename } .err.log"
8798 small .write_bytes (b"kept" )
88- subprocess . run ([ "zsh" , "-c" , prelude ], check = True )
99+ run_rotation_prelude ( fake_bin , prelude )
89100 assert not small .with_suffix (".log.1" ).exists ()
90101 assert small .read_bytes () == b"kept"
91102
92103
93- def check_retention_keeps_the_log_when_the_backup_fails (home : Path , plist : Path , basename : str , limit : int ) -> None :
104+ def check_retention_keeps_the_log_when_the_backup_fails (fake_bin : Path , home : Path , plist : Path , basename : str , limit : int ) -> None :
94105 """A failed backup must leave the live log alone.
95106
96107 Truncating on a failed copy would destroy the only record of the failure
@@ -105,7 +116,7 @@ def check_retention_keeps_the_log_when_the_backup_fails(home: Path, plist: Path,
105116 backup = live .with_suffix (".log.1" )
106117
107118 def run_prelude () -> subprocess .CompletedProcess [str ]:
108- result = subprocess . run ([ "zsh" , "-c" , prelude ], capture_output = True , text = True )
119+ result = run_rotation_prelude ( fake_bin , prelude )
109120 assert result .returncode == 0 , (result .stdout , result .stderr )
110121 assert live .read_bytes () == original , "a failed backup must not truncate the live log"
111122 assert not backup .is_file (), "a failed backup must not leave a partial generation"
@@ -171,6 +182,18 @@ def main() -> int:
171182 fake_bin .mkdir ()
172183 home .mkdir ()
173184
185+ # This fixture models macOS commands on Linux CI. Keep the real BSD
186+ # stat on macOS; emulate only its byte-count operation elsewhere.
187+ if sys .platform != "darwin" :
188+ write_executable (
189+ fake_bin / "stat" ,
190+ f"#!{ sys .executable } \n "
191+ "import os, sys\n "
192+ "if len(sys.argv) != 3 or sys.argv[1] != '-f%z':\n "
193+ " raise SystemExit(2)\n "
194+ "print(os.stat(sys.argv[2]).st_size)\n " ,
195+ )
196+
174197 write_executable (
175198 fake_bin / "uname" ,
176199 "#!/usr/bin/env bash\n printf 'Darwin\\ n'\n " ,
@@ -256,8 +279,8 @@ def main() -> int:
256279 run_script (fake_bin , home , ["install" ], schema_version = 2 ,
257280 extra_env = {"LOOPX_LOG_MAX_BYTES" : str (rotation_limit )})
258281 for plist , basename in ((status_plist , "status" ), (chat_plist , "chat" )):
259- check_log_rotation (home , plist , basename , rotation_limit )
260- check_retention_keeps_the_log_when_the_backup_fails (home , plist , basename , rotation_limit )
282+ check_log_rotation (fake_bin , home , plist , basename , rotation_limit )
283+ check_retention_keeps_the_log_when_the_backup_fails (fake_bin , home , plist , basename , rotation_limit )
261284 check_installed_retention_readback (fake_bin , home , plist , basename , rotation_limit )
262285 check_invalid_retention_is_rejected (fake_bin , home , status_plist , rotation_limit )
263286 assert f"- retention: rotated to .1 at each agent start once a log exceeds { rotation_limit } bytes" in run_script (
0 commit comments