github.com/rafaeltorres324/go/src@v0.0.0-20210519164414-9fdf653a9838/os/signal/signal_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package signal 8 9 import ( 10 "bytes" 11 "context" 12 "flag" 13 "fmt" 14 "internal/testenv" 15 "os" 16 "os/exec" 17 "runtime" 18 "strconv" 19 "sync" 20 "syscall" 21 "testing" 22 "time" 23 ) 24 25 // settleTime is an upper bound on how long we expect signals to take to be 26 // delivered. Lower values make the test faster, but also flakier — especially 27 // on heavily loaded systems. 28 // 29 // The current value is set based on flakes observed in the Go builders. 30 var settleTime = 100 * time.Millisecond 31 32 func init() { 33 if testenv.Builder() == "solaris-amd64-oraclerel" { 34 // The solaris-amd64-oraclerel builder has been observed to time out in 35 // TestNohup even with a 250ms settle time. 36 // 37 // Use a much longer settle time on that builder to try to suss out whether 38 // the test is flaky due to builder slowness (which may mean we need a 39 // longer GO_TEST_TIMEOUT_SCALE) or due to a dropped signal (which may 40 // instead need a test-skip and upstream bug filed against the Solaris 41 // kernel). 42 // 43 // This constant is chosen so as to make the test as generous as possible 44 // while still reliably completing within 3 minutes in non-short mode. 45 // 46 // See https://golang.org/issue/33174. 47 settleTime = 11 * time.Second 48 } else if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { 49 if scale, err := strconv.Atoi(s); err == nil { 50 settleTime *= time.Duration(scale) 51 } 52 } 53 } 54 55 func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) { 56 t.Helper() 57 waitSig1(t, c, sig, false) 58 } 59 func waitSigAll(t *testing.T, c <-chan os.Signal, sig os.Signal) { 60 t.Helper() 61 waitSig1(t, c, sig, true) 62 } 63 64 func waitSig1(t *testing.T, c <-chan os.Signal, sig os.Signal, all bool) { 65 t.Helper() 66 67 // Sleep multiple times to give the kernel more tries to 68 // deliver the signal. 69 start := time.Now() 70 timer := time.NewTimer(settleTime / 10) 71 defer timer.Stop() 72 // If the caller notified for all signals on c, filter out SIGURG, 73 // which is used for runtime preemption and can come at unpredictable times. 74 // General user code should filter out all unexpected signals instead of just 75 // SIGURG, but since os/signal is tightly coupled to the runtime it seems 76 // appropriate to be stricter here. 77 for time.Since(start) < settleTime { 78 select { 79 case s := <-c: 80 if s == sig { 81 return 82 } 83 if !all || s != syscall.SIGURG { 84 t.Fatalf("signal was %v, want %v", s, sig) 85 } 86 case <-timer.C: 87 timer.Reset(settleTime / 10) 88 } 89 } 90 t.Fatalf("timeout after %v waiting for %v", settleTime, sig) 91 } 92 93 // quiesce waits until we can be reasonably confident that all pending signals 94 // have been delivered by the OS. 95 func quiesce() { 96 // The kernel will deliver a signal as a thread returns 97 // from a syscall. If the only active thread is sleeping, 98 // and the system is busy, the kernel may not get around 99 // to waking up a thread to catch the signal. 100 // We try splitting up the sleep to give the kernel 101 // many chances to deliver the signal. 102 start := time.Now() 103 for time.Since(start) < settleTime { 104 time.Sleep(settleTime / 10) 105 } 106 } 107 108 // Test that basic signal handling works. 109 func TestSignal(t *testing.T) { 110 // Ask for SIGHUP 111 c := make(chan os.Signal, 1) 112 Notify(c, syscall.SIGHUP) 113 defer Stop(c) 114 115 // Send this process a SIGHUP 116 t.Logf("sighup...") 117 syscall.Kill(syscall.Getpid(), syscall.SIGHUP) 118 waitSig(t, c, syscall.SIGHUP) 119 120 // Ask for everything we can get. The buffer size has to be 121 // more than 1, since the runtime might send SIGURG signals. 122 // Using 10 is arbitrary. 123 c1 := make(chan os.Signal, 10) 124 Notify(c1) 125 126 // Send this process a SIGWINCH 127 t.Logf("sigwinch...") 128 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) 129 waitSigAll(t, c1, syscall.SIGWINCH) 130 131 // Send two more SIGHUPs, to make sure that 132 // they get delivered on c1 and that not reading 133 // from c does not block everything. 134 t.Logf("sighup...") 135 syscall.Kill(syscall.Getpid(), syscall.SIGHUP) 136 waitSigAll(t, c1, syscall.SIGHUP) 137 t.Logf("sighup...") 138 syscall.Kill(syscall.Getpid(), syscall.SIGHUP) 139 waitSigAll(t, c1, syscall.SIGHUP) 140 141 // The first SIGHUP should be waiting for us on c. 142 waitSig(t, c, syscall.SIGHUP) 143 } 144 145 func TestStress(t *testing.T) { 146 dur := 3 * time.Second 147 if testing.Short() { 148 dur = 100 * time.Millisecond 149 } 150 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) 151 152 sig := make(chan os.Signal, 1) 153 Notify(sig, syscall.SIGUSR1) 154 155 go func() { 156 stop := time.After(dur) 157 for { 158 select { 159 case <-stop: 160 // Allow enough time for all signals to be delivered before we stop 161 // listening for them. 162 quiesce() 163 Stop(sig) 164 // According to its documentation, “[w]hen Stop returns, it in 165 // guaranteed that c will receive no more signals.” So we can safely 166 // close sig here: if there is a send-after-close race here, that is a 167 // bug in Stop and we would like to detect it. 168 close(sig) 169 return 170 171 default: 172 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 173 runtime.Gosched() 174 } 175 } 176 }() 177 178 for range sig { 179 // Receive signals until the sender closes sig. 180 } 181 } 182 183 func testCancel(t *testing.T, ignore bool) { 184 // Ask to be notified on c1 when a SIGWINCH is received. 185 c1 := make(chan os.Signal, 1) 186 Notify(c1, syscall.SIGWINCH) 187 defer Stop(c1) 188 189 // Ask to be notified on c2 when a SIGHUP is received. 190 c2 := make(chan os.Signal, 1) 191 Notify(c2, syscall.SIGHUP) 192 defer Stop(c2) 193 194 // Send this process a SIGWINCH and wait for notification on c1. 195 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) 196 waitSig(t, c1, syscall.SIGWINCH) 197 198 // Send this process a SIGHUP and wait for notification on c2. 199 syscall.Kill(syscall.Getpid(), syscall.SIGHUP) 200 waitSig(t, c2, syscall.SIGHUP) 201 202 // Ignore, or reset the signal handlers for, SIGWINCH and SIGHUP. 203 // Either way, this should undo both calls to Notify above. 204 if ignore { 205 Ignore(syscall.SIGWINCH, syscall.SIGHUP) 206 // Don't bother deferring a call to Reset: it is documented to undo Notify, 207 // but its documentation says nothing about Ignore, and (as of the time of 208 // writing) it empirically does not undo an Ignore. 209 } else { 210 Reset(syscall.SIGWINCH, syscall.SIGHUP) 211 } 212 213 // Send this process a SIGWINCH. It should be ignored. 214 syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) 215 216 // If ignoring, Send this process a SIGHUP. It should be ignored. 217 if ignore { 218 syscall.Kill(syscall.Getpid(), syscall.SIGHUP) 219 } 220 221 quiesce() 222 223 select { 224 case s := <-c1: 225 t.Errorf("unexpected signal %v", s) 226 default: 227 // nothing to read - good 228 } 229 230 select { 231 case s := <-c2: 232 t.Errorf("unexpected signal %v", s) 233 default: 234 // nothing to read - good 235 } 236 237 // One or both of the signals may have been blocked for this process 238 // by the calling process. 239 // Discard any queued signals now to avoid interfering with other tests. 240 Notify(c1, syscall.SIGWINCH) 241 Notify(c2, syscall.SIGHUP) 242 quiesce() 243 } 244 245 // Test that Reset cancels registration for listed signals on all channels. 246 func TestReset(t *testing.T) { 247 testCancel(t, false) 248 } 249 250 // Test that Ignore cancels registration for listed signals on all channels. 251 func TestIgnore(t *testing.T) { 252 testCancel(t, true) 253 } 254 255 // Test that Ignored correctly detects changes to the ignored status of a signal. 256 func TestIgnored(t *testing.T) { 257 // Ask to be notified on SIGWINCH. 258 c := make(chan os.Signal, 1) 259 Notify(c, syscall.SIGWINCH) 260 261 // If we're being notified, then the signal should not be ignored. 262 if Ignored(syscall.SIGWINCH) { 263 t.Errorf("expected SIGWINCH to not be ignored.") 264 } 265 Stop(c) 266 Ignore(syscall.SIGWINCH) 267 268 // We're no longer paying attention to this signal. 269 if !Ignored(syscall.SIGWINCH) { 270 t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.") 271 } 272 273 Reset() 274 } 275 276 var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.") 277 278 // Test that Ignored(SIGHUP) correctly detects whether it is being run under nohup. 279 func TestDetectNohup(t *testing.T) { 280 if *checkSighupIgnored { 281 if !Ignored(syscall.SIGHUP) { 282 t.Fatal("SIGHUP is not ignored.") 283 } else { 284 t.Log("SIGHUP is ignored.") 285 } 286 } else { 287 defer Reset() 288 // Ugly: ask for SIGHUP so that child will not have no-hup set 289 // even if test is running under nohup environment. 290 // We have no intention of reading from c. 291 c := make(chan os.Signal, 1) 292 Notify(c, syscall.SIGHUP) 293 if out, err := exec.Command(os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil { 294 t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out) 295 } 296 Stop(c) 297 // Again, this time with nohup, assuming we can find it. 298 _, err := os.Stat("/usr/bin/nohup") 299 if err != nil { 300 t.Skip("cannot find nohup; skipping second half of test") 301 } 302 Ignore(syscall.SIGHUP) 303 os.Remove("nohup.out") 304 out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput() 305 306 data, _ := os.ReadFile("nohup.out") 307 os.Remove("nohup.out") 308 if err != nil { 309 t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data) 310 } 311 } 312 } 313 314 var ( 315 sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop") 316 dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP") 317 ) 318 319 // Test that Stop cancels the channel's registrations. 320 func TestStop(t *testing.T) { 321 sigs := []syscall.Signal{ 322 syscall.SIGWINCH, 323 syscall.SIGHUP, 324 syscall.SIGUSR1, 325 } 326 327 for _, sig := range sigs { 328 sig := sig 329 t.Run(fmt.Sprint(sig), func(t *testing.T) { 330 // When calling Notify with a specific signal, 331 // independent signals should not interfere with each other, 332 // and we end up needing to wait for signals to quiesce a lot. 333 // Test the three different signals concurrently. 334 t.Parallel() 335 336 // If the signal is not ignored, send the signal before registering a 337 // channel to verify the behavior of the default Go handler. 338 // If it's SIGWINCH or SIGUSR1 we should not see it. 339 // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do. 340 mayHaveBlockedSignal := false 341 if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) { 342 syscall.Kill(syscall.Getpid(), sig) 343 quiesce() 344 345 // We don't know whether sig is blocked for this process; see 346 // https://golang.org/issue/38165. Assume that it could be. 347 mayHaveBlockedSignal = true 348 } 349 350 // Ask for signal 351 c := make(chan os.Signal, 1) 352 Notify(c, sig) 353 354 // Send this process the signal again. 355 syscall.Kill(syscall.Getpid(), sig) 356 waitSig(t, c, sig) 357 358 if mayHaveBlockedSignal { 359 // We may have received a queued initial signal in addition to the one 360 // that we sent after Notify. If so, waitSig may have observed that 361 // initial signal instead of the second one, and we may need to wait for 362 // the second signal to clear. Do that now. 363 quiesce() 364 select { 365 case <-c: 366 default: 367 } 368 } 369 370 // Stop watching for the signal and send it again. 371 // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do. 372 Stop(c) 373 if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 { 374 syscall.Kill(syscall.Getpid(), sig) 375 quiesce() 376 377 select { 378 case s := <-c: 379 t.Errorf("unexpected signal %v", s) 380 default: 381 // nothing to read - good 382 } 383 384 // If we're going to receive a signal, it has almost certainly been 385 // received by now. However, it may have been blocked for this process — 386 // we don't know. Explicitly unblock it and wait for it to clear now. 387 Notify(c, sig) 388 quiesce() 389 Stop(c) 390 } 391 }) 392 } 393 } 394 395 // Test that when run under nohup, an uncaught SIGHUP does not kill the program. 396 func TestNohup(t *testing.T) { 397 // Ugly: ask for SIGHUP so that child will not have no-hup set 398 // even if test is running under nohup environment. 399 // We have no intention of reading from c. 400 c := make(chan os.Signal, 1) 401 Notify(c, syscall.SIGHUP) 402 403 // When run without nohup, the test should crash on an uncaught SIGHUP. 404 // When run under nohup, the test should ignore uncaught SIGHUPs, 405 // because the runtime is not supposed to be listening for them. 406 // Either way, TestStop should still be able to catch them when it wants them 407 // and then when it stops wanting them, the original behavior should resume. 408 // 409 // send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs. 410 // send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs. 411 // 412 // Both should fail without nohup and succeed with nohup. 413 414 var subTimeout time.Duration 415 416 var wg sync.WaitGroup 417 wg.Add(2) 418 if deadline, ok := t.Deadline(); ok { 419 subTimeout = time.Until(deadline) 420 subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output. 421 } 422 for i := 1; i <= 2; i++ { 423 i := i 424 go t.Run(fmt.Sprintf("uncaught-%d", i), func(t *testing.T) { 425 defer wg.Done() 426 427 args := []string{ 428 "-test.v", 429 "-test.run=TestStop", 430 "-send_uncaught_sighup=" + strconv.Itoa(i), 431 "-die_from_sighup", 432 } 433 if subTimeout != 0 { 434 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) 435 } 436 out, err := exec.Command(os.Args[0], args...).CombinedOutput() 437 438 if err == nil { 439 t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out) 440 } else { 441 t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out) 442 } 443 }) 444 } 445 wg.Wait() 446 447 Stop(c) 448 449 // Skip the nohup test below when running in tmux on darwin, since nohup 450 // doesn't work correctly there. See issue #5135. 451 if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" { 452 t.Skip("Skipping nohup test due to running in tmux on darwin") 453 } 454 455 // Again, this time with nohup, assuming we can find it. 456 _, err := exec.LookPath("nohup") 457 if err != nil { 458 t.Skip("cannot find nohup; skipping second half of test") 459 } 460 461 wg.Add(2) 462 if deadline, ok := t.Deadline(); ok { 463 subTimeout = time.Until(deadline) 464 subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output. 465 } 466 for i := 1; i <= 2; i++ { 467 i := i 468 go t.Run(fmt.Sprintf("nohup-%d", i), func(t *testing.T) { 469 defer wg.Done() 470 471 // POSIX specifies that nohup writes to a file named nohup.out if standard 472 // output is a terminal. However, for an exec.Command, standard output is 473 // not a terminal — so we don't need to read or remove that file (and, 474 // indeed, cannot even create it if the current user is unable to write to 475 // GOROOT/src, such as when GOROOT is installed and owned by root). 476 477 args := []string{ 478 os.Args[0], 479 "-test.v", 480 "-test.run=TestStop", 481 "-send_uncaught_sighup=" + strconv.Itoa(i), 482 } 483 if subTimeout != 0 { 484 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) 485 } 486 out, err := exec.Command("nohup", args...).CombinedOutput() 487 488 if err != nil { 489 t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out) 490 } else { 491 t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out) 492 } 493 }) 494 } 495 wg.Wait() 496 } 497 498 // Test that SIGCONT works (issue 8953). 499 func TestSIGCONT(t *testing.T) { 500 c := make(chan os.Signal, 1) 501 Notify(c, syscall.SIGCONT) 502 defer Stop(c) 503 syscall.Kill(syscall.Getpid(), syscall.SIGCONT) 504 waitSig(t, c, syscall.SIGCONT) 505 } 506 507 // Test race between stopping and receiving a signal (issue 14571). 508 func TestAtomicStop(t *testing.T) { 509 if os.Getenv("GO_TEST_ATOMIC_STOP") != "" { 510 atomicStopTestProgram(t) 511 t.Fatal("atomicStopTestProgram returned") 512 } 513 514 testenv.MustHaveExec(t) 515 516 // Call Notify for SIGINT before starting the child process. 517 // That ensures that SIGINT is not ignored for the child. 518 // This is necessary because if SIGINT is ignored when a 519 // Go program starts, then it remains ignored, and closing 520 // the last notification channel for SIGINT will switch it 521 // back to being ignored. In that case the assumption of 522 // atomicStopTestProgram, that it will either die from SIGINT 523 // or have it be reported, breaks down, as there is a third 524 // option: SIGINT might be ignored. 525 cs := make(chan os.Signal, 1) 526 Notify(cs, syscall.SIGINT) 527 defer Stop(cs) 528 529 const execs = 10 530 for i := 0; i < execs; i++ { 531 timeout := "0" 532 if deadline, ok := t.Deadline(); ok { 533 timeout = time.Until(deadline).String() 534 } 535 cmd := exec.Command(os.Args[0], "-test.run=TestAtomicStop", "-test.timeout="+timeout) 536 cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1") 537 out, err := cmd.CombinedOutput() 538 if err == nil { 539 if len(out) > 0 { 540 t.Logf("iteration %d: output %s", i, out) 541 } 542 } else { 543 t.Logf("iteration %d: exit status %q: output: %s", i, err, out) 544 } 545 546 lost := bytes.Contains(out, []byte("lost signal")) 547 if lost { 548 t.Errorf("iteration %d: lost signal", i) 549 } 550 551 // The program should either die due to SIGINT, 552 // or exit with success without printing "lost signal". 553 if err == nil { 554 if len(out) > 0 && !lost { 555 t.Errorf("iteration %d: unexpected output", i) 556 } 557 } else { 558 if ee, ok := err.(*exec.ExitError); !ok { 559 t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err) 560 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 561 t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys()) 562 } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT { 563 t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee) 564 } 565 } 566 } 567 } 568 569 // atomicStopTestProgram is run in a subprocess by TestAtomicStop. 570 // It tries to trigger a signal delivery race. This function should 571 // either catch a signal or die from it. 572 func atomicStopTestProgram(t *testing.T) { 573 // This test won't work if SIGINT is ignored here. 574 if Ignored(syscall.SIGINT) { 575 fmt.Println("SIGINT is ignored") 576 os.Exit(1) 577 } 578 579 const tries = 10 580 581 timeout := 2 * time.Second 582 if deadline, ok := t.Deadline(); ok { 583 // Give each try an equal slice of the deadline, with one slice to spare for 584 // cleanup. 585 timeout = time.Until(deadline) / (tries + 1) 586 } 587 588 pid := syscall.Getpid() 589 printed := false 590 for i := 0; i < tries; i++ { 591 cs := make(chan os.Signal, 1) 592 Notify(cs, syscall.SIGINT) 593 594 var wg sync.WaitGroup 595 wg.Add(1) 596 go func() { 597 defer wg.Done() 598 Stop(cs) 599 }() 600 601 syscall.Kill(pid, syscall.SIGINT) 602 603 // At this point we should either die from SIGINT or 604 // get a notification on cs. If neither happens, we 605 // dropped the signal. It is given 2 seconds to 606 // deliver, as needed for gccgo on some loaded test systems. 607 608 select { 609 case <-cs: 610 case <-time.After(timeout): 611 if !printed { 612 fmt.Print("lost signal on tries:") 613 printed = true 614 } 615 fmt.Printf(" %d", i) 616 } 617 618 wg.Wait() 619 } 620 if printed { 621 fmt.Print("\n") 622 } 623 624 os.Exit(0) 625 } 626 627 func TestTime(t *testing.T) { 628 // Test that signal works fine when we are in a call to get time, 629 // which on some platforms is using VDSO. See issue #34391. 630 dur := 3 * time.Second 631 if testing.Short() { 632 dur = 100 * time.Millisecond 633 } 634 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) 635 636 sig := make(chan os.Signal, 1) 637 Notify(sig, syscall.SIGUSR1) 638 639 stop := make(chan struct{}) 640 go func() { 641 for { 642 select { 643 case <-stop: 644 // Allow enough time for all signals to be delivered before we stop 645 // listening for them. 646 quiesce() 647 Stop(sig) 648 // According to its documentation, “[w]hen Stop returns, it in 649 // guaranteed that c will receive no more signals.” So we can safely 650 // close sig here: if there is a send-after-close race, that is a bug in 651 // Stop and we would like to detect it. 652 close(sig) 653 return 654 655 default: 656 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 657 runtime.Gosched() 658 } 659 } 660 }() 661 662 done := make(chan struct{}) 663 go func() { 664 for range sig { 665 // Receive signals until the sender closes sig. 666 } 667 close(done) 668 }() 669 670 t0 := time.Now() 671 for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() { 672 } // hammering on getting time 673 674 close(stop) 675 <-done 676 } 677 678 var ( 679 checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.") 680 ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received") 681 ) 682 683 func TestNotifyContextNotifications(t *testing.T) { 684 if *checkNotifyContext { 685 ctx, _ := NotifyContext(context.Background(), syscall.SIGINT) 686 // We want to make sure not to be calling Stop() internally on NotifyContext() when processing a received signal. 687 // Being able to wait for a number of received system signals allows us to do so. 688 var wg sync.WaitGroup 689 n := *ctxNotifyTimes 690 wg.Add(n) 691 for i := 0; i < n; i++ { 692 go func() { 693 syscall.Kill(syscall.Getpid(), syscall.SIGINT) 694 wg.Done() 695 }() 696 } 697 wg.Wait() 698 <-ctx.Done() 699 fmt.Print("received SIGINT") 700 // Sleep to give time to simultaneous signals to reach the process. 701 // These signals must be ignored given stop() is not called on this code. 702 // We want to guarantee a SIGINT doesn't cause a premature termination of the program. 703 time.Sleep(settleTime) 704 return 705 } 706 707 t.Parallel() 708 testCases := []struct { 709 name string 710 n int // number of times a SIGINT should be notified. 711 }{ 712 {"once", 1}, 713 {"multiple", 10}, 714 } 715 for _, tc := range testCases { 716 t.Run(tc.name, func(t *testing.T) { 717 var subTimeout time.Duration 718 if deadline, ok := t.Deadline(); ok { 719 subTimeout := time.Until(deadline) 720 subTimeout -= subTimeout / 10 // Leave 10% headroom for cleaning up subprocess. 721 } 722 723 args := []string{ 724 "-test.v", 725 "-test.run=TestNotifyContextNotifications$", 726 "-check_notify_ctx", 727 fmt.Sprintf("-ctx_notify_times=%d", tc.n), 728 } 729 if subTimeout != 0 { 730 args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout)) 731 } 732 out, err := exec.Command(os.Args[0], args...).CombinedOutput() 733 if err != nil { 734 t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out) 735 } 736 if want := []byte("received SIGINT"); !bytes.Contains(out, want) { 737 t.Errorf("got %q, wanted %q", out, want) 738 } 739 }) 740 } 741 } 742 743 func TestNotifyContextStop(t *testing.T) { 744 Ignore(syscall.SIGHUP) 745 if !Ignored(syscall.SIGHUP) { 746 t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.") 747 } 748 749 parent, cancelParent := context.WithCancel(context.Background()) 750 defer cancelParent() 751 c, stop := NotifyContext(parent, syscall.SIGHUP) 752 defer stop() 753 754 // If we're being notified, then the signal should not be ignored. 755 if Ignored(syscall.SIGHUP) { 756 t.Errorf("expected SIGHUP to not be ignored.") 757 } 758 759 if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got { 760 t.Errorf("c.String() = %q, wanted %q", got, want) 761 } 762 763 stop() 764 select { 765 case <-c.Done(): 766 if got := c.Err(); got != context.Canceled { 767 t.Errorf("c.Err() = %q, want %q", got, context.Canceled) 768 } 769 case <-time.After(time.Second): 770 t.Errorf("timed out waiting for context to be done after calling stop") 771 } 772 } 773 774 func TestNotifyContextCancelParent(t *testing.T) { 775 parent, cancelParent := context.WithCancel(context.Background()) 776 defer cancelParent() 777 c, stop := NotifyContext(parent, syscall.SIGINT) 778 defer stop() 779 780 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got { 781 t.Errorf("c.String() = %q, want %q", got, want) 782 } 783 784 cancelParent() 785 select { 786 case <-c.Done(): 787 if got := c.Err(); got != context.Canceled { 788 t.Errorf("c.Err() = %q, want %q", got, context.Canceled) 789 } 790 case <-time.After(time.Second): 791 t.Errorf("timed out waiting for parent context to be canceled") 792 } 793 } 794 795 func TestNotifyContextPrematureCancelParent(t *testing.T) { 796 parent, cancelParent := context.WithCancel(context.Background()) 797 defer cancelParent() 798 799 cancelParent() // Prematurely cancel context before calling NotifyContext. 800 c, stop := NotifyContext(parent, syscall.SIGINT) 801 defer stop() 802 803 if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got { 804 t.Errorf("c.String() = %q, want %q", got, want) 805 } 806 807 select { 808 case <-c.Done(): 809 if got := c.Err(); got != context.Canceled { 810 t.Errorf("c.Err() = %q, want %q", got, context.Canceled) 811 } 812 case <-time.After(time.Second): 813 t.Errorf("timed out waiting for parent context to be canceled") 814 } 815 } 816 817 func TestNotifyContextSimultaneousStop(t *testing.T) { 818 c, stop := NotifyContext(context.Background(), syscall.SIGINT) 819 defer stop() 820 821 if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got { 822 t.Errorf("c.String() = %q, want %q", got, want) 823 } 824 825 var wg sync.WaitGroup 826 n := 10 827 wg.Add(n) 828 for i := 0; i < n; i++ { 829 go func() { 830 stop() 831 wg.Done() 832 }() 833 } 834 wg.Wait() 835 select { 836 case <-c.Done(): 837 if got := c.Err(); got != context.Canceled { 838 t.Errorf("c.Err() = %q, want %q", got, context.Canceled) 839 } 840 case <-time.After(time.Second): 841 t.Errorf("expected context to be canceled") 842 } 843 } 844 845 func TestNotifyContextStringer(t *testing.T) { 846 parent, cancelParent := context.WithCancel(context.Background()) 847 defer cancelParent() 848 c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 849 defer stop() 850 851 want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])` 852 if got := fmt.Sprint(c); got != want { 853 t.Errorf("c.String() = %q, want %q", got, want) 854 } 855 }