github.com/comwrg/go/src@v0.0.0-20220319063731-c238d0440370/runtime/signal_windows_test.go (about) 1 //go:build windows 2 // +build windows 3 4 package runtime_test 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "github.com/comwrg/go/src/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 }