]>
Commit | Line | Data |
---|---|---|
5eef597e MP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | /*** | |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> | |
6 | ||
7 | systemd is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU Lesser General Public License as published by | |
9 | the Free Software Foundation; either version 2.1 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | systemd is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | Lesser General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU Lesser General Public License | |
18 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
19 | ***/ | |
20 | ||
21 | /* | |
22 | * Terminal Page/Line/Cell/Char Tests | |
23 | * This tests internals of terminal page, line, cell and char handling. It | |
24 | * relies on some implementation details, so it might need to be updated if | |
25 | * those internals are changed. They should be fairly obvious, though. | |
26 | */ | |
27 | ||
5eef597e | 28 | #include <stdio.h> |
5eef597e MP |
29 | #include <string.h> |
30 | #include "macro.h" | |
31 | #include "term-internal.h" | |
5eef597e MP |
32 | |
33 | #define MY_ASSERT_VALS __FILE__, __LINE__, __PRETTY_FUNCTION__ | |
34 | #define MY_ASSERT_FORW _FILE, _LINE, _FUNC | |
35 | #define MY_ASSERT_ARGS const char *_FILE, int _LINE, const char *_FUNC | |
36 | #define MY_ASSERT(expr) \ | |
37 | do { \ | |
38 | if (_unlikely_(!(expr))) \ | |
39 | log_assert_failed(#expr, _FILE, _LINE, _FUNC); \ | |
40 | } while (false) \ | |
41 | ||
42 | /* | |
43 | * Character Tests | |
44 | * | |
45 | * These tests rely on some implementation details of term_char_t, including | |
46 | * the way we pack characters and the internal layout of "term_char_t". These | |
47 | * tests have to be updated once we change the implementation. | |
48 | */ | |
49 | ||
50 | #define PACK(v1, v2, v3) \ | |
51 | TERM_CHAR_INIT( \ | |
52 | (((((uint64_t)v1) & 0x1fffffULL) << 43) | \ | |
53 | ((((uint64_t)v2) & 0x1fffffULL) << 22) | \ | |
54 | ((((uint64_t)v3) & 0x1fffffULL) << 1) | \ | |
55 | 0x1) \ | |
56 | ) | |
57 | #define PACK1(v1) PACK2((v1), 0x110000) | |
58 | #define PACK2(v1, v2) PACK3((v1), (v2), 0x110000) | |
59 | #define PACK3(v1, v2, v3) PACK((v1), (v2), (v3)) | |
60 | ||
61 | static void test_term_char_misc(void) { | |
62 | term_char_t c, t; | |
63 | ||
64 | /* test TERM_CHAR_NULL handling */ | |
65 | ||
66 | c = TERM_CHAR_NULL; /* c is NULL */ | |
67 | assert_se(term_char_same(c, TERM_CHAR_NULL)); | |
68 | assert_se(term_char_equal(c, TERM_CHAR_NULL)); | |
69 | assert_se(term_char_is_null(c)); | |
70 | assert_se(term_char_is_null(TERM_CHAR_NULL)); | |
71 | assert_se(!term_char_is_allocated(c)); | |
72 | ||
73 | /* test single char handling */ | |
74 | ||
75 | t = term_char_dup_append(c, 'A'); /* t is >A< now */ | |
76 | assert_se(!term_char_same(c, t)); | |
77 | assert_se(!term_char_equal(c, t)); | |
78 | assert_se(!term_char_is_allocated(t)); | |
79 | assert_se(!term_char_is_null(t)); | |
80 | ||
81 | /* test basic combined char handling */ | |
82 | ||
83 | t = term_char_dup_append(t, '~'); | |
84 | t = term_char_dup_append(t, '^'); /* t is >A~^< now */ | |
85 | assert_se(!term_char_same(c, t)); | |
86 | assert_se(!term_char_is_allocated(t)); | |
87 | assert_se(!term_char_is_null(t)); | |
88 | ||
89 | c = term_char_dup_append(c, 'A'); | |
90 | c = term_char_dup_append(c, '~'); | |
91 | c = term_char_dup_append(c, '^'); /* c is >A~^< now */ | |
92 | assert_se(term_char_same(c, t)); | |
93 | assert_se(term_char_equal(c, t)); | |
94 | ||
95 | /* test more than 2 comb-chars so the chars are allocated */ | |
96 | ||
97 | t = term_char_dup_append(t, '`'); /* t is >A~^`< now */ | |
98 | c = term_char_dup_append(c, '`'); /* c is >A~^`< now */ | |
99 | assert_se(!term_char_same(c, t)); | |
100 | assert_se(term_char_equal(c, t)); | |
101 | ||
102 | /* test dup_append() on allocated chars */ | |
103 | ||
104 | term_char_free(t); | |
105 | t = term_char_dup_append(c, '"'); /* t is >A~^`"< now */ | |
106 | assert_se(!term_char_same(c, t)); | |
107 | assert_se(!term_char_equal(c, t)); | |
108 | c = term_char_merge(c, '"'); /* c is >A~^`"< now */ | |
109 | assert_se(!term_char_same(c, t)); | |
110 | assert_se(term_char_equal(c, t)); | |
111 | ||
112 | term_char_free(t); | |
113 | term_char_free(c); | |
114 | } | |
115 | ||
116 | static void test_term_char_packing(void) { | |
117 | uint32_t seqs[][1024] = { | |
118 | { -1 }, | |
119 | { 0, -1 }, | |
120 | { 'A', '~', -1 }, | |
121 | { 'A', '~', 0, -1 }, | |
122 | { 'A', '~', 'a', -1 }, | |
123 | }; | |
124 | term_char_t res[] = { | |
125 | TERM_CHAR_NULL, | |
126 | PACK1(0), | |
127 | PACK2('A', '~'), | |
128 | PACK3('A', '~', 0), | |
129 | PACK3('A', '~', 'a'), | |
130 | }; | |
131 | uint32_t next; | |
132 | unsigned int i, j; | |
133 | term_char_t c = TERM_CHAR_NULL; | |
134 | ||
135 | /* | |
136 | * This creates term_char_t objects based on the data in @seqs and | |
137 | * compares the result to @res. Only basic packed types are tested, no | |
138 | * allocations are done. | |
139 | */ | |
140 | ||
141 | for (i = 0; i < ELEMENTSOF(seqs); ++i) { | |
142 | for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { | |
143 | next = seqs[i][j]; | |
144 | if (next == (uint32_t)-1) | |
145 | break; | |
146 | ||
147 | c = term_char_merge(c, next); | |
148 | } | |
149 | ||
150 | assert_se(!memcmp(&c, &res[i], sizeof(c))); | |
151 | c = term_char_free(c); | |
152 | } | |
153 | } | |
154 | ||
155 | static void test_term_char_allocating(void) { | |
156 | uint32_t seqs[][1024] = { | |
157 | { 0, -1 }, | |
158 | { 'A', '~', -1 }, | |
159 | { 'A', '~', 0, -1 }, | |
160 | { 'A', '~', 'a', -1 }, | |
161 | { 'A', '~', 'a', 'b', 'c', 'd', -1 }, | |
162 | { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, | |
163 | /* exceeding implementation-defined soft-limit of 64 */ | |
164 | { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
165 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
166 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
167 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
168 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
169 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
170 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
171 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, | |
172 | }; | |
173 | term_char_t res[] = { | |
174 | PACK1(0), | |
175 | PACK2('A', '~'), | |
176 | PACK3('A', '~', 0), | |
177 | PACK3('A', '~', 'a'), | |
178 | TERM_CHAR_NULL, /* allocated */ | |
179 | TERM_CHAR_NULL, /* allocated */ | |
180 | TERM_CHAR_NULL, /* allocated */ | |
181 | }; | |
182 | uint32_t str[][1024] = { | |
183 | { 0, -1 }, | |
184 | { 'A', '~', -1 }, | |
185 | { 'A', '~', 0, -1 }, | |
186 | { 'A', '~', 'a', -1 }, | |
187 | { 'A', '~', 'a', 'b', 'c', 'd', -1 }, | |
188 | { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, | |
189 | { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
190 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
191 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
192 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
193 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
194 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
195 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', | |
196 | 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, | |
197 | }; | |
198 | size_t n; | |
199 | uint32_t next; | |
200 | unsigned int i, j; | |
201 | const uint32_t *t; | |
202 | ||
203 | /* | |
204 | * This builds term_char_t objects based on the data in @seqs. It | |
205 | * compares the result to @res for packed chars, otherwise it requires | |
206 | * them to be allocated. | |
207 | * After that, we resolve the UCS-4 string and compare it to the | |
208 | * expected strings in @str. | |
209 | */ | |
210 | ||
211 | for (i = 0; i < ELEMENTSOF(seqs); ++i) { | |
212 | _term_char_free_ term_char_t c = TERM_CHAR_NULL; | |
213 | ||
214 | for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { | |
215 | next = seqs[i][j]; | |
216 | if (next == (uint32_t)-1) | |
217 | break; | |
218 | ||
219 | c = term_char_merge(c, next); | |
220 | } | |
221 | ||
222 | /* we use TERM_CHAR_NULL as marker for allocated chars here */ | |
223 | if (term_char_is_null(res[i])) | |
224 | assert_se(term_char_is_allocated(c)); | |
225 | else | |
226 | assert_se(!memcmp(&c, &res[i], sizeof(c))); | |
227 | ||
228 | t = term_char_resolve(c, &n, NULL); | |
229 | for (j = 0; j < ELEMENTSOF(str[i]); ++j) { | |
230 | next = str[i][j]; | |
231 | if (next == (uint32_t)-1) | |
232 | break; | |
233 | ||
234 | assert_se(t[j] == next); | |
235 | } | |
236 | ||
237 | assert_se(n == j); | |
238 | } | |
239 | } | |
240 | ||
241 | /* | |
242 | * Line Tests | |
243 | * | |
244 | * The following tests work on term_line objects and verify their behavior when | |
245 | * we modify them. To verify and set line layouts, we have two simple helpers | |
246 | * to avoid harcoding the cell-verification all the time: | |
247 | * line_set(): Set a line to a given layout | |
248 | * line_assert(): Verify that a line has a given layout | |
249 | * | |
250 | * These functions take the line-layout encoded as a string and verify it | |
251 | * against, or set it on, a term_line object. The format used to describe a | |
252 | * line looks like this: | |
253 | * example: "| | A | | | | | | 10 *AB* |" | |
254 | * | |
255 | * The string describes the contents of all cells of a line, separated by | |
256 | * pipe-symbols ('|'). Whitespace are ignored, the leading pipe-symbol is | |
257 | * optional. | |
258 | * The description of each cell can contain an arbitrary amount of characters | |
259 | * in the range 'A'-'Z', 'a'-'z'. All those are combined and used as term_char_t | |
260 | * on this cell. Any numbers in the description are combined and are used as | |
261 | * cell-age. | |
262 | * The occurrence of a '*'-symbol marks the cell as bold, '/' marks it as italic. | |
263 | * You can use those characters multiple times, but only the first one has an | |
264 | * effect. | |
265 | * For further symbols, see parse_attr(). | |
266 | * | |
267 | * Therefore, the following descriptions are equivalent: | |
268 | * 1) "| | /A* | | | | | | 10 *AB* |" | |
269 | * 2) "| | /A** | | | | | | 10 *AB* |" | |
270 | * 3) "| | A* // | | | | | | 10 *AB* |" | |
271 | * 4) "| | A* // | | | | | | 1 *AB* 0 |" | |
272 | * 5) "| | A* // | | | | | | A1B0* |" | |
273 | * | |
274 | * The parser isn't very strict about placement of alpha/numerical characters, | |
275 | * but simply appends all found chars. Don't make use of that feature! It's | |
276 | * just a stupid parser to simplify these tests. Make them readable! | |
277 | */ | |
278 | ||
279 | static void parse_attr(char c, term_char_t *ch, term_attr *attr, term_age_t *age) { | |
280 | switch (c) { | |
281 | case ' ': | |
282 | /* ignore */ | |
283 | break; | |
284 | case '0' ... '9': | |
285 | /* increase age */ | |
286 | *age = *age * 10; | |
287 | *age = *age + c - '0'; | |
288 | break; | |
289 | case 'A' ... 'Z': | |
290 | case 'a' ... 'z': | |
291 | /* add to character */ | |
292 | *ch = term_char_merge(*ch, c); | |
293 | break; | |
294 | case '*': | |
295 | attr->bold = true; | |
296 | break; | |
297 | case '/': | |
298 | attr->italic = true; | |
299 | break; | |
300 | default: | |
301 | assert_se(0); | |
302 | break; | |
303 | } | |
304 | } | |
305 | ||
306 | static void cell_assert(MY_ASSERT_ARGS, term_cell *c, term_char_t ch, const term_attr *attr, term_age_t age) { | |
307 | MY_ASSERT(term_char_equal(c->ch, ch)); | |
308 | MY_ASSERT(!memcmp(&c->attr, attr, sizeof(*attr))); | |
309 | MY_ASSERT(c->age == age); | |
310 | } | |
311 | #define CELL_ASSERT(_cell, _ch, _attr, _age) cell_assert(MY_ASSERT_VALS, (_cell), (_ch), (_attr), (_age)) | |
312 | ||
313 | static void line_assert(MY_ASSERT_ARGS, term_line *l, const char *str, unsigned int fill) { | |
314 | unsigned int cell_i; | |
315 | term_char_t ch = TERM_CHAR_NULL; | |
316 | term_attr attr = { }; | |
317 | term_age_t age = TERM_AGE_NULL; | |
318 | char c; | |
319 | ||
320 | assert_se(l->fill == fill); | |
321 | ||
322 | /* skip leading whitespace */ | |
323 | while (*str == ' ') | |
324 | ++str; | |
325 | ||
326 | /* skip leading '|' */ | |
327 | if (*str == '|') | |
328 | ++str; | |
329 | ||
330 | cell_i = 0; | |
331 | while ((c = *str++)) { | |
332 | switch (c) { | |
333 | case '|': | |
334 | /* end of cell-description; compare it */ | |
335 | assert_se(cell_i < l->n_cells); | |
336 | cell_assert(MY_ASSERT_FORW, | |
337 | &l->cells[cell_i], | |
338 | ch, | |
339 | &attr, | |
340 | age); | |
341 | ||
342 | ++cell_i; | |
343 | ch = term_char_free(ch); | |
344 | zero(attr); | |
345 | age = TERM_AGE_NULL; | |
346 | break; | |
347 | default: | |
348 | parse_attr(c, &ch, &attr, &age); | |
349 | break; | |
350 | } | |
351 | } | |
352 | ||
353 | assert_se(cell_i == l->n_cells); | |
354 | } | |
355 | #define LINE_ASSERT(_line, _str, _fill) line_assert(MY_ASSERT_VALS, (_line), (_str), (_fill)) | |
356 | ||
357 | static void line_set(term_line *l, unsigned int pos, const char *str, bool insert_mode) { | |
358 | term_char_t ch = TERM_CHAR_NULL; | |
359 | term_attr attr = { }; | |
360 | term_age_t age = TERM_AGE_NULL; | |
361 | char c; | |
362 | ||
363 | while ((c = *str++)) | |
364 | parse_attr(c, &ch, &attr, &age); | |
365 | ||
366 | term_line_write(l, pos, ch, 1, &attr, age, insert_mode); | |
367 | } | |
368 | ||
369 | static void line_resize(term_line *l, unsigned int width, const term_attr *attr, term_age_t age) { | |
370 | assert_se(term_line_reserve(l, width, attr, age, width) >= 0); | |
371 | term_line_set_width(l, width); | |
372 | } | |
373 | ||
374 | static void test_term_line_misc(void) { | |
375 | term_line *l; | |
376 | ||
377 | assert_se(term_line_new(&l) >= 0); | |
378 | assert_se(!term_line_free(l)); | |
379 | ||
380 | assert_se(term_line_new(NULL) < 0); | |
381 | assert_se(!term_line_free(NULL)); | |
382 | ||
383 | assert_se(term_line_new(&l) >= 0); | |
384 | assert_se(l->n_cells == 0); | |
385 | assert_se(l->fill == 0); | |
386 | assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); | |
387 | assert_se(l->n_cells == 16); | |
388 | assert_se(l->fill == 0); | |
389 | assert_se(term_line_reserve(l, 512, NULL, 0, 0) >= 0); | |
390 | assert_se(l->n_cells == 512); | |
391 | assert_se(l->fill == 0); | |
392 | assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); | |
393 | assert_se(l->n_cells == 512); | |
394 | assert_se(l->fill == 0); | |
395 | assert_se(!term_line_free(l)); | |
396 | } | |
397 | ||
398 | static void test_term_line_ops(void) { | |
399 | term_line *l; | |
400 | term_attr attr_regular = { }; | |
401 | term_attr attr_bold = { .bold = true }; | |
402 | term_attr attr_italic = { .italic = true }; | |
403 | ||
404 | assert_se(term_line_new(&l) >= 0); | |
405 | line_resize(l, 8, NULL, 0); | |
406 | assert_se(l->n_cells == 8); | |
407 | ||
408 | LINE_ASSERT(l, "| | | | | | | | |", 0); | |
409 | ||
410 | term_line_write(l, 4, TERM_CHAR_NULL, 0, NULL, TERM_AGE_NULL, 0); | |
411 | LINE_ASSERT(l, "| | | | | | | | |", 5); | |
412 | ||
413 | term_line_write(l, 1, PACK1('A'), 1, NULL, TERM_AGE_NULL, 0); | |
414 | LINE_ASSERT(l, "| |A| | | | | | |", 5); | |
415 | ||
416 | term_line_write(l, 8, PACK2('A', 'B'), 1, NULL, TERM_AGE_NULL, 0); | |
417 | LINE_ASSERT(l, "| |A| | | | | | |", 5); | |
418 | ||
419 | term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_regular, 10, 0); | |
420 | LINE_ASSERT(l, "| |A| | | | | | 10 AB |", 8); | |
421 | ||
422 | term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_bold, 10, 0); | |
423 | LINE_ASSERT(l, "| |A| | | | | | 10 *AB* |", 8); | |
424 | ||
425 | term_line_reset(l, NULL, TERM_AGE_NULL); | |
426 | ||
427 | LINE_ASSERT(l, "| | | | | | | | |", 0); | |
428 | line_set(l, 2, "*wxyz* 8", 0); | |
429 | line_set(l, 3, "/wxyz/ 8", 0); | |
430 | LINE_ASSERT(l, "| | | *wxyz* 8 | /wxyz/ 8 | | | | |", 4); | |
431 | line_set(l, 2, "*abc* 9", true); | |
432 | LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | 9 |", 5); | |
433 | line_set(l, 7, "*abc* 10", true); | |
434 | LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | *abc* 10 |", 8); | |
435 | ||
436 | term_line_erase(l, 6, 1, NULL, 11, 0); | |
437 | LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 11 | *abc* 10 |", 8); | |
438 | term_line_erase(l, 6, 2, &attr_italic, 12, 0); | |
439 | LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 12 // |", 6); | |
440 | term_line_erase(l, 7, 2, &attr_regular, 13, 0); | |
441 | LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 13 |", 6); | |
442 | term_line_delete(l, 1, 3, &attr_bold, 14); | |
443 | LINE_ASSERT(l, "| | /wxyz/ 14 | 14 | 14 // | 14 | 14 ** | 14 ** | 14 ** |", 3); | |
444 | term_line_insert(l, 2, 2, &attr_regular, 15); | |
445 | LINE_ASSERT(l, "| | /wxyz/ 14 | 15 | 15 | 15 | 15 // | 15 | 15 ** |", 5); | |
446 | ||
447 | assert_se(!term_line_free(l)); | |
448 | } | |
449 | ||
450 | int main(int argc, char *argv[]) { | |
451 | test_term_char_misc(); | |
452 | test_term_char_packing(); | |
453 | test_term_char_allocating(); | |
454 | ||
455 | test_term_line_misc(); | |
456 | test_term_line_ops(); | |
457 | ||
458 | return 0; | |
459 | } |