feat: add cache, outbound HTTP, request protection, tasks and module scaffolding
This commit is contained in:
102
task/task_test.go
Normal file
102
task/task_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCapacityDrainPanicAndClose(t *testing.T) {
|
||||
r, err := New(Config{Workers: 1, QueueSize: 1, OnError: func(string, error) { panic("callback failure") }})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
started, release := make(chan struct{}), make(chan struct{})
|
||||
first, _ := r.Submit(context.Background(), "first", func(context.Context) error { close(started); <-release; return nil })
|
||||
<-started
|
||||
second, _ := r.Submit(context.Background(), "panic", func(context.Context) error { panic("job failure") })
|
||||
if _, err := r.Submit(context.Background(), "overflow", func(context.Context) error { return nil }); !errors.Is(err, ErrQueueFull) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- r.Close() }()
|
||||
close(release)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
if err := first.Wait(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := second.Wait(ctx); err == nil {
|
||||
t.Fatal("panic lost")
|
||||
}
|
||||
if err := <-done; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s := r.Stats(); s.Completed != 2 || s.Failed != 1 || s.Active != 0 || s.Queued != 0 {
|
||||
t.Fatal(s)
|
||||
}
|
||||
if _, err := r.Submit(ctx, "late", func(context.Context) error { return nil }); !errors.Is(err, ErrClosed) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutdownDeadlineAndQueuedCancellation(t *testing.T) {
|
||||
r, _ := New(Config{Workers: 1, QueueSize: 1, ShutdownTimeout: 20 * time.Millisecond, OnError: func(string, error) {}})
|
||||
started, release := make(chan struct{}), make(chan struct{})
|
||||
var queued atomic.Bool
|
||||
first, _ := r.Submit(context.Background(), "ignoresCancel", func(context.Context) error { close(started); <-release; return nil })
|
||||
<-started
|
||||
second, _ := r.Submit(context.Background(), "queued", func(context.Context) error { queued.Store(true); return nil })
|
||||
if err := r.Close(); !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
close(release)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
if err := first.Wait(ctx); !errors.Is(err, context.Canceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := second.Wait(ctx); !errors.Is(err, context.Canceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if queued.Load() {
|
||||
t.Fatal("ran queued work after forced shutdown")
|
||||
}
|
||||
if err := r.Shutdown(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntervalsDoNotOverlapAndTaskDeadline(t *testing.T) {
|
||||
r, _ := New(Config{Workers: 2, TaskTimeout: 25 * time.Millisecond, OnError: func(string, error) {}})
|
||||
defer r.Close()
|
||||
var active, maxActive atomic.Int32
|
||||
job := func(ctx context.Context) error {
|
||||
n := active.Add(1)
|
||||
if n > maxActive.Load() {
|
||||
maxActive.Store(n)
|
||||
}
|
||||
defer active.Add(-1)
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
if err := r.Every("periodic", 3*time.Millisecond, job); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := r.Every("periodic", time.Second, job); err == nil {
|
||||
t.Fatal("duplicate schedule accepted")
|
||||
}
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for r.Stats().Completed < 2 && time.Now().Before(deadline) {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if maxActive.Load() != 1 || r.Stats().Skipped == 0 || r.Stats().Failed < 2 {
|
||||
t.Fatal(maxActive.Load(), r.Stats())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user