| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build windows |
| // +build windows |
| |
| package mgr_test |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| "syscall" |
| "testing" |
| "time" |
| |
| "golang.org/x/sys/windows" |
| "golang.org/x/sys/windows/svc" |
| "golang.org/x/sys/windows/svc/mgr" |
| ) |
| |
| func TestOpenLanManServer(t *testing.T) { |
| m, err := mgr.Connect() |
| if err != nil { |
| if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED { |
| t.Skip("Skipping test: we don't have rights to manage services.") |
| } |
| t.Fatalf("SCM connection failed: %s", err) |
| } |
| defer m.Disconnect() |
| |
| s, err := m.OpenService("LanmanServer") |
| if err != nil { |
| t.Fatalf("OpenService(lanmanserver) failed: %s", err) |
| } |
| defer s.Close() |
| |
| _, err = s.Config() |
| if err != nil { |
| t.Fatalf("Config failed: %s", err) |
| } |
| } |
| |
| func install(t *testing.T, m *mgr.Mgr, name, exepath string, c mgr.Config) { |
| // Sometimes it takes a while for the service to get |
| // removed after previous test run. |
| for i := 0; ; i++ { |
| s, err := m.OpenService(name) |
| if err != nil { |
| break |
| } |
| s.Close() |
| |
| if i > 10 { |
| t.Fatalf("service %s already exists", name) |
| } |
| time.Sleep(300 * time.Millisecond) |
| } |
| |
| s, err := m.CreateService(name, exepath, c) |
| if err != nil { |
| t.Fatalf("CreateService(%s) failed: %v", name, err) |
| } |
| defer s.Close() |
| } |
| |
| func depString(d []string) string { |
| if len(d) == 0 { |
| return "" |
| } |
| for i := range d { |
| d[i] = strings.ToLower(d[i]) |
| } |
| ss := sort.StringSlice(d) |
| ss.Sort() |
| return strings.Join([]string(ss), " ") |
| } |
| |
| func testConfig(t *testing.T, s *mgr.Service, should mgr.Config) mgr.Config { |
| is, err := s.Config() |
| if err != nil { |
| t.Fatalf("Config failed: %s", err) |
| } |
| if should.DelayedAutoStart != is.DelayedAutoStart { |
| t.Fatalf("config mismatch: DelayedAutoStart is %v, but should have %v", is.DelayedAutoStart, should.DelayedAutoStart) |
| } |
| if should.DisplayName != is.DisplayName { |
| t.Fatalf("config mismatch: DisplayName is %q, but should have %q", is.DisplayName, should.DisplayName) |
| } |
| if should.StartType != is.StartType { |
| t.Fatalf("config mismatch: StartType is %v, but should have %v", is.StartType, should.StartType) |
| } |
| if should.Description != is.Description { |
| t.Fatalf("config mismatch: Description is %q, but should have %q", is.Description, should.Description) |
| } |
| if depString(should.Dependencies) != depString(is.Dependencies) { |
| t.Fatalf("config mismatch: Dependencies is %v, but should have %v", is.Dependencies, should.Dependencies) |
| } |
| return is |
| } |
| |
| func testRecoveryActions(t *testing.T, s *mgr.Service, should []mgr.RecoveryAction) { |
| is, err := s.RecoveryActions() |
| if err != nil { |
| t.Fatalf("RecoveryActions failed: %s", err) |
| } |
| if len(should) != len(is) { |
| t.Errorf("recovery action mismatch: contains %v actions, but should have %v", len(is), len(should)) |
| } |
| for i := range is { |
| if should[i].Type != is[i].Type { |
| t.Errorf("recovery action mismatch: Type is %v, but should have %v", is[i].Type, should[i].Type) |
| } |
| if should[i].Delay != is[i].Delay { |
| t.Errorf("recovery action mismatch: Delay is %v, but should have %v", is[i].Delay, should[i].Delay) |
| } |
| } |
| } |
| |
| func testResetPeriod(t *testing.T, s *mgr.Service, should uint32) { |
| is, err := s.ResetPeriod() |
| if err != nil { |
| t.Fatalf("ResetPeriod failed: %s", err) |
| } |
| if should != is { |
| t.Errorf("reset period mismatch: reset period is %v, but should have %v", is, should) |
| } |
| } |
| |
| func testSetRecoveryActions(t *testing.T, s *mgr.Service) { |
| r := []mgr.RecoveryAction{ |
| { |
| Type: mgr.NoAction, |
| Delay: 60000 * time.Millisecond, |
| }, |
| { |
| Type: mgr.ServiceRestart, |
| Delay: 4 * time.Minute, |
| }, |
| { |
| Type: mgr.ServiceRestart, |
| Delay: time.Minute, |
| }, |
| { |
| Type: mgr.RunCommand, |
| Delay: 4000 * time.Millisecond, |
| }, |
| } |
| |
| // 4 recovery actions with reset period |
| err := s.SetRecoveryActions(r, uint32(10000)) |
| if err != nil { |
| t.Fatalf("SetRecoveryActions failed: %v", err) |
| } |
| testRecoveryActions(t, s, r) |
| testResetPeriod(t, s, uint32(10000)) |
| |
| // Infinite reset period |
| err = s.SetRecoveryActions(r, syscall.INFINITE) |
| if err != nil { |
| t.Fatalf("SetRecoveryActions failed: %v", err) |
| } |
| testRecoveryActions(t, s, r) |
| testResetPeriod(t, s, syscall.INFINITE) |
| |
| // nil recovery actions |
| err = s.SetRecoveryActions(nil, 0) |
| if err.Error() != "recoveryActions cannot be nil" { |
| t.Fatalf("SetRecoveryActions failed with unexpected error message of %q", err) |
| } |
| |
| // Delete all recovery actions and reset period |
| err = s.ResetRecoveryActions() |
| if err != nil { |
| t.Fatalf("ResetRecoveryActions failed: %v", err) |
| } |
| testRecoveryActions(t, s, nil) |
| testResetPeriod(t, s, 0) |
| } |
| |
| func testRebootMessage(t *testing.T, s *mgr.Service, should string) { |
| err := s.SetRebootMessage(should) |
| if err != nil { |
| t.Fatalf("SetRebootMessage failed: %v", err) |
| } |
| is, err := s.RebootMessage() |
| if err != nil { |
| t.Fatalf("RebootMessage failed: %v", err) |
| } |
| if should != is { |
| t.Errorf("reboot message mismatch: message is %q, but should have %q", is, should) |
| } |
| } |
| |
| func testRecoveryCommand(t *testing.T, s *mgr.Service, should string) { |
| err := s.SetRecoveryCommand(should) |
| if err != nil { |
| t.Fatalf("SetRecoveryCommand failed: %v", err) |
| } |
| is, err := s.RecoveryCommand() |
| if err != nil { |
| t.Fatalf("RecoveryCommand failed: %v", err) |
| } |
| if should != is { |
| t.Errorf("recovery command mismatch: command is %q, but should have %q", is, should) |
| } |
| } |
| |
| func testRecoveryActionsOnNonCrashFailures(t *testing.T, s *mgr.Service, should bool) { |
| err := s.SetRecoveryActionsOnNonCrashFailures(should) |
| if err != nil { |
| t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err) |
| } |
| is, err := s.RecoveryActionsOnNonCrashFailures() |
| if err != nil { |
| t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err) |
| } |
| if should != is { |
| t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", is, should) |
| } |
| } |
| |
| func testMultipleRecoverySettings(t *testing.T, s *mgr.Service, rebootMsgShould, recoveryCmdShould string, actionsFlagShould bool) { |
| err := s.SetRebootMessage(rebootMsgShould) |
| if err != nil { |
| t.Fatalf("SetRebootMessage failed: %v", err) |
| } |
| err = s.SetRecoveryActionsOnNonCrashFailures(actionsFlagShould) |
| if err != nil { |
| t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err) |
| } |
| err = s.SetRecoveryCommand(recoveryCmdShould) |
| if err != nil { |
| t.Fatalf("SetRecoveryCommand failed: %v", err) |
| } |
| |
| rebootMsgIs, err := s.RebootMessage() |
| if err != nil { |
| t.Fatalf("RebootMessage failed: %v", err) |
| } |
| if rebootMsgShould != rebootMsgIs { |
| t.Errorf("reboot message mismatch: message is %q, but should have %q", rebootMsgIs, rebootMsgShould) |
| } |
| recoveryCommandIs, err := s.RecoveryCommand() |
| if err != nil { |
| t.Fatalf("RecoveryCommand failed: %v", err) |
| } |
| if recoveryCmdShould != recoveryCommandIs { |
| t.Errorf("recovery command mismatch: command is %q, but should have %q", recoveryCommandIs, recoveryCmdShould) |
| } |
| actionsFlagIs, err := s.RecoveryActionsOnNonCrashFailures() |
| if err != nil { |
| t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err) |
| } |
| if actionsFlagShould != actionsFlagIs { |
| t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", actionsFlagIs, actionsFlagShould) |
| } |
| } |
| |
| func testControl(t *testing.T, s *mgr.Service, c svc.Cmd, expectedErr error, expectedStatus svc.Status) { |
| status, err := s.Control(c) |
| if err != expectedErr { |
| t.Fatalf("Unexpected return from s.Control: %v (expected %v)", err, expectedErr) |
| } |
| if expectedStatus != status { |
| t.Fatalf("Unexpected status from s.Control: %+v (expected %+v)", status, expectedStatus) |
| } |
| } |
| |
| func remove(t *testing.T, s *mgr.Service) { |
| err := s.Delete() |
| if err != nil { |
| t.Fatalf("Delete failed: %s", err) |
| } |
| } |
| |
| func TestMyService(t *testing.T) { |
| if os.Getenv("GO_BUILDER_NAME") == "" { |
| // Don't install services on arbitrary users' machines. |
| t.Skip("skipping test that modifies system services: GO_BUILDER_NAME not set") |
| } |
| if testing.Short() { |
| t.Skip("skipping test in short mode that modifies system services") |
| } |
| |
| const name = "mgrtestservice" |
| |
| m, err := mgr.Connect() |
| if err != nil { |
| t.Fatalf("SCM connection failed: %s", err) |
| } |
| defer m.Disconnect() |
| |
| c := mgr.Config{ |
| StartType: mgr.StartDisabled, |
| DisplayName: "x-sys mgr test service", |
| Description: "x-sys mgr test service is just a test", |
| Dependencies: []string{"LanmanServer", "W32Time"}, |
| } |
| |
| exename := os.Args[0] |
| exepath, err := filepath.Abs(exename) |
| if err != nil { |
| t.Fatalf("filepath.Abs(%s) failed: %s", exename, err) |
| } |
| |
| install(t, m, name, exepath, c) |
| |
| s, err := m.OpenService(name) |
| if err != nil { |
| t.Fatalf("service %s is not installed", name) |
| } |
| defer s.Close() |
| defer s.Delete() |
| |
| c.BinaryPathName = exepath |
| c = testConfig(t, s, c) |
| |
| c.StartType = mgr.StartManual |
| err = s.UpdateConfig(c) |
| if err != nil { |
| t.Fatalf("UpdateConfig failed: %v", err) |
| } |
| |
| testConfig(t, s, c) |
| |
| c.StartType = mgr.StartAutomatic |
| c.DelayedAutoStart = true |
| err = s.UpdateConfig(c) |
| if err != nil { |
| t.Fatalf("UpdateConfig failed: %v", err) |
| } |
| |
| testConfig(t, s, c) |
| |
| svcnames, err := m.ListServices() |
| if err != nil { |
| t.Fatalf("ListServices failed: %v", err) |
| } |
| var serviceIsInstalled bool |
| for _, sn := range svcnames { |
| if sn == name { |
| serviceIsInstalled = true |
| break |
| } |
| } |
| if !serviceIsInstalled { |
| t.Errorf("ListServices failed to find %q service", name) |
| } |
| |
| testSetRecoveryActions(t, s) |
| testRebootMessage(t, s, fmt.Sprintf("%s failed", name)) |
| testRebootMessage(t, s, "") // delete reboot message |
| testRecoveryCommand(t, s, fmt.Sprintf("sc query %s", name)) |
| testRecoveryCommand(t, s, "") // delete recovery command |
| testRecoveryActionsOnNonCrashFailures(t, s, true) |
| testRecoveryActionsOnNonCrashFailures(t, s, false) |
| testMultipleRecoverySettings(t, s, fmt.Sprintf("%s failed", name), fmt.Sprintf("sc query %s", name), true) |
| |
| expectedStatus := svc.Status{ |
| State: svc.Stopped, |
| } |
| testControl(t, s, svc.Stop, windows.ERROR_SERVICE_NOT_ACTIVE, expectedStatus) |
| |
| remove(t, s) |
| } |
| |
| func TestListDependentServices(t *testing.T) { |
| m, err := mgr.Connect() |
| if err != nil { |
| if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED { |
| t.Skip("Skipping test: we don't have rights to manage services.") |
| } |
| t.Fatalf("SCM connection failed: %s", err) |
| } |
| defer m.Disconnect() |
| |
| baseServiceName := "testservice1" |
| dependentServiceName := "testservice2" |
| install(t, m, baseServiceName, "", mgr.Config{}) |
| baseService, err := m.OpenService(baseServiceName) |
| if err != nil { |
| t.Fatalf("OpenService failed: %v", err) |
| } |
| defer remove(t, baseService) |
| install(t, m, dependentServiceName, "", mgr.Config{Dependencies: []string{baseServiceName}}) |
| dependentService, err := m.OpenService(dependentServiceName) |
| if err != nil { |
| t.Fatalf("OpenService failed: %v", err) |
| } |
| defer remove(t, dependentService) |
| |
| // test that both the base service and dependent service list the correct dependencies |
| dependentServices, err := baseService.ListDependentServices(svc.AnyActivity) |
| if err != nil { |
| t.Fatalf("baseService.ListDependentServices failed: %v", err) |
| } |
| if len(dependentServices) != 1 || dependentServices[0] != dependentServiceName { |
| t.Errorf("Found %v, instead of expected contents %s", dependentServices, dependentServiceName) |
| } |
| dependentServices, err = dependentService.ListDependentServices(svc.AnyActivity) |
| if err != nil { |
| t.Fatalf("dependentService.ListDependentServices failed: %v", err) |
| } |
| if len(dependentServices) != 0 { |
| t.Errorf("Found %v, where no service should be listed", dependentService) |
| } |
| } |