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 }