-
Notifications
You must be signed in to change notification settings - Fork 1
/
killer_subscriber_test.go
137 lines (125 loc) · 4.69 KB
/
killer_subscriber_test.go
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// the test is in the same package as the "usable" code in order to be able to reinitialize `signal.killerSubscriberFunc`
// at the end of each test
package signal
import (
"os"
"os/signal"
"syscall"
"testing"
"time"
"unsafe"
"bou.ke/monkey"
"github.com/stretchr/testify/assert"
)
func TestKillerSubscribe_Interrupt(t *testing.T) {
var killerSignalChan chan<- os.Signal
var killerSubscribedSignals = []os.Signal{os.Kill, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGSTOP}
var exitCalled = false
monkey.Patch(os.Exit, func(code int) {
assert.Equal(t, 130, code)
exitCalled = true
})
monkey.Patch(signal.Notify, func(c chan<- os.Signal, signals ...os.Signal) {
// get subscriber internal chan in order to simulate signal receive
killerSignalChan = c
for _, s := range killerSubscribedSignals {
assert.Contains(t, signals, s)
}
})
defer func() {
monkey.UnpatchAll()
// reinitialize global var in order not to mess with other tests
killerSubscriberFunc = nil
}()
// subscribe to os signals related to an interruption signal
KillerSubscriber()
// simulate signal receive
killerSignalChan <- os.Interrupt
// wait for goroutine callback func fulfilment (@see subscriber.go `go callback(killerSubscribedSignals)`)
time.Sleep(1 * time.Millisecond)
assert.False(t, exitCalled, "exit func should not be called when first interrupt raised.")
killerSignalChan <- os.Interrupt
time.Sleep(1 * time.Millisecond)
assert.True(t, exitCalled, "exit func should be called.")
}
func TestSubscribeWithKiller_Interrupt(t *testing.T) {
var killerSignalChan chan<- os.Signal
var killerSubscribedSignals = []os.Signal{os.Kill, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGSTOP}
var exitCalled = false
var signalChan chan<- os.Signal
var subscribedSignal = os.Interrupt
var callbackCalledTimes = 0
var realSignal = os.Interrupt
monkey.Patch(os.Exit, func(code int) {
assert.Equal(t, 130, code)
exitCalled = true
})
monkey.Patch(signal.Notify, func(c chan<- os.Signal, signals ...os.Signal) {
if len(signals) == 1 {
signalChan = c
assert.Equal(t, subscribedSignal, signals[0])
return
}
// get subscriber internal chan in order to simulate signal receive
killerSignalChan = c
for _, s := range killerSubscribedSignals {
assert.Contains(t, signals, s)
}
})
defer func() {
monkey.UnpatchAll()
// reinitialize global var in order not to mess with other tests
killerSubscriberFunc = nil
}()
SubscribeWithKiller(func(signal os.Signal) {
assert.Equal(t, realSignal, signal, "wrong signal ingested by subscriber.")
callbackCalledTimes++
}, subscribedSignal)
// simulate signal receive
killerSignalChan <- realSignal
signalChan <- realSignal
// wait for goroutine callback func fulfilment (@see subscriber.go `go callback(killerSubscribedSignals)`)
time.Sleep(1 * time.Millisecond)
assert.False(t, exitCalled, "exit func should not be called yet.")
assert.Equal(t, 1, callbackCalledTimes, "callback func should be called once.")
killerSignalChan <- realSignal
time.Sleep(1 * time.Millisecond)
assert.True(t, exitCalled, "exit func should be called.")
assert.Equal(t, 1, callbackCalledTimes, "callback func should be called only once.")
}
func TestUnKillerSubscribe(t *testing.T) {
var killerSignalChan chan<- os.Signal
var killerSubscribedSignals = []os.Signal{os.Kill, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGSTOP}
var stopCalled = false
var exitCalled = false
monkey.Patch(os.Exit, func(code int) {
t.Error("exit func must not be called.")
exitCalled = true
})
monkey.Patch(signal.Notify, func(c chan<- os.Signal, signals ...os.Signal) {
// get subscriber internal chan in order to simulate signal receive
killerSignalChan = c
for _, s := range killerSubscribedSignals {
assert.Contains(t, signals, s)
}
})
monkey.Patch(signal.Stop, func(c chan<- os.Signal) {
assert.Equal(t, killerSignalChan, c)
stopCalled = true
})
defer func() {
monkey.UnpatchAll()
// reinitialize global var in order not to mess with other tests
killerSubscriberFunc = nil
}()
unsubscribeFunc := KillerSubscriber()
// unsubscribe from os signals
unsubscribeFunc()
// wait for goroutine signal.Stop func call (@see subscriber.go `case <-stopChan:`)
time.Sleep(1 * time.Millisecond)
// `killerSignalChan` is a bidirectional chan as it is equal to the `subscriber killerSignalChan` value
_, ok := <-*(*chan os.Signal)(unsafe.Pointer(&killerSignalChan))
assert.False(t, ok, "signal channel should be closed.")
assert.True(t, stopCalled, "`signal.Stop` should be called when `unsubscribeFunc` is called.")
assert.False(t, exitCalled, "exit func should not be called as `unsubscribeFunc` is called.")
}