Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (868) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (869) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (257: 45 daemon_client + 19 conn-manager + 22 app-commands + 129 renderer smoke + 16 i18n + 7 integration + 3 commands + 7 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
9 changes: 6 additions & 3 deletions emrg/_stop_all.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -353,13 +353,16 @@ def stop_tui() -> None:
(same contract as the POSIX branch's ``own_pid`` exclusion)."""
if is_win():
own = os.getpid()
# Literal PowerShell script-block braces must be escaped as {{ }} —
# otherwise str.format() treats them as replacement fields and raises
# ValueError: unexpected '{' in field name at runtime on Windows.
ps_cmd = (
"Get-CimInstance Win32_Process | "
"Where-Object { $_.ProcessId -ne {own} -and "
"Where-Object {{ $_.ProcessId -ne {own} -and "
"$_.Name -match '^python(\\.exe|w\\.exe)?$' -and "
"$_.CommandLine -match '-m emrg' -and "
"$_.CommandLine -notmatch 'emrg\\.server' } | "
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"
"$_.CommandLine -notmatch 'emrg\\.server' }} | "
"ForEach-Object {{ Stop-Process -Id $_.ProcessId -Force }}"
).format(own=own)
subprocess.run(
["powershell", "-NoProfile", "-Command", ps_cmd],
Expand Down
34 changes: 34 additions & 0 deletions tests/test_stop_all.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -165,6 +165,40 @@ def test_main_exits_with_code(self, monkeypatch):
assert exc.value.code == 1


class TestStopTuiPsTemplate:
"""Windows stop_tui() must render its PowerShell template without raising.

Regression: the template's literal script-block braces (``Where-Object {``
/ ``ForEach-Object {``) were fed to str.format() unescaped, raising
ValueError: unexpected '{' in field name at runtime on Windows — crashing
`emrg stop` before stop_bundled_git + verify ever ran. The existing
TestStopAllExitCode monkeypatches stop_tui entirely, so CI never rendered
the template; this test pins the render path itself.
"""

def test_stop_tui_renders_ps_template_win(self, monkeypatch):
import os

calls: list = []
monkeypatch.setattr(_stop_all, "is_win", lambda: True)
monkeypatch.setattr(
_stop_all.subprocess, "run",
lambda cmd, **kw: calls.append(cmd) or type("CP", (), {})(),
)

_stop_all.stop_tui() # must not raise ValueError

assert len(calls) == 1
cmd = calls[0]
assert cmd[0] == "powershell"
ps = cmd[-1]
# invoking-PID exclusion substituted into the template
assert f"-ne {os.getpid()}" in ps
# literal PowerShell script-block braces survived the format() call
assert "Where-Object { $_.ProcessId" in ps
assert "ForEach-Object { Stop-Process" in ps


class TestMainDelegatesToStopAll:
def test_emrg_stop_cli_exits_nonzero(self):
"""`emrg stop` must sys.exit with the stop_all() code (installer gate)."""
Expand Down
Loading