diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-09-27 19:46:35 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-09-27 19:46:35 +0900 |
| commit | e906cae9ee0fd8b7b35c4a71af8ba86e60dd3c24 (patch) | |
| tree | 71279c229d9ba92d6e239be0761d01586fa94021 /container/output_test.go | |
| parent | ae2df2c450dd655b93a950124b6ab16e2b4d967b (diff) | |
container/output: export suspendable writer
This is quite useful for other packages as well. This change prepares internal/hlog for removal.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/output_test.go')
| -rw-r--r-- | container/output_test.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/container/output_test.go b/container/output_test.go index 85ef756f..54eba8fe 100644 --- a/container/output_test.go +++ b/container/output_test.go @@ -1,9 +1,155 @@ package container import ( + "bytes" + "errors" + "reflect" + "strconv" + "syscall" "testing" + + "hakurei.app/container/stub" ) +func TestSuspendable(t *testing.T) { + const ( + // equivalent to len(want.pt) + nSpecialPtEquiv = -iota - 1 + // equivalent to len(want.w) + nSpecialWEquiv + // suspends writer before executing test case, implies nSpecialWEquiv + nSpecialSuspend + // offset: resume writer and measure against dump instead, implies nSpecialPtEquiv + nSpecialDump + ) + + // shares the same writer + testCases := []struct { + name string + w, pt []byte + err error + wantErr error + n int + }{ + {"simple", []byte{0xde, 0xad, 0xbe, 0xef}, []byte{0xde, 0xad, 0xbe, 0xef}, + nil, nil, nSpecialPtEquiv}, + + {"error", []byte{0xb, 0xad}, []byte{0xb, 0xad}, + stub.UniqueError(0), stub.UniqueError(0), nSpecialPtEquiv}, + + {"suspend short", []byte{0}, nil, + nil, nil, nSpecialSuspend}, + {"sw short 0", []byte{0xca, 0xfe, 0xba, 0xbe}, nil, + nil, nil, nSpecialWEquiv}, + {"sw short 1", []byte{0xff}, nil, + nil, nil, nSpecialWEquiv}, + {"resume short", nil, []byte{0, 0xca, 0xfe, 0xba, 0xbe, 0xff}, nil, nil, + nSpecialDump}, + + {"long pt", bytes.Repeat([]byte{0xff}, suspendBufMax+1), bytes.Repeat([]byte{0xff}, suspendBufMax+1), + nil, nil, nSpecialPtEquiv}, + + {"suspend fill", bytes.Repeat([]byte{0xfe}, suspendBufMax), nil, + nil, nil, nSpecialSuspend}, + {"drop", []byte{0}, nil, + nil, syscall.ENOMEM, 0}, + {"drop error", []byte{0}, nil, + stub.UniqueError(1), syscall.ENOMEM, 0}, + {"resume fill", nil, bytes.Repeat([]byte{0xfe}, suspendBufMax), + nil, nil, nSpecialDump - 2}, + + {"suspend fill partial", bytes.Repeat([]byte{0xfd}, suspendBufMax-0xf), nil, + nil, nil, nSpecialSuspend}, + {"partial write", bytes.Repeat([]byte{0xad}, 0x1f), nil, + nil, syscall.ENOMEM, 0xf}, + {"full drop", []byte{0}, nil, + nil, syscall.ENOMEM, 0}, + {"resume fill partial", nil, append(bytes.Repeat([]byte{0xfd}, suspendBufMax-0xf), bytes.Repeat([]byte{0xad}, 0xf)...), + nil, nil, nSpecialDump - 0x10 - 1}, + } + + var dw expectWriter + + w := Suspendable{Downstream: &dw} + for _, tc := range testCases { + // these share the same writer, so cannot be subtests + t.Logf("writing step %q", tc.name) + dw.expect, dw.err = tc.pt, tc.err + + var ( + gotN int + gotErr error + ) + + wantN := tc.n + switch wantN { + case nSpecialPtEquiv: + wantN = len(tc.pt) + gotN, gotErr = w.Write(tc.w) + + case nSpecialWEquiv: + wantN = len(tc.w) + gotN, gotErr = w.Write(tc.w) + + case nSpecialSuspend: + s := w.IsSuspended() + if ok := w.Suspend(); s && ok { + t.Fatal("Suspend: unexpected success") + } + + wantN = len(tc.w) + gotN, gotErr = w.Write(tc.w) + + default: + if wantN <= nSpecialDump { + if !w.IsSuspended() { + t.Fatal("IsSuspended unexpected false") + } + + resumed, dropped, n, err := w.Resume() + if !resumed { + t.Fatal("Resume: resumed = false") + } + if wantDropped := nSpecialDump - wantN; int(dropped) != wantDropped { + t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped) + } + + wantN = len(tc.pt) + gotN, gotErr = int(n), err + } else { + gotN, gotErr = w.Write(tc.w) + } + } + + if gotN != wantN { + t.Errorf("Write: n = %d, want %d", gotN, wantN) + } + + if !reflect.DeepEqual(gotErr, tc.wantErr) { + t.Errorf("Write: %v", gotErr) + } + } +} + +// expectWriter compares Write calls to expect. +type expectWriter struct { + expect []byte + err error +} + +func (w *expectWriter) Write(p []byte) (n int, err error) { + defer func() { w.expect = nil }() + + n, err = len(p), w.err + if w.expect == nil { + return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p))) + } + if string(p) != string(w.expect) { + return 0, errors.New("p = " + strconv.Quote(string(p)) + ", want " + strconv.Quote(string(w.expect))) + } + return +} + func TestGetSetOutput(t *testing.T) { { out := GetOutput() |
