os/signal: avoid flaky TestCtrlBreak by waiting for child stdout

TestCtrlBreak relied on a fixed sleep to wait for a testing child
program to be ready to receive the signal. This may cause flaky timeouts
on slow machines. Instead, wait for the child to print a "ready" message
like other signal tests, particularly runtime.TestLibraryCtrlHandler.

Also fix a typo in the child program's file name to ensure it's easy to
associate with the test.

Fixes #80036

Change-Id: Ib3d8e6194538df95e76548771b401cf9b9de5674
Reviewed-on: https://go-review.googlesource.com/c/go/+/792020
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/src/os/signal/signal_windows_test.go b/src/os/signal/signal_windows_test.go
index 145a805..d39f6c1 100644
--- a/src/os/signal/signal_windows_test.go
+++ b/src/os/signal/signal_windows_test.go
@@ -5,28 +5,30 @@
 package signal
 
 import (
+	"bufio"
+	"fmt"
 	"internal/testenv"
 	"os"
 	"path/filepath"
 	"strings"
 	"syscall"
 	"testing"
-	"time"
 )
 
-func sendCtrlBreak(t *testing.T, pid int) {
+func sendCtrlBreak(pid int) error {
 	d, e := syscall.LoadDLL("kernel32.dll")
 	if e != nil {
-		t.Fatalf("LoadDLL: %v\n", e)
+		return fmt.Errorf("LoadDLL: %v\n", e)
 	}
 	p, e := d.FindProc("GenerateConsoleCtrlEvent")
 	if e != nil {
-		t.Fatalf("FindProc: %v\n", e)
+		return fmt.Errorf("FindProc: %v\n", e)
 	}
 	r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
 	if r == 0 {
-		t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
+		return fmt.Errorf("GenerateConsoleCtrlEvent: %v\n", e)
 	}
+	return nil
 }
 
 func TestCtrlBreak(t *testing.T) {
@@ -35,6 +37,7 @@
 package main
 
 import (
+	"fmt"
 	"log"
 	"os"
 	"os/signal"
@@ -45,6 +48,7 @@
 func main() {
 	c := make(chan os.Signal, 10)
 	signal.Notify(c)
+	fmt.Println("ready")
 	select {
 	case s := <-c:
 		if s != os.Interrupt {
@@ -58,7 +62,7 @@
 	tmp := t.TempDir()
 
 	// write ctrlbreak.go
-	name := filepath.Join(tmp, "ctlbreak")
+	name := filepath.Join(tmp, "ctrlbreak")
 	src := name + ".go"
 	f, err := os.Create(src)
 	if err != nil {
@@ -78,8 +82,11 @@
 	// run it
 	cmd := testenv.Command(t, exe)
 	var buf strings.Builder
-	cmd.Stdout = &buf
 	cmd.Stderr = &buf
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		t.Fatalf("StdoutPipe failed: %v", err)
+	}
 	cmd.SysProcAttr = &syscall.SysProcAttr{
 		CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
 	}
@@ -87,10 +94,22 @@
 	if err != nil {
 		t.Fatalf("Start failed: %v", err)
 	}
+
+	outReader := bufio.NewReader(stdout)
+	errCh := make(chan error, 1)
 	go func() {
-		time.Sleep(1 * time.Second)
-		sendCtrlBreak(t, cmd.Process.Pid)
+		if line, err := outReader.ReadString('\n'); err != nil {
+			errCh <- fmt.Errorf("could not read stdout: %v", err)
+		} else if strings.TrimSpace(line) != "ready" {
+			errCh <- fmt.Errorf("unexpected message: %v", line)
+		} else {
+			errCh <- sendCtrlBreak(cmd.Process.Pid)
+		}
 	}()
+
+	if err := <-errCh; err != nil {
+		t.Fatal(err)
+	}
 	err = cmd.Wait()
 	if err != nil {
 		t.Fatalf("Program exited with error: %v\n%v", err, buf.String())