batch_image_worker_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "errors"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestBatchImageWorker_ProcessesJobOnce(t *testing.T) {
  11. queue := newFakeBatchImageQueue("imgbatch_worker_once")
  12. processor := &fakeBatchImageProcessor{}
  13. worker := NewBatchImageWorker(queue, processor, BatchImageWorkerOptions{ReserveBlockTimeout: time.Millisecond})
  14. require.NoError(t, worker.RunOnce(context.Background()))
  15. require.Equal(t, []string{"imgbatch_worker_once"}, processor.processed)
  16. require.Len(t, queue.requeued, 1)
  17. require.Equal(t, defaultBatchImageWorkerRequeueDelay, queue.requeued[0].delay)
  18. require.Equal(t, 1, queue.releaseCount)
  19. }
  20. func TestBatchImageWorker_RequeuesNonTerminalResultWithRequestedDelay(t *testing.T) {
  21. queue := newFakeBatchImageQueue("imgbatch_worker_requeue")
  22. processor := &fakeBatchImageProcessor{result: BatchImageProcessResult{RequeueAfter: 42 * time.Second}}
  23. worker := NewBatchImageWorker(queue, processor, BatchImageWorkerOptions{})
  24. require.NoError(t, worker.RunOnce(context.Background()))
  25. require.Len(t, queue.requeued, 1)
  26. require.Equal(t, "imgbatch_worker_requeue", queue.requeued[0].batchID)
  27. require.Equal(t, 42*time.Second, queue.requeued[0].delay)
  28. require.Empty(t, queue.acked)
  29. }
  30. func TestBatchImageWorker_AcksTerminalResult(t *testing.T) {
  31. queue := newFakeBatchImageQueue("imgbatch_worker_terminal")
  32. processor := &fakeBatchImageProcessor{result: BatchImageProcessResult{Terminal: true}}
  33. worker := NewBatchImageWorker(queue, processor, BatchImageWorkerOptions{})
  34. require.NoError(t, worker.RunOnce(context.Background()))
  35. require.Equal(t, []string{"imgbatch_worker_terminal"}, queue.acked)
  36. require.Empty(t, queue.requeued)
  37. }
  38. func TestBatchImageWorker_RequeuesOnProcessorError(t *testing.T) {
  39. queue := newFakeBatchImageQueue("imgbatch_worker_error")
  40. processor := &fakeBatchImageProcessor{err: errors.New("processor failed")}
  41. worker := NewBatchImageWorker(queue, processor, BatchImageWorkerOptions{ErrorRetryDelay: 7 * time.Second})
  42. require.NoError(t, worker.RunOnce(context.Background()))
  43. require.Len(t, queue.requeued, 1)
  44. require.Equal(t, 7*time.Second, queue.requeued[0].delay)
  45. require.Empty(t, queue.acked)
  46. }
  47. func TestBatchImageWorker_RequeuesWhenJobLockNotAcquired(t *testing.T) {
  48. queue := newFakeBatchImageQueue("imgbatch_worker_locked")
  49. queue.lockAcquired = false
  50. processor := &fakeBatchImageProcessor{}
  51. worker := NewBatchImageWorker(queue, processor, BatchImageWorkerOptions{LockConflictDelay: 3 * time.Second})
  52. // 锁冲突必须按冲突延迟重新入队;直接丢弃会让 job 滞留 active zset,
  53. // 要等 StaleActiveAfter(默认 10 分钟)才被恢复。
  54. require.NoError(t, worker.RunOnce(context.Background()))
  55. require.Empty(t, processor.processed)
  56. require.Len(t, queue.requeued, 1)
  57. require.Equal(t, 3*time.Second, queue.requeued[0].delay)
  58. require.Empty(t, queue.acked)
  59. }
  60. func TestNewBatchImageWorkerOptionsFromConfig_UsesFiniteReserveTimeout(t *testing.T) {
  61. opts := NewBatchImageWorkerOptionsFromConfig(nil)
  62. require.Equal(t, defaultBatchImageWorkerReserveBlockTimeout, opts.ReserveBlockTimeout)
  63. require.Positive(t, opts.ReserveBlockTimeout)
  64. }
  65. type fakeBatchImageQueue struct {
  66. reserved ReservedBatchImageJob
  67. lockAcquired bool
  68. acked []string
  69. requeued []fakeBatchImageRequeue
  70. releaseCount int
  71. }
  72. type fakeBatchImageRequeue struct {
  73. batchID string
  74. delay time.Duration
  75. }
  76. func newFakeBatchImageQueue(batchID string) *fakeBatchImageQueue {
  77. return &fakeBatchImageQueue{
  78. reserved: ReservedBatchImageJob{BatchID: batchID},
  79. lockAcquired: true,
  80. }
  81. }
  82. func (q *fakeBatchImageQueue) Enqueue(context.Context, string) error {
  83. return nil
  84. }
  85. func (q *fakeBatchImageQueue) Reserve(context.Context, time.Duration) (ReservedBatchImageJob, error) {
  86. return q.reserved, nil
  87. }
  88. func (q *fakeBatchImageQueue) RequeueAfter(_ context.Context, batchID string, delay time.Duration) error {
  89. q.requeued = append(q.requeued, fakeBatchImageRequeue{batchID: batchID, delay: delay})
  90. return nil
  91. }
  92. func (q *fakeBatchImageQueue) Ack(_ context.Context, batchID string) error {
  93. q.acked = append(q.acked, batchID)
  94. return nil
  95. }
  96. func (q *fakeBatchImageQueue) Heartbeat(context.Context, string) error {
  97. return nil
  98. }
  99. func (q *fakeBatchImageQueue) MoveDueDelayedToReady(context.Context, int) (int, error) {
  100. return 0, nil
  101. }
  102. func (q *fakeBatchImageQueue) RecoverStaleActive(context.Context, time.Duration, int) (int, error) {
  103. return 0, nil
  104. }
  105. func (q *fakeBatchImageQueue) TryAcquireJobLock(context.Context, string, time.Duration) (BatchImageJobLock, bool, error) {
  106. if !q.lockAcquired {
  107. return nil, false, nil
  108. }
  109. return fakeBatchImageLock{release: func() { q.releaseCount++ }}, true, nil
  110. }
  111. type fakeBatchImageLock struct {
  112. release func()
  113. }
  114. func (l fakeBatchImageLock) Release(context.Context) error {
  115. if l.release != nil {
  116. l.release()
  117. }
  118. return nil
  119. }
  120. type fakeBatchImageProcessor struct {
  121. result BatchImageProcessResult
  122. err error
  123. processed []string
  124. }
  125. func (p *fakeBatchImageProcessor) Process(_ context.Context, batchID string) (BatchImageProcessResult, error) {
  126. p.processed = append(p.processed, batchID)
  127. return p.result, p.err
  128. }