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
172
173
174
|
package main
import (
"os"
"path/filepath"
"testing"
)
func readFixture(t *testing.T, name string) string {
t.Helper()
b, err := os.ReadFile(filepath.Join("testdata", name))
if err != nil {
t.Fatalf("reading fixture %s: %s", name, err)
}
return string(b)
}
// The real output of `xrandr` on rolery with only the laptop panel attached.
// Captured from the machine, not written by hand.
func TestParseRoleryLaptopOnly(t *testing.T) {
outputs := Parse(readFixture(t, "rolery-laptop-only.txt"))
if got, want := len(outputs), 4; got != want {
t.Fatalf("got %d outputs, want %d", got, want)
}
for i, want := range []struct {
name string
connected bool
modes int
}{
{"eDP", true, 9},
{"HDMI-A-0", false, 0},
{"DisplayPort-0", false, 0},
{"DisplayPort-1", false, 0},
} {
got := outputs[i]
if got.Name != want.name {
t.Errorf("output %d: got name %q, want %q", i, got.Name, want.name)
}
if got.Connected != want.connected {
t.Errorf("%s: got connected=%v, want %v", want.name, got.Connected, want.connected)
}
if len(got.Modes) != want.modes {
t.Errorf("%s: got %d modes, want %d", want.name, len(got.Modes), want.modes)
}
if got.Index != i {
t.Errorf("%s: got index %d, want %d", want.name, got.Index, i)
}
}
eDP := outputs[0]
if eDP.Native == nil || eDP.Current == nil {
t.Fatalf("eDP: got native=%v current=%v, want both set", eDP.Native, eDP.Current)
}
// `1920x1080 60.03*+` is both the active and the preferred mode.
if eDP.Native.Width != "1920" || eDP.Native.Height != "1080" {
t.Errorf("eDP native: got %sx%s, want 1920x1080", eDP.Native.Width, eDP.Native.Height)
}
if eDP.Current.Width != "1920" || eDP.Current.Height != "1080" {
t.Errorf("eDP current: got %sx%s, want 1920x1080", eDP.Current.Width, eDP.Current.Height)
}
if eDP.Native.Rate != 60.03 {
t.Errorf("eDP native rate: got %v, want 60.03", eDP.Native.Rate)
}
if eDP.Width == nil || *eDP.Width != 1920 || eDP.Height == nil || *eDP.Height != 1080 {
t.Errorf("eDP geometry: got %v x %v, want 1920 x 1080", eDP.Width, eDP.Height)
}
}
// HDMI-A-0 is reported as `disconnected` while still carrying `primary` and a
// resolution. This is a real state on rolery, and treating "has a resolution" as
// "is connected" would make the tool try to configure a monitor that is not
// plugged in.
func TestParseDisconnectedWithResolutionIsNotConnected(t *testing.T) {
outputs := Parse(readFixture(t, "rolery-laptop-only.txt"))
hdmi, ok := Find(outputs, "HDMI-A-0")
if !ok {
t.Fatal("HDMI-A-0 not found")
}
if hdmi.Connected {
t.Error("HDMI-A-0 is reported as `disconnected` by xrandr, but was parsed as connected")
}
if hdmi.Width != nil || hdmi.Height != nil {
t.Errorf("HDMI-A-0: got geometry %v x %v, want none: the geometry is on the "+
"`disconnected` line, which the regex deliberately does not capture",
hdmi.Width, hdmi.Height)
}
if got, want := len(Connected(outputs)), 1; got != want {
t.Errorf("got %d connected outputs, want %d (only eDP)", got, want)
}
}
// The indented modeline blocks that xrandr prints for some outputs must not be
// picked up as modes. They are separated from the connector line by other
// lines, and any non-matching line resets which output modes attach to.
func TestParseIgnoresVerboseModelines(t *testing.T) {
outputs := Parse(readFixture(t, "interlaced-and-verbose.txt"))
dp, ok := Find(outputs, "DisplayPort-0")
if !ok {
t.Fatal("DisplayPort-0 not found")
}
if len(dp.Modes) != 0 {
t.Errorf("DisplayPort-0: got %d modes, want 0; the `2560x1440 (0x60) 241.500MHz` "+
"modeline block must not be attached to it", len(dp.Modes))
}
}
// Interlaced modes have an `i` suffix on the height, which is why Height is a
// string rather than a number.
func TestParseInterlacedMode(t *testing.T) {
outputs := Parse(readFixture(t, "interlaced-and-verbose.txt"))
eDP, ok := Find(outputs, "eDP")
if !ok {
t.Fatal("eDP not found")
}
var found bool
for _, m := range eDP.Modes {
if m.Height == "1080i" {
found = true
if m.Width != "1920" || m.Rate != 50.00 {
t.Errorf("interlaced mode: got %sx%s@%v, want 1920x1080i@50", m.Width, m.Height, m.Rate)
}
}
}
if !found {
t.Errorf("did not parse the interlaced mode `1920x1080i`, got modes %v", eDP.Modes)
}
}
func TestParseTwoMonitors(t *testing.T) {
outputs := Parse(readFixture(t, "rolery-two-monitors.txt"))
connected := Connected(outputs)
if got, want := len(connected), 2; got != want {
t.Fatalf("got %d connected outputs, want %d", got, want)
}
hdmi, ok := Find(outputs, "HDMI-A-0")
if !ok {
t.Fatal("HDMI-A-0 not found")
}
if !hdmi.Connected {
t.Error("HDMI-A-0: got connected=false, want true")
}
if hdmi.Native == nil || hdmi.Native.Width != "2560" || hdmi.Native.Height != "1440" {
t.Errorf("HDMI-A-0 native: got %v, want 2560x1440", hdmi.Native)
}
// `1920x1080 60.00 50.00 59.94` -- several rates on one line. The
// regex takes the first, as the node parser did.
var has1920 bool
for _, m := range hdmi.Modes {
if m.Width == "1920" && m.Height == "1080" {
has1920 = true
if m.Rate != 60.00 {
t.Errorf("HDMI-A-0 1920x1080: got rate %v, want 60.00 (the first on the line)", m.Rate)
}
}
}
if !has1920 {
t.Error("HDMI-A-0: did not parse the 1920x1080 mode")
}
}
func TestParseEmptyInput(t *testing.T) {
if got := Parse(""); len(got) != 0 {
t.Errorf("got %d outputs for empty input, want 0", len(got))
}
}
|