test(e2e/windows): the app-update driver waits out the real update and checks staged main

Once launch capture works, two Windows hermes-desktop-app-update failures
show up (run 36286580917):

- v2026.9.24 -> HEAD (installer-script, installer-script+desktop,
  desktop-installer@latest): the app's update is correct but slow. From
  v2026.9.24 it runs the historical venv->PM takeover and a full Desktop
  rebuild (the `hermes update` alone took 9m43s). launch-from-spec's 10 min
  default gave up 9m55s after the Update now click while the updater window
  showed "Updating code and dependencies 9m 22s elapsed". windows-e2e.ps1
  now passes --timeout-ms 1800000, in line with open-app-update's 35 min
  wait. The driver's self-deadline is now measured after the update wait
  instead of being a flat 20 min inside it.
- HEAD -> NEXT: Desktop offered real GitHub main (0f4a98f8) instead of the
  staged NEXT, so assertStagedBranch refused to click. HEAD's Electron main
  is one ESM bundle, and checkout-source.ts binds promisify(execFile) at
  load, before installSourceBranchProbe patches execFile in the packaged
  app. POSIX avoids this by wrapping the launcher script, which Windows
  skips. The probe now also hooks ChildProcess.prototype.spawn, which every
  child passes through, and reuses branchProbeArgs, which already knows both
  the --run-module and cmd.exe .cmd shapes. Verified locally: a promisify
  captured before the hook now gets `--git <real> --branch main`, and other
  commands are untouched.
This commit is contained in:
teknium1
2026-09-26 19:35:24 -07:00
committed by Teknium
parent 03ffc307ee
commit a965c4052e
3 changed files with 25 additions and 8 deletions

View File

@@ -114,13 +114,6 @@ async function main() {
// holding node's event loop open, so the driver can outlive its own
// finished test. Success and failure paths exit explicitly; this unref'd
// timer is the backstop so no unknown state holds a runner past its budget.
const SELF_DEADLINE_MS = 20 * 60 * 1000;
const selfDeadline = setTimeout(() => {
log(`DRIVER SELF-TIMEOUT after ${SELF_DEADLINE_MS / 60000}min - exiting 124 (phase: ${currentPhase})`);
process.exit(124);
}, SELF_DEADLINE_MS);
selfDeadline.unref();
const { values } = parseArgs({
options: {
spec: { type: 'string' },
@@ -134,6 +127,14 @@ async function main() {
'timeout-ms': { type: 'string', default: '600000' },
},
});
// After the update wait (--timeout-ms), not inside it: launch, OLD chat and
// relaunch checks get their own 20 minutes.
const SELF_DEADLINE_MS = Number(values['timeout-ms']) + 20 * 60 * 1000;
const selfDeadline = setTimeout(() => {
log(`DRIVER SELF-TIMEOUT after ${SELF_DEADLINE_MS / 60000}min - exiting 124 (phase: ${currentPhase})`);
process.exit(124);
}, SELF_DEADLINE_MS);
selfDeadline.unref();
if (!values.spec) throw new Error('--spec is required');
if (!values['old-sha'] || !values['chat-out'] || !values['mock-url'] || !values['repo-dir']) {
throw new Error('--old-sha, --chat-out, --mock-url and --repo-dir are required for OLD chat');

View File

@@ -131,6 +131,19 @@ if (process.env.HERMES_E2E_SOURCE_ROOT && process.env.HERMES_E2E_SOURCE_GIT) {
}
const custom = Symbol.for('nodejs.util.promisify.custom')
childProcess.execFile[custom] = (file, args, options) => original[custom](file, select(args), options)
// HEAD's Electron main is one ESM bundle: checkout-source.ts bound
// promisify(execFile) at load, before installSourceBranchProbe runs in the
// packaged app, so neither hook above reaches it. Every child still goes
// through ChildProcess.prototype.spawn, whose args carry argv0 first.
const proto = childProcess.ChildProcess.prototype
const spawnChild = proto.spawn
proto.spawn = function (options) {
const rest = Array.isArray(options?.args) ? options.args.slice(1) : null
const selected = rest && select(rest)
return spawnChild.call(this, selected && selected !== rest
? { ...options, args: [options.args[0], ...selected] }
: options)
}
// v2026.9.21 does its source check in Electron: remote get-url selects
// GitHub's public REST API unless its real Git process sees staged origin.
// Redirect only the legacy check's two Git reads, not arbitrary subprocesses.

View File

@@ -610,10 +610,13 @@ function Invoke-HermesDesktopAppUpdate([string]$TargetSha) {
$prevEap = $ErrorActionPreference; $ErrorActionPreference = "Continue"
Push-Location $WorkRoot
try {
# 30 min, not the 10 min default: from v2026.9.24 the app's update
# runs the historical venv->PM takeover plus a full Desktop rebuild
# (9m43s measured), like the 35 min open-app-update wait below.
& $node (Join-Path $AssetsDir "launch-from-spec.mjs") --spec $spec `
--old-sha (Read-State).old --chat-out $chatOut --mock-url $env:HERMES_E2E_MOCK_URL `
--result (Join-Path $HermesHome ".hermes-update-result.json") `
--expect-sha $TargetSha --repo-dir $InstallDir 2>&1 |
--expect-sha $TargetSha --repo-dir $InstallDir --timeout-ms 1800000 2>&1 |
ForEach-Object { Write-Host " pw| $_" }
$driveExit = $LASTEXITCODE
} finally {