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