Source file src/runtime/signal_windows_test.go

     1  //go:build windows
     2  
     3  package runtime_test
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"fmt"
     9  	"internal/testenv"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  )
    18  
    19  func TestVectoredHandlerDontCrashOnLibrary(t *testing.T) {
    20  	if *flagQuick {
    21  		t.Skip("-quick")
    22  	}
    23  	if runtime.GOARCH != "amd64" {
    24  		t.Skip("this test can only run on windows/amd64")
    25  	}
    26  	testenv.MustHaveGoBuild(t)
    27  	testenv.MustHaveCGO(t)
    28  	testenv.MustHaveExecPath(t, "gcc")
    29  	testprog.Lock()
    30  	defer testprog.Unlock()
    31  	dir := t.TempDir()
    32  
    33  	// build go dll
    34  	dll := filepath.Join(dir, "testwinlib.dll")
    35  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "-buildmode", "c-shared", "testdata/testwinlib/main.go")
    36  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    37  	if err != nil {
    38  		t.Fatalf("failed to build go library: %s\n%s", err, out)
    39  	}
    40  
    41  	// build c program
    42  	exe := filepath.Join(dir, "test.exe")
    43  	cmd = exec.Command("gcc", "-L"+dir, "-I"+dir, "-ltestwinlib", "-o", exe, "testdata/testwinlib/main.c")
    44  	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
    45  	if err != nil {
    46  		t.Fatalf("failed to build c exe: %s\n%s", err, out)
    47  	}
    48  
    49  	// run test program
    50  	cmd = exec.Command(exe)
    51  	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
    52  	if err != nil {
    53  		t.Fatalf("failure while running executable: %s\n%s", err, out)
    54  	}
    55  	expectedOutput := "exceptionCount: 1\ncontinueCount: 1\n"
    56  	// cleaning output
    57  	cleanedOut := strings.ReplaceAll(string(out), "\r\n", "\n")
    58  	if cleanedOut != expectedOutput {
    59  		t.Errorf("expected output %q, got %q", expectedOutput, cleanedOut)
    60  	}
    61  }
    62  
    63  func sendCtrlBreak(pid int) error {
    64  	kernel32, err := syscall.LoadDLL("kernel32.dll")
    65  	if err != nil {
    66  		return fmt.Errorf("LoadDLL: %v\n", err)
    67  	}
    68  	generateEvent, err := kernel32.FindProc("GenerateConsoleCtrlEvent")
    69  	if err != nil {
    70  		return fmt.Errorf("FindProc: %v\n", err)
    71  	}
    72  	result, _, err := generateEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
    73  	if result == 0 {
    74  		return fmt.Errorf("GenerateConsoleCtrlEvent: %v\n", err)
    75  	}
    76  	return nil
    77  }
    78  
    79  // TestCtrlHandler tests that Go can gracefully handle closing the console window.
    80  // See https://golang.org/issues/41884.
    81  func TestCtrlHandler(t *testing.T) {
    82  	testenv.MustHaveGoBuild(t)
    83  	t.Parallel()
    84  
    85  	// build go program
    86  	exe := filepath.Join(t.TempDir(), "test.exe")
    87  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "testdata/testwinsignal/main.go")
    88  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    89  	if err != nil {
    90  		t.Fatalf("failed to build go exe: %v\n%s", err, out)
    91  	}
    92  
    93  	// run test program
    94  	cmd = exec.Command(exe)
    95  	var stderr bytes.Buffer
    96  	cmd.Stderr = &stderr
    97  	outPipe, err := cmd.StdoutPipe()
    98  	if err != nil {
    99  		t.Fatalf("Failed to create stdout pipe: %v", err)
   100  	}
   101  	outReader := bufio.NewReader(outPipe)
   102  
   103  	// in a new command window
   104  	const _CREATE_NEW_CONSOLE = 0x00000010
   105  	cmd.SysProcAttr = &syscall.SysProcAttr{
   106  		CreationFlags: _CREATE_NEW_CONSOLE,
   107  		HideWindow:    true,
   108  	}
   109  	if err := cmd.Start(); err != nil {
   110  		t.Fatalf("Start failed: %v", err)
   111  	}
   112  	defer func() {
   113  		cmd.Process.Kill()
   114  		cmd.Wait()
   115  	}()
   116  
   117  	// wait for child to be ready to receive signals
   118  	if line, err := outReader.ReadString('\n'); err != nil {
   119  		t.Fatalf("could not read stdout: %v", err)
   120  	} else if strings.TrimSpace(line) != "ready" {
   121  		t.Fatalf("unexpected message: %s", line)
   122  	}
   123  
   124  	// gracefully kill pid, this closes the command window
   125  	if err := exec.Command("taskkill.exe", "/pid", strconv.Itoa(cmd.Process.Pid)).Run(); err != nil {
   126  		t.Fatalf("failed to kill: %v", err)
   127  	}
   128  
   129  	// check child received, handled SIGTERM
   130  	if line, err := outReader.ReadString('\n'); err != nil {
   131  		t.Fatalf("could not read stdout: %v", err)
   132  	} else if expected, got := syscall.SIGTERM.String(), strings.TrimSpace(line); expected != got {
   133  		t.Fatalf("Expected '%s' got: %s", expected, got)
   134  	}
   135  
   136  	// check child exited gracefully, did not timeout
   137  	if err := cmd.Wait(); err != nil {
   138  		t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
   139  	}
   140  }
   141  
   142  // TestLibraryCtrlHandler tests that Go DLL allows calling program to handle console control events.
   143  // See https://golang.org/issues/35965.
   144  func TestLibraryCtrlHandler(t *testing.T) {
   145  	if *flagQuick {
   146  		t.Skip("-quick")
   147  	}
   148  	if runtime.GOARCH != "amd64" {
   149  		t.Skip("this test can only run on windows/amd64")
   150  	}
   151  	testenv.MustHaveGoBuild(t)
   152  	testenv.MustHaveCGO(t)
   153  	testenv.MustHaveExecPath(t, "gcc")
   154  	testprog.Lock()
   155  	defer testprog.Unlock()
   156  	dir := t.TempDir()
   157  
   158  	// build go dll
   159  	dll := filepath.Join(dir, "dummy.dll")
   160  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "-buildmode", "c-shared", "testdata/testwinlibsignal/dummy.go")
   161  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   162  	if err != nil {
   163  		t.Fatalf("failed to build go library: %s\n%s", err, out)
   164  	}
   165  
   166  	// build c program
   167  	exe := filepath.Join(dir, "test.exe")
   168  	cmd = exec.Command("gcc", "-o", exe, "testdata/testwinlibsignal/main.c")
   169  	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
   170  	if err != nil {
   171  		t.Fatalf("failed to build c exe: %s\n%s", err, out)
   172  	}
   173  
   174  	// run test program
   175  	cmd = exec.Command(exe)
   176  	var stderr bytes.Buffer
   177  	cmd.Stderr = &stderr
   178  	outPipe, err := cmd.StdoutPipe()
   179  	if err != nil {
   180  		t.Fatalf("Failed to create stdout pipe: %v", err)
   181  	}
   182  	outReader := bufio.NewReader(outPipe)
   183  
   184  	cmd.SysProcAttr = &syscall.SysProcAttr{
   185  		CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
   186  	}
   187  	if err := cmd.Start(); err != nil {
   188  		t.Fatalf("Start failed: %v", err)
   189  	}
   190  
   191  	errCh := make(chan error, 1)
   192  	go func() {
   193  		if line, err := outReader.ReadString('\n'); err != nil {
   194  			errCh <- fmt.Errorf("could not read stdout: %v", err)
   195  		} else if strings.TrimSpace(line) != "ready" {
   196  			errCh <- fmt.Errorf("unexpected message: %v", line)
   197  		} else {
   198  			errCh <- sendCtrlBreak(cmd.Process.Pid)
   199  		}
   200  	}()
   201  
   202  	if err := <-errCh; err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	if err := cmd.Wait(); err != nil {
   206  		t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
   207  	}
   208  }
   209  

View as plain text