golang.org/x/sys@v0.20.1-0.20240517151509-673e0f94c16d/windows/svc/mgr/mgr_test.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build windows 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 // Sometimes it takes a while for the service to get 48 // removed after previous test run. 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 // 4 recovery actions with reset period 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 // Infinite reset period 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 // nil recovery actions 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 // Delete all recovery actions and reset period 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 // Don't install services on arbitrary users' machines. 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, "") // delete reboot message 356 testRecoveryCommand(t, s, fmt.Sprintf("sc query %s", name)) 357 testRecoveryCommand(t, s, "") // delete recovery command 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 // test that both the base service and dependent service list the correct dependencies 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 }