-
Notifications
You must be signed in to change notification settings - Fork 1
/
atexit_test.go
176 lines (145 loc) · 3.02 KB
/
atexit_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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright 2020 Manlio Perillo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package atexit
import (
"sync"
"testing"
"time"
)
// resource represents a simple resource that can be acquired and released. It
// is implemented using an integer so a test case can easily check if it was
// correctly released
type resource struct {
sync.Mutex // protects n
n int
}
func (r *resource) acquire() {
r.Lock()
r.n++
r.Unlock()
}
func (r *resource) release() {
r.Lock()
r.n--
r.Unlock()
}
func (r *resource) value() int {
r.Lock()
n := r.n
r.Unlock()
return n
}
// TestExit tests that an atexit function is called after a normal function
// termination.
func TestExit(t *testing.T) {
var (
res resource
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
res.acquire()
defer Do(func() {
res.release()
})()
}()
wg.Wait()
if res.value() != 0 {
t.Error("the resource was not released")
}
}
// TestAbort tests that an atexit function is called after an abnormal function
// termination.
func TestAbort(t *testing.T) {
var (
res resource
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
res.acquire()
defer Do(func() {
res.release()
})()
// Sleep to allow an asynchronous abortion.
time.Sleep(time.Second)
}()
// Simulate an abort.
time.AfterFunc(500*time.Millisecond, func() {
exit()
})
wg.Wait()
if res.value() != 0 {
t.Error("the resource was not released")
}
}
// operation implements two operations that are not commutative. The value
// method will return different results depending on whether op1 is called
// first followed by op2, or op2 is called first followed by op1.
type operation struct {
n int
}
func (op *operation) op1() {
op.n--
}
func (op *operation) op2() {
op.n *= 10
}
func (op *operation) value() int {
return op.n
}
// TestExitOrder tests that atexit functions are called in the correct order
// after a normal function termination.
func TestExitOrder(t *testing.T) {
var (
op operation
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
defer Do(func() {
op.op1()
})()
defer Do(func() {
op.op2()
})()
}()
wg.Wait()
if op.value() != (0*10)-1 {
t.Error("the atexit functions where not called in the correct order")
}
}
// TestAbortOrder tests that atexit functions are called in the correct order
// after an abnormal function termination.
func TestAbortOrder(t *testing.T) {
var (
op operation
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
// Change the order of operations compared to TestExitOrder, to make
// sure the code is actually correct.
defer Do(func() {
op.op2()
})()
defer Do(func() {
op.op1()
})()
// Sleep to allow an asynchronous abortion.
time.Sleep(time.Second)
}()
// Simulate an abort.
time.AfterFunc(500*time.Millisecond, func() {
exit()
})
wg.Wait()
if op.value() != (0-1)*10 {
t.Error("the atexit functions where not called in the correct order")
}
}