cacheCleanCtx_test.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package loaders
  2. import (
  3. "context"
  4. "errors"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. // TC-1112: parent 取消后 detached ctx 仍存活,到 3s 硬超时才 Done。
  11. func TestDetachCacheCleanCtx_ParentCancelDoesNotPropagate(t *testing.T) {
  12. parent, cancel := context.WithCancel(context.Background())
  13. cleanCtx, cleanCancel := DetachCacheCleanCtx(parent)
  14. defer cleanCancel()
  15. cancel()
  16. assert.NoError(t, cleanCtx.Err(), "parent 取消后 detached ctx 不应提前终止")
  17. select {
  18. case <-cleanCtx.Done():
  19. t.Fatal("parent cancel 被传播到了 detached ctx")
  20. case <-time.After(100 * time.Millisecond):
  21. }
  22. }
  23. // TC-1113: deadline 必须落在 [now+2.5s, now+3.5s]。
  24. func TestDetachCacheCleanCtx_HasThreeSecondDeadline(t *testing.T) {
  25. parent := context.Background()
  26. before := time.Now()
  27. cleanCtx, cancel := DetachCacheCleanCtx(parent)
  28. defer cancel()
  29. deadline, ok := cleanCtx.Deadline()
  30. require.True(t, ok, "detached ctx 必须挂 timeout deadline")
  31. lower := before.Add(2500 * time.Millisecond)
  32. upper := before.Add(3500 * time.Millisecond)
  33. assert.WithinRange(t, deadline, lower, upper, "deadline 必须在 ~3s 窗口内")
  34. }
  35. // TC-1114: parent 的 Value 透传不被剥离。
  36. func TestDetachCacheCleanCtx_PreservesValues(t *testing.T) {
  37. type ctxKey struct{ name string }
  38. k := ctxKey{name: "trace"}
  39. parent := context.WithValue(context.Background(), k, "v1")
  40. cleanCtx, cancel := DetachCacheCleanCtx(parent)
  41. defer cancel()
  42. assert.Equal(t, "v1", cleanCtx.Value(k), "trace / tenant 等 Value 必须透传")
  43. }
  44. // TC-1115: isCtxCanceledErr 分类口径。
  45. func TestIsCtxCanceledErr_Classification(t *testing.T) {
  46. assert.True(t, isCtxCanceledErr(context.Canceled))
  47. assert.True(t, isCtxCanceledErr(context.DeadlineExceeded))
  48. assert.True(t, isCtxCanceledErr(
  49. // 包装过一层仍应识别
  50. wrapErr(context.Canceled, "clean userDetails: "),
  51. ))
  52. assert.False(t, isCtxCanceledErr(errors.New("redis down")))
  53. assert.False(t, isCtxCanceledErr(nil))
  54. }
  55. type wrappedErr struct {
  56. prefix string
  57. inner error
  58. }
  59. func (w *wrappedErr) Error() string { return w.prefix + w.inner.Error() }
  60. func (w *wrappedErr) Unwrap() error { return w.inner }
  61. func wrapErr(inner error, prefix string) error { return &wrappedErr{prefix: prefix, inner: inner} }
  62. // TC-1116: nil 错误时 logCacheInvalidationErr 早退,不触发任何日志写入。
  63. func TestLogCacheInvalidationErr_NilShortCircuit(t *testing.T) {
  64. // 无返回值可断言,此用例仅需保证不 panic / 不阻塞即可。
  65. // 若将来接入 logx buffer,可在此注入 TestCollector 断言"零条日志"。
  66. done := make(chan struct{})
  67. go func() {
  68. logCacheInvalidationErr(context.Background(), "scope", "detail", nil)
  69. close(done)
  70. }()
  71. select {
  72. case <-done:
  73. case <-time.After(200 * time.Millisecond):
  74. t.Fatal("logCacheInvalidationErr(nil) 不应阻塞")
  75. }
  76. }
  77. // TC-1116 附加:ctx 取消 / 普通错误两条分支都要走通,不允许 panic。
  78. func TestLogCacheInvalidationErr_DoesNotPanic(t *testing.T) {
  79. assert.NotPanics(t, func() {
  80. logCacheInvalidationErr(context.Background(), "scope", "detail", context.Canceled)
  81. })
  82. assert.NotPanics(t, func() {
  83. logCacheInvalidationErr(context.Background(), "scope", "detail", context.DeadlineExceeded)
  84. })
  85. assert.NotPanics(t, func() {
  86. logCacheInvalidationErr(context.Background(), "scope", "detail", errors.New("redis down"))
  87. })
  88. }