diff --git a/src/runtime/signal_windows_test.go b/src/runtime/signal_windows_test.go index 7c88ab573eab2a..c3ad95d62f0835 100644 --- a/src/runtime/signal_windows_test.go +++ b/src/runtime/signal_windows_test.go @@ -10,7 +10,6 @@ import ( "os/exec" "path/filepath" "runtime" - "strconv" "strings" "syscall" "testing" @@ -91,13 +90,16 @@ func TestCtrlHandler(t *testing.T) { // run test program cmd = exec.Command(exe) + var stdout bytes.Buffer var stderr bytes.Buffer + cmd.Stdout = &stdout cmd.Stderr = &stderr - outPipe, err := cmd.StdoutPipe() + inPipe, err := cmd.StdinPipe() if err != nil { - t.Fatalf("Failed to create stdout pipe: %v", err) + t.Fatalf("Failed to create stdin pipe: %v", err) } - outReader := bufio.NewReader(outPipe) + // keep inPipe alive until the end of the test + defer inPipe.Close() // in a new command window const _CREATE_NEW_CONSOLE = 0x00000010 @@ -113,29 +115,15 @@ func TestCtrlHandler(t *testing.T) { cmd.Wait() }() - // wait for child to be ready to receive signals - if line, err := outReader.ReadString('\n'); err != nil { - t.Fatalf("could not read stdout: %v", err) - } else if strings.TrimSpace(line) != "ready" { - t.Fatalf("unexpected message: %s", line) - } - - // gracefully kill pid, this closes the command window - if err := exec.Command("taskkill.exe", "/pid", strconv.Itoa(cmd.Process.Pid)).Run(); err != nil { - t.Fatalf("failed to kill: %v", err) + // check child exited gracefully, did not timeout + if err := cmd.Wait(); err != nil { + t.Fatalf("Program exited with error: %v\n%s", err, &stderr) } // check child received, handled SIGTERM - if line, err := outReader.ReadString('\n'); err != nil { - t.Fatalf("could not read stdout: %v", err) - } else if expected, got := syscall.SIGTERM.String(), strings.TrimSpace(line); expected != got { + if expected, got := syscall.SIGTERM.String(), strings.TrimSpace(stdout.String()); expected != got { t.Fatalf("Expected '%s' got: %s", expected, got) } - - // check child exited gracefully, did not timeout - if err := cmd.Wait(); err != nil { - t.Fatalf("Program exited with error: %v\n%s", err, &stderr) - } } // TestLibraryCtrlHandler tests that Go DLL allows calling program to handle console control events. diff --git a/src/runtime/testdata/testwinsignal/main.go b/src/runtime/testdata/testwinsignal/main.go index 1e7c9475fd6631..e1136f3887929b 100644 --- a/src/runtime/testdata/testwinsignal/main.go +++ b/src/runtime/testdata/testwinsignal/main.go @@ -2,18 +2,52 @@ package main import ( "fmt" + "io" + "log" "os" "os/signal" + "syscall" "time" ) func main() { + // Ensure that this process terminates when the test times out, + // even if the expected signal never arrives. + go func() { + io.Copy(io.Discard, os.Stdin) + log.Fatal("stdin is closed; terminating") + }() + + // Register to receive all signals. c := make(chan os.Signal, 1) signal.Notify(c) - fmt.Println("ready") + // Get console window handle. + kernel32 := syscall.NewLazyDLL("kernel32.dll") + getConsoleWindow := kernel32.NewProc("GetConsoleWindow") + hwnd, _, err := getConsoleWindow.Call() + if hwnd == 0 { + log.Fatal("no associated console: ", err) + } + + // Send message to close the console window. + const _WM_CLOSE = 0x0010 + user32 := syscall.NewLazyDLL("user32.dll") + postMessage := user32.NewProc("PostMessageW") + ok, _, err := postMessage.Call(hwnd, _WM_CLOSE, 0, 0) + if ok == 0 { + log.Fatal("post message failed: ", err) + } + sig := <-c + // Allow some time for the handler to complete if it's going to. + // + // (In https://go.dev/issue/41884 the handler returned immediately, + // which caused Windows to terminate the program before the goroutine + // that received the SIGTERM had a chance to actually clean up.) time.Sleep(time.Second) + + // Print the signal's name: "terminated" makes the test succeed. fmt.Println(sig) }