1
+ /*
2
+ Copyright 2024 The Vitess Authors.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
1
17
package hook
2
18
3
19
import (
4
20
"context"
21
+ "io"
5
22
"os"
6
23
"os/exec"
7
24
"path"
25
+ "strings"
26
+ "sync"
8
27
"testing"
9
28
"time"
10
29
@@ -22,6 +41,11 @@ func TestExecuteContext(t *testing.T) {
22
41
require .NoError (t , err )
23
42
24
43
sleepHookPath := path .Join (vtroot , "vthook" , "sleep" )
44
+
45
+ if _ , err := os .Lstat (sleepHookPath ); err == nil {
46
+ require .NoError (t , os .Remove (sleepHookPath ))
47
+ }
48
+
25
49
require .NoError (t , os .Symlink (sleep , sleepHookPath ))
26
50
defer func () {
27
51
require .NoError (t , os .Remove (sleepHookPath ))
@@ -38,3 +62,234 @@ func TestExecuteContext(t *testing.T) {
38
62
hr = h .Execute ()
39
63
assert .Equal (t , HOOK_SUCCESS , hr .ExitStatus )
40
64
}
65
+
66
+ func TestExecuteOptional (t * testing.T ) {
67
+ vtroot , err := vtenv .VtRoot ()
68
+ require .NoError (t , err )
69
+
70
+ echo , err := exec .LookPath ("echo" )
71
+ require .NoError (t , err )
72
+
73
+ echoHookPath := path .Join (vtroot , "vthook" , "echo" )
74
+
75
+ if _ , err := os .Lstat (echoHookPath ); err == nil {
76
+ require .NoError (t , os .Remove (echoHookPath ))
77
+ }
78
+
79
+ require .NoError (t , os .Symlink (echo , echoHookPath ))
80
+ defer func () {
81
+ require .NoError (t , os .Remove (echoHookPath ))
82
+ }()
83
+ tt := []struct {
84
+ name string
85
+ hookName string
86
+ parameters []string
87
+ expectedError string
88
+ }{
89
+ {
90
+ name : "HookSuccess" ,
91
+ hookName : "echo" ,
92
+ parameters : []string {"test" },
93
+ },
94
+ {
95
+ name : "HookDoesNotExist" ,
96
+ hookName : "nonexistent-hook" ,
97
+ parameters : []string {"test" },
98
+ },
99
+ }
100
+
101
+ for _ , tc := range tt {
102
+ t .Run (tc .name , func (t * testing.T ) {
103
+ h := NewHook (tc .hookName , tc .parameters )
104
+ err := h .ExecuteOptional ()
105
+ if tc .expectedError == "" {
106
+ assert .NoError (t , err )
107
+ } else {
108
+ assert .Error (t , err )
109
+ assert .ErrorContains (t , err , tc .expectedError )
110
+ }
111
+ })
112
+ }
113
+ }
114
+
115
+ func TestNewHook (t * testing.T ) {
116
+ h := NewHook ("test-hook" , []string {"arg1" , "arg2" })
117
+ assert .Equal (t , "test-hook" , h .Name )
118
+ assert .Equal (t , []string {"arg1" , "arg2" }, h .Parameters )
119
+ }
120
+
121
+ func TestNewSimpleHook (t * testing.T ) {
122
+ h := NewSimpleHook ("simple-hook" )
123
+ assert .Equal (t , "simple-hook" , h .Name )
124
+ assert .Empty (t , h .Parameters )
125
+ }
126
+
127
+ func TestNewHookWithEnv (t * testing.T ) {
128
+ h := NewHookWithEnv ("env-hook" , []string {"arg1" , "arg2" }, map [string ]string {"KEY" : "VALUE" })
129
+ assert .Equal (t , "env-hook" , h .Name )
130
+ assert .Equal (t , []string {"arg1" , "arg2" }, h .Parameters )
131
+ assert .Equal (t , map [string ]string {"KEY" : "VALUE" }, h .ExtraEnv )
132
+ }
133
+
134
+ func TestString (t * testing.T ) {
135
+ tt := []struct {
136
+ name string
137
+ input HookResult
138
+ expected string
139
+ }{
140
+ {
141
+ name : "HOOK_SUCCESS" ,
142
+ input : HookResult {ExitStatus : HOOK_SUCCESS , Stdout : "output" },
143
+ expected : "result: HOOK_SUCCESS\n stdout:\n output" ,
144
+ },
145
+ {
146
+ name : "HOOK_DOES_NOT_EXIST" ,
147
+ input : HookResult {ExitStatus : HOOK_DOES_NOT_EXIST },
148
+ expected : "result: HOOK_DOES_NOT_EXIST" ,
149
+ },
150
+ {
151
+ name : "HOOK_STAT_FAILED" ,
152
+ input : HookResult {ExitStatus : HOOK_STAT_FAILED },
153
+ expected : "result: HOOK_STAT_FAILED" ,
154
+ },
155
+ {
156
+ name : "HOOK_CANNOT_GET_EXIT_STATUS" ,
157
+ input : HookResult {ExitStatus : HOOK_CANNOT_GET_EXIT_STATUS },
158
+ expected : "result: HOOK_CANNOT_GET_EXIT_STATUS" ,
159
+ },
160
+ {
161
+ name : "HOOK_INVALID_NAME" ,
162
+ input : HookResult {ExitStatus : HOOK_INVALID_NAME },
163
+ expected : "result: HOOK_INVALID_NAME" ,
164
+ },
165
+ {
166
+ name : "HOOK_VTROOT_ERROR" ,
167
+ input : HookResult {ExitStatus : HOOK_VTROOT_ERROR },
168
+ expected : "result: HOOK_VTROOT_ERROR" ,
169
+ },
170
+ {
171
+ name : "case default" ,
172
+ input : HookResult {ExitStatus : 42 },
173
+ expected : "result: exit(42)" ,
174
+ },
175
+ {
176
+ name : "WithStderr" ,
177
+ input : HookResult {ExitStatus : HOOK_SUCCESS , Stderr : "error" },
178
+ expected : "result: HOOK_SUCCESS\n stderr:\n error" ,
179
+ },
180
+ {
181
+ name : "WithStderr" ,
182
+ input : HookResult {ExitStatus : HOOK_SUCCESS , Stderr : "error" },
183
+ expected : "result: HOOK_SUCCESS\n stderr:\n error" ,
184
+ },
185
+ {
186
+ name : "WithStdoutAndStderr" ,
187
+ input : HookResult {ExitStatus : HOOK_SUCCESS , Stdout : "output" , Stderr : "error" },
188
+ expected : "result: HOOK_SUCCESS\n stdout:\n output\n stderr:\n error" ,
189
+ },
190
+ }
191
+
192
+ for _ , tc := range tt {
193
+ t .Run (tc .name , func (t * testing.T ) {
194
+ result := tc .input .String ()
195
+ assert .Equal (t , tc .expected , result )
196
+ })
197
+ }
198
+ }
199
+
200
+ func TestExecuteAsReadPipe (t * testing.T ) {
201
+ vtroot , err := vtenv .VtRoot ()
202
+ require .NoError (t , err )
203
+
204
+ cat , err := exec .LookPath ("cat" )
205
+ require .NoError (t , err )
206
+
207
+ catHookPath := path .Join (vtroot , "vthook" , "cat" )
208
+
209
+ if _ , err := os .Lstat (catHookPath ); err == nil {
210
+ require .NoError (t , os .Remove (catHookPath ))
211
+ }
212
+
213
+ require .NoError (t , os .Symlink (cat , catHookPath ))
214
+ defer func () {
215
+ require .NoError (t , os .Remove (catHookPath ))
216
+ }()
217
+
218
+ h := NewHook ("cat" , nil )
219
+ reader , waitFunc , status , err := h .ExecuteAsReadPipe (strings .NewReader ("Hello, World!\n " ))
220
+ require .NoError (t , err )
221
+ defer reader .(io.Closer ).Close ()
222
+
223
+ output , err := io .ReadAll (reader )
224
+ require .NoError (t , err )
225
+ assert .Equal (t , "Hello, World!\n " , string (output ))
226
+
227
+ stderr , waitErr := waitFunc ()
228
+ assert .Empty (t , stderr )
229
+ assert .NoError (t , waitErr )
230
+ assert .Equal (t , HOOK_SUCCESS , status )
231
+ }
232
+
233
+ func TestExecuteAsReadPipeErrorFindingHook (t * testing.T ) {
234
+ h := NewHook ("nonexistent-hook" , nil )
235
+ reader , waitFunc , status , err := h .ExecuteAsReadPipe (strings .NewReader ("Hello, World!\n " ))
236
+ require .Error (t , err )
237
+ assert .Nil (t , reader )
238
+ assert .Nil (t , waitFunc )
239
+ assert .Equal (t , HOOK_DOES_NOT_EXIST , status )
240
+ }
241
+
242
+ func TestExecuteAsWritePipe (t * testing.T ) {
243
+ var writer strings.Builder
244
+ var writerMutex sync.Mutex
245
+
246
+ vtroot , err := vtenv .VtRoot ()
247
+ require .NoError (t , err )
248
+
249
+ echo , err := exec .LookPath ("echo" )
250
+ require .NoError (t , err )
251
+
252
+ echoHookPath := path .Join (vtroot , "vthook" , "echo" )
253
+
254
+ if _ , err := os .Lstat (echoHookPath ); err == nil {
255
+ require .NoError (t , os .Remove (echoHookPath ))
256
+ }
257
+
258
+ require .NoError (t , os .Symlink (echo , echoHookPath ))
259
+ defer func () {
260
+ require .NoError (t , os .Remove (echoHookPath ))
261
+ }()
262
+
263
+ h := NewHook ("echo" , nil )
264
+
265
+ writerMutex .Lock ()
266
+ var writerTemp strings.Builder
267
+ _ , waitFunc , status , err := h .ExecuteAsWritePipe (& writerTemp )
268
+ writerMutex .Unlock ()
269
+
270
+ require .NoError (t , err )
271
+ defer func () {
272
+ writerMutex .Lock ()
273
+ writer .Reset ()
274
+ writerMutex .Unlock ()
275
+ }()
276
+
277
+ writerMutex .Lock ()
278
+ _ , err = writer .Write ([]byte ("Hello, World!\n " ))
279
+ writerMutex .Unlock ()
280
+ require .NoError (t , err )
281
+
282
+ stderr , waitErr := waitFunc ()
283
+ assert .Empty (t , stderr )
284
+ assert .NoError (t , waitErr )
285
+ assert .Equal (t , HOOK_SUCCESS , status )
286
+ }
287
+
288
+ func TestExecuteAsWritePipeErrorFindingHook (t * testing.T ) {
289
+ h := NewHook ("nonexistent-hook" , nil )
290
+ var writer strings.Builder
291
+ writerPtr := & writer
292
+ _ , _ , status , err := h .ExecuteAsWritePipe (writerPtr )
293
+ assert .Error (t , err )
294
+ assert .Equal (t , HOOK_DOES_NOT_EXIST , status )
295
+ }
0 commit comments