detached f02b08f

hardening_test.go RawHistoryFolder

1package wlturbo
2
3import (
4	"bytes"
5	"encoding/binary"
6	"errors"
7	"io"
8	"net"
9	"os"
10	"testing"
11	"time"
12)
13
14// A read deadline must not leave the Display permanently broken.
15func TestDispatchDeadlineIsNotSticky(t *testing.T) {
16	client, server := net.Pipe()
17	defer client.Close()
18	defer server.Close()
19	d := newDisplay(client)
20	records := []EventRecord{}
21	p := &recordingProxy{BaseProxy: BaseProxy{id: 7, context: d.context}, records: &records}
22	d.objects.Store(uint32(7), p)
23	d.RegisterEventSignature(7, 4, "uint,")
24
25	if err := client.SetReadDeadline(time.Now().Add(20 * time.Millisecond)); err != nil {
26		t.Fatal(err)
27	}
28	if err := d.Dispatch(); !errors.Is(err, os.ErrDeadlineExceeded) {
29		t.Fatalf("Dispatch = %v, want deadline exceeded", err)
30	}
31	if err := client.SetReadDeadline(time.Time{}); err != nil {
32		t.Fatal(err)
33	}
34	go func() { _, _ = server.Write(message(7, 4, []byte{1, 0, 0, 0})) }()
35	if err := d.Dispatch(); err != nil {
36		t.Fatalf("Dispatch after clearing deadline = %v", err)
37	}
38	if len(records) != 1 {
39		t.Fatalf("records = %d, want 1", len(records))
40	}
41}
42
43type countingGlobalHandler struct{ n *int }
44
45func (h countingGlobalHandler) HandleRegistryGlobal(RegistryGlobalEvent) { *h.n++ }
46
47// Every registered global handler runs, not only the last one.
48func TestRegistryGlobalHandlersAccumulate(t *testing.T) {
49	d := newDisplay(&chunkConn{})
50	var a, b, specific int
51	d.registry.AddGlobalHandler(countingGlobalHandler{&a})
52	d.registry.AddGlobalHandler(countingGlobalHandler{&b})
53	d.registry.AddHandler("abc", func(*Registry, uint32, uint32) { specific++ })
54	d.registry.handleGlobal(1, "abc", 1)
55	if a != 1 || b != 1 || specific != 1 {
56		t.Fatalf("calls a=%d b=%d specific=%d, want 1 each", a, b, specific)
57	}
58}
59
60type fdSigProxy struct {
61	BaseProxy
62	calls int
63}
64
65func (p *fdSigProxy) EventSignature(op uint16) (string, bool) {
66	switch op {
67	case 0:
68		return "uint,", true
69	case 1:
70		return "fd,", true
71	}
72	return "", false
73}
74func (p *fdSigProxy) Dispatch(*Event) { p.calls++ }
75
76// Events already in flight for an object the client destroyed are dropped,
77// with their descriptors closed, until the compositor sends delete_id.
78func TestDestroyedObjectEventsAreDroppedUntilDeleteID(t *testing.T) {
79	client, peer := unixSocketPair(t)
80	d := newDisplay(client)
81	p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
82	d.context.Register(p)
83	if err := d.context.SendDestructor(p, 0); err != nil {
84		t.Fatal(err)
85	}
86	if err := d.context.SendRequest(p, 1); err == nil {
87		t.Fatal("request on destroyed proxy succeeded")
88	}
89
90	r, w, err := os.Pipe()
91	if err != nil {
92		t.Fatal(err)
93	}
94	defer r.Close()
95	sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
96	sendMessageWithFDs(t, peer, message(p.ID(), 1, nil), []int{int(w.Fd())})
97	w.Close()
98	for i := 0; i < 2; i++ {
99		if err := d.Dispatch(); err != nil {
100			t.Fatalf("in-flight event %d: %v", i, err)
101		}
102	}
103	if p.calls != 0 {
104		t.Fatalf("destroyed proxy received %d events", p.calls)
105	}
106	if err := r.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
107		t.Fatal(err)
108	}
109	if _, err := r.Read(make([]byte, 1)); !errors.Is(err, io.EOF) {
110		t.Fatalf("descriptor for destroyed object not closed: %v", err)
111	}
112
113	body := make([]byte, 4)
114	binary.LittleEndian.PutUint32(body, p.ID())
115	sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil)
116	if err := d.Dispatch(); err != nil {
117		t.Fatalf("delete_id for zombie: %v", err)
118	}
119	sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
120	if err := d.Dispatch(); !errors.Is(err, ErrUnknownObject) {
121		t.Fatalf("event after delete_id = %v, want ErrUnknownObject", err)
122	}
123	if got := d.AllocateID(); got != p.ID() {
124		t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got)
125	}
126}
127
128// An abandoned object (a wl_callback the compositor destroys itself) sends no
129// request: a late event is dropped and the compositor's delete_id retires the
130// ID, both without a protocol error.
131func TestAbandonedObjectAcceptsLateEventAndServerDeleteID(t *testing.T) {
132	client, peer := unixSocketPair(t)
133	d := newDisplay(client)
134	p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
135	d.context.Register(p)
136	if err := d.context.Abandon(p); err != nil {
137		t.Fatal(err)
138	}
139	if err := d.context.Abandon(p); err == nil {
140		t.Fatal("second Abandon succeeded")
141	}
142	if err := d.context.SendRequest(p, 0); err == nil {
143		t.Fatal("request on abandoned proxy succeeded")
144	}
145	if err := peer.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
146		t.Fatal(err)
147	}
148	if n, _ := peer.Read(make([]byte, 64)); n != 0 {
149		t.Fatalf("Abandon sent %d bytes", n)
150	}
151
152	sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil)
153	if err := d.Dispatch(); err != nil {
154		t.Fatalf("late event for abandoned object: %v", err)
155	}
156	if p.calls != 0 {
157		t.Fatalf("abandoned proxy received %d events", p.calls)
158	}
159	body := make([]byte, 4)
160	binary.LittleEndian.PutUint32(body, p.ID())
161	sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil)
162	if err := d.Dispatch(); err != nil {
163		t.Fatalf("delete_id for abandoned object: %v", err)
164	}
165	if got := d.AllocateID(); got != p.ID() {
166		t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got)
167	}
168}
169
170// A plain Go int is ambiguous (value or descriptor) and must be rejected
171// instead of being written as zero.
172func TestMarshalRejectsPlainInt(t *testing.T) {
173	d := newDisplay(&chunkConn{})
174	if err := d.marshalArg(&bytes.Buffer{}, 42); err == nil {
175		t.Fatal("marshalArg(int) succeeded, want error")
176	}
177	if err := d.SendRequest(9, 0, 42); err == nil {
178		t.Fatal("SendRequest with int argument succeeded, want error")
179	}
180}
181
182func TestCheckVersion(t *testing.T) {
183	if err := CheckVersion(0, 5, "x.y"); err != nil {
184		t.Fatalf("unknown version rejected: %v", err)
185	}
186	if err := CheckVersion(5, 5, "x.y"); err != nil {
187		t.Fatalf("exact version rejected: %v", err)
188	}
189	if err := CheckVersion(4, 5, "x.y"); !errors.Is(err, ErrVersionTooLow) {
190		t.Fatalf("older version = %v, want ErrVersionTooLow", err)
191	}
192}
193
194// Bind records the negotiated version so generated requests can check it.
195func TestBindRecordsVersion(t *testing.T) {
196	d := newDisplay(&chunkConn{})
197	p := &BaseProxy{}
198	if err := d.Registry().Bind(1, "wl_seat", 3, p); err != nil {
199		t.Fatal(err)
200	}
201	if p.Version() != 3 {
202		t.Fatalf("version = %d, want 3", p.Version())
203	}
204}
205
206// A late Unregister of a destroyed proxy (for example from a handler that was
207// already running) must not remove its zombie.
208func TestUnregisterKeepsZombie(t *testing.T) {
209	d := newDisplay(&chunkConn{})
210	p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}}
211	d.context.Register(p)
212	if err := d.context.SendDestructor(p, 0); err != nil {
213		t.Fatal(err)
214	}
215	d.context.Unregister(p)
216	if obj, _ := d.objects.Load(p.ID()); obj == nil {
217		t.Fatal("Unregister removed the zombie")
218	} else if _, ok := obj.(*zombie); !ok {
219		t.Fatalf("object = %T, want *zombie", obj)
220	}
221}
222
223// Context.Request owns the request lifecycle generated code relies on.
224func TestContextRequestLifecycle(t *testing.T) {
225	d := newDisplay(&chunkConn{})
226	parent := &BaseProxy{id: d.AllocateID(), context: d.context, version: 2}
227	d.context.Register(parent)
228
229	child := &BaseProxy{context: d.context}
230	if err := d.context.Request(Request{Proxy: parent, Opcode: 0, Name: "x.make", Child: child}, child); err != nil {
231		t.Fatal(err)
232	}
233	if child.ID() == 0 || child.Version() != 2 {
234		t.Fatalf("child id=%d version=%d, want allocated id and version 2", child.ID(), child.Version())
235	}
236	if _, ok := d.objects.Load(child.ID()); !ok {
237		t.Fatal("child not registered")
238	}
239
240	late := &BaseProxy{context: d.context}
241	err := d.context.Request(Request{Proxy: parent, Opcode: 1, Name: "x.late", Since: 3, Child: late}, late)
242	if !errors.Is(err, ErrVersionTooLow) {
243		t.Fatalf("since=3 on v2 = %v, want ErrVersionTooLow", err)
244	}
245	if late.ID() != 0 {
246		t.Fatal("child allocated for a refused request")
247	}
248
249	bad := &BaseProxy{context: d.context}
250	if err := d.context.Request(Request{Proxy: parent, Opcode: 2, Name: "x.bad", Child: bad}, bad, 42); err == nil {
251		t.Fatal("marshal error not reported")
252	}
253	if _, ok := d.objects.Load(bad.ID()); ok {
254		t.Fatal("child left registered after a failed send")
255	}
256}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close