Black Lives Matter. Support the Equal Justice Initiative.

Source file src/runtime/signal_windows_test.go

Documentation: runtime

     1  //go:build windows
     2  // +build windows
     3  
     4  package runtime_test
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  )
    19  
    20  func TestVectoredHandlerDontCrashOnLibrary(t *testing.T) {
    21  	if *flagQuick {
    22  		t.Skip("-quick")
    23  	}
    24  	if runtime.GOARCH != "amd64" {
    25  		t.Skip("this test can only run on windows/amd64")
    26  	}
    27  	testenv.MustHaveGoBuild(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.MustHaveExecPath(t, "gcc")
   153  	testprog.Lock()
   154  	defer testprog.Unlock()
   155  	dir := t.TempDir()
   156  
   157  	// build go dll
   158  	dll := filepath.Join(dir, "dummy.dll")
   159  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "-buildmode", "c-shared", "testdata/testwinlibsignal/dummy.go")
   160  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   161  	if err != nil {
   162  		t.Fatalf("failed to build go library: %s\n%s", err, out)
   163  	}
   164  
   165  	// build c program
   166  	exe := filepath.Join(dir, "test.exe")
   167  	cmd = exec.Command("gcc", "-o", exe, "testdata/testwinlibsignal/main.c")
   168  	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
   169  	if err != nil {
   170  		t.Fatalf("failed to build c exe: %s\n%s", err, out)
   171  	}
   172  
   173  	// run test program
   174  	cmd = exec.Command(exe)
   175  	var stderr bytes.Buffer
   176  	cmd.Stderr = &stderr
   177  	outPipe, err := cmd.StdoutPipe()
   178  	if err != nil {
   179  		t.Fatalf("Failed to create stdout pipe: %v", err)
   180  	}
   181  	outReader := bufio.NewReader(outPipe)
   182  
   183  	cmd.SysProcAttr = &syscall.SysProcAttr{
   184  		CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
   185  	}
   186  	if err := cmd.Start(); err != nil {
   187  		t.Fatalf("Start failed: %v", err)
   188  	}
   189  
   190  	errCh := make(chan error, 1)
   191  	go func() {
   192  		if line, err := outReader.ReadString('\n'); err != nil {
   193  			errCh <- fmt.Errorf("could not read stdout: %v", err)
   194  		} else if strings.TrimSpace(line) != "ready" {
   195  			errCh <- fmt.Errorf("unexpected message: %v", line)
   196  		} else {
   197  			errCh <- sendCtrlBreak(cmd.Process.Pid)
   198  		}
   199  	}()
   200  
   201  	if err := <-errCh; err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	if err := cmd.Wait(); err != nil {
   205  		t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
   206  	}
   207  }
   208  

View as plain text