Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

at icy/qmlqxq 11 kB View raw
1package knotcompat 2 3import ( 4 "strconv" 5 "sync" 6 "testing" 7 "time" 8) 9 10type fakeLatch struct { 11 mu sync.Mutex 12 native map[string]bool 13 marks []string 14 reads int 15} 16 17func newFakeLatch() *fakeLatch { 18 return &fakeLatch{native: map[string]bool{}} 19} 20 21func (f *fakeLatch) IsNative(host string) bool { 22 f.mu.Lock() 23 defer f.mu.Unlock() 24 f.reads++ 25 return f.native[host] 26} 27 28func (f *fakeLatch) readCount() int { 29 f.mu.Lock() 30 defer f.mu.Unlock() 31 return f.reads 32} 33 34func (f *fakeLatch) MarkNative(host string) { 35 f.mu.Lock() 36 defer f.mu.Unlock() 37 f.native[host] = true 38 f.marks = append(f.marks, host) 39} 40 41func (f *fakeLatch) markCount() int { 42 f.mu.Lock() 43 defer f.mu.Unlock() 44 return len(f.marks) 45} 46 47func probeReturning(s CapStatus, calls *int) func() CapStatus { 48 return func() CapStatus { 49 *calls++ 50 return s 51 } 52} 53 54func isNativeForTest(g *nativeGate, host string, probe func() CapStatus) bool { 55 return g.status(host, probe) == CapPresent 56} 57 58func TestNativeGateMemoSkipsSecondProbe(t *testing.T) { 59 g := &nativeGate{} 60 calls := 0 61 probe := probeReturning(CapPresent, &calls) 62 63 if !isNativeForTest(g, "clam.nel.pet", probe) { 64 t.Fatal("first probe true: want native") 65 } 66 if !isNativeForTest(g, "clam.nel.pet", probe) { 67 t.Fatal("memoized: want native") 68 } 69 if calls != 1 { 70 t.Fatalf("calls = %d, want 1; a memoized native host must skip the probe", calls) 71 } 72} 73 74func TestNativeGateLatchHitSkipsProbe(t *testing.T) { 75 g := &nativeGate{} 76 fl := newFakeLatch() 77 fl.native["whelk.nel.pet"] = true 78 g.use(fl) 79 80 calls := 0 81 if !isNativeForTest(g, "whelk.nel.pet", probeReturning(CapAbsent, &calls)) { 82 t.Fatal("latched native: want native even though the probe would fail") 83 } 84 if calls != 0 { 85 t.Fatalf("calls = %d, want 0; a latched host must never be probed", calls) 86 } 87 if fl.markCount() != 0 { 88 t.Fatalf("marks = %d, want 0; a latch hit must not re-mark", fl.markCount()) 89 } 90} 91 92func TestNativeGateProbeMarksLatchOnce(t *testing.T) { 93 g := &nativeGate{} 94 fl := newFakeLatch() 95 g.use(fl) 96 97 calls := 0 98 probe := probeReturning(CapPresent, &calls) 99 if !isNativeForTest(g, "limpet.nel.pet", probe) { 100 t.Fatal("probe true: want native") 101 } 102 if fl.markCount() != 1 { 103 t.Fatalf("marks = %d, want 1; a first successful probe must latch the host", fl.markCount()) 104 } 105 106 isNativeForTest(g, "limpet.nel.pet", probe) 107 if fl.markCount() != 1 { 108 t.Fatalf("marks = %d, want 1; the memo must prevent a second mark", fl.markCount()) 109 } 110} 111 112func TestNativeGateProbeFalseDoesNotMark(t *testing.T) { 113 g := &nativeGate{} 114 fl := newFakeLatch() 115 g.use(fl) 116 117 calls := 0 118 if isNativeForTest(g, "clam.nel.pet", probeReturning(CapAbsent, &calls)) { 119 t.Fatal("probe false on a fresh host: want not native") 120 } 121 if fl.markCount() != 0 { 122 t.Fatalf("marks = %d, want 0; a failed probe must not latch", fl.markCount()) 123 } 124} 125 126func TestNativeGateDurableAcrossMemoReset(t *testing.T) { 127 fl := newFakeLatch() 128 129 warm := &nativeGate{} 130 warm.use(fl) 131 calls := 0 132 if !isNativeForTest(warm, "whelk.nel.pet", probeReturning(CapPresent, &calls)) { 133 t.Fatal("warm gate probe true: want native") 134 } 135 136 restarted := &nativeGate{} 137 restarted.use(fl) 138 cold := 0 139 if !isNativeForTest(restarted, "whelk.nel.pet", probeReturning(CapAbsent, &cold)) { 140 t.Fatal("after restart the durable latch must resolve native without a probe") 141 } 142 if cold != 0 { 143 t.Fatalf("calls = %d, want 0; a durably latched host survives a memo reset without probing", cold) 144 } 145} 146 147func TestNativeGateNegativeMemoThrottlesLatchReads(t *testing.T) { 148 now := time.Now() 149 g := &nativeGate{now: func() time.Time { return now }} 150 fl := newFakeLatch() 151 g.use(fl) 152 153 calls := 0 154 probe := probeReturning(CapAbsent, &calls) 155 156 for range 5 { 157 if isNativeForTest(g, "clam.nel.pet", probe) { 158 t.Fatal("a probe-false host must not be native") 159 } 160 } 161 if fl.readCount() != 1 { 162 t.Fatalf("latch reads = %d, want 1; the negative memo must throttle repeat latch reads within the window", fl.readCount()) 163 } 164 if calls != 1 { 165 t.Fatalf("probe calls = %d, want 1; the negative memo must throttle repeat probes within the window", calls) 166 } 167 168 now = now.Add(versionProbeFresh + time.Second) 169 if isNativeForTest(g, "clam.nel.pet", probe) { 170 t.Fatal("still not native after the window") 171 } 172 if fl.readCount() != 2 { 173 t.Fatalf("latch reads = %d, want 2; an expired negative memo must re-read the latch", fl.readCount()) 174 } 175 if calls != 2 { 176 t.Fatalf("probe calls = %d, want 2; an expired negative memo must re-probe", calls) 177 } 178} 179 180func TestNativeGateNegativeMemoNeverShadowsLatchedNative(t *testing.T) { 181 now := time.Now() 182 g := &nativeGate{now: func() time.Time { return now }} 183 fl := newFakeLatch() 184 g.use(fl) 185 186 calls := 0 187 if isNativeForTest(g, "whelk.nel.pet", probeReturning(CapAbsent, &calls)) { 188 t.Fatal("probe false on a fresh host: want not native") 189 } 190 191 fl.mu.Lock() 192 fl.native["whelk.nel.pet"] = true 193 fl.mu.Unlock() 194 195 now = now.Add(versionProbeFresh + time.Second) 196 if !isNativeForTest(g, "whelk.nel.pet", probeReturning(CapAbsent, &calls)) { 197 t.Fatal("once the negative memo expires a latched host must resolve native again") 198 } 199} 200 201func TestNativeGateUnknownDistinctFromAbsent(t *testing.T) { 202 now := time.Now() 203 g := &nativeGate{now: func() time.Time { return now }} 204 fl := newFakeLatch() 205 g.use(fl) 206 207 calls := 0 208 probe := probeReturning(CapUnknown, &calls) 209 210 for range 3 { 211 if got := g.status("clam.nel.pet", probe); got != CapUnknown { 212 t.Fatalf("status = %v, want CapUnknown for a failed probe", got) 213 } 214 } 215 if calls != 1 { 216 t.Fatalf("probe calls = %d, want 1; a memoized unknown must throttle re-probes within the window", calls) 217 } 218 if fl.markCount() != 0 { 219 t.Fatalf("marks = %d, want 0; an unknown probe must never latch", fl.markCount()) 220 } 221 222 now = now.Add(versionProbeFresh + time.Second) 223 if got := g.status("clam.nel.pet", probeReturning(CapAbsent, &calls)); got != CapAbsent { 224 t.Fatalf("status = %v, want CapAbsent once a fresh probe reaches the knot", got) 225 } 226} 227 228func TestAtLeast(t *testing.T) { 229 cases := []struct { 230 in string 231 minMajor int 232 minMinor int 233 want bool 234 }{ 235 {"v1.14.0", 1, 14, true}, 236 {"v1.14.0-alpha", 1, 14, true}, 237 {"v1.14.5", 1, 14, true}, 238 {"v1.13.0", 1, 14, false}, 239 {"v1.13.0-alpha", 1, 14, false}, 240 {"v1.0.0", 1, 14, false}, 241 {"v2.0.0", 1, 14, true}, 242 {"1.14.0", 1, 14, true}, 243 {"1.13.99", 1, 14, false}, 244 {"(devel)", 1, 14, true}, 245 {"", 1, 14, false}, 246 {"garbagio-furioso", 1, 14, false}, 247 {"v1", 1, 14, false}, 248 {"vX.Y.Z", 1, 14, false}, 249 {"unknown", 1, 14, false}, 250 {"unknown-abc1234", 1, 14, false}, 251 {"unknown-abc1234-modified", 1, 14, false}, 252 {"v1.15.0", 1, 15, true}, 253 {"v1.15.2-alpha", 1, 15, true}, 254 {"v1.16.0", 1, 15, true}, 255 {"v1.14.1-alpha", 1, 15, false}, 256 {"v1.14.9", 1, 15, false}, 257 {"v2.0.0", 1, 15, true}, 258 {"(devel)", 1, 15, true}, 259 {"", 1, 15, false}, 260 } 261 for _, c := range cases { 262 t.Run(c.in, func(t *testing.T) { 263 if got := atLeast(c.in, c.minMajor, c.minMinor); got != c.want { 264 t.Errorf("atLeast(%q, %d, %d) = %v, want %v", c.in, c.minMajor, c.minMinor, got, c.want) 265 } 266 }) 267 } 268} 269 270func newProbeCache() *versionProbeCache { 271 return &versionProbeCache{entries: map[string]versionProbeEntry{}} 272} 273 274func TestVersionProbeCacheFreshSkipsProbe(t *testing.T) { 275 c := newProbeCache() 276 now := time.Unix(1_000_000, 0) 277 calls := 0 278 probe := func() (string, bool) { calls++; return "v1.15.0", true } 279 280 if !c.supports(now, "knot.nel.pet", 1, 15, false, probe) { 281 t.Fatal("cold probe: want supported") 282 } 283 if calls != 1 { 284 t.Fatalf("calls = %d, want 1 after cold probe", calls) 285 } 286 if !c.supports(now.Add(time.Minute), "knot.nel.pet", 1, 15, false, probe) { 287 t.Fatal("cached: want supported") 288 } 289 if calls != 1 { 290 t.Fatalf("calls = %d, want 1; a fresh cache entry must skip the probe", calls) 291 } 292} 293 294func TestVersionProbeCacheServesStaleOnFailure(t *testing.T) { 295 c := newProbeCache() 296 now := time.Unix(1_000_000, 0) 297 if !c.supports(now, "knot.nel.pet", 1, 15, false, func() (string, bool) { return "v1.15.0", true }) { 298 t.Fatal("seed: want supported") 299 } 300 301 failProbe := func() (string, bool) { return "", false } 302 if !c.supports(now.Add(10*time.Minute), "knot.nel.pet", 1, 15, false, failProbe) { 303 t.Error("a probe failure within the trust window must serve the last-known version, not fail closed") 304 } 305} 306 307func TestVersionProbeCacheFailsClosedWhenUntrusted(t *testing.T) { 308 c := newProbeCache() 309 now := time.Unix(1_000_000, 0) 310 failProbe := func() (string, bool) { return "", false } 311 if c.supports(now, "knot.nel.pet", 1, 15, false, failProbe) { 312 t.Error("a cold probe failure on a fail-closed gate must return false") 313 } 314 if !c.supports(now, "knot.nel.pet", 1, 14, true, failProbe) { 315 t.Error("a cold probe failure on a fail-open gate must return true") 316 } 317} 318 319func TestVersionProbeCacheExpiresTrust(t *testing.T) { 320 c := newProbeCache() 321 now := time.Unix(1_000_000, 0) 322 if !c.supports(now, "knot.nel.pet", 1, 15, false, func() (string, bool) { return "v1.15.0", true }) { 323 t.Fatal("seed: want supported") 324 } 325 if c.supports(now.Add(2*time.Hour), "knot.nel.pet", 1, 15, false, func() (string, bool) { return "", false }) { 326 t.Error("a probe failure past the trust window must fail closed") 327 } 328} 329 330func TestVersionProbeCacheRefreshesAfterFresh(t *testing.T) { 331 c := newProbeCache() 332 now := time.Unix(1_000_000, 0) 333 if c.supports(now, "knot.nel.pet", 1, 15, false, func() (string, bool) { return "v1.14.0", true }) { 334 t.Fatal("seed: 1.14 must not satisfy 1.15") 335 } 336 if !c.supports(now.Add(10*time.Minute), "knot.nel.pet", 1, 15, false, func() (string, bool) { return "v1.15.0", true }) { 337 t.Error("a re-probe past the fresh window must pick up the upgraded version") 338 } 339} 340 341func TestVersionProbeCacheEnforcesHardCap(t *testing.T) { 342 c := newProbeCache() 343 now := time.Unix(1_000_000, 0) 344 probe := func() (string, bool) { return "v1.15.0", true } 345 for i := 0; i < versionProbeCacheMax+200; i++ { 346 c.supports(now, "knot"+strconv.Itoa(i)+".nel.pet", 1, 15, false, probe) 347 } 348 if len(c.entries) > versionProbeCacheMax { 349 t.Fatalf("entries = %d, must never exceed cap %d even when every entry is fresh", len(c.entries), versionProbeCacheMax) 350 } 351} 352 353func TestVersionProbeCacheEvictsOldestWhenFull(t *testing.T) { 354 c := newProbeCache() 355 base := time.Unix(1_000_000, 0) 356 probe := func() (string, bool) { return "v1.15.0", true } 357 for i := 0; i < versionProbeCacheMax; i++ { 358 c.supports(base.Add(time.Duration(i)*time.Millisecond), "knot"+strconv.Itoa(i)+".nel.pet", 1, 15, false, probe) 359 } 360 c.supports(base.Add(versionProbeCacheMax*time.Millisecond), "newcomer.nel.pet", 1, 15, false, probe) 361 362 if len(c.entries) != versionProbeCacheMax { 363 t.Fatalf("entries = %d, want exactly cap %d after evicting one for the newcomer", len(c.entries), versionProbeCacheMax) 364 } 365 if _, ok := c.get("knot0.nel.pet"); ok { 366 t.Error("oldest entry must be evicted to make room") 367 } 368 if _, ok := c.get("newcomer.nel.pet"); !ok { 369 t.Error("newcomer must be retained") 370 } 371}