mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-06-13 15:47:38 +08:00
improve: MergeCtx 支持 cause 传播, 使用 WithCancelCause/WithDeadlineCause
- 内部改用 context.WithCancelCause 和 WithDeadlineCause, 父 context 取消原因自动传播 - Value() 先检查嵌入 context 再查 parents, 确保 context.Cause() 正确工作 - Done()/Err() 同时监听 cancelCtx 和 deadlineCtx, 支持 deadline 到期 cause - 新增 Cause() 便捷方法 - 单 parent 短路径改用 WithCancelCause 保留 cause - 新增 mergectx_test.go, 覆盖 cause 传播、deadline、Value 查找等场景 - API 兼容: 返回类型保持 CancelFunc 不变 Alina Agent生成
This commit is contained in:
parent
e7c7d5e41f
commit
7487369125
2 changed files with 338 additions and 24 deletions
256
mergectx_test.go
Normal file
256
mergectx_test.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package touka
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMergeCtx_NoParents(t *testing.T) {
|
||||
ctx, cancel := MergeCtx()
|
||||
defer cancel()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
t.Fatal("expected no error before cancel")
|
||||
}
|
||||
cancel()
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_SingleParent(t *testing.T) {
|
||||
parent, parentCancel := context.WithCancel(context.Background())
|
||||
|
||||
ctx, cancel := MergeCtx(parent)
|
||||
defer cancel()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
t.Fatal("expected no error before parent cancel")
|
||||
}
|
||||
|
||||
parentCancel()
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after parent cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_MultipleParents_FirstCancels(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel2()
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
cancel1()
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after p1 cancel")
|
||||
}
|
||||
// p2 should still be fine
|
||||
if p2.Err() != nil {
|
||||
t.Fatal("expected p2 to be unaffected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_MultipleParents_SecondCancels(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel1()
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
cancel2()
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after p2 cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_ExternalCancel(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel1()
|
||||
defer cancel2()
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
|
||||
cancel()
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after external cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_CausePropagation(t *testing.T) {
|
||||
testErr := errors.New("test cause")
|
||||
|
||||
p1, cancel1 := context.WithCancelCause(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel2()
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
cancel1(testErr)
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after p1 cancel")
|
||||
}
|
||||
|
||||
cause := context.Cause(ctx)
|
||||
if cause != testErr {
|
||||
t.Fatalf("expected cause %v, got %v", testErr, cause)
|
||||
}
|
||||
cancel1(nil) // cleanup (already cancelled, no-op)
|
||||
}
|
||||
|
||||
func TestMergeCtx_CausePropagation_SecondParent(t *testing.T) {
|
||||
testErr := errors.New("second parent cause")
|
||||
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancelCause(context.Background())
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
cancel2(testErr)
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after p2 cancel")
|
||||
}
|
||||
|
||||
cause := context.Cause(ctx)
|
||||
if cause != testErr {
|
||||
t.Fatalf("expected cause %v, got %v", testErr, cause)
|
||||
}
|
||||
|
||||
cancel1()
|
||||
}
|
||||
|
||||
func TestMergeCtx_Deadline_Earliest(t *testing.T) {
|
||||
now := time.Now()
|
||||
early := now.Add(100 * time.Millisecond)
|
||||
late := now.Add(1 * time.Hour)
|
||||
|
||||
p1, cancel1 := context.WithDeadline(context.Background(), late)
|
||||
p2, cancel2 := context.WithDeadline(context.Background(), early)
|
||||
defer cancel1()
|
||||
defer cancel2()
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
dl, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
t.Fatal("expected deadline to be set")
|
||||
}
|
||||
if !dl.Equal(early) {
|
||||
t.Fatalf("expected deadline %v, got %v", early, dl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_Deadline_Expires(t *testing.T) {
|
||||
p, cancelP := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancelP()
|
||||
|
||||
ctx, cancel := MergeCtx(p)
|
||||
defer cancel()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
t.Fatal("expected error after deadline expires")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_ValueLookup(t *testing.T) {
|
||||
type key struct{}
|
||||
p1 := context.WithValue(context.Background(), key{}, "from_p1")
|
||||
p2 := context.WithValue(context.Background(), key{}, "from_p2")
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
val := ctx.Value(key{})
|
||||
if val != "from_p1" {
|
||||
t.Fatalf("expected 'from_p1', got %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_ValueLookup_SecondParent(t *testing.T) {
|
||||
type key1 struct{}
|
||||
type key2 struct{}
|
||||
p1 := context.WithValue(context.Background(), key1{}, "val1")
|
||||
p2 := context.WithValue(context.Background(), key2{}, "val2")
|
||||
|
||||
ctx, cancel := MergeCtx(p1, p2)
|
||||
defer cancel()
|
||||
|
||||
if v := ctx.Value(key1{}); v != "val1" {
|
||||
t.Fatalf("expected 'val1', got %v", v)
|
||||
}
|
||||
if v := ctx.Value(key2{}); v != "val2" {
|
||||
t.Fatalf("expected 'val2', got %v", v)
|
||||
}
|
||||
if v := ctx.Value("missing"); v != nil {
|
||||
t.Fatalf("expected nil, got %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCtx_ContextInterface(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel1()
|
||||
defer cancel2()
|
||||
|
||||
var ctx context.Context
|
||||
ctx, _ = MergeCtx(p1, p2)
|
||||
|
||||
// Verify all Context interface methods work
|
||||
_ = ctx.Done()
|
||||
_ = ctx.Err()
|
||||
_, _ = ctx.Deadline()
|
||||
_ = ctx.Value("any")
|
||||
}
|
||||
|
||||
func TestOrDone_SingleContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := orDone(ctx)
|
||||
|
||||
cancel()
|
||||
<-done // should not block
|
||||
}
|
||||
|
||||
func TestOrDone_MultipleContexts(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel2()
|
||||
|
||||
done := orDone(p1, p2)
|
||||
|
||||
cancel1()
|
||||
<-done // should not block
|
||||
}
|
||||
|
||||
func TestOrDone_SecondContextCancels(t *testing.T) {
|
||||
p1, cancel1 := context.WithCancel(context.Background())
|
||||
p2, cancel2 := context.WithCancel(context.Background())
|
||||
defer cancel1()
|
||||
|
||||
done := orDone(p1, p2)
|
||||
|
||||
cancel2()
|
||||
<-done // should not block
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue