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