Skip to content

Commit

Permalink
runtime: allow TestCtrlHandler to run in ConPTY
Browse files Browse the repository at this point in the history
Fixes #51602. Previous test would not run in a pseudo-console (ConPTY).

New test avoids taskkill entirely by having the child request its own
console window be closed.

Verified that this runs locally (within a real console), over SSH
(within a pseudo-console), and that it breaks if #41884 were reverted.

Change-Id: If868b92ec36647e5d0e4107e29a2a6e048d35ced
GitHub-Last-Rev: b1421e4
GitHub-Pull-Request: #51681
Reviewed-on: https://go-review.googlesource.com/c/go/+/392874
Reviewed-by: Bryan Mills <[email protected]>
Trust: Bryan Mills <[email protected]>
Run-TryBot: Bryan Mills <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Alex Brainman <[email protected]>
Trust: Alex Brainman <[email protected]>
  • Loading branch information
ncruces authored and alexbrainman committed Mar 18, 2022
1 parent 3d19e8d commit f021086
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 23 deletions.
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 @@ -92,13 +91,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 @@ -114,29 +116,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)
}

0 comments on commit f021086

Please sign in to comment.