2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
- //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
5
+ //go:build unix
6
6
7
7
package main_test
8
8
9
9
import (
10
+ "bufio"
11
+ "context"
12
+ "internal/testenv"
13
+ "io"
10
14
"os"
15
+ "os/exec"
16
+ "slices"
17
+ "strings"
11
18
"syscall"
12
19
"testing"
13
20
)
@@ -33,3 +40,80 @@ func TestGoBuildUmask(t *testing.T) {
33
40
t .Fatalf ("wrote x with mode=%v, wanted no 0077 bits" , mode )
34
41
}
35
42
}
43
+
44
+ // TestTestInterrupt verifies the fix for issue #60203.
45
+ //
46
+ // If the whole process group for a 'go test' invocation receives
47
+ // SIGINT (as would be sent by pressing ^C on a console),
48
+ // it should return quickly, not deadlock.
49
+ func TestTestInterrupt (t * testing.T ) {
50
+ if testing .Short () {
51
+ t .Skipf ("skipping in short mode: test executes many subprocesses" )
52
+ }
53
+ // Don't run this test in parallel, for the same reason.
54
+
55
+ tg := testgo (t )
56
+ defer tg .cleanup ()
57
+ tg .setenv ("GOROOT" , testGOROOT )
58
+
59
+ ctx , cancel := context .WithCancel (context .Background ())
60
+ cmd := testenv .CommandContext (t , ctx , tg .goTool (), "test" , "std" , "-short" , "-count=1" )
61
+ cmd .Dir = tg .execDir
62
+
63
+ // Override $TMPDIR when running the tests: since we're terminating the tests
64
+ // with a signal they might fail to clean up some temp files, and we don't
65
+ // want that to cause an "unexpected files" failure at the end of the run.
66
+ cmd .Env = append (slices .Clip (tg .env ), tempEnvName ()+ "=" + t .TempDir ())
67
+
68
+ cmd .SysProcAttr = & syscall.SysProcAttr {
69
+ Setpgid : true ,
70
+ }
71
+ cmd .Cancel = func () error {
72
+ pgid := cmd .Process .Pid
73
+ return syscall .Kill (- pgid , syscall .SIGINT )
74
+ }
75
+
76
+ pipe , err := cmd .StdoutPipe ()
77
+ if err != nil {
78
+ t .Fatal (err )
79
+ }
80
+
81
+ t .Logf ("running %v" , cmd )
82
+ if err := cmd .Start (); err != nil {
83
+ t .Fatal (err )
84
+ }
85
+
86
+ stdout := new (strings.Builder )
87
+ r := bufio .NewReader (pipe )
88
+ line , err := r .ReadString ('\n' )
89
+ if err != nil {
90
+ t .Fatal (err )
91
+ }
92
+ stdout .WriteString (line )
93
+
94
+ // The output line for some test was written, so we know things are in progress.
95
+ //
96
+ // Cancel the rest of the run by sending SIGINT to the process group:
97
+ // it should finish up and exit with a nonzero status,
98
+ // not have to be killed with SIGKILL.
99
+ cancel ()
100
+
101
+ io .Copy (stdout , r )
102
+ if stdout .Len () > 0 {
103
+ t .Logf ("stdout:\n %s" , stdout )
104
+ }
105
+ err = cmd .Wait ()
106
+
107
+ ee , _ := err .(* exec.ExitError )
108
+ if ee == nil {
109
+ t .Fatalf ("unexpectedly finished with nonzero status" )
110
+ }
111
+ if len (ee .Stderr ) > 0 {
112
+ t .Logf ("stderr:\n %s" , ee .Stderr )
113
+ }
114
+ if ! ee .Exited () {
115
+ t .Fatalf ("'go test' did not exit after interrupt: %v" , err )
116
+ }
117
+
118
+ t .Logf ("interrupted tests without deadlocking" )
119
+ }
0 commit comments