Files
OpsLog/relaunchargs_test.go
T
rouggy 97cc446c15 Revert to the PowerShell relaunch helper, on the operator's call
Restored verbatim from before 0430aab:

  Wait-Process -Id <pid>; Start-Sleep -Milliseconds 400;
  Start-Process -FilePath <exe> -ArgumentList '--post-update'

Two rewrites tried to start the new exe from inside the dying process
and both failed on real stations — 0.27.23 and again after the SW_HIDE
fix. The original's own comment had already said why: "Launching the new
exe directly while we're still alive raced the mutex and often left
nothing running." Telling the new instance our pid so it could wait on
the other side looked equivalent and is not. What the helper has that
neither rewrite did is that it OUTLIVES us — the launch happens once
this process is completely gone, from a process that was never our
child.

The Defender cost is known and accepted: an unsigned binary that
replaces itself, clears the mark-of-the-web and spawns a windowless
script to start another executable has the shape of a dropper, and
0.27.14 was removed from a station as Trojan:Script/Wacatac.H!ml. The
operator's decision is that whitelisting OpsLog beats an updater that
leaves people with no running program, and the changelog says so.

HideWindow stays on the PowerShell and is not the bug that made the
updated OpsLog invisible: it hides the helper's console, which is the
point, while Start-Process shows the new window normally.

relaunchCmd and its detachment stay for RestartApp, the database-switch
relaunch, which has never been reported broken.

Two tests kept honest: the update path must contain Wait-Process and
Start-Process and must not spawn the exe directly again, and
TestEveryRelaunchPassesItsPid had quietly stopped matching anything when
the spawn sites changed spelling — its pattern now covers relaunchCmd
too, so it guards RestartApp instead of passing vacuously.
2026-09-11 08:57:11 +02:00

54 lines
1.8 KiB
Go

package main
import (
"os"
"regexp"
"strings"
"testing"
)
func TestWaitPidArg(t *testing.T) {
for _, tc := range []struct {
name string
args []string
want int
}{
{"after the flag", []string{"--post-update", "--wait-pid", "4321"}, 4321},
{"joined with =", []string{"--wait-pid=4321"}, 4321},
{"absent", []string{"--post-update"}, 0},
{"flag with nothing after it", []string{"--wait-pid"}, 0},
{"not a number", []string{"--wait-pid", "later"}, 0},
} {
if got := waitPidArg(tc.args); got != tc.want {
t.Errorf("%s: got %d, wanted %d", tc.name, got, tc.want)
}
}
}
// A relaunch started from INSIDE this process has to tell the new one which
// process to wait for.
//
// The update path no longer does that from inside: its PowerShell helper waits
// for the pid and starts the exe once we are gone, which is what two rewrites
// failed to reproduce. RestartApp still spawns directly, and if a relaunch is
// ever added the same way without --wait-pid, it is the old bug again.
func TestEveryRelaunchPassesItsPid(t *testing.T) {
// Both spellings: a relaunch built inline with exec.Command, and one built
// through relaunchCmd. The pattern used to name only the first, and when the
// update path moved to a PowerShell helper and RestartApp to relaunchCmd it
// quietly matched nothing at all — a guard that passes because it looks
// nowhere.
spawn := regexp.MustCompile(`(exec\.Command|relaunchCmd)\(exe, "--(post-update|relaunch)"[^)]*\)`)
for _, file := range []string{"update.go", "app.go"} {
src, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
for _, call := range spawn.FindAllString(string(src), -1) {
if !strings.Contains(call, "--wait-pid") {
t.Errorf("%s: %s does not tell the new instance which process to wait for", file, call)
}
}
}
}