container: check output helper functions
All checks were successful
Test / Hakurei (race detector) (push) Successful in 5m17s
Test / Flake checks (push) Successful in 1m46s
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m28s
Test / Hpkg (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 4m35s
All checks were successful
Test / Hakurei (race detector) (push) Successful in 5m17s
Test / Flake checks (push) Successful in 1m46s
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m28s
Test / Hpkg (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 4m35s
The container test suite has always been somewhat inadequate due to the inability of coverage tooling to reach into containers. This has become an excuse for not testing non-container code as well, which lead to the general lack of confidence when working with container code. This change aims to be one of many to address that to some extent. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
9c1a5d43ba
commit
f35733810e
@ -20,7 +20,6 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/vfs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -207,20 +206,13 @@ var containerTestCases = []struct {
|
||||
}
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
{
|
||||
oldVerbose := hlog.Load()
|
||||
oldOutput := container.GetOutput()
|
||||
hlog.Store(testing.Verbose())
|
||||
container.SetOutput(hlog.Output{})
|
||||
t.Cleanup(func() { hlog.Store(oldVerbose) })
|
||||
t.Cleanup(func() { container.SetOutput(oldOutput) })
|
||||
}
|
||||
replaceOutput(t)
|
||||
|
||||
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||
wantErr := context.Canceled
|
||||
wantExitCode := 0
|
||||
if err := c.Wait(); !errors.Is(err, wantErr) {
|
||||
hlog.PrintBaseError(err, "wait:")
|
||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
||||
t.Errorf("Wait: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
if ps := c.ProcessState(); ps == nil {
|
||||
@ -235,7 +227,7 @@ func TestContainer(t *testing.T) {
|
||||
}, func(t *testing.T, c *container.Container) {
|
||||
var exitError *exec.ExitError
|
||||
if err := c.Wait(); !errors.As(err, &exitError) {
|
||||
hlog.PrintBaseError(err, "wait:")
|
||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
||||
t.Errorf("Wait: error = %v", err)
|
||||
}
|
||||
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
||||
@ -315,16 +307,16 @@ func TestContainer(t *testing.T) {
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
_, _ = output.WriteTo(os.Stdout)
|
||||
hlog.PrintBaseError(err, "start:")
|
||||
container.GetOutput().PrintBaseErr(err, "start:")
|
||||
t.Fatalf("cannot start container: %v", err)
|
||||
} else if err = c.Serve(); err != nil {
|
||||
_, _ = output.WriteTo(os.Stdout)
|
||||
hlog.PrintBaseError(err, "serve:")
|
||||
container.GetOutput().PrintBaseErr(err, "serve:")
|
||||
t.Errorf("cannot serve setup params: %v", err)
|
||||
}
|
||||
if err := c.Wait(); err != nil {
|
||||
_, _ = output.WriteTo(os.Stdout)
|
||||
hlog.PrintBaseError(err, "wait:")
|
||||
container.GetOutput().PrintBaseErr(err, "wait:")
|
||||
t.Fatalf("wait: %v", err)
|
||||
}
|
||||
})
|
||||
@ -378,10 +370,10 @@ func testContainerCancel(
|
||||
}
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
hlog.PrintBaseError(err, "start:")
|
||||
container.GetOutput().PrintBaseErr(err, "start:")
|
||||
t.Fatalf("cannot start container: %v", err)
|
||||
} else if err = c.Serve(); err != nil {
|
||||
hlog.PrintBaseError(err, "serve:")
|
||||
container.GetOutput().PrintBaseErr(err, "serve:")
|
||||
t.Errorf("cannot serve setup params: %v", err)
|
||||
}
|
||||
<-ready
|
||||
|
139
container/msg_test.go
Normal file
139
container/msg_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package container_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func TestDefaultMsg(t *testing.T) {
|
||||
{
|
||||
w := log.Writer()
|
||||
f := log.Flags()
|
||||
t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) })
|
||||
}
|
||||
msg := new(container.DefaultMsg)
|
||||
|
||||
t.Run("is verbose", func(t *testing.T) {
|
||||
if !msg.IsVerbose() {
|
||||
t.Error("IsVerbose unexpected outcome")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("verbose", func(t *testing.T) {
|
||||
log.SetOutput(panicWriter{})
|
||||
msg.Suspend()
|
||||
msg.Verbose()
|
||||
msg.Verbosef("\x00")
|
||||
msg.Resume()
|
||||
|
||||
buf := new(strings.Builder)
|
||||
log.SetOutput(buf)
|
||||
log.SetFlags(0)
|
||||
msg.Verbose()
|
||||
msg.Verbosef("\x00")
|
||||
|
||||
want := "\n\x00\n"
|
||||
if buf.String() != want {
|
||||
t.Errorf("Verbose: %q, want %q", buf.String(), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrapErr", func(t *testing.T) {
|
||||
buf := new(strings.Builder)
|
||||
log.SetOutput(buf)
|
||||
log.SetFlags(0)
|
||||
if err := msg.WrapErr(syscall.EBADE, "\x00", "\x00"); err != syscall.EBADE {
|
||||
t.Errorf("WrapErr: %v", err)
|
||||
}
|
||||
msg.PrintBaseErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:")
|
||||
|
||||
want := "\x00 \x00\ncannot cuddle cat: state not recoverable\n"
|
||||
if buf.String() != want {
|
||||
t.Errorf("WrapErr: %q, want %q", buf.String(), want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inactive", func(t *testing.T) {
|
||||
{
|
||||
inactive := msg.Resume()
|
||||
if inactive {
|
||||
t.Cleanup(func() { msg.Suspend() })
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Resume() {
|
||||
t.Error("Resume unexpected outcome")
|
||||
}
|
||||
|
||||
msg.Suspend()
|
||||
if !msg.Resume() {
|
||||
t.Error("Resume unexpected outcome")
|
||||
}
|
||||
})
|
||||
|
||||
// the function is a noop
|
||||
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
|
||||
}
|
||||
|
||||
type panicWriter struct{}
|
||||
|
||||
func (panicWriter) Write([]byte) (int, error) { panic("unreachable") }
|
||||
|
||||
func saveRestoreOutput(t *testing.T) {
|
||||
out := container.GetOutput()
|
||||
t.Cleanup(func() { container.SetOutput(out) })
|
||||
}
|
||||
|
||||
func replaceOutput(t *testing.T) {
|
||||
saveRestoreOutput(t)
|
||||
container.SetOutput(&testOutput{t: t})
|
||||
}
|
||||
|
||||
type testOutput struct {
|
||||
t *testing.T
|
||||
suspended atomic.Bool
|
||||
}
|
||||
|
||||
func (out *testOutput) IsVerbose() bool { return testing.Verbose() }
|
||||
|
||||
func (out *testOutput) Verbose(v ...any) {
|
||||
if !out.IsVerbose() {
|
||||
return
|
||||
}
|
||||
out.t.Log(v...)
|
||||
}
|
||||
|
||||
func (out *testOutput) Verbosef(format string, v ...any) {
|
||||
if !out.IsVerbose() {
|
||||
return
|
||||
}
|
||||
out.t.Logf(format, v...)
|
||||
}
|
||||
|
||||
func (out *testOutput) WrapErr(err error, a ...any) error { return hlog.WrapErr(err, a...) }
|
||||
func (out *testOutput) PrintBaseErr(err error, fallback string) { hlog.PrintBaseError(err, fallback) }
|
||||
|
||||
func (out *testOutput) Suspend() {
|
||||
if out.suspended.CompareAndSwap(false, true) {
|
||||
out.Verbose("suspend called")
|
||||
return
|
||||
}
|
||||
out.Verbose("suspend called on suspended output")
|
||||
}
|
||||
|
||||
func (out *testOutput) Resume() bool {
|
||||
if out.suspended.CompareAndSwap(true, false) {
|
||||
out.Verbose("resume called")
|
||||
return true
|
||||
}
|
||||
out.Verbose("resume called on unsuspended output")
|
||||
return false
|
||||
}
|
||||
|
||||
func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") }
|
110
container/output_test.go
Normal file
110
container/output_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSetOutput(t *testing.T) {
|
||||
{
|
||||
out := GetOutput()
|
||||
t.Cleanup(func() { SetOutput(out) })
|
||||
}
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
SetOutput(new(stubOutput))
|
||||
if v, ok := GetOutput().(*DefaultMsg); ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", v)
|
||||
}
|
||||
SetOutput(nil)
|
||||
if _, ok := GetOutput().(*DefaultMsg); !ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", GetOutput())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("stub", func(t *testing.T) {
|
||||
SetOutput(new(stubOutput))
|
||||
if _, ok := GetOutput().(*stubOutput); !ok {
|
||||
t.Fatalf("SetOutput: got unexpected output %#v", GetOutput())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapErr(t *testing.T) {
|
||||
{
|
||||
out := GetOutput()
|
||||
t.Cleanup(func() { SetOutput(out) })
|
||||
}
|
||||
|
||||
var wrapFp *func(error, ...any) error
|
||||
s := new(stubOutput)
|
||||
SetOutput(s)
|
||||
wrapFp = &s.wrapF
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
f func(t *testing.T)
|
||||
wantErr error
|
||||
wantA []any
|
||||
}{
|
||||
{"suffix nil", func(t *testing.T) {
|
||||
if err := wrapErrSuffix(nil, "\x00"); err != nil {
|
||||
t.Errorf("wrapErrSuffix: %v", err)
|
||||
}
|
||||
}, nil, nil},
|
||||
{"suffix val", func(t *testing.T) {
|
||||
if err := wrapErrSuffix(syscall.ENOTRECOVERABLE, "\x00\x00"); err != syscall.ENOTRECOVERABLE {
|
||||
t.Errorf("wrapErrSuffix: %v", err)
|
||||
}
|
||||
}, syscall.ENOTRECOVERABLE, []any{"\x00\x00", syscall.ENOTRECOVERABLE}},
|
||||
{"self nil", func(t *testing.T) {
|
||||
if err := wrapErrSelf(nil); err != nil {
|
||||
t.Errorf("wrapErrSelf: %v", err)
|
||||
}
|
||||
}, nil, nil},
|
||||
{"self val", func(t *testing.T) {
|
||||
if err := wrapErrSelf(syscall.ENOTRECOVERABLE); err != syscall.ENOTRECOVERABLE {
|
||||
t.Errorf("wrapErrSelf: %v", err)
|
||||
}
|
||||
}, syscall.ENOTRECOVERABLE, []any{"state not recoverable"}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
gotErr error
|
||||
gotA []any
|
||||
)
|
||||
*wrapFp = func(err error, a ...any) error { gotErr = err; gotA = a; return err }
|
||||
|
||||
tc.f(t)
|
||||
if gotErr != tc.wantErr {
|
||||
t.Errorf("WrapErr: err = %v, want %v", gotErr, tc.wantErr)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(gotA, tc.wantA) {
|
||||
t.Errorf("WrapErr: a = %v, want %v", gotA, tc.wantA)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubOutput struct {
|
||||
wrapF func(error, ...any) error
|
||||
}
|
||||
|
||||
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
|
||||
func (*stubOutput) Verbose(...any) { panic("unreachable") }
|
||||
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
|
||||
func (*stubOutput) PrintBaseErr(error, string) { panic("unreachable") }
|
||||
func (*stubOutput) Suspend() { panic("unreachable") }
|
||||
func (*stubOutput) Resume() bool { panic("unreachable") }
|
||||
func (*stubOutput) BeforeExit() { panic("unreachable") }
|
||||
|
||||
func (s *stubOutput) WrapErr(err error, v ...any) error {
|
||||
if s.wrapF == nil {
|
||||
panic("unreachable")
|
||||
}
|
||||
return s.wrapF(err, v...)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user