-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathmain_test.go
More file actions
111 lines (91 loc) · 3.26 KB
/
Copy pathmain_test.go
File metadata and controls
111 lines (91 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//go:build !windows
/*
* ChatCLI - Command Line Interface for LLM interaction
* Copyright (c) 2024 Edilson Freitas
* License: Apache-2.0
*/
package main
import (
"os"
"os/signal"
"sync/atomic"
"syscall"
"testing"
"time"
"go.uber.org/zap"
)
// raiseSignal delivers sig to the current process. signal.Notify (installed by
// installSignalHandlers) captures it before the default disposition fires.
func raiseSignal(t *testing.T, sig syscall.Signal) {
t.Helper()
if err := syscall.Kill(os.Getpid(), sig); err != nil {
t.Fatalf("kill(self, %v): %v", sig, err)
}
}
// TestInstallSignalHandlers_SIGINTWhileExecuting is the regression test for the
// coder/agent re-entry bug: a Ctrl+C that arrives as a REAL SIGINT while an
// operation is in flight — e.g. at a cooked-mode security confirmation — must
// cancel ONLY the in-flight operation, never the root/session context. A root
// cancel there permanently poisoned every later coder/agent run (born from a
// cancelled context) while chat kept working on a fresh background context, and
// the only recovery was restarting the process.
func TestInstallSignalHandlers_SIGINTWhileExecuting(t *testing.T) {
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
var rootCanceled atomic.Bool
opCanceled := make(chan struct{}, 1)
installSignalHandlers(
func() bool { return true }, // operation in flight
func() { opCanceled <- struct{}{} },
func() { rootCanceled.Store(true) },
zap.NewNop(),
)
raiseSignal(t, syscall.SIGINT)
select {
case <-opCanceled:
case <-time.After(3 * time.Second):
t.Fatal("SIGINT while executing did not cancel the operation")
}
// Give a wrong root-cancel a chance to fire before asserting it did not.
time.Sleep(150 * time.Millisecond)
if rootCanceled.Load() {
t.Fatal("SIGINT while executing cancelled the ROOT context — this is the coder/agent re-entry bug")
}
}
// TestInstallSignalHandlers_SIGINTWhileIdle confirms the other half: when no
// operation is running, Ctrl+C at the prompt is a shutdown request and cancels
// the root context so Start() returns and cleanup runs.
func TestInstallSignalHandlers_SIGINTWhileIdle(t *testing.T) {
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
rootDone := make(chan struct{}, 1)
installSignalHandlers(
func() bool { return false }, // idle at the prompt
func() { t.Error("operation cancel must not run when idle") },
func() { rootDone <- struct{}{} },
zap.NewNop(),
)
raiseSignal(t, syscall.SIGINT)
select {
case <-rootDone:
case <-time.After(3 * time.Second):
t.Fatal("idle SIGINT did not cancel the root context (shutdown regressed)")
}
}
// TestInstallSignalHandlers_SIGTERMAlwaysShutsDown verifies SIGTERM cancels the
// root context even mid-operation — a kill is always a shutdown, never an
// operation interrupt.
func TestInstallSignalHandlers_SIGTERMAlwaysShutsDown(t *testing.T) {
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
rootDone := make(chan struct{}, 1)
installSignalHandlers(
func() bool { return true }, // even while executing
func() { t.Error("SIGTERM must not be treated as an operation cancel") },
func() { rootDone <- struct{}{} },
zap.NewNop(),
)
raiseSignal(t, syscall.SIGTERM)
select {
case <-rootDone:
case <-time.After(3 * time.Second):
t.Fatal("SIGTERM did not cancel the root context")
}
}