From 4aba014eac0c11bd184364b506b3e15d5716e947 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Thu, 7 May 2026 00:50:47 +0900 Subject: [PATCH] container: abandon response on termination This prevents blocking on early failure. Signed-off-by: Ophestra --- container/container_test.go | 159 ++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 72 deletions(-) diff --git a/container/container_test.go b/container/container_test.go index 00a86b29..f34035b2 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -16,6 +16,7 @@ import ( "strings" "syscall" "testing" + "time" "unsafe" "hakurei.app/check" @@ -408,37 +409,6 @@ var containerTestCases = []struct { func TestContainer(t *testing.T) { t.Parallel() - t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { - wantErr := context.Canceled - wantExitCode := 0 - if err := c.Wait(); !reflect.DeepEqual(err, wantErr) { - if m, ok := container.InternalMessageFromError(err); ok { - t.Error(m) - } - t.Errorf("Wait: error = %#v, want %#v", err, wantErr) - } - if ps := c.ProcessState(); ps == nil { - t.Errorf("ProcessState unexpectedly returned nil") - } else if code := ps.ExitCode(); code != wantExitCode { - t.Errorf("ExitCode: %d, want %d", code, wantExitCode) - } - })) - - t.Run("forward", testContainerCancel(func(c *container.Container) { - c.ForwardCancel = true - }, func(t *testing.T, c *container.Container) { - var exitError *exec.ExitError - if err := c.Wait(); !errors.As(err, &exitError) { - if m, ok := container.InternalMessageFromError(err); ok { - t.Error(m) - } - t.Errorf("Wait: error = %v", err) - } - if code := exitError.ExitCode(); code != blockExitCodeInterrupt { - t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt) - } - })) - for i, tc := range containerTestCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -563,49 +533,94 @@ func hostnameFromTestCase(name string) string { } func testContainerCancel( + t *testing.T, containerExtra func(c *container.Container), - waitCheck func(t *testing.T, c *container.Container), -) func(t *testing.T) { - return func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(t.Context()) + waitCheck func(ps *os.ProcessState, waitErr error), +) { + t.Parallel() + ctx, cancel := context.WithCancel(t.Context()) - c := helperNewContainer(ctx, "block") - c.Stdout, c.Stderr = os.Stdout, os.Stderr - if containerExtra != nil { - containerExtra(c) - } - - ready := make(chan struct{}) - if r, w, err := os.Pipe(); err != nil { - t.Fatalf("cannot pipe: %v", err) - } else { - c.ExtraFiles = append(c.ExtraFiles, w) - go func() { - defer close(ready) - if _, err = r.Read(make([]byte, 1)); err != nil { - panic(err.Error()) - } - }() - } - - if err := c.Start(); err != nil { - if m, ok := container.InternalMessageFromError(err); ok { - t.Fatal(m) - } else { - t.Fatalf("cannot start container: %v", err) - } - } else if err = c.Serve(); err != nil { - if m, ok := container.InternalMessageFromError(err); ok { - t.Error(m) - } else { - t.Errorf("cannot serve setup params: %v", err) - } - } - <-ready - cancel() - waitCheck(t, c) + c := helperNewContainer(ctx, "block") + c.Stdout, c.Stderr = os.Stdout, os.Stderr + if containerExtra != nil { + containerExtra(c) } + + ready := make(chan struct{}) + var waitErr error + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("cannot pipe: %v", err) + } + + c.ExtraFiles = append(c.ExtraFiles, w) + go func() { + defer close(ready) + if _, _err := r.Read(make([]byte, 1)); _err != nil { + panic(_err) + } + }() + + if err = c.Start(); err != nil { + if m, ok := container.InternalMessageFromError(err); ok { + t.Fatal(m) + } else { + t.Fatalf("cannot start container: %v", err) + } + } + + done := make(chan struct{}) + go func() { + defer close(done) + waitErr = c.Wait() + _ = r.SetReadDeadline(time.Now()) + }() + + if err = c.Serve(); err != nil { + if m, ok := container.InternalMessageFromError(err); ok { + t.Error(m) + } else { + t.Errorf("cannot serve setup params: %v", err) + } + } + <-ready + cancel() + <-done + waitCheck(c.ProcessState(), waitErr) +} + +func TestForward(t *testing.T) { + testContainerCancel(t, func(c *container.Container) { + c.ForwardCancel = true + }, func(ps *os.ProcessState, waitErr error) { + var exitError *exec.ExitError + if !errors.As(waitErr, &exitError) { + if m, ok := container.InternalMessageFromError(waitErr); ok { + t.Error(m) + } + t.Errorf("Wait: error = %v", waitErr) + } + if code := exitError.ExitCode(); code != blockExitCodeInterrupt { + t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt) + } + }) +} + +func TestCancel(t *testing.T) { + testContainerCancel(t, nil, func(ps *os.ProcessState, waitErr error) { + wantErr := context.Canceled + if !reflect.DeepEqual(waitErr, wantErr) { + if m, ok := container.InternalMessageFromError(waitErr); ok { + t.Error(m) + } + t.Errorf("Wait: error = %#v, want %#v", waitErr, wantErr) + } + if ps == nil { + t.Errorf("ProcessState unexpectedly returned nil") + } else if code := ps.ExitCode(); code != 0 { + t.Errorf("ExitCode: %d, want %d", code, 0) + } + }) } func TestContainerString(t *testing.T) { @@ -827,7 +842,7 @@ func TestMain(m *testing.M) { } c.MustParse(os.Args[1:], func(err error) { if err != nil { - log.Fatal(err.Error()) + log.Fatal(err) } }) return