Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""Regression: the Windows Desktop update hand-off must run through python.exe.
|
||||
|
||||
`scripts/desktop-update/windows.ps1` drives `hermes update` for the in-app
|
||||
Desktop updater. It used to invoke the update through the venv's
|
||||
`venv\\Scripts\\hermes.exe` console-script launcher. On Windows that launcher is
|
||||
a real process that keeps `hermes.exe` mapped as its running image and spawns
|
||||
`python.exe` as a child. The update ends in `uv pip install -e .`, which rewrites
|
||||
the console-script shims -- including the `hermes.exe` the launcher still has
|
||||
mapped -- and Windows refuses to replace a file mapped as a running image
|
||||
("os error 32"). The rename fallback then defers to next reboot via
|
||||
`MOVEFILE_DELAY_UNTIL_REBOOT`, which needs elevation a Desktop-driven update
|
||||
does not have, so `uv pip install -e .` exits non-zero, the ZIP fallback repeats
|
||||
the same sequence, the desktop build stage is never reached, and the pre-build
|
||||
clean has already removed `apps/desktop/release` -- leaving an install whose
|
||||
Start Menu shortcut points at a `Hermes.exe` that no longer exists.
|
||||
|
||||
Driving the update as `python.exe -m hermes_cli.main update` puts the inherited
|
||||
image handle on `python.exe`, which uv never has to replace, so the shim is an
|
||||
ordinary unlocked file when uv rewrites it.
|
||||
|
||||
This test is source-level because Linux CI cannot execute the PowerShell
|
||||
hand-off. The invariant it guards is that every `Invoke-HermesStep` call site
|
||||
(the update, its retry, and the desktop rebuild) drives `$pythonExe`, never the
|
||||
`$hermesExe` shim. `hermes.exe` may still be *named* in the file for the
|
||||
step-2 unlock preflight -- that is a lock probe, not an invocation -- so we
|
||||
assert against the invocation sites specifically.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
WINDOWS_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1"
|
||||
|
||||
|
||||
def _read() -> str:
|
||||
# windows.ps1 is eol=crlf in .gitattributes, so checkouts materialize
|
||||
# CRLF on disk (CI included). Normalize so the SelfTest-block strip's
|
||||
# `\n}\n` anchors match regardless of the working-copy line endings.
|
||||
return WINDOWS_PS1.read_text(encoding="utf-8").replace("\r\n", "\n")
|
||||
|
||||
|
||||
def _handoff_source() -> str:
|
||||
"""The script with its ``-SelfTest*`` fixture blocks removed.
|
||||
|
||||
Those blocks exercise the hand-off machinery deliberately -- the pipe-drain
|
||||
fixture runs a synthetic PowerShell step through ``Invoke-HermesStep`` to
|
||||
prove the drain cannot deadlock (#90455) -- so they are not update steps
|
||||
and the "must drive python.exe" rule does not apply to them. Each exits
|
||||
before any marker/venv/desktop machinery runs.
|
||||
|
||||
Scoped here rather than allow-listing a target, so the rule stays absolute
|
||||
for every real step. The non-greedy match ends at the first closing brace
|
||||
in column 0; the blocks' own braces are all indented.
|
||||
"""
|
||||
return re.sub(r"\nif \(\$SelfTest\w+\) \{.*?\n\}\n", "\n", _read(), flags=re.S)
|
||||
|
||||
|
||||
def test_invoke_hermes_step_calls_drive_python_not_the_shim() -> None:
|
||||
source = _handoff_source()
|
||||
|
||||
invocations = re.findall(r"Invoke-HermesStep\s+(\$\w+)", source)
|
||||
assert invocations, (
|
||||
"Expected at least one Invoke-HermesStep call in "
|
||||
"scripts/desktop-update/windows.ps1; the update hand-off structure "
|
||||
"changed -- update this guard."
|
||||
)
|
||||
|
||||
offenders = [exe for exe in invocations if exe != "$pythonExe"]
|
||||
assert not offenders, (
|
||||
"Every Invoke-HermesStep call in scripts/desktop-update/windows.ps1 "
|
||||
"must drive $pythonExe, not the hermes.exe shim. Driving the update "
|
||||
"through the shim keeps hermes.exe mapped as a running image, so uv's "
|
||||
"final shim rewrite fails with os error 32 and the Desktop update can "
|
||||
"never complete. Offending target(s): "
|
||||
f"{sorted(set(offenders))}."
|
||||
)
|
||||
|
||||
|
||||
def test_update_invocation_uses_module_entrypoint() -> None:
|
||||
source = _read()
|
||||
|
||||
assert '@("-m", "hermes_cli.main", "update"' in source, (
|
||||
"The update step must invoke `python.exe -m hermes_cli.main update ...` "
|
||||
"so the inherited image handle lands on python.exe, which uv never has "
|
||||
"to replace."
|
||||
)
|
||||
assert (
|
||||
'@("-m", "hermes_cli.main", "desktop", "--force-build", "--build-only")'
|
||||
in source
|
||||
), (
|
||||
"The desktop rebuild step must also go through "
|
||||
"`python.exe -m hermes_cli.main desktop ...` for the same reason."
|
||||
)
|
||||
|
||||
|
||||
def test_update_no_longer_invokes_the_hermes_exe_shim() -> None:
|
||||
source = _read()
|
||||
|
||||
assert "Invoke-HermesStep $hermesExe" not in source, (
|
||||
"scripts/desktop-update/windows.ps1 still invokes the update through "
|
||||
"the hermes.exe shim (`Invoke-HermesStep $hermesExe`). That is the "
|
||||
"exact self-lock this fix removes -- route it through $pythonExe "
|
||||
"instead."
|
||||
)
|
||||
|
||||
|
||||
def test_desktop_relaunch_waits_for_an_in_place_rebuild() -> None:
|
||||
source = _read()
|
||||
relaunch = re.search(
|
||||
r"function Start-DesktopRelaunch \{(?P<body>.*?)\n\}\n\nfunction Invoke-HermesStep",
|
||||
source,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert relaunch, "Expected Start-DesktopRelaunch in the Windows hand-off script."
|
||||
|
||||
body = relaunch.group("body")
|
||||
assert "if (-not $RelaunchExe) { return $false }" in body
|
||||
assert "$relaunchDeadline = (Get-Date).AddSeconds(120)" in body
|
||||
assert "while (-not (Test-Path -LiteralPath $RelaunchExe))" in body
|
||||
assert "if ((Get-Date) -ge $relaunchDeadline)" in body
|
||||
assert "Start-Sleep -Milliseconds 500" in body
|
||||
assert "[System.Windows.Forms.Application]::DoEvents()" in body
|
||||
Reference in New Issue
Block a user