1
2
3
4
5
6
7 package mgr_test
8
9 import (
10 "fmt"
11 "os"
12 "path/filepath"
13 "sort"
14 "strings"
15 "syscall"
16 "testing"
17 "time"
18
19 "golang.org/x/sys/windows"
20 "golang.org/x/sys/windows/svc"
21 "golang.org/x/sys/windows/svc/mgr"
22 )
23
24 func TestOpenLanManServer(t *testing.T) {
25 m, err := mgr.Connect()
26 if err != nil {
27 if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
28 t.Skip("Skipping test: we don't have rights to manage services.")
29 }
30 t.Fatalf("SCM connection failed: %s", err)
31 }
32 defer m.Disconnect()
33
34 s, err := m.OpenService("LanmanServer")
35 if err != nil {
36 t.Fatalf("OpenService(lanmanserver) failed: %s", err)
37 }
38 defer s.Close()
39
40 _, err = s.Config()
41 if err != nil {
42 t.Fatalf("Config failed: %s", err)
43 }
44 }
45
46 func install(t *testing.T, m *mgr.Mgr, name, exepath string, c mgr.Config) {
47
48
49 for i := 0; ; i++ {
50 s, err := m.OpenService(name)
51 if err != nil {
52 break
53 }
54 s.Close()
55
56 if i > 10 {
57 t.Fatalf("service %s already exists", name)
58 }
59 time.Sleep(300 * time.Millisecond)
60 }
61
62 s, err := m.CreateService(name, exepath, c)
63 if err != nil {
64 t.Fatalf("CreateService(%s) failed: %v", name, err)
65 }
66 defer s.Close()
67 }
68
69 func depString(d []string) string {
70 if len(d) == 0 {
71 return ""
72 }
73 for i := range d {
74 d[i] = strings.ToLower(d[i])
75 }
76 ss := sort.StringSlice(d)
77 ss.Sort()
78 return strings.Join([]string(ss), " ")
79 }
80
81 func testConfig(t *testing.T, s *mgr.Service, should mgr.Config) mgr.Config {
82 is, err := s.Config()
83 if err != nil {
84 t.Fatalf("Config failed: %s", err)
85 }
86 if should.DelayedAutoStart != is.DelayedAutoStart {
87 t.Fatalf("config mismatch: DelayedAutoStart is %v, but should have %v", is.DelayedAutoStart, should.DelayedAutoStart)
88 }
89 if should.DisplayName != is.DisplayName {
90 t.Fatalf("config mismatch: DisplayName is %q, but should have %q", is.DisplayName, should.DisplayName)
91 }
92 if should.StartType != is.StartType {
93 t.Fatalf("config mismatch: StartType is %v, but should have %v", is.StartType, should.StartType)
94 }
95 if should.Description != is.Description {
96 t.Fatalf("config mismatch: Description is %q, but should have %q", is.Description, should.Description)
97 }
98 if depString(should.Dependencies) != depString(is.Dependencies) {
99 t.Fatalf("config mismatch: Dependencies is %v, but should have %v", is.Dependencies, should.Dependencies)
100 }
101 return is
102 }
103
104 func testRecoveryActions(t *testing.T, s *mgr.Service, should []mgr.RecoveryAction) {
105 is, err := s.RecoveryActions()
106 if err != nil {
107 t.Fatalf("RecoveryActions failed: %s", err)
108 }
109 if len(should) != len(is) {
110 t.Errorf("recovery action mismatch: contains %v actions, but should have %v", len(is), len(should))
111 }
112 for i := range is {
113 if should[i].Type != is[i].Type {
114 t.Errorf("recovery action mismatch: Type is %v, but should have %v", is[i].Type, should[i].Type)
115 }
116 if should[i].Delay != is[i].Delay {
117 t.Errorf("recovery action mismatch: Delay is %v, but should have %v", is[i].Delay, should[i].Delay)
118 }
119 }
120 }
121
122 func testResetPeriod(t *testing.T, s *mgr.Service, should uint32) {
123 is, err := s.ResetPeriod()
124 if err != nil {
125 t.Fatalf("ResetPeriod failed: %s", err)
126 }
127 if should != is {
128 t.Errorf("reset period mismatch: reset period is %v, but should have %v", is, should)
129 }
130 }
131
132 func testSetRecoveryActions(t *testing.T, s *mgr.Service) {
133 r := []mgr.RecoveryAction{
134 {
135 Type: mgr.NoAction,
136 Delay: 60000 * time.Millisecond,
137 },
138 {
139 Type: mgr.ServiceRestart,
140 Delay: 4 * time.Minute,
141 },
142 {
143 Type: mgr.ServiceRestart,
144 Delay: time.Minute,
145 },
146 {
147 Type: mgr.RunCommand,
148 Delay: 4000 * time.Millisecond,
149 },
150 }
151
152
153 err := s.SetRecoveryActions(r, uint32(10000))
154 if err != nil {
155 t.Fatalf("SetRecoveryActions failed: %v", err)
156 }
157 testRecoveryActions(t, s, r)
158 testResetPeriod(t, s, uint32(10000))
159
160
161 err = s.SetRecoveryActions(r, syscall.INFINITE)
162 if err != nil {
163 t.Fatalf("SetRecoveryActions failed: %v", err)
164 }
165 testRecoveryActions(t, s, r)
166 testResetPeriod(t, s, syscall.INFINITE)
167
168
169 err = s.SetRecoveryActions(nil, 0)
170 if err.Error() != "recoveryActions cannot be nil" {
171 t.Fatalf("SetRecoveryActions failed with unexpected error message of %q", err)
172 }
173
174
175 err = s.ResetRecoveryActions()
176 if err != nil {
177 t.Fatalf("ResetRecoveryActions failed: %v", err)
178 }
179 testRecoveryActions(t, s, nil)
180 testResetPeriod(t, s, 0)
181 }
182
183 func testRebootMessage(t *testing.T, s *mgr.Service, should string) {
184 err := s.SetRebootMessage(should)
185 if err != nil {
186 t.Fatalf("SetRebootMessage failed: %v", err)
187 }
188 is, err := s.RebootMessage()
189 if err != nil {
190 t.Fatalf("RebootMessage failed: %v", err)
191 }
192 if should != is {
193 t.Errorf("reboot message mismatch: message is %q, but should have %q", is, should)
194 }
195 }
196
197 func testRecoveryCommand(t *testing.T, s *mgr.Service, should string) {
198 err := s.SetRecoveryCommand(should)
199 if err != nil {
200 t.Fatalf("SetRecoveryCommand failed: %v", err)
201 }
202 is, err := s.RecoveryCommand()
203 if err != nil {
204 t.Fatalf("RecoveryCommand failed: %v", err)
205 }
206 if should != is {
207 t.Errorf("recovery command mismatch: command is %q, but should have %q", is, should)
208 }
209 }
210
211 func testRecoveryActionsOnNonCrashFailures(t *testing.T, s *mgr.Service, should bool) {
212 err := s.SetRecoveryActionsOnNonCrashFailures(should)
213 if err != nil {
214 t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err)
215 }
216 is, err := s.RecoveryActionsOnNonCrashFailures()
217 if err != nil {
218 t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err)
219 }
220 if should != is {
221 t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", is, should)
222 }
223 }
224
225 func testMultipleRecoverySettings(t *testing.T, s *mgr.Service, rebootMsgShould, recoveryCmdShould string, actionsFlagShould bool) {
226 err := s.SetRebootMessage(rebootMsgShould)
227 if err != nil {
228 t.Fatalf("SetRebootMessage failed: %v", err)
229 }
230 err = s.SetRecoveryActionsOnNonCrashFailures(actionsFlagShould)
231 if err != nil {
232 t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err)
233 }
234 err = s.SetRecoveryCommand(recoveryCmdShould)
235 if err != nil {
236 t.Fatalf("SetRecoveryCommand failed: %v", err)
237 }
238
239 rebootMsgIs, err := s.RebootMessage()
240 if err != nil {
241 t.Fatalf("RebootMessage failed: %v", err)
242 }
243 if rebootMsgShould != rebootMsgIs {
244 t.Errorf("reboot message mismatch: message is %q, but should have %q", rebootMsgIs, rebootMsgShould)
245 }
246 recoveryCommandIs, err := s.RecoveryCommand()
247 if err != nil {
248 t.Fatalf("RecoveryCommand failed: %v", err)
249 }
250 if recoveryCmdShould != recoveryCommandIs {
251 t.Errorf("recovery command mismatch: command is %q, but should have %q", recoveryCommandIs, recoveryCmdShould)
252 }
253 actionsFlagIs, err := s.RecoveryActionsOnNonCrashFailures()
254 if err != nil {
255 t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err)
256 }
257 if actionsFlagShould != actionsFlagIs {
258 t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", actionsFlagIs, actionsFlagShould)
259 }
260 }
261
262 func testControl(t *testing.T, s *mgr.Service, c svc.Cmd, expectedErr error, expectedStatus svc.Status) {
263 status, err := s.Control(c)
264 if err != expectedErr {
265 t.Fatalf("Unexpected return from s.Control: %v (expected %v)", err, expectedErr)
266 }
267 if expectedStatus != status {
268 t.Fatalf("Unexpected status from s.Control: %+v (expected %+v)", status, expectedStatus)
269 }
270 }
271
272 func remove(t *testing.T, s *mgr.Service) {
273 err := s.Delete()
274 if err != nil {
275 t.Fatalf("Delete failed: %s", err)
276 }
277 }
278
279 func TestMyService(t *testing.T) {
280 if os.Getenv("GO_BUILDER_NAME") == "" {
281
282 t.Skip("skipping test that modifies system services: GO_BUILDER_NAME not set")
283 }
284 if testing.Short() {
285 t.Skip("skipping test in short mode that modifies system services")
286 }
287
288 const name = "mgrtestservice"
289
290 m, err := mgr.Connect()
291 if err != nil {
292 t.Fatalf("SCM connection failed: %s", err)
293 }
294 defer m.Disconnect()
295
296 c := mgr.Config{
297 StartType: mgr.StartDisabled,
298 DisplayName: "x-sys mgr test service",
299 Description: "x-sys mgr test service is just a test",
300 Dependencies: []string{"LanmanServer", "W32Time"},
301 }
302
303 exename := os.Args[0]
304 exepath, err := filepath.Abs(exename)
305 if err != nil {
306 t.Fatalf("filepath.Abs(%s) failed: %s", exename, err)
307 }
308
309 install(t, m, name, exepath, c)
310
311 s, err := m.OpenService(name)
312 if err != nil {
313 t.Fatalf("service %s is not installed", name)
314 }
315 defer s.Close()
316 defer s.Delete()
317
318 c.BinaryPathName = exepath
319 c = testConfig(t, s, c)
320
321 c.StartType = mgr.StartManual
322 err = s.UpdateConfig(c)
323 if err != nil {
324 t.Fatalf("UpdateConfig failed: %v", err)
325 }
326
327 testConfig(t, s, c)
328
329 c.StartType = mgr.StartAutomatic
330 c.DelayedAutoStart = true
331 err = s.UpdateConfig(c)
332 if err != nil {
333 t.Fatalf("UpdateConfig failed: %v", err)
334 }
335
336 testConfig(t, s, c)
337
338 svcnames, err := m.ListServices()
339 if err != nil {
340 t.Fatalf("ListServices failed: %v", err)
341 }
342 var serviceIsInstalled bool
343 for _, sn := range svcnames {
344 if sn == name {
345 serviceIsInstalled = true
346 break
347 }
348 }
349 if !serviceIsInstalled {
350 t.Errorf("ListServices failed to find %q service", name)
351 }
352
353 testSetRecoveryActions(t, s)
354 testRebootMessage(t, s, fmt.Sprintf("%s failed", name))
355 testRebootMessage(t, s, "")
356 testRecoveryCommand(t, s, fmt.Sprintf("sc query %s", name))
357 testRecoveryCommand(t, s, "")
358 testRecoveryActionsOnNonCrashFailures(t, s, true)
359 testRecoveryActionsOnNonCrashFailures(t, s, false)
360 testMultipleRecoverySettings(t, s, fmt.Sprintf("%s failed", name), fmt.Sprintf("sc query %s", name), true)
361
362 expectedStatus := svc.Status{
363 State: svc.Stopped,
364 }
365 testControl(t, s, svc.Stop, windows.ERROR_SERVICE_NOT_ACTIVE, expectedStatus)
366
367 remove(t, s)
368 }
369
370 func TestListDependentServices(t *testing.T) {
371 m, err := mgr.Connect()
372 if err != nil {
373 if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
374 t.Skip("Skipping test: we don't have rights to manage services.")
375 }
376 t.Fatalf("SCM connection failed: %s", err)
377 }
378 defer m.Disconnect()
379
380 baseServiceName := "testservice1"
381 dependentServiceName := "testservice2"
382 install(t, m, baseServiceName, "", mgr.Config{})
383 baseService, err := m.OpenService(baseServiceName)
384 if err != nil {
385 t.Fatalf("OpenService failed: %v", err)
386 }
387 defer remove(t, baseService)
388 install(t, m, dependentServiceName, "", mgr.Config{Dependencies: []string{baseServiceName}})
389 dependentService, err := m.OpenService(dependentServiceName)
390 if err != nil {
391 t.Fatalf("OpenService failed: %v", err)
392 }
393 defer remove(t, dependentService)
394
395
396 dependentServices, err := baseService.ListDependentServices(svc.AnyActivity)
397 if err != nil {
398 t.Fatalf("baseService.ListDependentServices failed: %v", err)
399 }
400 if len(dependentServices) != 1 || dependentServices[0] != dependentServiceName {
401 t.Errorf("Found %v, instead of expected contents %s", dependentServices, dependentServiceName)
402 }
403 dependentServices, err = dependentService.ListDependentServices(svc.AnyActivity)
404 if err != nil {
405 t.Fatalf("dependentService.ListDependentServices failed: %v", err)
406 }
407 if len(dependentServices) != 0 {
408 t.Errorf("Found %v, where no service should be listed", dependentService)
409 }
410 }
411
View as plain text