1package xkbcommon
2
3import (
4 "bytes"
5 "errors"
6 "io"
7 "testing"
8
9 "github.com/bnema/purego-xkbcommon/raw"
10)
11
12func TestStateUTF8IntoBoundsAndLifetime(t *testing.T) {
13 c := live(t)
14 s := state(t, rules(t, c, "de"))
15 var scratch [MaxUTF8Buffer]byte
16 n, err := s.UTF8Into(WaylandKeycode(16), scratch[:])
17 if err != nil || !bytes.Equal(scratch[:n], []byte{'q'}) {
18 t.Fatalf("ASCII length=%d err=%v", n, err)
19 }
20 mod, err := rules(t, c, "de").ModIndex("Mod5")
21 if err != nil {
22 t.Fatal(err)
23 }
24 if _, err := s.UpdateMask(1<<mod, 0, 0, 0, 0, 0); err != nil {
25 t.Fatal(err)
26 }
27 n, err = s.UTF8Into(WaylandKeycode(18), scratch[:])
28 if err != nil || !bytes.Equal(scratch[:n], []byte{0xe2, 0x82, 0xac}) {
29 t.Fatalf("multibyte length=%d err=%v", n, err)
30 }
31 short := []byte{0xff, 0xff, 0xff}
32 n, err = s.UTF8Into(WaylandKeycode(18), short)
33 if !errors.Is(err, io.ErrShortBuffer) || n != 3 || !bytes.Equal(short, make([]byte, len(short))) {
34 t.Fatalf("short: n=%d err=%v", n, err)
35 }
36 for _, length := range []int{0, MaxUTF8Buffer + 1} {
37 buf := bytes.Repeat([]byte{0xff}, length)
38 if _, err := s.UTF8Into(WaylandKeycode(18), buf); !errors.Is(err, ErrUTF8Buffer) {
39 t.Fatal(err)
40 }
41 if !bytes.Equal(buf, make([]byte, length)) {
42 t.Fatal("invalid buffer not cleared")
43 }
44 }
45 s.Close()
46 for i := range scratch {
47 scratch[i] = 0xff
48 }
49 if _, err := s.UTF8Into(WaylandKeycode(18), scratch[:]); !errors.Is(err, ErrClosed) {
50 t.Fatal(err)
51 }
52 if !bytes.Equal(scratch[:], make([]byte, len(scratch))) {
53 t.Fatal("closed handle left old bytes")
54 }
55}
56
57func TestComposeUTF8Into(t *testing.T) {
58 c := live(t)
59 table, err := c.NewComposeTable("en_US.UTF-8")
60 if err != nil {
61 t.Fatal(err)
62 }
63 defer table.Close()
64 compose, err := table.NewState()
65 if err != nil {
66 t.Fatal(err)
67 }
68 defer compose.Close()
69 for _, sym := range []uint32{raw.XKB_KEY_dead_circumflex, raw.XKB_KEY_e} {
70 if _, err := compose.Feed(sym); err != nil {
71 t.Fatal(err)
72 }
73 }
74 var buf [MaxUTF8Buffer]byte
75 if n, err := compose.UTF8Into(buf[:]); err != nil || !bytes.Equal(buf[:n], []byte{0xc3, 0xaa}) {
76 t.Fatalf("compose length=%d err=%v", n, err)
77 }
78 if n, err := compose.UTF8Into(buf[:2]); !errors.Is(err, io.ErrShortBuffer) || n != 2 {
79 t.Fatalf("compose short length=%d err=%v", n, err)
80 }
81 if buf[0] != 0 || buf[1] != 0 {
82 t.Fatal("short compose retained bytes")
83 }
84 if err := compose.Reset(); err != nil {
85 t.Fatal(err)
86 }
87 if n, err := compose.UTF8Into(buf[:]); err != nil || n != 0 {
88 t.Fatalf("reset length=%d err=%v", n, err)
89 }
90}
91
92func TestUTF8IntoBoundedAllocations(t *testing.T) {
93 s := state(t, rules(t, live(t), "us"))
94 var buf [MaxUTF8Buffer]byte
95 if n, err := s.UTF8Into(WaylandKeycode(16), buf[:]); err != nil || n != 1 {
96 t.Fatal(n, err)
97 }
98 allocs := testing.AllocsPerRun(100, func() {
99 if _, err := s.UTF8Into(WaylandKeycode(16), buf[:]); err != nil {
100 t.Fatal(err)
101 }
102 clear(buf[:])
103 })
104 // Purego's native dispatch may allocate fixed ABI scratch; the wrapper
105 // must not allocate result-sized buffers or string payloads.
106 // Current purego dispatch plus scoped pinning costs six fixed allocations.
107 if allocs > 6 {
108 t.Fatalf("UTF8Into allocations=%v", allocs)
109 }
110}
111
112func TestUTF8IntoNilAndClosedReceiversClearScratch(t *testing.T) {
113 var nilState *State
114 var nilCompose *ComposeState
115 buf := []byte{0xff, 0xff, 0xff}
116 if n, err := nilState.UTF8Into(WaylandKeycode(16), buf); n != 0 || !errors.Is(err, ErrClosed) {
117 t.Fatal(n, err)
118 }
119 if !bytes.Equal(buf, []byte{0, 0, 0}) {
120 t.Fatal("nil state retained bytes")
121 }
122 for i := range buf {
123 buf[i] = 0xff
124 }
125 if n, err := nilCompose.UTF8Into(buf); n != 0 || !errors.Is(err, ErrClosed) {
126 t.Fatal(n, err)
127 }
128 if !bytes.Equal(buf, []byte{0, 0, 0}) {
129 t.Fatal("nil compose retained bytes")
130 }
131 c := live(t)
132 table, err := c.NewComposeTable("en_US.UTF-8")
133 if err != nil {
134 t.Fatal(err)
135 }
136 defer table.Close()
137 compose, err := table.NewState()
138 if err != nil {
139 t.Fatal(err)
140 }
141 compose.Close()
142 for i := range buf {
143 buf[i] = 0xff
144 }
145 if n, err := compose.UTF8Into(buf); n != 0 || !errors.Is(err, ErrClosed) {
146 t.Fatal(n, err)
147 }
148 if !bytes.Equal(buf, []byte{0, 0, 0}) {
149 t.Fatal("closed compose retained bytes")
150 }
151}