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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
/*
* Tests for the per-terminal grapheme store (gstore).
*
* Verifies:
* - Two terminals do not share clusters: freeing one leaves the other intact.
* - Clusters that remain visible on screen survive GC sweeps.
* - More than 65,536 distinct clusters can be interned as old ones scroll
* out of history, without reaching any cap or exhausting memory.
* - Direct unit tests of intern, expand, deduplication, and sweep.
*/
#include "gstore.h"
#include "internal.h"
#include "term/term.h"
#include "term/unicode/utf8.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void test_direct_unit(void)
{
struct gstore *g = term_gstore_new(NULL);
assert(g != NULL);
assert(term_gstore_count(g) == 0);
/* Single code point returns the code point itself. */
uint32_t single = 'A';
assert(term_gstore_intern(g, &single, 1) == 'A');
assert(term_gstore_count(g) == 0);
/* Cluster returns a handle. */
uint32_t c1[2] = { 'e', 0x0301 };
uint32_t h1 = term_gstore_intern(g, c1, 2);
assert(term_grapheme_is_handle(h1));
assert(term_gstore_count(g) == 1);
/* Identical cluster deduplicates to the same handle. */
uint32_t h1_dup = term_gstore_intern(g, c1, 2);
assert(h1_dup == h1);
assert(term_gstore_count(g) == 1);
/* Different cluster gets a distinct handle. */
uint32_t c2[2] = { 'a', 0x0300 };
uint32_t h2 = term_gstore_intern(g, c2, 2);
assert(term_grapheme_is_handle(h2));
assert(h2 != h1);
assert(term_gstore_count(g) == 2);
/* Expand both handles. */
uint32_t out[TERM_GRAPHEME_MAX_LEN] = {0};
int n = term_gstore_expand(g, h1, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(out[0] == 'e' && out[1] == 0x0301);
n = term_gstore_expand(g, h2, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(out[0] == 'a' && out[1] == 0x0300);
/* Sweep with h1 marked and h2 unmarked. */
term_gstore_mark(g, h1);
term_gstore_sweep(g, NULL);
assert(term_gstore_count(g) == 1);
/* h1 is still valid. */
n = term_gstore_expand(g, h1, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(out[0] == 'e' && out[1] == 0x0301);
/* h2 was reclaimed. */
n = term_gstore_expand(g, h2, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 1 && out[0] == 0xFFFD);
/* New cluster can reuse the reclaimed slot. */
uint32_t c3[3] = { 'o', 0x0308, 0x0301 };
uint32_t h3 = term_gstore_intern(g, c3, 3);
assert(term_grapheme_is_handle(h3));
assert(term_gstore_count(g) == 2);
n = term_gstore_expand(g, h3, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 3);
assert(out[0] == 'o' && out[1] == 0x0308 && out[2] == 0x0301);
term_gstore_free(g);
}
static void test_two_terminals_isolation(void)
{
struct term_options opt = { .size = { .x = 20, .y = 4 } };
struct term *t1 = term_new(&opt);
struct term *t2 = term_new(&opt);
assert(t1 && t2);
const char *cluster_text = "e\xcc\x81"; /* 'e' with acute */
term_feed(t1, cluster_text, strlen(cluster_text), 0);
term_feed(t2, cluster_text, strlen(cluster_text), 0);
/* Free terminal 1. */
term_free(t1);
/* Terminal 2 must still expand its cluster correctly. */
struct term_snapshot *snap = term_snapshot_new();
assert(snap);
assert(term_snapshot_update(snap, t2));
const struct term_cell *row = term_snapshot_row(snap, 0);
assert(row[0].text & TERM_TEXT_CLUSTER);
uint32_t cps[TERM_GRAPHEME_MAX_LEN] = {0};
int n = term_snapshot_cluster(snap, row[0].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(cps[0] == 'e' && cps[1] == 0x0301);
term_snapshot_free(snap);
term_free(t2);
}
static void test_screen_clusters_survive_sweep(void)
{
struct term_options opt = { .size = { .x = 40, .y = 10 }, .scrollback = 10 };
struct term *t = term_new(&opt);
assert(t);
/* Write 5 clusters on the top row. */
term_feed(t, "a\xcc\x80" "b\xcc\x81" "c\xcc\x82" "d\xcc\x83" "e\xcc\x84", 15, 0);
/* Write ephemeral clusters on row 1, then overwrite them. */
for (int i = 0; i < 50; i++) {
term_feed(t, "\033[2;1H\033[2K", 8, 0);
char buf[32];
int len = snprintf(buf, sizeof(buf), "z\xcc\x80\xcc\x81%d", i);
term_feed(t, buf, (size_t)len, 0);
}
term_feed(t, "\033[2;1H\033[2K", 8, 0);
/* Explicit sweep. The clusters on row 0 are on screen and must survive. */
term_gstore_sweep(t->gstore, t);
struct term_snapshot *snap = term_snapshot_new();
assert(snap);
assert(term_snapshot_update(snap, t));
/* Row 0 of screen has "a\xcc\x80...". */
const struct term_cell *row = term_snapshot_row(snap, 0);
assert(row[0].text & TERM_TEXT_CLUSTER);
uint32_t cps[TERM_GRAPHEME_MAX_LEN] = {0};
int n = term_snapshot_cluster(snap, row[0].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(cps[0] == 'a' && cps[1] == 0x0300);
n = term_snapshot_cluster(snap, row[1].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(cps[0] == 'b' && cps[1] == 0x0301);
n = term_snapshot_cluster(snap, row[2].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(cps[0] == 'c' && cps[1] == 0x0302);
term_snapshot_free(snap);
term_free(t);
}
static void test_more_than_65536_clusters(void)
{
/* Bounded history so old lines scroll out. */
struct term_options opt = { .size = { .x = 80, .y = 24 }, .scrollback = 10 };
struct term *t = term_new(&opt);
assert(t);
/*
* Generate 66,000 distinct clusters.
* Base 'a' (0x61) + mark1 (0x0300..0x034F, 80 options) + mark2 (0x0350..0x036F + 0xFE00..0xFE0F, 830 options)
* 256 * 260 = 66,560 distinct clusters.
*/
char buf[64];
uint32_t total = 0;
const uint32_t target = 66000;
for (uint32_t i = 0; total < target; i++) {
char base = (char)('a' + (i % 26));
uint32_t cp1 = 0x0300 + ((i / 26) % 55);
uint32_t cp2 = 0x0340 + ((i / (26 * 55)) % 47);
size_t len = 0;
buf[len++] = base;
len += (size_t)term_utf8_encode(cp1, buf + len);
len += (size_t)term_utf8_encode(cp2, buf + len);
buf[len++] = '\r';
buf[len++] = '\n';
term_feed(t, buf, len, 0);
total++;
}
assert(total >= 65536);
/* Verify the latest cluster on screen reads back correctly. */
struct term_snapshot *snap = term_snapshot_new();
assert(snap);
assert(term_snapshot_update(snap, t));
struct term_cursor c = term_cursor(t);
int y = c.pos.y > 0 ? c.pos.y - 1 : 0;
const struct term_cell *row = term_snapshot_row(snap, y);
assert(row[0].text & TERM_TEXT_CLUSTER);
uint32_t cps[TERM_GRAPHEME_MAX_LEN] = {0};
int n = term_snapshot_cluster(snap, row[0].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 3);
/* Live cluster count must be small (only bounded screen and history). */
uint32_t count = term_gstore_count(t->gstore);
assert(count < 2000);
term_snapshot_free(snap);
term_free(t);
}
int main(void)
{
test_direct_unit();
test_two_terminals_isolation();
test_screen_clusters_survive_sweep();
test_more_than_65536_clusters();
puts("PASS: gstore tests");
return 0;
}
|