Skip to content

runtime: allow TestCtrlHandler to run in ConPTY #51681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions src/runtime/signal_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
36 changes: 35 additions & 1 deletion src/runtime/testdata/testwinsignal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}