"os/exec"
"path/filepath"
"runtime"
- "strconv"
"strings"
"syscall"
"testing"
// 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
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.
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)
}