Skip to content

Commit 112f790

Browse files
committed
Add a ProcessManager interface for managing processes by pid file
This commit does the following: - Adds ProcessManager interface and Process struct - Read and write to a pidFile specified by the user - Adds tests for process lifecycle with a dummy process
1 parent 97c5364 commit 112f790

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

pkg/process/process.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package process
2+
3+
import (
4+
"os"
5+
"strconv"
6+
"strings"
7+
"syscall"
8+
9+
"github.com/pkg/errors"
10+
)
11+
12+
type ProcessManager interface {
13+
Pid() (int, error)
14+
Name() string
15+
PidFilePath() string
16+
Exists() bool
17+
Kill() error
18+
FindProcess() (*os.Process, error)
19+
WritePidFile(pid int) error
20+
}
21+
22+
type Process struct {
23+
name string
24+
pidFilePath string
25+
}
26+
27+
func New(name, pidFilePath string) (*Process, error) {
28+
return &Process{name: name, pidFilePath: pidFilePath}, nil
29+
}
30+
31+
func (p *Process) Name() string {
32+
return p.name
33+
}
34+
35+
func (p *Process) PidFilePath() string {
36+
return p.pidFilePath
37+
}
38+
39+
func (p *Process) Pid() (int, error) {
40+
_, err := os.Stat(p.PidFilePath())
41+
if err != nil {
42+
return -1, err
43+
}
44+
data, err := os.ReadFile(p.PidFilePath())
45+
if err != nil {
46+
return -1, err
47+
}
48+
pidStr := strings.TrimSpace(string(data))
49+
pid, err := strconv.Atoi(pidStr)
50+
return pid, err
51+
}
52+
53+
func (p *Process) FindProcess() (*os.Process, error) {
54+
pid, err := p.Pid()
55+
if err != nil {
56+
return nil, errors.Wrap(err, "cannot find process")
57+
}
58+
proc, err := os.FindProcess(pid)
59+
if err != nil {
60+
return nil, errors.Wrap(err, "cannot find process")
61+
}
62+
return proc, nil
63+
}
64+
65+
func (p *Process) Exists() bool {
66+
proc, err := p.FindProcess()
67+
if err != nil {
68+
return false
69+
}
70+
if err := proc.Signal(syscall.Signal(0)); err != nil {
71+
return false
72+
}
73+
return true
74+
}
75+
76+
func (p *Process) Kill() error {
77+
proc, err := p.FindProcess()
78+
if err != nil {
79+
return errors.Wrap(err, "cannot find process")
80+
}
81+
if err := proc.Signal(syscall.SIGKILL); err != nil {
82+
return errors.Wrap(err, "cannot kill process")
83+
}
84+
_, err = proc.Wait()
85+
if err != nil {
86+
return errors.Wrap(err, "failed to wait for process termination")
87+
}
88+
return nil
89+
}
90+
91+
func (p *Process) WritePidFile(pid int) error {
92+
return os.WriteFile(p.pidFilePath, []byte(strconv.Itoa(pid)), 0644)
93+
}

pkg/process/process_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package process
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
const (
14+
dummyProcessName = "sleep"
15+
dummyProcessArgs = "60"
16+
)
17+
18+
var (
19+
dummyProcess *exec.Cmd
20+
managedProcess *Process
21+
pidFilePath = filepath.Join(os.TempDir(), "pid")
22+
)
23+
24+
func startDummyProcess() error {
25+
dummyProcess = exec.Command(dummyProcessName, dummyProcessArgs)
26+
err := dummyProcess.Start()
27+
if err != nil {
28+
return err
29+
}
30+
return nil
31+
}
32+
33+
func TestMain(m *testing.M) {
34+
err := startDummyProcess()
35+
if err != nil {
36+
fmt.Fprintln(os.Stderr, "Failed to start process:", err)
37+
os.Exit(1)
38+
}
39+
40+
managedProcess, err = New(dummyProcessName, pidFilePath)
41+
if err != nil {
42+
fmt.Fprintln(os.Stderr, "Failed to create process:", err)
43+
os.Exit(1)
44+
}
45+
err = managedProcess.WritePidFile(dummyProcess.Process.Pid)
46+
if err != nil {
47+
fmt.Fprintln(os.Stderr, err)
48+
os.Exit(1)
49+
}
50+
exitCode := m.Run()
51+
if dummyProcess.Process != nil {
52+
_ = dummyProcess.Process.Kill()
53+
}
54+
55+
os.Exit(exitCode)
56+
}
57+
58+
func TestProcess_Name(t *testing.T) {
59+
assert.Equal(t, dummyProcessName, managedProcess.Name())
60+
}
61+
62+
func TestProcess_FindProcess(t *testing.T) {
63+
foundProcess, err := managedProcess.FindProcess()
64+
assert.NoError(t, err)
65+
assert.NotNil(t, foundProcess)
66+
assert.Equal(t, dummyProcess.Process.Pid, foundProcess.Pid)
67+
68+
assert.True(t, managedProcess.Exists())
69+
}
70+
71+
func TestProcess_KillProcess(t *testing.T) {
72+
err := managedProcess.Kill()
73+
assert.NoError(t, err)
74+
75+
assert.False(t, managedProcess.Exists())
76+
}

0 commit comments

Comments
 (0)