//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") } }