oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "context"
5 "io"
6 "log/slog"
7 "sync/atomic"
8 "testing"
9 "time"
10
11 "amadan.net/oficina/calls/internal/calls/hub"
12)
13
14// Review W-12: the fifteen-second housekeeping pass reads standing once
15// per joined participation, serially, and it used to be given the
16// process context and nothing else. A driver that stopped answering
17// would then stall §2.4's quiet case for as long as it stayed stuck —
18// no lapsed resume windows collected, no kind changes applied, and a
19// removed member dropped only by the next thing they did.
20//
21// The property under test is that the pass ends on its own deadline.
22// `within` is a parameter so this costs milliseconds rather than the
23// fifteen seconds tickForever passes.
24func TestATickPassEndsOnItsOwnDeadline(t *testing.T) {
25 var stuck atomic.Bool
26 reached := make(chan struct{}, 1)
27 h := hub.New(hub.Config{
28 Now: time.Now,
29 Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
30 StandingFor: func(ctx context.Context, room string, personID int64) (hub.Kind, bool, error) {
31 if !stuck.Load() {
32 return hub.KindMember, true, nil
33 }
34 select {
35 case reached <- struct{}{}:
36 default:
37 }
38 // A read that never comes back on its own: the only thing
39 // that ends it is the context the tick handed down.
40 <-ctx.Done()
41 return 0, false, ctx.Err()
42 },
43 })
44 p, err := h.Open("r1", "s1", 1, "Mia", hub.KindMember)
45 if err != nil {
46 t.Fatal(err)
47 }
48 // Joined, so the tick has a standing to read for it.
49 if err := h.Join(context.Background(), "r1", "s1", p.Peer); err != nil {
50 t.Fatal(err)
51 }
52 stuck.Store(true)
53
54 a := &app{hub: h}
55 done := make(chan struct{})
56 started := time.Now()
57 go func() {
58 a.tickOnce(context.Background(), 200*time.Millisecond)
59 close(done)
60 }()
61
62 select {
63 case <-done:
64 case <-time.After(5 * time.Second):
65 t.Fatal("a tick pass whose standing read never returned did not end on its own deadline")
66 }
67 select {
68 case <-reached:
69 default:
70 t.Fatal("the pass never reached a standing read, so it proves nothing about the deadline")
71 }
72 if took := time.Since(started); took < 150*time.Millisecond {
73 t.Fatalf("the pass ended after %v, before its own deadline", took)
74 }
75}
76
77// The deadline is not decoration: the scheduling path gives each pass
78// the interval between passes, so a pass can never overrun the slot the
79// next one needs. This used to assert that a constant equalled itself,
80// which left the relationship free to be changed with the test green
81// (Codex S2-14) — so what runs here is the loop itself, in milliseconds,
82// with a recorder in the pass's place.
83func TestTheSchedulingPathGivesEachPassTheIntervalAsItsDeadline(t *testing.T) {
84 const every = 20 * time.Millisecond
85 got := make(chan time.Duration, 1)
86 a := &app{tickPass: func(ctx context.Context, within time.Duration) {
87 select {
88 case got <- within:
89 default:
90 }
91 }}
92 ctx, cancel := context.WithCancel(context.Background())
93 defer cancel()
94 go a.tickLoop(ctx, every)
95
96 select {
97 case within := <-got:
98 if within != every {
99 t.Fatalf("a pass scheduled every %v was given %v to run in", every, within)
100 }
101 case <-time.After(5 * time.Second):
102 t.Fatal("the scheduling path never ran a pass")
103 }
104
105 // And the interval production runs on is the spec's.
106 if tickEvery != 15*time.Second {
107 t.Fatalf("tickEvery is %v, and spec §2.4 says fifteen seconds", tickEvery)
108 }
109}
110