summaryrefslogtreecommitdiff
path: root/src/libsystemd-terminal/test-term-page.c
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2014-06-12 17:51:14 +0200
committerDavid Herrmann <dh.herrmann@gmail.com>2014-07-17 11:48:40 +0200
commit84da4a3022bc599b26d9601cf1b7bf51d1d9f915 (patch)
tree02653677a6eb819c6a44824723483fa5c068be40 /src/libsystemd-terminal/test-term-page.c
parent3496b9eeafa50234371da1642dca424e4ca0e5f4 (diff)
ui/term: add line/cell/char handling for terminal pages
This commit introduces libsystemd-ui, a systemd-internal helper library that will contain all the UI related functionality. It is going to be used by systemd-welcomed, systemd-consoled, systemd-greeter and systemd-er. Further use-cases may follow. For now, this commit only adds terminal-page handling based on lines only. Follow-up commits will add more functionality.
Diffstat (limited to 'src/libsystemd-terminal/test-term-page.c')
-rw-r--r--src/libsystemd-terminal/test-term-page.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/libsystemd-terminal/test-term-page.c b/src/libsystemd-terminal/test-term-page.c
new file mode 100644
index 0000000000..bba83ee405
--- /dev/null
+++ b/src/libsystemd-terminal/test-term-page.c
@@ -0,0 +1,463 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/*
+ * Terminal Page/Line/Cell/Char Tests
+ * This tests internals of terminal page, line, cell and char handling. It
+ * relies on some implementation details, so it might need to be updated if
+ * those internals are changed. They should be fairly obvious, though.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "macro.h"
+#include "term-internal.h"
+#include "util.h"
+
+#define MY_ASSERT_VALS __FILE__, __LINE__, __PRETTY_FUNCTION__
+#define MY_ASSERT_FORW _FILE, _LINE, _FUNC
+#define MY_ASSERT_ARGS const char *_FILE, int _LINE, const char *_FUNC
+#define MY_ASSERT(expr) \
+ do { \
+ if (_unlikely_(!(expr))) \
+ log_assert_failed(#expr, _FILE, _LINE, _FUNC); \
+ } while (false) \
+
+/*
+ * Character Tests
+ *
+ * These tests rely on some implementation details of term_char_t, including
+ * the way we pack characters and the internal layout of "term_char_t". These
+ * tests have to be updated once we change the implementation.
+ */
+
+#define PACK(v1, v2, v3) \
+ TERM_CHAR_INIT( \
+ (((((uint64_t)v1) & 0x1fffffULL) << 43) | \
+ ((((uint64_t)v2) & 0x1fffffULL) << 22) | \
+ ((((uint64_t)v3) & 0x1fffffULL) << 1) | \
+ 0x1) \
+ )
+#define PACK1(v1) PACK2((v1), 0x110000)
+#define PACK2(v1, v2) PACK3((v1), (v2), 0x110000)
+#define PACK3(v1, v2, v3) PACK((v1), (v2), (v3))
+
+static void test_term_char_misc(void) {
+ term_char_t c, t;
+
+ /* test TERM_CHAR_NULL handling */
+
+ c = TERM_CHAR_NULL; /* c is NULL */
+ assert_se(term_char_same(c, TERM_CHAR_NULL));
+ assert_se(term_char_equal(c, TERM_CHAR_NULL));
+ assert_se(term_char_is_null(c));
+ assert_se(term_char_is_null(TERM_CHAR_NULL));
+ assert_se(!term_char_is_allocated(c));
+
+ /* test single char handling */
+
+ t = term_char_dup_append(c, 'A'); /* t is >A< now */
+ assert_se(!term_char_same(c, t));
+ assert_se(!term_char_equal(c, t));
+ assert_se(!term_char_is_allocated(t));
+ assert_se(!term_char_is_null(t));
+
+ /* test basic combined char handling */
+
+ t = term_char_dup_append(t, '~');
+ t = term_char_dup_append(t, '^'); /* t is >A~^< now */
+ assert_se(!term_char_same(c, t));
+ assert_se(!term_char_is_allocated(t));
+ assert_se(!term_char_is_null(t));
+
+ c = term_char_dup_append(c, 'A');
+ c = term_char_dup_append(c, '~');
+ c = term_char_dup_append(c, '^'); /* c is >A~^< now */
+ assert_se(term_char_same(c, t));
+ assert_se(term_char_equal(c, t));
+
+ /* test more than 2 comb-chars so the chars are allocated */
+
+ t = term_char_dup_append(t, '`'); /* t is >A~^`< now */
+ c = term_char_dup_append(c, '`'); /* c is >A~^`< now */
+ assert_se(!term_char_same(c, t));
+ assert_se(term_char_equal(c, t));
+
+ /* test dup_append() on allocated chars */
+
+ term_char_free(t);
+ t = term_char_dup_append(c, '"'); /* t is >A~^`"< now */
+ assert_se(!term_char_same(c, t));
+ assert_se(!term_char_equal(c, t));
+ c = term_char_merge(c, '"'); /* c is >A~^`"< now */
+ assert_se(!term_char_same(c, t));
+ assert_se(term_char_equal(c, t));
+
+ term_char_free(t);
+ term_char_free(c);
+}
+
+static void test_term_char_packing(void) {
+ uint32_t seqs[][1024] = {
+ { -1 },
+ { 0, -1 },
+ { 'A', '~', -1 },
+ { 'A', '~', 0, -1 },
+ { 'A', '~', 'a', -1 },
+ };
+ term_char_t res[] = {
+ TERM_CHAR_NULL,
+ PACK1(0),
+ PACK2('A', '~'),
+ PACK3('A', '~', 0),
+ PACK3('A', '~', 'a'),
+ };
+ uint32_t next;
+ unsigned int i, j;
+ term_char_t c = TERM_CHAR_NULL;
+
+ /*
+ * This creates term_char_t objects based on the data in @seqs and
+ * compares the result to @res. Only basic packed types are tested, no
+ * allocations are done.
+ */
+
+ for (i = 0; i < ELEMENTSOF(seqs); ++i) {
+ for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) {
+ next = seqs[i][j];
+ if (next == (uint32_t)-1)
+ break;
+
+ c = term_char_merge(c, next);
+ }
+
+ assert_se(!memcmp(&c, &res[i], sizeof(c)));
+ c = term_char_free(c);
+ }
+}
+
+static void test_term_char_allocating(void) {
+ uint32_t seqs[][1024] = {
+ { 0, -1 },
+ { 'A', '~', -1 },
+ { 'A', '~', 0, -1 },
+ { 'A', '~', 'a', -1 },
+ { 'A', '~', 'a', 'b', 'c', 'd', -1 },
+ { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 },
+ /* exceeding implementation-defined soft-limit of 64 */
+ { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 },
+ };
+ term_char_t res[] = {
+ PACK1(0),
+ PACK2('A', '~'),
+ PACK3('A', '~', 0),
+ PACK3('A', '~', 'a'),
+ TERM_CHAR_NULL, /* allocated */
+ TERM_CHAR_NULL, /* allocated */
+ TERM_CHAR_NULL, /* allocated */
+ };
+ uint32_t str[][1024] = {
+ { 0, -1 },
+ { 'A', '~', -1 },
+ { 'A', '~', 0, -1 },
+ { 'A', '~', 'a', -1 },
+ { 'A', '~', 'a', 'b', 'c', 'd', -1 },
+ { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 },
+ { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
+ 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 },
+ };
+ size_t n;
+ uint32_t next;
+ unsigned int i, j;
+ const uint32_t *t;
+
+ /*
+ * This builds term_char_t objects based on the data in @seqs. It
+ * compares the result to @res for packed chars, otherwise it requires
+ * them to be allocated.
+ * After that, we resolve the UCS-4 string and compare it to the
+ * expected strings in @str.
+ */
+
+ for (i = 0; i < ELEMENTSOF(seqs); ++i) {
+ _term_char_free_ term_char_t c = TERM_CHAR_NULL;
+
+ for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) {
+ next = seqs[i][j];
+ if (next == (uint32_t)-1)
+ break;
+
+ c = term_char_merge(c, next);
+ }
+
+ /* we use TERM_CHAR_NULL as marker for allocated chars here */
+ if (term_char_is_null(res[i]))
+ assert_se(term_char_is_allocated(c));
+ else
+ assert_se(!memcmp(&c, &res[i], sizeof(c)));
+
+ t = term_char_resolve(c, &n, NULL);
+ for (j = 0; j < ELEMENTSOF(str[i]); ++j) {
+ next = str[i][j];
+ if (next == (uint32_t)-1)
+ break;
+
+ assert_se(t[j] == next);
+ }
+
+ assert_se(n == j);
+ }
+}
+
+/*
+ * Line Tests
+ *
+ * The following tests work on term_line objects and verify their behavior when
+ * we modify them. To verify and set line layouts, we have two simple helpers
+ * to avoid harcoding the cell-verification all the time:
+ * line_set(): Set a line to a given layout
+ * line_assert(): Verify that a line has a given layout
+ *
+ * These functions take the line-layout encoded as a string and verify it
+ * against, or set it on, a term_line object. The format used to describe a
+ * line looks like this:
+ * example: "| | A | | | | | | 10 *AB* |"
+ *
+ * The string describes the contents of all cells of a line, separated by
+ * pipe-symbols ('|'). Whitespace are ignored, the leading pipe-symbol is
+ * optional.
+ * The description of each cell can contain an arbitrary amount of characters
+ * in the range 'A'-'Z', 'a'-'z'. All those are combined and used as term_char_t
+ * on this cell. Any numbers in the description are combined and are used as
+ * cell-age.
+ * The occurance of a '*'-symbol marks the cell as bold, '/' marks it as italic.
+ * You can use those characters multiple times, but only the first one has an
+ * effect.
+ * For further symbols, see parse_attr().
+ *
+ * Therefore, the following descriptions are equivalent:
+ * 1) "| | /A* | | | | | | 10 *AB* |"
+ * 2) "| | /A** | | | | | | 10 *AB* |"
+ * 3) "| | A* // | | | | | | 10 *AB* |"
+ * 4) "| | A* // | | | | | | 1 *AB* 0 |"
+ * 5) "| | A* // | | | | | | A1B0* |"
+ *
+ * The parser isn't very strict about placement of alpha/numerical characters,
+ * but simply appends all found chars. Don't make use of that feature! It's
+ * just a stupid parser to simplify these tests. Make them readable!
+ */
+
+static void parse_attr(char c, term_char_t *ch, term_attr *attr, term_age_t *age) {
+ switch (c) {
+ case ' ':
+ /* ignore */
+ break;
+ case '0' ... '9':
+ /* increase age */
+ *age = *age * 10;
+ *age = *age + c - '0';
+ break;
+ case 'A' ... 'Z':
+ case 'a' ... 'z':
+ /* add to character */
+ *ch = term_char_merge(*ch, c);
+ break;
+ case '*':
+ attr->bold = true;
+ break;
+ case '/':
+ attr->italic = true;
+ break;
+ default:
+ assert_se(0);
+ break;
+ }
+}
+
+static void cell_assert(MY_ASSERT_ARGS, term_cell *c, term_char_t ch, const term_attr *attr, term_age_t age) {
+ MY_ASSERT(term_char_equal(c->ch, ch));
+ MY_ASSERT(!memcmp(&c->attr, attr, sizeof(*attr)));
+ MY_ASSERT(c->age == age);
+}
+#define CELL_ASSERT(_cell, _ch, _attr, _age) cell_assert(MY_ASSERT_VALS, (_cell), (_ch), (_attr), (_age))
+
+static void line_assert(MY_ASSERT_ARGS, term_line *l, const char *str, unsigned int fill) {
+ unsigned int cell_i;
+ term_char_t ch = TERM_CHAR_NULL;
+ term_attr attr = { };
+ term_age_t age = TERM_AGE_NULL;
+ char c;
+
+ assert_se(l->fill == fill);
+
+ /* skip leading whitespace */
+ while (*str == ' ')
+ ++str;
+
+ /* skip leading '|' */
+ if (*str == '|')
+ ++str;
+
+ cell_i = 0;
+ while ((c = *str++)) {
+ switch (c) {
+ case '|':
+ /* end of cell-description; compare it */
+ assert_se(cell_i < l->n_cells);
+ cell_assert(MY_ASSERT_FORW,
+ &l->cells[cell_i],
+ ch,
+ &attr,
+ age);
+
+ ++cell_i;
+ ch = term_char_free(ch);
+ zero(attr);
+ age = TERM_AGE_NULL;
+ break;
+ default:
+ parse_attr(c, &ch, &attr, &age);
+ break;
+ }
+ }
+
+ assert_se(cell_i == l->n_cells);
+}
+#define LINE_ASSERT(_line, _str, _fill) line_assert(MY_ASSERT_VALS, (_line), (_str), (_fill))
+
+static void line_set(term_line *l, unsigned int pos, const char *str, bool insert_mode) {
+ term_char_t ch = TERM_CHAR_NULL;
+ term_attr attr = { };
+ term_age_t age = TERM_AGE_NULL;
+ char c;
+
+ while ((c = *str++))
+ parse_attr(c, &ch, &attr, &age);
+
+ term_line_write(l, pos, ch, 1, &attr, age, insert_mode);
+}
+
+static void line_resize(term_line *l, unsigned int width, const term_attr *attr, term_age_t age) {
+ assert_se(term_line_reserve(l, width, attr, age, width) >= 0);
+ term_line_set_width(l, width);
+}
+
+static void test_term_line_misc(void) {
+ term_line *l;
+
+ assert_se(term_line_new(&l) >= 0);
+ assert_se(!term_line_free(l));
+
+ assert_se(term_line_new(NULL) < 0);
+ assert_se(!term_line_free(NULL));
+
+ assert_se(term_line_new(&l) >= 0);
+ assert_se(l->n_cells == 0);
+ assert_se(l->fill == 0);
+ assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0);
+ assert_se(l->n_cells == 16);
+ assert_se(l->fill == 0);
+ assert_se(term_line_reserve(l, 512, NULL, 0, 0) >= 0);
+ assert_se(l->n_cells == 512);
+ assert_se(l->fill == 0);
+ assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0);
+ assert_se(l->n_cells == 512);
+ assert_se(l->fill == 0);
+ assert_se(!term_line_free(l));
+}
+
+static void test_term_line_ops(void) {
+ term_line *l;
+ term_attr attr_regular = { };
+ term_attr attr_bold = { .bold = true };
+ term_attr attr_italic = { .italic = true };
+
+ assert_se(term_line_new(&l) >= 0);
+ line_resize(l, 8, NULL, 0);
+ assert_se(l->n_cells == 8);
+
+ LINE_ASSERT(l, "| | | | | | | | |", 0);
+
+ term_line_write(l, 4, TERM_CHAR_NULL, 0, NULL, TERM_AGE_NULL, 0);
+ LINE_ASSERT(l, "| | | | | | | | |", 5);
+
+ term_line_write(l, 1, PACK1('A'), 1, NULL, TERM_AGE_NULL, 0);
+ LINE_ASSERT(l, "| |A| | | | | | |", 5);
+
+ term_line_write(l, 8, PACK2('A', 'B'), 1, NULL, TERM_AGE_NULL, 0);
+ LINE_ASSERT(l, "| |A| | | | | | |", 5);
+
+ term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_regular, 10, 0);
+ LINE_ASSERT(l, "| |A| | | | | | 10 AB |", 8);
+
+ term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_bold, 10, 0);
+ LINE_ASSERT(l, "| |A| | | | | | 10 *AB* |", 8);
+
+ term_line_reset(l, NULL, TERM_AGE_NULL);
+
+ LINE_ASSERT(l, "| | | | | | | | |", 0);
+ line_set(l, 2, "*wxyz* 8", 0);
+ line_set(l, 3, "/wxyz/ 8", 0);
+ LINE_ASSERT(l, "| | | *wxyz* 8 | /wxyz/ 8 | | | | |", 4);
+ line_set(l, 2, "*abc* 9", true);
+ LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | 9 |", 5);
+ line_set(l, 7, "*abc* 10", true);
+ LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | *abc* 10 |", 8);
+
+ term_line_erase(l, 6, 1, NULL, 11, 0);
+ LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 11 | *abc* 10 |", 8);
+ term_line_erase(l, 6, 2, &attr_italic, 12, 0);
+ LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 12 // |", 6);
+ term_line_erase(l, 7, 2, &attr_regular, 13, 0);
+ LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 13 |", 6);
+ term_line_delete(l, 1, 3, &attr_bold, 14);
+ LINE_ASSERT(l, "| | /wxyz/ 14 | 14 | 14 // | 14 | 14 ** | 14 ** | 14 ** |", 3);
+ term_line_insert(l, 2, 2, &attr_regular, 15);
+ LINE_ASSERT(l, "| | /wxyz/ 14 | 15 | 15 | 15 | 15 // | 15 | 15 ** |", 5);
+
+ assert_se(!term_line_free(l));
+}
+
+int main(int argc, char *argv[]) {
+ test_term_char_misc();
+ test_term_char_packing();
+ test_term_char_allocating();
+
+ test_term_line_misc();
+ test_term_line_ops();
+
+ return 0;
+}