1package wayland
2
3import (
4 "slices"
5 "testing"
6 "time"
7
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/bnema/purego-libwayland/protocol/wayland"
10)
11
12func TestFramePeriod(t *testing.T) {
13 for _, tc := range []struct {
14 milli int
15 want time.Duration
16 }{{0, time.Second / 60}, {60000, time.Second / 60}, {165058, 6058476}, {144000, 6944444}} {
17 if got := framePeriod(tc.milli); got != tc.want {
18 t.Errorf("%d: %v want %v", tc.milli, got, tc.want)
19 }
20 }
21}
22
23// Each output is paced at its own refresh; a flip fires its queue and
24// pushes its deadline back; the pacer idles with nothing queued.
25func TestDueFrames(t *testing.T) {
26 fast := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "DP-2", RefreshMilli: 165000}}}
27 slow := &output{place: ports.OutputPlacement{Info: ports.OutputInfo{Name: "HDMI-A-1", RefreshMilli: 60000}}}
28 s := &Server{outputs: []*output{fast, slow}, frameDue: map[string]time.Time{}, awaiting: map[string][]*wayland.Callback{}}
29 // dueFrames only counts the queued callbacks: nil ones stand for them.
30 queue := func(name string) { s.awaiting[name] = append(s.awaiting[name], nil) }
31 fired := func(fire []string) {
32 for _, name := range fire {
33 delete(s.awaiting, name)
34 }
35 }
36 now := time.Unix(100, 0)
37 if _, _, idle := s.dueFrames(now, nil); !idle {
38 t.Fatal("not idle without callbacks")
39 }
40 // A first callback goes out at once, then one per refresh.
41 queue("DP-2")
42 queue("HDMI-A-1")
43 fire, _, idle := s.dueFrames(now, nil)
44 if len(fire) != 2 || !idle {
45 t.Fatalf("first fire %v idle %v", fire, idle)
46 }
47 fired(fire)
48 queue("DP-2")
49 queue("HDMI-A-1")
50 fire, wait, _ := s.dueFrames(now.Add(time.Millisecond), nil)
51 if len(fire) != 0 || wait != framePeriod(165000)-time.Millisecond {
52 t.Fatalf("early fire %v wait %v", fire, wait)
53 }
54 at := now.Add(framePeriod(165000))
55 fire, wait, _ = s.dueFrames(at, nil)
56 if !slices.Equal(fire, []string{"DP-2"}) || wait != time.Second/60-framePeriod(165000) {
57 t.Fatalf("165 Hz fire %v wait %v", fire, wait)
58 }
59 fired(fire)
60 // A flip fires its output early and pushes its deadline to 1.5 periods.
61 queue("DP-2")
62 flip := at.Add(time.Millisecond)
63 fire, _, _ = s.dueFrames(flip, map[string]bool{"DP-2": true})
64 if !slices.Equal(fire, []string{"DP-2"}) || s.frameDue["DP-2"] != flip.Add(framePeriod(165000)*3/2) {
65 t.Fatalf("flip fire %v due %v", fire, s.frameDue["DP-2"].Sub(flip))
66 }
67 fired(fire)
68 // A queue on an unplugged output moves to the outputless one (60 Hz).
69 s.outputs = []*output{fast}
70 queue("HDMI-A-1")
71 fire, _, _ = s.dueFrames(flip, nil)
72 fired(fire)
73 if _, ok := s.frameDue["HDMI-A-1"]; ok || len(s.awaiting["HDMI-A-1"]) != 0 || !slices.Equal(fire, []string{""}) {
74 t.Fatalf("unplugged: fire %v due %v", fire, s.frameDue)
75 }
76}