diff --git a/bin/stop-emrg.cmd b/bin/stop-emrg.cmd index 3cec911..f1838fd 100644 --- a/bin/stop-emrg.cmd +++ b/bin/stop-emrg.cmd @@ -13,13 +13,15 @@ REM fallback), excludes the daemon (pythonw.exe -m emrg.server) REM 3. daemon: `emrg server stop` protocol shutdown via the OLD install's CLI REM (present since #364 -- version-safe), emrgd.pid poll (<=10s), then REM taskkill /F /PID fallback -REM 4. bundled git: GUILT-BY-ASSOCIATION force-kill via stop-git.ps1 -REM (host 2026-08-11T19:47:44 FINAL decision OVERRIDES #689: install success +REM 4. bundled git: INLINE guilt-by-association force-kill (single file, +REM rant 2026-08-12T14:00:05 -- stop-git.ps1 merged into this script). +REM Host 2026-08-11T19:47:44 FINAL decision OVERRIDES #689: install success REM has priority -- if sh/vim hold msys-2.0.dll and block the git-tree kill, REM they are killed too; only the install\git\ prefix is touched, system Git -REM in Program Files is never affected). stop-git.ps1 is a standalone script -REM (no cmd inline-PowerShell quoting escapes). The installer passes the -REM {tmp}-extracted copy as %~1 when the OLD install lacks it. +REM in Program Files is never affected. Step 4 runs PowerShell inline (same +REM \" escaping as the TUI/verify snippets below, proven on v0.2.25-v0.2.27); +REM every pass re-queries Get-CimInstance (no stale snapshot) and residual +REM processes are reported truthfully via exit 1 (rant 2026-08-12T12:30:41). REM REM Returns 0 when nothing EMRG-related survives; 1 if a process could not be REM stopped (installer aborts with a clear message instead of hanging). @@ -69,19 +71,19 @@ set "DPID=" for /f "usebackq delims=" %%p in ("%EMRG_DIR%\emrgd.pid") do set "DPID=%%p" if defined DPID taskkill /F /PID %DPID% >nul 2>&1 -REM --- 4. bundled git: guilt-by-association force-kill via stop-git.ps1 --- +REM --- 4. bundled git: inline guilt-by-association force-kill (single file) --- REM (host 2026-08-11T19:47:44 FINAL decision OVERRIDES #689's snapshot-only -REM approach -- "如果杀死git时,发现sh/vim导致杀不掉,则一起杀": install success -REM has priority; sh/vim holding msys-2.0.dll under install\git\ are killed too. -REM Only the install\git\ prefix is touched; system Git (Program Files) is never -REM matched. 0.2.26's cmd inline-PowerShell quoting escape is avoided by calling -REM the standalone stop-git.ps1 via -File.) -set "GITSTOP=%INSTALL%\bin\stop-git.ps1" -if not exist "%GITSTOP%" set "GITSTOP=%~1" -if exist "%GITSTOP%" ( - powershell -NoProfile -ExecutionPolicy Bypass -File "%GITSTOP%" >nul 2>&1 - if errorlevel 1 set "EXIT_CODE=1" -) +REM approach: install success has priority -- if sh/vim hold msys-2.0.dll under +REM install\git\ and block the git-tree kill, they are killed too; only the +REM install\git\ prefix is touched, system Git (Program Files) is never matched. +REM Rant 2026-08-12T14:00:05: stop-git.ps1 deleted, logic inlined below. +REM Each pass re-queries Get-CimInstance (no stale snapshot); the survivor +REM check uses the latest snapshot; residual -> exit 1 (truthful failure #701). +REM The \" escaping matches the TUI/verify inline snippets proven on real +REM Windows in v0.2.25-v0.2.27.) +powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference='SilentlyContinue'; $prefix=\"$env:USERPROFILE\.emrg\install\git\*\"; Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix -and $_.Name -in @('git.exe','ssh.exe','plink.exe','bash.exe') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Start-Sleep -Milliseconds 300; Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Start-Sleep -Milliseconds 300; $left = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix }); if ($left.Count -gt 0) { $left | ForEach-Object { Write-Host (\"still running: {0} (pid {1})\" -f $_.Name, $_.ProcessId) }; exit 1 }; exit 0" >nul 2>&1 +if errorlevel 1 set "EXIT_CODE=1" + :verify tasklist /FI "IMAGENAME eq EMRG.exe" 2>nul | findstr /i "EMRG.exe" >nul && set "EXIT_CODE=1" @@ -95,9 +97,9 @@ if exist "%EMRG_DIR%\emrgd.pid" ( ) ) REM Bundled-git survival check (plain prefix; guilt-by-association semantics): -REM any process still under install\git\ -> exit 1 (installer aborts). This is the -REM same condition stop-git.ps1 reports; kept as belt-and-braces for the case the -REM script file could not be located (no old install, no {tmp} arg). +REM any process still under install\git\ -> exit 1 (installer aborts). Same +REM condition as step 4's inline PowerShell; kept as belt-and-braces in case the +REM PowerShell invocation itself fails. powershell -NoProfile -Command "$p = Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }; if ($p) { exit 1 }" >nul 2>&1 if errorlevel 1 set "EXIT_CODE=1" endlocal & exit /b %EXIT_CODE% diff --git a/bin/stop-git.ps1 b/bin/stop-git.ps1 deleted file mode 100644 index dee4c17..0000000 --- a/bin/stop-git.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# stop-git.ps1 -- force-kill every process under the bundled git install prefix -# (host 2026-08-11T19:47:44 FINAL decision, OVERRIDES #689: install success has -# priority -- "要杀死相关进程,如果杀死git时,发现sh/vim导致杀不掉,则一起杀。" -# If sh/vim hold msys-2.0.dll and block the git-tree kill, they are killed too.) -# -# 1. collect all processes whose ExecutablePath is under -# %USERPROFILE%\.emrg\install\git\ (the portable Git the daemon spawns for -# evolution-cycle ops; system Git under Program Files is NEVER matched) -# 2. pass 1: force-kill the git tree (git.exe/ssh.exe/plink.exe/bash.exe) -# 3. pass 2: guilt-by-association -- force-kill EVERY survivor under the prefix -# (sh/vim included, per host decision; 0.2.25 msys-2.0.dll popup must not regress) -# 4. exit 0 when nothing survives (or nothing was running -- clean install safe); -# exit 1 when a process still holds the prefix (installer aborts with a hint) -# -# Run via `powershell -NoProfile -ExecutionPolicy Bypass -File stop-git.ps1` -# (a standalone script -- no cmd inline-PowerShell quoting escapes, the root -# cause of the 0.2.26 step-4 escape bug). - -$ErrorActionPreference = 'SilentlyContinue' -$prefix = "$env:USERPROFILE\.emrg\install\git\*" - -# pass 1: git tree (portable Git executables under the prefix) -Get-CimInstance Win32_Process | - Where-Object { $_.ExecutablePath -like $prefix -and $_.Name -in @('git.exe', 'ssh.exe', 'plink.exe', 'bash.exe') } | - ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } -Start-Sleep -Milliseconds 300 - -# pass 2: guilt-by-association -- anything still under the prefix (sh/vim/...) -Get-CimInstance Win32_Process | - Where-Object { $_.ExecutablePath -like $prefix } | - ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } -Start-Sleep -Milliseconds 300 - -$left = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix }) -if ($left.Count -gt 0) { - # Truthful failure (rant 2026-08-12T12:30:41): never report success while a - # process still holds the prefix. Name the survivors so the installer can - # show a useful message instead of a silent bogus exit=0. - $left | ForEach-Object { Write-Host ("still running: {0} (pid {1})" -f $_.Name, $_.ProcessId) } - exit 1 -} -exit 0 diff --git a/packaging/build-runtime.sh b/packaging/build-runtime.sh index 1a4f91a..041496c 100755 --- a/packaging/build-runtime.sh +++ b/packaging/build-runtime.sh @@ -69,7 +69,6 @@ cp -R "$PY_ROOT/." "$DIST/bin/python-dist/" cp "$ROOT/bin/emrg.cmd" emrg.cmd 2>/dev/null || true cp "$ROOT/bin/emrgd.cmd" emrgd.cmd 2>/dev/null || true cp "$ROOT/bin/stop-emrg.cmd" stop-emrg.cmd 2>/dev/null || true - cp "$ROOT/bin/stop-git.ps1" stop-git.ps1 2>/dev/null || true cp "$ROOT/bin/emrg-uninstall" emrg-uninstall chmod +x emrg emrgd emrg-uninstall ) diff --git a/packaging/make-installer.sh b/packaging/make-installer.sh index 63330b8..6a2193e 100755 --- a/packaging/make-installer.sh +++ b/packaging/make-installer.sh @@ -251,10 +251,6 @@ Source: "$STAGE_WIN/payload\\*"; DestDir: "{app}"; Flags: recursesubdirs createa ; pythonw daemon 锁文件导致卡在"停止已有进程")。正常安装时该文件仍由上方通配符 ; 装入 {app}\bin\stop-emrg.cmd。 Source: "$STAGE_WIN/payload\\bin\\stop-emrg.cmd"; DestDir: "{tmp}"; Flags: dontcopy -; R128: dontcopy — 供 [Code] PrepareToInstall 在覆盖文件前 ExtractTemporaryFile 取出并 -; 作为 %~1 传给 stop-emrg.cmd step 4(连坐强杀 bundled git,宿主 2026-08-11T19:47:44 -; 拍板覆盖 #689:旧安装 %INSTALL%\bin\stop-git.ps1 不存在时用 {tmp} 提取版)。 -Source: "$STAGE_WIN/payload\\bin\\stop-git.ps1"; DestDir: "{tmp}"; Flags: dontcopy [Icons] Name: "{userprograms}\\EMRG"; Filename: "{app}\\emrg-gui\\EMRG\\EMRG.exe"; IconFilename: "{app}\\emrg-gui\\EMRG\\EMRG.exe" [UninstallRun] @@ -376,26 +372,23 @@ end; // -m emrg.server 常驻锁文件),覆盖 ~/.emrg\install 时卡在"停止已有进程"。 // PrepareToInstall 在安装开始前运行 bin\stop-emrg.cmd:taskkill EMRG.exe // 优雅→/F 兜底、wmic/PowerShell 命令行过滤 TUI、emrg server stop 协议关闭 -// daemon + emrgd.pid 轮询兜底、step 4 调 stop-git.ps1 连坐强杀 bundled git +// daemon + emrgd.pid 轮询兜底、step 4 内联 PowerShell 连坐强杀 bundled git // (宿主 2026-08-11T19:47:44 拍板覆盖 #689:sh/vim 锁 install\git\usr\bin\ // msys-2.0.dll 导致 Inno DeleteFile code 5 时一并强杀,安装成功优先;只碰 -// install\git\ 前缀,系统 Git 不受影响)。旧安装可能没有 stop-git.ps1 → -// {tmp} dontcopy 提取版作为 %~1 传入(cmd 内先查 %INSTALL%\bin,缺失才用 -// 参数)。干净安装(无旧 install)脚本自行跳过。返回非空字符串 = 中止安装 +// install\git\ 前缀,系统 Git 不受影响;rant 2026-08-12T14:00:05 合并为 +// 单文件——stop-git.ps1 已删除,逻辑内联进 stop-emrg.cmd step 4)。 +// 干净安装(无旧 install)脚本自行跳过。返回非空字符串 = 中止安装 // 并显示该消息(宁可中止也不卡死)。 // {cmd} = cmd.exe(Inno 预定义常量,批处理文件须经 cmd 启动)。 function PrepareToInstall(var NeedsRestart: Boolean): String; var ResultCode: Integer; StopScript: string; - GitStopScript: string; begin Result := ''; ExtractTemporaryFile('stop-emrg.cmd'); - ExtractTemporaryFile('stop-git.ps1'); StopScript := ExpandConstant('{tmp}\\stop-emrg.cmd'); - GitStopScript := ExpandConstant('{tmp}\\stop-git.ps1'); - if Exec(ExpandConstant('{cmd}'), '/c "' + StopScript + '" "' + GitStopScript + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + if Exec(ExpandConstant('{cmd}'), '/c "' + StopScript + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin if ResultCode <> 0 then Result := 'EMRG could not stop all running processes (stop-emrg.cmd exit code ' + IntToStr(ResultCode) + '). Please close EMRG (GUI/TUI) and retry the install, or restart the computer and retry (a helper process such as the bundled Git may still hold a file lock).'; diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index fbfeeff..81a9ea0 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -1,17 +1,20 @@ -"""Windows 安装器预停止接线回归测试(rant 2026-08-10T08:50:44 + 宿主 2026-08-11T19:47:44 连坐强杀拍板)。 +"""Windows 安装器预停止接线回归测试(rant 2026-08-10T08:50:44 + 宿主 2026-08-11T19:47:44 连坐强杀拍板 ++ rant 2026-08-12T14:00:05 合并单文件)。 安装器(Inno Setup,make-installer.sh 生成 emrg.iss)覆盖 ~/.emrg/install 前必须 先优雅关闭 GUI/TUI/daemon/bundled-git,否则无窗口的 pythonw daemon 独占锁文件 → 卡在 "停止已有进程"(宿主只能重启系统)。本测试纯文本断言(不执行 iscc/cmd —— macOS/CI 无 Windows),钉死接线: - 1. bin/stop-emrg.cmd 覆盖三步 + step 4 调 stop-git.ps1:GUI 优雅关闭+/F 兜底、 - TUI 命令行过滤、daemon 协议关闭 + emrgd.pid 轮询兜底(顺序 GUI → daemon) - 2. bin/stop-git.ps1 独立脚本:install\\git\\ 前缀收集 → 先杀 git 树 → 连坐强杀全部 - 残留(sh/vim 一并杀,宿主拍板覆盖 #689)→ 无残留 exit 0 / 有残留 exit 1 + 1. bin/stop-emrg.cmd 单文件覆盖全流程:GUI 优雅关闭+/F 兜底、TUI 命令行过滤、 + daemon 协议关闭 + emrgd.pid 轮询兜底(顺序 GUI → daemon)、step 4 内联 + PowerShell 连坐强杀 bundled git(stop-git.ps1 已删除合并,rant 2026-08-12T14:00:05) + 2. bin/stop-git.ps1 不存在;内联逻辑:install\\git\\ 前缀收集 → 先杀 git 树 → + 连坐强杀全部残留(sh/vim 一并杀)→ 每次枚举最新 Get-CimInstance → 无残留 + exit 0 / 有残留 exit 1(truthful failure,#701) 3. bin/emrgd.cmd 含 stop 分支(复用 `emrg server stop`) - 4. make-installer.sh 的 .iss 模板含 [Files] dontcopy(stop-emrg.cmd + stop-git.ps1) - + [Code] PrepareToInstall 传 {tmp} 提取版作 %~1 - 5. build-runtime.sh 把 stop-emrg.cmd / stop-git.ps1 复制进 runtime bin/ + 4. make-installer.sh 的 .iss 模板含 [Files] dontcopy(stop-emrg.cmd)+ [Code] + PrepareToInstall 运行 {tmp} 提取版 + 5. build-runtime.sh 把 stop-emrg.cmd 复制进 runtime bin/ """ from pathlib import Path @@ -19,7 +22,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent -def test_stop_emrg_cmd_covers_gui_tui_daemon_and_calls_stop_git(): +def test_stop_emrg_cmd_covers_gui_tui_daemon_and_inline_step4(): content = (REPO_ROOT / "bin" / "stop-emrg.cmd").read_text(encoding="utf-8") # GUI:无 /F 优雅 WM_CLOSE 优先,/F 兜底 assert "taskkill /IM EMRG.exe" in content @@ -41,26 +44,26 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_and_calls_stop_git(): # 顺序:GUI 在 daemon 之前(GUI 不能复活 daemon) daemon_line = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop') assert content.index("taskkill /IM EMRG.exe") < daemon_line - # step 4(宿主 2026-08-11T19:47:44 拍板覆盖 #689):调独立 stop-git.ps1 连坐强杀。 - # 判别:powershell -File 调用(无 cmd 内联转义)+ -ExecutionPolicy Bypass。 - step4_idx = content.index("REM --- 4. bundled git: guilt-by-association") + # step 4(rant 2026-08-12T14:00:05 合并单文件):内联 PowerShell 连坐强杀。 + # 判别:-Command 内联(无 -File 独立脚本),\" 转义与 TUI/verify 段同模式。 + step4_idx = content.index("REM --- 4. bundled git: inline guilt-by-association") verify_idx = content.index("\n:verify\n") # 标签定义处(\n 前缀排除前面 goto :verify 行) assert daemon_line < step4_idx < verify_idx - assert "stop-git.ps1" in content - assert "-ExecutionPolicy Bypass -File" in content - assert r'"%GITSTOP%"' in content - # 旧安装可能没有 stop-git.ps1 → {tmp} 提取版(%~1)兜底 - assert 'set "GITSTOP=%~1"' in content - # %TEMP% 兜底已移除(review 2026-08-11T19:57: {tmp} 参数或已装副本恒胜出;缺失时 :verify 前缀检查兜底) - assert "GITSTOP=%TEMP%" not in content - # #689 的 snapshot/step0/PIDFILE 机制已删除(宿主否决——误伤宿主工具的根源) - assert "emrg-stop-pids.txt" not in content - assert "ParentProcessId" not in content - assert "PIDFILE" not in content - assert "Set-Content" not in content # 快照写盘命令不复存在 + assert "-ExecutionPolicy Bypass -Command" in content + assert "-ExecutionPolicy Bypass -File" not in content # 独立脚本已删除 + assert "GITSTOP" not in content # %~1/{tmp} 传递机制随 stop-git.ps1 一并移除 + # pass 1:先杀 git 树(git/ssh/plink/bash) + assert "-in @('git.exe','ssh.exe','plink.exe','bash.exe')" in content + # 前缀过滤:只碰 %USERPROFILE%\.emrg\install\git\*(系统 Git 永不命中); + # cmd 内联转义形态(\" 包裹 PS 字符串字面量) + assert r'\"$env:USERPROFILE\.emrg\install\git\*\"' in content + # 存活检查基于最新枚举(Get-CimInstance 每次重新查询,无旧快照) + assert content.count("Get-CimInstance Win32_Process") >= 4 # pass1 + pass2 + $left + verify + # truthful failure:幸存进程点名输出 + exit 1(rant 2026-08-12T12:30:41 语义) + assert r'Write-Host (\"still running: {0} (pid {1})\" -f $_.Name, $_.ProcessId)' in content # step 4 失败必须传导到退出码(EXIT_CODE 初始化在 setlocal 之后、verify 不再清零) assert 'set "EXIT_CODE=0"' in content - assert "if errorlevel 1 set \"EXIT_CODE=1\"" in content + assert 'if errorlevel 1 set "EXIT_CODE=1"' in content assert content.index('set "EXIT_CODE=0"') < step4_idx # 干净安装安全:无旧 install 目录时跳过 assert 'set "INSTALL=%EMRG_DIR%\\install"' in content @@ -75,31 +78,29 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_and_calls_stop_git(): assert "%DPID%" not in verify_block # :verify 的 bundled-git 存活判定:无差别前缀检查(连坐语义——连坐杀后仍存活才中止) assert "exit 1" in verify_block - assert "if errorlevel 1 set \"EXIT_CODE=1\"" in verify_block + assert 'if errorlevel 1 set "EXIT_CODE=1"' in verify_block assert r'$env:USERPROFILE\.emrg\install\git\*' in verify_block assert "Get-CimInstance" in verify_block -def test_stop_git_ps1_guilt_by_association(): - content = (REPO_ROOT / "bin" / "stop-git.ps1").read_text(encoding="utf-8") - # 前缀过滤:只碰 %USERPROFILE%\.emrg\install\git\*(系统 Git 永不命中) - assert r'$env:USERPROFILE\.emrg\install\git\*' in content - # pass 1:先杀 git 树(git/ssh/plink/bash) - assert "-in @('git.exe', 'ssh.exe', 'plink.exe', 'bash.exe')" in content - # pass 2:连坐强杀——前缀内全部残留(sh/vim 一并杀,宿主拍板) - # 判别:第二次 Get-CimInstance 无 Name 过滤(不再区分工具类型) - assert content.count("Get-CimInstance Win32_Process") >= 3 - pass2_idx = content.index("# pass 2") - assert "Stop-Process -Id $_.ProcessId -Force" in content +def test_stop_git_merged_single_file(): + # rant 2026-08-12T14:00:05 验收:bin/ 下无 stop-git.ps1;grep stop-git 仅历史注释 + assert not (REPO_ROOT / "bin" / "stop-git.ps1").exists() + cmd = (REPO_ROOT / "bin" / "stop-emrg.cmd").read_text(encoding="utf-8") + step4 = cmd[cmd.index("REM --- 4. bundled git:"):cmd.index("\n:verify\n")] + # pass 2:连坐强杀——前缀内全部残留(sh/vim 一并杀,宿主 2026-08-11T19:47:44 拍板) + assert "Start-Sleep -Milliseconds 300" in step4 + # 每次枚举重新 Get-CimInstance(不用旧快照)——pass1/pass2/$left 共 3 次 + assert step4.count("Get-CimInstance Win32_Process") == 3 # 退出码语义:无残留(或本来无进程)→ 0;仍有存活 → 1 - assert "exit 1" in content and "exit 0" in content - # rant 2026-08-12T12:30:41(spec B):存活检查用最新快照 + 如实报失败—— - # 幸存进程必须点名输出(不静默吞掉),有存活即 exit 1,绝不误报成功 - assert 'if ($left.Count -gt 0) {' in content - assert "still running: {0} (pid {1})" in content - assert "Write-Host" in content - # 独立脚本 + 非交互(无 cmd 内联转义问题,0.2.26 转义 bug 根因规避) - assert "$ErrorActionPreference = 'SilentlyContinue'" in content + assert "exit 1" in step4 and "exit 0" in step4 + # 打包链路无功能引用:make-installer.sh / build-runtime.sh 不再处理 stop-git.ps1 + mi = (REPO_ROOT / "packaging" / "make-installer.sh").read_text(encoding="utf-8") + assert '"$STAGE_WIN/payload\\\\bin\\\\stop-git.ps1"' not in mi + assert "ExtractTemporaryFile('stop-git.ps1')" not in mi + assert "GitStopScript" not in mi + br = (REPO_ROOT / "packaging" / "build-runtime.sh").read_text(encoding="utf-8") + assert 'cp "$ROOT/bin/stop-git.ps1"' not in br def test_emrgd_cmd_has_stop_branch(): @@ -113,13 +114,11 @@ def test_make_installer_iss_has_prepare_to_install(): content = (REPO_ROOT / "packaging" / "make-installer.sh").read_text(encoding="utf-8") assert "dontcopy" in content assert "stop-emrg.cmd" in content - assert "stop-git.ps1" in content assert "PrepareToInstall" in content assert "ExtractTemporaryFile('stop-emrg.cmd')" in content - assert "ExtractTemporaryFile('stop-git.ps1')" in content - # {tmp} 提取版 stop-git.ps1 作为 %~1 传给 stop-emrg.cmd(旧安装可能无此文件) - assert "GitStopScript" in content - assert "'/c \"' + StopScript + '\" \"' + GitStopScript" in content + # 单文件:只提取/执行 stop-emrg.cmd(stop-git.ps1 已删除) + assert "ExtractTemporaryFile('stop-git.ps1')" not in content + assert "/c \"' + StopScript + '\"" in content assert "SW_HIDE" in content # 批处理执行不弹控制台窗口(#592 纪律) # rant 2026-08-11T17:56:25:中止消息含重启兜底引导(杀不掉时宿主可重启后重试) assert "restart the computer" in content @@ -128,4 +127,4 @@ def test_make_installer_iss_has_prepare_to_install(): def test_build_runtime_copies_stop_scripts(): content = (REPO_ROOT / "packaging" / "build-runtime.sh").read_text(encoding="utf-8") assert 'cp "$ROOT/bin/stop-emrg.cmd" stop-emrg.cmd' in content - assert 'cp "$ROOT/bin/stop-git.ps1" stop-git.ps1' in content + assert 'cp "$ROOT/bin/stop-git.ps1"' not in content