Repository navigation
Expand file tree
/
Copy pathapply_error_difference_test.go
More file actions
130 lines (123 loc) · 4.17 KB
/
Copy pathapply_error_difference_test.go
File metadata and controls
130 lines (123 loc) · 4.17 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
package updates
import (
"context"
"errors"
"sync/atomic"
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/require"
"github.com/gotd/td/telegram"
"github.com/gotd/td/tg"
)
// Pending updates that continue the state set by a difference are applied
// after it (sequenceBox.resyncPending). The difference itself is a completed
// step and is not rolled back when the pending updates fail.
func TestManagerStopOnApplyErrorDifferencePending(t *testing.T) {
for _, channel := range []bool{false, true} {
for _, stop := range []bool{false, true} {
name := "Common"
if channel {
name = "Channel"
}
mode := "Default"
if stop {
mode = "Stop"
}
t.Run(name+"/"+mode, func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
memory, hashes := newMemStorage(), newMemAccessHasher()
require.NoError(t, memory.SetState(ctx, 1, State{Pts: 10, Date: 1}))
if channel {
require.NoError(t, memory.SetChannelPts(ctx, 1, 42, 10))
require.NoError(t, hashes.SetChannelAccessHash(ctx, 1, 42, 123))
}
failure := errors.New("pending handler failure")
var armed atomic.Bool
var hits, delivered atomic.Int32
api := &applyBoundaryAPI{
common: func(context.Context, *tg.UpdatesGetDifferenceRequest) (tg.UpdatesDifferenceClass, error) {
if armed.Load() && !channel {
// Covers pts up to 20, the pending update 21 continues it.
return &tg.UpdatesDifference{State: tg.UpdatesState{Pts: 20, Date: 2}}, nil
}
return &tg.UpdatesDifferenceEmpty{Date: 1}, nil
},
channel: func(_ context.Context, r *tg.UpdatesGetChannelDifferenceRequest) (tg.UpdatesChannelDifferenceClass, error) {
if armed.Load() {
return &tg.UpdatesChannelDifference{Final: true, Pts: 20}, nil
}
return &tg.UpdatesChannelDifferenceEmpty{Final: true, Pts: r.Pts}, nil
},
}
m := New(Config{Storage: memory, AccessHasher: hashes, StopOnApplyError: stop,
Handler: telegram.UpdateHandlerFunc(func(context.Context, tg.UpdatesClass) error {
if armed.Load() {
hits.Add(1)
return failure
}
delivered.Add(1)
return nil
}),
})
done := make(chan error, 1)
go func() { done <- m.Run(ctx, api, 1, AuthOptions{}) }()
synctest.Wait()
armed.Store(true)
// A gap: 11..20 are missing.
var u tg.UpdateClass = &tg.UpdateDeleteMessages{Messages: []int{1}, Pts: 21, PtsCount: 1}
if channel {
u = &tg.UpdateDeleteChannelMessages{ChannelID: 42, Messages: []int{1}, Pts: 21, PtsCount: 1}
}
require.NoError(t, m.Handle(ctx, &tg.Updates{Updates: []tg.UpdateClass{u}}))
synctest.Wait()
require.Zero(t, hits.Load(), "postponed by the gap")
time.Sleep(fastgapTimeout)
synctest.Wait()
require.EqualValues(t, 1, hits.Load(), "pending update applied after the difference")
state, _, err := memory.GetState(ctx, 1)
require.NoError(t, err)
channelPts, _, err := memory.GetChannelPts(ctx, 1, 42)
require.NoError(t, err)
if stop {
select {
case err := <-done:
require.ErrorIs(t, err, failure)
default:
t.Fatal("Run did not stop after the pending failure")
}
// The difference is checkpointed, the failed pending
// update is not.
if channel {
require.Equal(t, 20, channelPts)
} else {
require.Equal(t, State{Pts: 20, Date: 2}, state)
}
return
}
select {
case err := <-done:
t.Fatalf("default mode stopped: %v", err)
default:
}
if channel {
// Unchanged default: a failed channel handler skips the
// checkpoint write.
require.Equal(t, 20, channelPts)
} else {
require.Equal(t, State{Pts: 21, Date: 2}, state)
}
armed.Store(false)
before := delivered.Load()
require.NoError(t, m.Handle(ctx, &tg.Updates{Updates: []tg.UpdateClass{&tg.UpdateConfig{}}}))
synctest.Wait()
require.Equal(t, before+1, delivered.Load(), "default mode continues delivery")
cancel()
require.ErrorIs(t, <-done, context.Canceled)
})
})
}
}
}