1
2
3
4
5
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
52
53 func stopAndDeleteIfInstalled(t *testing.T, m *mgr.Mgr, name string) {
54 s, err := m.OpenService(name)
55 if err != nil {
56
57 return
58
59 }
60 defer s.Close()
61
62
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
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
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
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
175
176
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
189 dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE")
190 if dumpPath == "" {
191
192
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
206
207 os.Exit(2)
208 }
209 os.Exit(0)
210 }
211
212
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 }
242
View as plain text