helper: stub helper for tests

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-09-29 14:40:01 +09:00
parent 0e7849fac2
commit d530a9e9f9
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
3 changed files with 108 additions and 2 deletions

18
helper/export_test.go Normal file
View File

@ -0,0 +1,18 @@
package helper
import (
"os"
"os/exec"
"testing"
)
// replace execCommand to have the resulting *exec.Cmd launch TestHelperChildStub
func ReplaceExecCommand(t *testing.T) {
t.Cleanup(func() {
execCommand = exec.Command
})
execCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command(os.Args[0], append([]string{"-test.run=TestHelperChildStub", "--", name}, arg...)...)
}
}

View File

@ -16,6 +16,11 @@ var (
ErrStatusRead = errors.New("unexpected status response") ErrStatusRead = errors.New("unexpected status response")
) )
const (
FortifyHelper = "FORTIFY_HELPER"
FortifyStatus = "FORTIFY_STATUS"
)
// Helper wraps *exec.Cmd and manages status and args fd. // Helper wraps *exec.Cmd and manages status and args fd.
// Args is always 3 and status if set is always 4. // Args is always 3 and status if set is always 4.
type Helper struct { type Helper struct {
@ -52,15 +57,20 @@ func (h *Helper) StartNotify(ready chan error) error {
h.argsP[0], h.argsP[1] = pr, pw h.argsP[0], h.argsP[1] = pr, pw
} }
// create status pipes if ready signal is requested // create status pipes if ready signal is requested
var sv string
if ready != nil { if ready != nil {
if pr, pw, err := os.Pipe(); err != nil { if pr, pw, err := os.Pipe(); err != nil {
return err return err
} else { } else {
h.statP[0], h.statP[1] = pr, pw h.statP[0], h.statP[1] = pr, pw
} }
sv = FortifyStatus + "=1"
} else {
sv = FortifyStatus + "=0"
} }
// prepare extra files // prepare extra files from caller
el := len(h.ExtraFiles) el := len(h.ExtraFiles)
if ready != nil { if ready != nil {
el += 2 el += 2
@ -76,6 +86,7 @@ func (h *Helper) StartNotify(ready chan error) error {
// prepare and start process // prepare and start process
h.Cmd.ExtraFiles = ef h.Cmd.ExtraFiles = ef
h.Cmd.Env = append(h.Cmd.Env, FortifyHelper+"=1", sv)
if err := h.Cmd.Start(); err != nil { if err := h.Cmd.Start(); err != nil {
return err return err
} }
@ -175,10 +186,12 @@ func (h *Helper) Start() error {
return h.StartNotify(nil) return h.StartNotify(nil)
} }
var execCommand = exec.Command
func New(wt io.WriterTo, name string, arg ...string) *Helper { func New(wt io.WriterTo, name string, arg ...string) *Helper {
if wt == nil { if wt == nil {
panic("attempted to create helper with invalid argument writer") panic("attempted to create helper with invalid argument writer")
} }
return &Helper{args: wt, Cmd: exec.Command(name, arg...)} return &Helper{args: wt, Cmd: execCommand(name, arg...)}
} }

75
helper/stub_test.go Normal file
View File

@ -0,0 +1,75 @@
package helper_test
import (
"io"
"os"
"strconv"
"syscall"
"testing"
"git.ophivana.moe/cat/fortify/helper"
)
func TestHelperChildStub(t *testing.T) {
// this test mocks the helper process
if os.Getenv(helper.FortifyHelper) != "1" {
return
}
// simulate args pipe behaviour
func() {
f := os.NewFile(3, "|0")
if f == nil {
panic("attempted to start helper without args pipe")
}
if _, err := io.Copy(os.Stdout, f); err != nil {
panic("cannot read args: " + err.Error())
}
}()
var wait chan struct{}
// simulate status pipe behaviour
if os.Getenv(helper.FortifyStatus) == "1" {
wait = make(chan struct{})
go func() {
f := os.NewFile(4, "|1")
if f == nil {
panic("attempted to start with status reporting without status pipe")
}
if _, err := f.Write([]byte{'x'}); err != nil {
panic("cannot write to status pipe: " + err.Error())
}
// wait for status pipe close
var epoll int
if fd, err := syscall.EpollCreate1(0); err != nil {
panic("cannot open epoll fd: " + err.Error())
} else {
defer func() {
if err = syscall.Close(fd); err != nil {
panic("cannot close epoll fd: " + err.Error())
}
}()
epoll = fd
}
if err := syscall.EpollCtl(epoll, syscall.EPOLL_CTL_ADD, int(f.Fd()), &syscall.EpollEvent{}); err != nil {
panic("cannot add status pipe to epoll: " + err.Error())
}
events := make([]syscall.EpollEvent, 1)
if _, err := syscall.EpollWait(epoll, events, -1); err != nil {
panic("cannot poll status pipe: " + err.Error())
}
if events[0].Events != syscall.EPOLLERR {
panic(strconv.Itoa(int(events[0].Events)))
}
close(wait)
}()
}
if wait != nil {
<-wait
}
}