| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build windows |
| |
| package svc_test |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| "testing" |
| "time" |
| |
| "golang.org/x/sys/windows/svc" |
| "golang.org/x/sys/windows/svc/mgr" |
| ) |
| |
| func getState(t *testing.T, s *mgr.Service) svc.State { |
| status, err := s.Query() |
| if err != nil { |
| t.Fatalf("Query(%s) failed: %s", s.Name, err) |
| } |
| return status.State |
| } |
| |
| func testState(t *testing.T, s *mgr.Service, want svc.State) { |
| have := getState(t, s) |
| if have != want { |
| t.Fatalf("%s state is=%d want=%d", s.Name, have, want) |
| } |
| } |
| |
| func waitState(t *testing.T, s *mgr.Service, want svc.State) { |
| for i := 0; ; i++ { |
| have := getState(t, s) |
| if have == want { |
| return |
| } |
| if i > 10 { |
| t.Fatalf("%s state is=%d, waiting timeout", s.Name, have) |
| } |
| time.Sleep(300 * time.Millisecond) |
| } |
| } |
| |
| // stopAndDeleteIfInstalled stops and deletes service name, |
| // if the service is running and / or installed. |
| func stopAndDeleteIfInstalled(t *testing.T, m *mgr.Mgr, name string) { |
| s, err := m.OpenService(name) |
| if err != nil { |
| // Service is not installed. |
| return |
| |
| } |
| defer s.Close() |
| |
| // Make sure the service is not running, otherwise we won't be able to delete it. |
| if getState(t, s) == svc.Running { |
| _, err = s.Control(svc.Stop) |
| if err != nil { |
| t.Fatalf("Control(%s) failed: %s", s.Name, err) |
| } |
| waitState(t, s, svc.Stopped) |
| } |
| |
| err = s.Delete() |
| if err != nil { |
| t.Fatalf("Delete failed: %s", err) |
| } |
| } |
| |
| func TestExample(t *testing.T) { |
| if os.Getenv("GO_BUILDER_NAME") == "" { |
| // Don't install services on arbitrary users' machines. |
| t.Skip("skipping test that modifies system services: GO_BUILDER_NAME not set") |
| } |
| if testing.Short() { |
| t.Skip("skipping test in short mode that modifies system services") |
| } |
| |
| const name = "svctestservice" |
| |
| m, err := mgr.Connect() |
| if err != nil { |
| t.Fatalf("SCM connection failed: %s", err) |
| } |
| defer m.Disconnect() |
| |
| exepath := filepath.Join(t.TempDir(), "a.exe") |
| o, err := exec.Command("go", "build", "-o", exepath, "golang.org/x/sys/windows/svc/example").CombinedOutput() |
| if err != nil { |
| t.Fatalf("failed to build service program: %v\n%v", err, string(o)) |
| } |
| |
| stopAndDeleteIfInstalled(t, m, name) |
| |
| s, err := m.CreateService(name, exepath, mgr.Config{DisplayName: "x-sys svc test service"}, "-name", name) |
| if err != nil { |
| t.Fatalf("CreateService(%s) failed: %v", name, err) |
| } |
| defer s.Close() |
| |
| args := []string{"is", "manual-started", fmt.Sprintf("%d", rand.Int())} |
| |
| testState(t, s, svc.Stopped) |
| err = s.Start(args...) |
| if err != nil { |
| t.Fatalf("Start(%s) failed: %s", s.Name, err) |
| } |
| waitState(t, s, svc.Running) |
| time.Sleep(1 * time.Second) |
| |
| // testing deadlock from issues 4. |
| _, err = s.Control(svc.Interrogate) |
| if err != nil { |
| t.Fatalf("Control(%s) failed: %s", s.Name, err) |
| } |
| _, err = s.Control(svc.Interrogate) |
| if err != nil { |
| t.Fatalf("Control(%s) failed: %s", s.Name, err) |
| } |
| time.Sleep(1 * time.Second) |
| |
| _, err = s.Control(svc.Stop) |
| if err != nil { |
| t.Fatalf("Control(%s) failed: %s", s.Name, err) |
| } |
| waitState(t, s, svc.Stopped) |
| |
| err = s.Delete() |
| if err != nil { |
| t.Fatalf("Delete failed: %s", err) |
| } |
| |
| out, err := exec.Command("wevtutil.exe", "qe", "Application", "/q:*[System[Provider[@Name='"+name+"']]]", "/rd:true", "/c:10").CombinedOutput() |
| if err != nil { |
| t.Fatalf("wevtutil failed: %v\n%v", err, string(out)) |
| } |
| want := strings.Join(append([]string{name}, args...), "-") |
| // Test context passing (see servicemain in sys_386.s and sys_amd64.s). |
| want += "-123456" |
| if !strings.Contains(string(out), want) { |
| t.Errorf("%q string does not contain %q", out, want) |
| } |
| } |
| |
| func TestIsAnInteractiveSession(t *testing.T) { |
| isInteractive, err := svc.IsAnInteractiveSession() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !isInteractive { |
| t.Error("IsAnInteractiveSession returns false when running interactively.") |
| } |
| } |
| |
| func TestIsWindowsService(t *testing.T) { |
| isSvc, err := svc.IsWindowsService() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if isSvc { |
| t.Error("IsWindowsService returns true when not running in a service.") |
| } |
| } |
| |
| func TestIsWindowsServiceWhenParentExits(t *testing.T) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" { |
| // in parent process |
| |
| // Start the child and exit quickly. |
| child := exec.Command(os.Args[0], "-test.run=^TestIsWindowsServiceWhenParentExits$") |
| child.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=child") |
| err := child.Start() |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "child start failed: %v", err) |
| os.Exit(1) |
| } |
| os.Exit(0) |
| } |
| |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "child" { |
| // in child process |
| dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE") |
| if dumpPath == "" { |
| // We cannot report this error. But main test will notice |
| // that we did not create dump file. |
| os.Exit(1) |
| } |
| var msg string |
| isSvc, err := svc.IsWindowsService() |
| if err != nil { |
| msg = err.Error() |
| } |
| if isSvc { |
| msg = "IsWindowsService returns true when not running in a service." |
| } |
| err = os.WriteFile(dumpPath, []byte(msg), 0644) |
| if err != nil { |
| // We cannot report this error. But main test will notice |
| // that we did not create dump file. |
| os.Exit(2) |
| } |
| os.Exit(0) |
| } |
| |
| // Run in a loop until it fails. |
| for i := 0; i < 10; i++ { |
| childDumpPath := filepath.Join(t.TempDir(), "issvc.txt") |
| |
| parent := exec.Command(os.Args[0], "-test.run=^TestIsWindowsServiceWhenParentExits$") |
| parent.Env = append(os.Environ(), |
| "GO_WANT_HELPER_PROCESS=parent", |
| "GO_WANT_HELPER_PROCESS_FILE="+childDumpPath) |
| parentOutput, err := parent.CombinedOutput() |
| if err != nil { |
| t.Errorf("parent failed: %v: %v", err, string(parentOutput)) |
| } |
| for i := 0; ; i++ { |
| if _, err := os.Stat(childDumpPath); err == nil { |
| break |
| } |
| time.Sleep(100 * time.Millisecond) |
| if i > 10 { |
| t.Fatal("timed out waiting for child output file to be created.") |
| } |
| } |
| childOutput, err := os.ReadFile(childDumpPath) |
| if err != nil { |
| t.Fatalf("reading child output failed: %v", err) |
| } |
| if got, want := string(childOutput), ""; got != want { |
| t.Fatalf("child output: want %q, got %q", want, got) |
| } |
| } |
| } |