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