golang.org/x/sys@v0.20.1-0.20240517151509-673e0f94c16d/windows/svc/svc_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 svc_test
     8  
     9  import (
    10  	"fmt"
    11  	"math/rand"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"golang.org/x/sys/windows/svc"
    20  	"golang.org/x/sys/windows/svc/mgr"
    21  )
    22  
    23  func getState(t *testing.T, s *mgr.Service) svc.State {
    24  	status, err := s.Query()
    25  	if err != nil {
    26  		t.Fatalf("Query(%s) failed: %s", s.Name, err)
    27  	}
    28  	return status.State
    29  }
    30  
    31  func testState(t *testing.T, s *mgr.Service, want svc.State) {
    32  	have := getState(t, s)
    33  	if have != want {
    34  		t.Fatalf("%s state is=%d want=%d", s.Name, have, want)
    35  	}
    36  }
    37  
    38  func waitState(t *testing.T, s *mgr.Service, want svc.State) {
    39  	for i := 0; ; i++ {
    40  		have := getState(t, s)
    41  		if have == want {
    42  			return
    43  		}
    44  		if i > 10 {
    45  			t.Fatalf("%s state is=%d, waiting timeout", s.Name, have)
    46  		}
    47  		time.Sleep(300 * time.Millisecond)
    48  	}
    49  }
    50  
    51  // stopAndDeleteIfInstalled stops and deletes service name,
    52  // if the service is running and / or installed.
    53  func stopAndDeleteIfInstalled(t *testing.T, m *mgr.Mgr, name string) {
    54  	s, err := m.OpenService(name)
    55  	if err != nil {
    56  		// Service is not installed.
    57  		return
    58  
    59  	}
    60  	defer s.Close()
    61  
    62  	// Make sure the service is not running, otherwise we won't be able to delete it.
    63  	if getState(t, s) == svc.Running {
    64  		_, err = s.Control(svc.Stop)
    65  		if err != nil {
    66  			t.Fatalf("Control(%s) failed: %s", s.Name, err)
    67  		}
    68  		waitState(t, s, svc.Stopped)
    69  	}
    70  
    71  	err = s.Delete()
    72  	if err != nil {
    73  		t.Fatalf("Delete failed: %s", err)
    74  	}
    75  }
    76  
    77  func TestExample(t *testing.T) {
    78  	if os.Getenv("GO_BUILDER_NAME") == "" {
    79  		// Don't install services on arbitrary users' machines.
    80  		t.Skip("skipping test that modifies system services: GO_BUILDER_NAME not set")
    81  	}
    82  	if testing.Short() {
    83  		t.Skip("skipping test in short mode that modifies system services")
    84  	}
    85  
    86  	const name = "svctestservice"
    87  
    88  	m, err := mgr.Connect()
    89  	if err != nil {
    90  		t.Fatalf("SCM connection failed: %s", err)
    91  	}
    92  	defer m.Disconnect()
    93  
    94  	exepath := filepath.Join(t.TempDir(), "a.exe")
    95  	o, err := exec.Command("go", "build", "-o", exepath, "golang.org/x/sys/windows/svc/example").CombinedOutput()
    96  	if err != nil {
    97  		t.Fatalf("failed to build service program: %v\n%v", err, string(o))
    98  	}
    99  
   100  	stopAndDeleteIfInstalled(t, m, name)
   101  
   102  	s, err := m.CreateService(name, exepath, mgr.Config{DisplayName: "x-sys svc test service"}, "-name", name)
   103  	if err != nil {
   104  		t.Fatalf("CreateService(%s) failed: %v", name, err)
   105  	}
   106  	defer s.Close()
   107  
   108  	args := []string{"is", "manual-started", fmt.Sprintf("%d", rand.Int())}
   109  
   110  	testState(t, s, svc.Stopped)
   111  	err = s.Start(args...)
   112  	if err != nil {
   113  		t.Fatalf("Start(%s) failed: %s", s.Name, err)
   114  	}
   115  	waitState(t, s, svc.Running)
   116  	time.Sleep(1 * time.Second)
   117  
   118  	// testing deadlock from issues 4.
   119  	_, err = s.Control(svc.Interrogate)
   120  	if err != nil {
   121  		t.Fatalf("Control(%s) failed: %s", s.Name, err)
   122  	}
   123  	_, err = s.Control(svc.Interrogate)
   124  	if err != nil {
   125  		t.Fatalf("Control(%s) failed: %s", s.Name, err)
   126  	}
   127  	time.Sleep(1 * time.Second)
   128  
   129  	_, err = s.Control(svc.Stop)
   130  	if err != nil {
   131  		t.Fatalf("Control(%s) failed: %s", s.Name, err)
   132  	}
   133  	waitState(t, s, svc.Stopped)
   134  
   135  	err = s.Delete()
   136  	if err != nil {
   137  		t.Fatalf("Delete failed: %s", err)
   138  	}
   139  
   140  	out, err := exec.Command("wevtutil.exe", "qe", "Application", "/q:*[System[Provider[@Name='"+name+"']]]", "/rd:true", "/c:10").CombinedOutput()
   141  	if err != nil {
   142  		t.Fatalf("wevtutil failed: %v\n%v", err, string(out))
   143  	}
   144  	want := strings.Join(append([]string{name}, args...), "-")
   145  	// Test context passing (see servicemain in sys_386.s and sys_amd64.s).
   146  	want += "-123456"
   147  	if !strings.Contains(string(out), want) {
   148  		t.Errorf("%q string does not contain %q", out, want)
   149  	}
   150  }
   151  
   152  func TestIsAnInteractiveSession(t *testing.T) {
   153  	isInteractive, err := svc.IsAnInteractiveSession()
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	if !isInteractive {
   158  		t.Error("IsAnInteractiveSession returns false when running interactively.")
   159  	}
   160  }
   161  
   162  func TestIsWindowsService(t *testing.T) {
   163  	isSvc, err := svc.IsWindowsService()
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	if isSvc {
   168  		t.Error("IsWindowsService returns true when not running in a service.")
   169  	}
   170  }
   171  
   172  func TestIsWindowsServiceWhenParentExits(t *testing.T) {
   173  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" {
   174  		// in parent process
   175  
   176  		// Start the child and exit quickly.
   177  		child := exec.Command(os.Args[0], "-test.run=^TestIsWindowsServiceWhenParentExits$")
   178  		child.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=child")
   179  		err := child.Start()
   180  		if err != nil {
   181  			fmt.Fprintf(os.Stderr, fmt.Sprintf("child start failed: %v", err))
   182  			os.Exit(1)
   183  		}
   184  		os.Exit(0)
   185  	}
   186  
   187  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "child" {
   188  		// in child process
   189  		dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE")
   190  		if dumpPath == "" {
   191  			// We cannot report this error. But main test will notice
   192  			// that we did not create dump file.
   193  			os.Exit(1)
   194  		}
   195  		var msg string
   196  		isSvc, err := svc.IsWindowsService()
   197  		if err != nil {
   198  			msg = err.Error()
   199  		}
   200  		if isSvc {
   201  			msg = "IsWindowsService returns true when not running in a service."
   202  		}
   203  		err = os.WriteFile(dumpPath, []byte(msg), 0644)
   204  		if err != nil {
   205  			// We cannot report this error. But main test will notice
   206  			// that we did not create dump file.
   207  			os.Exit(2)
   208  		}
   209  		os.Exit(0)
   210  	}
   211  
   212  	// Run in a loop until it fails.
   213  	for i := 0; i < 10; i++ {
   214  		childDumpPath := filepath.Join(t.TempDir(), "issvc.txt")
   215  
   216  		parent := exec.Command(os.Args[0], "-test.run=^TestIsWindowsServiceWhenParentExits$")
   217  		parent.Env = append(os.Environ(),
   218  			"GO_WANT_HELPER_PROCESS=parent",
   219  			"GO_WANT_HELPER_PROCESS_FILE="+childDumpPath)
   220  		parentOutput, err := parent.CombinedOutput()
   221  		if err != nil {
   222  			t.Errorf("parent failed: %v: %v", err, string(parentOutput))
   223  		}
   224  		for i := 0; ; i++ {
   225  			if _, err := os.Stat(childDumpPath); err == nil {
   226  				break
   227  			}
   228  			time.Sleep(100 * time.Millisecond)
   229  			if i > 10 {
   230  				t.Fatal("timed out waiting for child output file to be created.")
   231  			}
   232  		}
   233  		childOutput, err := os.ReadFile(childDumpPath)
   234  		if err != nil {
   235  			t.Fatalf("reading child output failed: %v", err)
   236  		}
   237  		if got, want := string(childOutput), ""; got != want {
   238  			t.Fatalf("child output: want %q, got %q", want, got)
   239  		}
   240  	}
   241  }