174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package devserver
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestManagedProcessStopsAndWaits(t *testing.T) {
|
|
command := exec.Command(os.Args[0], "-test.run=TestManagedProcessHelper$")
|
|
command.Env = append(os.Environ(), "HIMESAN_PROCESS_HELPER=1")
|
|
candidate, err := startManagedProcess(command, "127.0.0.1:1", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
if err := candidate.stop(ctx); err != nil {
|
|
t.Fatalf("stop() error = %v", err)
|
|
}
|
|
if !candidate.hasExited() {
|
|
t.Fatal("candidate process was not reaped")
|
|
}
|
|
}
|
|
|
|
func TestManagedProcessStopsDescendantTree(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("helper-process integration test")
|
|
}
|
|
directory := t.TempDir()
|
|
gatePath := filepath.Join(directory, "start-child")
|
|
readyPath := filepath.Join(directory, "child-address")
|
|
command := exec.Command(os.Args[0], "-test.run=TestManagedProcessHelper$")
|
|
command.Env = append(os.Environ(),
|
|
"HIMESAN_PROCESS_HELPER=tree-parent",
|
|
"HIMESAN_PROCESS_GATE="+gatePath,
|
|
"HIMESAN_PROCESS_READY="+readyPath,
|
|
)
|
|
candidate, err := startManagedProcess(command, "127.0.0.1:1", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(gatePath, []byte("start"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
address := waitForChildAddress(t, readyPath)
|
|
waitForChildListener(t, address)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
if err := candidate.stop(ctx); err != nil {
|
|
t.Fatalf("stop() error = %v", err)
|
|
}
|
|
if !candidate.hasExited() {
|
|
t.Fatal("candidate root process was not reaped")
|
|
}
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for {
|
|
connection, dialErr := net.DialTimeout("tcp", address, 100*time.Millisecond)
|
|
if dialErr != nil {
|
|
break
|
|
}
|
|
_ = connection.Close()
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("managed descendant still accepts connections at %s", address)
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func TestTaskkillArguments(t *testing.T) {
|
|
t.Parallel()
|
|
if got, want := taskkillArguments(42, false), []string{"/PID", "42", "/T"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("taskkillArguments(graceful) = %q, want %q", got, want)
|
|
}
|
|
if got, want := taskkillArguments(42, true), []string{"/PID", "42", "/T", "/F"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("taskkillArguments(force) = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestManagedProcessHelper(t *testing.T) {
|
|
switch os.Getenv("HIMESAN_PROCESS_HELPER") {
|
|
case "":
|
|
return
|
|
case "tree-parent":
|
|
runTreeParentHelper()
|
|
case "tree-child":
|
|
runTreeChildHelper()
|
|
}
|
|
signals := make(chan os.Signal, 1)
|
|
signal.Notify(signals)
|
|
<-signals
|
|
os.Exit(0)
|
|
}
|
|
|
|
func runTreeParentHelper() {
|
|
gatePath := os.Getenv("HIMESAN_PROCESS_GATE")
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
if _, err := os.Stat(gatePath); err == nil {
|
|
break
|
|
}
|
|
if time.Now().After(deadline) {
|
|
os.Exit(2)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
child := exec.Command(os.Args[0], "-test.run=TestManagedProcessHelper$")
|
|
child.Env = replaceEnvironment(os.Environ(), "HIMESAN_PROCESS_HELPER", "tree-child")
|
|
if err := child.Start(); err != nil {
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func runTreeChildHelper() {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
os.Exit(2)
|
|
}
|
|
defer listener.Close()
|
|
if err := os.WriteFile(os.Getenv("HIMESAN_PROCESS_READY"), []byte(listener.Addr().String()), 0o600); err != nil {
|
|
os.Exit(2)
|
|
}
|
|
for {
|
|
connection, acceptErr := listener.Accept()
|
|
if acceptErr != nil {
|
|
os.Exit(0)
|
|
}
|
|
_ = connection.Close()
|
|
}
|
|
}
|
|
|
|
func waitForChildAddress(t *testing.T, readyPath string) string {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
contents, err := os.ReadFile(readyPath)
|
|
if err == nil && strings.TrimSpace(string(contents)) != "" {
|
|
return strings.TrimSpace(string(contents))
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("managed descendant did not report its address: %v", err)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func waitForChildListener(t *testing.T, address string) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
var lastErr error
|
|
for {
|
|
connection, err := net.DialTimeout("tcp", address, 100*time.Millisecond)
|
|
if err == nil {
|
|
_ = connection.Close()
|
|
return
|
|
}
|
|
lastErr = err
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("managed descendant did not accept a connection at %s: %v", address, lastErr)
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
}
|