Repository navigation
Expand file tree
/
Copy pathflush_test.go
More file actions
139 lines (127 loc) · 4.13 KB
/
Copy pathflush_test.go
File metadata and controls
139 lines (127 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package slogbetterstack
import (
"context"
"errors"
"log/slog"
"net/http"
"sync"
"testing"
"time"
)
func TestFlushWaitsForUploadsFromEarlierFlush(t *testing.T) {
for _, cancelFirst := range []bool{false, true} {
name := "overlapping"
if cancelFirst {
name = "after cancellation"
}
t.Run(name, func(t *testing.T) {
started, release := make(chan struct{}), make(chan struct{})
var releaseOnce sync.Once
unblock := func() { releaseOnce.Do(func() { close(release) }) }
defer unblock()
server, _ := newServer(t, func(w http.ResponseWriter, r *http.Request, _ []map[string]any) {
close(started)
select {
case <-release:
case <-r.Context().Done():
}
w.WriteHeader(http.StatusAccepted)
})
handler := newHandler(t, server, Option{BatchInterval: time.Hour})
slog.New(handler).Info("pending")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
first := make(chan error, 1)
go func() { first <- handler.Flush(ctx) }()
// Only Flush can dispatch this partial batch before its hour-long interval.
waitForFlushSignal(t, started)
if cancelFirst {
cancel()
if err := waitForFlushResult(t, first); !errors.Is(err, context.Canceled) {
t.Fatalf("first Flush = %v, want context.Canceled", err)
}
}
ctx, cancelSecond := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancelSecond()
if err := handler.Flush(ctx); !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("second Flush = %v, want context.DeadlineExceeded while the earlier upload is blocked", err)
}
unblock()
if !cancelFirst {
if err := waitForFlushResult(t, first); err != nil {
t.Errorf("first Flush = %v, want nil after delivery", err)
}
}
if err := handler.Flush(context.Background()); err != nil {
t.Errorf("Flush after release = %v, want nil", err)
}
if err := handler.Close(); err != nil {
t.Fatal(err)
}
if stats := handler.Stats(); stats.Sent != 1 || stats.Enqueued != 1 {
t.Errorf("stats = %+v, want exactly one record delivered", stats)
}
})
}
}
func TestFlushDoesNotWaitForLaterUploads(t *testing.T) {
started := map[string]chan struct{}{"earlier": make(chan struct{}), "later": make(chan struct{})}
releaseEarlier, releaseLater := make(chan struct{}), make(chan struct{})
var earlierOnce sync.Once
unblockEarlier := func() { earlierOnce.Do(func() { close(releaseEarlier) }) }
defer unblockEarlier()
defer close(releaseLater)
server, _ := newServer(t, func(w http.ResponseWriter, r *http.Request, records []map[string]any) {
message := records[0]["message"].(string)
close(started[message])
release := releaseEarlier
if message == "later" {
release = releaseLater
}
select {
case <-release:
case <-r.Context().Done():
}
w.WriteHeader(http.StatusAccepted)
})
handler := newHandler(t, server, Option{BatchInterval: time.Hour})
logger := slog.New(handler)
logger.Info("earlier")
first := make(chan error, 1)
go func() { first <- handler.Flush(context.Background()) }()
waitForFlushSignal(t, started["earlier"])
logger.Info("later")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
second := make(chan error, 1)
go func() { second <- handler.Flush(ctx) }()
waitForFlushSignal(t, started["later"])
unblockEarlier()
if err := waitForFlushResult(t, first); err != nil {
t.Errorf("first Flush = %v, want nil while the later upload is still blocked", err)
}
cancel()
if err := waitForFlushResult(t, second); !errors.Is(err, context.Canceled) {
t.Errorf("second Flush = %v, want context.Canceled while the later upload is blocked", err)
}
}
// Test helpers: bound synchronization waits so a broken barrier fails without hanging the suite.
func waitForFlushSignal(t *testing.T, signal <-chan struct{}) {
t.Helper()
select {
case <-signal:
case <-time.After(5 * time.Second):
t.Fatal("upload did not reach the server within 5s")
}
}
func waitForFlushResult(t *testing.T, result <-chan error) error {
t.Helper()
select {
case err := <-result:
return err
case <-time.After(5 * time.Second):
t.Fatal("Flush did not return within 5s")
return nil
}
}
// End test helpers.