Files
OpsLog/relaunch_windows_test.go
T
rouggy f8d47cd3b5 fix(linux): the update relaunch is per-platform again
Restoring the PowerShell helper put it in update.go, which is shared —
so a Linux build would have tried to run `powershell` to relaunch
itself. It compiled and vetted cleanly for linux/amd64, which is exactly
why it needed catching before somebody built it: the fault only shows on
a real update, on a machine that has no PowerShell.

scheduleRelaunch now lives in the platform files. Windows keeps the
helper. Linux starts the new binary directly, which is right there and
not a compromise: nothing holds an executable open while it runs, so the
swap has already succeeded, and there is no mutex to race — the
single-instance guard is an flock the dying process releases as it
exits, and the new one waits for our pid first.

The two guards were looking at the old location and had to follow: the
Wait-Process/Start-Process check moves into relaunch_windows_test.go
where it belongs, and TestEveryRelaunchPassesItsPid now scans
updateswap_linux.go too — the direct spawn moved there, and without it
the test would have gone quiet again.

Checked from Windows, as BUILDING-LINUX.md says is done at every
release: GOOS=linux go build ./... and go vet ./... both clean.
2026-09-11 09:28:55 +02:00

78 lines
3.1 KiB
Go

//go:build windows
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// A relaunch of OpsLog itself must not suppress the new process's window.
//
// HideWindow becomes SW_HIDE in the STARTUPINFO, and Windows applies it to the
// first top-level window the new process shows. That is how the updated OpsLog
// came to start perfectly — mutex taken, rig connected — and stay invisible:
// two operators on 0.27.23 reported a process in the task manager, no window,
// and killing it then starting OpsLog by hand working every time.
//
// CREATE_NO_WINDOW is refused for the same reason it is pointless: there is no
// console to suppress on a GUI-subsystem binary, and it is one flag away from
// the one that broke this.
func TestRelaunchNeverHidesTheWindow(t *testing.T) {
cmd := relaunchCmd(filepath.Join("C:", "OpsLog", "OpsLog.exe"), "--post-update")
if cmd.SysProcAttr == nil {
t.Fatal("no SysProcAttr — the relaunch should be detached (see detachProcess)")
}
if cmd.SysProcAttr.HideWindow {
t.Error("HideWindow is set: the relaunched OpsLog would start with no window")
}
const createNoWindow = 0x08000000
if cmd.SysProcAttr.CreationFlags&createNoWindow != 0 {
t.Error("CREATE_NO_WINDOW is set on a GUI-subsystem relaunch")
}
}
// Detached and in its own process group, which is what Start-Process gave the
// relaunch before the PowerShell helper was removed. Being a child of the
// instance that is dying is the one difference from the code that worked.
func TestRelaunchIsDetached(t *testing.T) {
cmd := relaunchCmd(filepath.Join("C:", "OpsLog", "OpsLog.exe"), "--post-update")
const (
detachedProcess = 0x00000008
createNewProcessGroup = 0x00000200
)
if cmd.SysProcAttr.CreationFlags&detachedProcess == 0 {
t.Error("DETACHED_PROCESS is missing: the new instance keeps the old one's console")
}
if cmd.SysProcAttr.CreationFlags&createNewProcessGroup == 0 {
t.Error("CREATE_NEW_PROCESS_GROUP is missing: a cleanup aimed at the old instance can reach the new one")
}
}
// The update relaunch goes through a helper that OUTLIVES this process.
//
// Two rewrites started the new exe from inside the dying one instead, and both
// left operators with no window after an update: the launch then happens while
// this process is still alive and still holds the mutex. The helper waits for
// our pid first. Restored from before 0430aab and pinned here so a third
// rewrite has to argue with the two reports rather than rediscover them.
func TestWindowsUpdateRelaunchWaitsForUsFromOutside(t *testing.T) {
src, err := os.ReadFile("updateswap_windows.go")
if err != nil {
t.Fatalf("read updateswap_windows.go: %v", err)
}
got := string(src)
for _, want := range []string{"Wait-Process -Id", "Start-Process -FilePath"} {
if !strings.Contains(got, want) {
t.Errorf("the relaunch no longer contains %q — it must wait for this process from outside it", want)
}
}
// Start-Process shows the new window normally. A direct exec.Command(exe…)
// is the shape that broke it, twice.
if strings.Contains(got, "exec.Command(exe") {
t.Error("the Windows relaunch starts the new exe directly again")
}
}