-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchallenge_wiring_test.go
More file actions
171 lines (164 loc) · 6.73 KB
/
Copy pathchallenge_wiring_test.go
File metadata and controls
171 lines (164 loc) · 6.73 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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package main
import (
"context"
"testing"
"time"
"github.com/pingular/pingularity/internal/settings"
"github.com/pingular/pingularity/internal/speedtest"
"github.com/pingular/pingularity/internal/store"
)
func seedWinners(t *testing.T, st *store.Store, base int64, reasons ...string) {
t.Helper()
for i, r := range reasons {
row := store.SpeedServerRow{RunTS: base + int64(i)*60, ServerID: "s" + string(rune('a'+i)), Server: "S", RankOrder: 1, Winner: true, WinReason: r}
if err := st.InsertSpeedServers(context.Background(), []store.SpeedServerRow{row}); err != nil {
t.Fatal(err)
}
}
}
// A challenge is due once N automatic winners have passed with no challenge
// among them; pinned runs neither count nor reset; 0 turns it off.
func TestChallengeFnIsDueEveryNAutoRunsFromHistory(t *testing.T) {
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
set, err := settings.New(context.Background(), st, settings.Values{SpeedChallengeEvery: 3,
Latency: 5 * time.Second, Speed: time.Hour, Timeout: 2 * time.Second, DownAfter: 3, UpAfter: 2})
if err != nil {
t.Fatal(err)
}
fn := newChallengeFn(st, set)
if fn() {
t.Fatal("no history: nothing to challenge yet")
}
base := time.Now().Add(-2 * time.Hour).Unix()
seedWinners(t, st, base, "fastest_ranked", "incumbent")
if fn() {
t.Error("two auto runs, N=3: not due")
}
seedWinners(t, st, base+1000, speedtest.WinReasonPinned) // a pinned run does not count
if fn() {
t.Error("a pinned run must not count toward N")
}
seedWinners(t, st, base+2000, "incumbent")
if !fn() {
t.Error("three auto runs without a challenge: due")
}
seedWinners(t, st, base+3000, speedtest.WinReasonChallenger) // the challenge happened (and lost)
if fn() {
t.Error("just challenged: not due")
}
seedWinners(t, st, base+4000, "incumbent", "incumbent")
if fn() {
t.Error("two auto runs since the challenge: not due")
}
seedWinners(t, st, base+5000, "incumbent")
if !fn() {
t.Error("three auto runs since the challenge: due again")
}
// A pinned stretch longer than N must not start the count over.
seedWinners(t, st, base+6000, speedtest.WinReasonPinned, speedtest.WinReasonPinnedBestOf, speedtest.WinReasonPinnedCompanion, speedtest.WinReasonPinned)
if !fn() {
t.Error("four pinned runs in a row: still due - pinned runs neither count nor reset")
}
off, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { off.Close() })
setOff, err := settings.New(context.Background(), off, settings.Values{SpeedChallengeEvery: 0,
Latency: 5 * time.Second, Speed: time.Hour, Timeout: 2 * time.Second, DownAfter: 3, UpAfter: 2})
if err != nil {
t.Fatal(err)
}
seedWinners(t, off, base, "incumbent", "incumbent", "incumbent", "incumbent")
if newChallengeFn(off, setOff)() {
t.Error("0 = never challenge")
}
}
// The incumbent lookup skips a challenger that lost, and takes one that won.
func TestIncumbentFnSkipsLosingChallengers(t *testing.T) {
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
fn := newIncumbentFn(st)
base := time.Now().Add(-time.Hour).Unix()
row := func(ts int64, id, reason string) store.SpeedServerRow {
return store.SpeedServerRow{RunTS: ts, ServerID: id, Server: "S", RankOrder: 1, Winner: true, WinReason: reason}
}
if err := st.InsertSpeedServers(context.Background(), []store.SpeedServerRow{row(base, "seat", "incumbent")}); err != nil {
t.Fatal(err)
}
if err := st.InsertSpeedServers(context.Background(), []store.SpeedServerRow{row(base+60, "loser", speedtest.WinReasonChallenger)}); err != nil {
t.Fatal(err)
}
if got := fn(); got != "seat" {
t.Errorf("incumbent %q, want seat: a challenger that lost must not take the seat", got)
}
if err := st.InsertSpeedServers(context.Background(), []store.SpeedServerRow{row(base+120, "winner", speedtest.WinReasonChallengerWon)}); err != nil {
t.Fatal(err)
}
if got := fn(); got != "winner" {
t.Errorf("incumbent %q, want winner: a challenger that won holds the seat", got)
}
}
func TestIncumbentScoresFnReadsTheRecord(t *testing.T) {
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
base := time.Now().Add(-time.Hour).Unix()
rows := []store.SpeedServerRow{
{RunTS: base, ServerID: "1993", Server: "EBOX", RankOrder: 1, Measured: true, Winner: true, Score: 50, DownMbps: 100, UpMbps: 20},
{RunTS: base + 60, ServerID: "1993", Server: "EBOX", RankOrder: 1, Measured: true, Winner: true, Score: 60, DownMbps: 100, UpMbps: 20},
{RunTS: base + 120, ServerID: "1993", Server: "EBOX", RankOrder: 1, Measured: true, Winner: true, Score: 70, DownMbps: 100, UpMbps: 20},
// A download-only run: judged under another direction, not comparable to a "both" record.
{RunTS: base + 180, ServerID: "1993", Server: "EBOX", RankOrder: 1, Measured: true, Winner: true, Score: 90, DownMbps: 100},
// A partial run whose upload failed scores 0: not a record, and must not eat a window slot.
{RunTS: base + 240, ServerID: "1993", Server: "EBOX", RankOrder: 1, Measured: true, Winner: true, Score: 0, DownMbps: 100, UpMbps: 20},
{RunTS: base + 240, ServerID: "2", Server: "X", RankOrder: 2},
}
for _, r := range rows {
if err := st.InsertSpeedServers(context.Background(), []store.SpeedServerRow{r}); err != nil {
t.Fatal(err)
}
}
got := newIncumbentScoresFn(st)("1993", "both")
if len(got) != 3 || got[0] != 70 || got[2] != 50 {
t.Errorf("both: scores %v, want 70, 60, 50 newest first - the download-only and the zero rows excluded", got)
}
if got := newIncumbentScoresFn(st)("1993", "down"); len(got) != 1 || got[0] != 90 {
t.Errorf("down: scores %v, want the one download-only row", got)
}
if got := newIncumbentScoresFn(st)("2", "both"); len(got) != 0 {
t.Errorf("a server never measured has no record, got %v", got)
}
}
// The recent-winner origin: the newest decided race's city, as a candidate
// for the next race; nil with no such race.
func TestRecentWinnerFnNamesTheLastDecidedRace(t *testing.T) {
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
fn := newRecentWinnerFn(st)
if fn() != nil {
t.Fatal("no history: no recent winner")
}
la, lo := 45.5017, -73.5673
if err := st.InsertSpeed(context.Background(), store.SpeedSample{TS: time.Now().Add(-time.Hour).Unix(), Server: "s", Engine: "ookla",
DownMbps: 1, UpMbps: 1, PingMS: 1, RaceOutcome: "decided", RaceWinnerKind: "exit", RaceWinnerLabel: "Montréal, CA",
RaceWinnerLat: &la, RaceWinnerLon: &lo}); err != nil {
t.Fatal(err)
}
got := fn()
if got == nil || got.Kind != "recent" || got.Label != "Montréal, CA" || got.Lat != la || got.Lon != lo || !got.Anchored {
t.Errorf("got %+v, want the Montréal race as an anchored 'recent' origin", got)
}
}