main_test.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"runtime"
"strings"
"testing"
"time"
)
const interruptHelperEnvironment = "CROC_INTERRUPT_HELPER"
const secondInterruptHelperEnvironment = "CROC_SECOND_INTERRUPT_HELPER"
func TestWaitingSendExitsPromptlyOnInterrupt(t *testing.T) {
if relayAddress := os.Getenv(interruptHelperEnvironment); relayAddress != "" {
os.Args = []string{
"croc", "--relay", relayAddress, "--ignore-stdin", "--disable-clipboard",
"send", "--no-local", "--text", "interrupt-test",
}
main()
return
}
if runtime.GOOS == "windows" {
t.Skip("os.Interrupt cannot be sent to a child process on Windows")
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
ready := make(chan struct{})
relayDone := make(chan error, 1)
go func() {
connection, err := listener.Accept()
if err != nil {
relayDone <- err
return
}
defer connection.Close()
if err = connection.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
relayDone <- err
return
}
if _, err = io.ReadFull(connection, make([]byte, 1)); err != nil {
relayDone <- err
return
}
close(ready)
_, err = io.Copy(io.Discard, connection)
relayDone <- err
}()
command := exec.Command(os.Args[0], "-test.run=^TestWaitingSendExitsPromptlyOnInterrupt$")
command.Dir = t.TempDir()
command.Env = append(os.Environ(), interruptHelperEnvironment+"="+listener.Addr().String(), "CROC_CONFIG_DIR="+t.TempDir(), "CROC_SECRET=interrupt-test-code")
command.Stdout = io.Discard
var output bytes.Buffer
command.Stderr = &output
if err = command.Start(); err != nil {
t.Fatal(err)
}
var waitErr error
waitDone := make(chan struct{})
go func() {
waitErr = command.Wait()
close(waitDone)
}()
stopCommand := func() {
_ = command.Process.Kill()
<-waitDone
}
defer stopCommand()
select {
case <-ready:
case <-waitDone:
t.Fatalf("helper exited before reaching relay: %v; stderr: %s", waitErr, output.String())
case err = <-relayDone:
stopCommand()
t.Fatalf("helper did not start relay handshake: %v; stderr: %s", err, output.String())
case <-time.After(5 * time.Second):
stopCommand()
t.Fatalf("helper send did not become ready; stderr: %s", output.String())
}
started := time.Now()
if err = command.Process.Signal(os.Interrupt); err != nil {
t.Fatal(err)
}
deadline := 500 * time.Millisecond
if raceDetectorEnabled {
deadline = 2 * time.Second
}
select {
case <-waitDone:
elapsed := time.Since(started)
if waitErr != nil {
t.Fatalf("interrupted send exited with %v; stderr: %s", waitErr, output.String())
}
if elapsed >= deadline {
t.Fatalf("interrupted send took %s to exit; stderr: %s", elapsed, output.String())
}
if strings.Contains(strings.ToLower(output.String()), "context canceled") {
t.Fatalf("interrupted send printed a cancellation error: %s", output.String())
}
case <-time.After(deadline):
stopCommand()
t.Fatalf("interrupted send did not exit within %s; stderr: %s", deadline, output.String())
}
select {
case err = <-relayDone:
if err != nil {
t.Fatalf("relay connection did not close cleanly: %v", err)
}
case <-time.After(deadline):
t.Fatal("relay connection remained open after interrupted send")
}
}
func TestSecondInterruptForcesImmediateExit(t *testing.T) {
if os.Getenv(secondInterruptHelperEnvironment) == "1" {
runCLIContext = func(ctx context.Context) error {
fmt.Fprintln(os.Stderr, "second-interrupt-ready")
<-ctx.Done()
fmt.Fprintln(os.Stderr, "first-interrupt-observed")
select {}
}
main()
return
}
if runtime.GOOS == "windows" {
t.Skip("os.Interrupt cannot be sent to a child process on Windows")
}
command := exec.Command(os.Args[0], "-test.run=^TestSecondInterruptForcesImmediateExit$")
command.Env = append(os.Environ(), secondInterruptHelperEnvironment+"=1")
stderr, err := command.StderrPipe()
if err != nil {
t.Fatal(err)
}
if err = command.Start(); err != nil {
t.Fatal(err)
}
defer command.Process.Kill()
ready := make(chan struct{})
firstObserved := make(chan struct{})
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
switch scanner.Text() {
case "second-interrupt-ready":
close(ready)
case "first-interrupt-observed":
close(firstObserved)
}
}
}()
select {
case <-ready:
case <-time.After(2 * time.Second):
t.Fatal("second-interrupt helper did not become ready")
}
if err = command.Process.Signal(os.Interrupt); err != nil {
t.Fatal(err)
}
select {
case <-firstObserved:
case <-time.After(2 * time.Second):
t.Fatal("helper did not observe the first interrupt")
}
// The command goroutine observes cancellation just before main restores
// default signal handling, so allow that scheduling handoff to complete.
time.Sleep(25 * time.Millisecond)
started := time.Now()
if err = command.Process.Signal(os.Interrupt); err != nil {
t.Fatal(err)
}
waitDone := make(chan error, 1)
go func() { waitDone <- command.Wait() }()
deadline := 500 * time.Millisecond
if raceDetectorEnabled {
deadline = 2 * time.Second
}
select {
case err = <-waitDone:
if err == nil {
t.Fatal("second interrupt unexpectedly allowed graceful completion")
}
if elapsed := time.Since(started); elapsed >= deadline {
t.Fatalf("second interrupt took %s to force exit", elapsed)
}
case <-time.After(deadline):
_ = command.Process.Kill()
t.Fatalf("second interrupt did not force exit within %s", deadline)
}
}