summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/api_args_test.c140
-rw-r--r--tests/dcs_test.c53
-rw-r--r--tests/gstore_test.c38
-rw-r--r--tests/header_test.sh75
-rw-r--r--tests/hist_test.c12
-rw-r--r--tests/meson.build18
-rw-r--r--tests/model.c34
-rw-r--r--tests/model.h7
-rw-r--r--tests/model_row_test.c2
-rw-r--r--tests/model_test.c14
-rw-r--r--tests/osc_test.c32
-rw-r--r--tests/term_test.c215
-rw-r--r--tests/unicode/unicode_test.c68
-rw-r--r--tests/version_test.c27
14 files changed, 604 insertions, 131 deletions
diff --git a/tests/api_args_test.c b/tests/api_args_test.c
new file mode 100644
index 0000000..18af728
--- /dev/null
+++ b/tests/api_args_test.c
@@ -0,0 +1,140 @@
+#include "term_api.h"
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+/* A feed that returns false must not move the cursor or change a cell. */
+static void expect_unchanged(struct term *t, int x, int y, uint32_t cp)
+{
+ assert(tt_cursor_x(t) == x);
+ assert(tt_cursor_y(t) == y);
+ assert(tt_cell(t, 0, 0)->text == cp);
+}
+
+static void test_feed_unknown_flag(void)
+{
+ struct term *t = tt_new(10, 3);
+ assert(t);
+ assert(term_feed(t, "abc", 3, 0));
+ expect_unchanged(t, 3, 0, 'a');
+
+ assert(!term_feed(t, "xy", 2, 1u << 1));
+ expect_unchanged(t, 3, 0, 'a');
+ assert(!term_feed(t, "xy", 2, TERM_FEED_ONLCR | (1u << 4)));
+ expect_unchanged(t, 3, 0, 'a');
+ assert(!term_feed(t, "xy", 2, 1u << 31));
+ expect_unchanged(t, 3, 0, 'a');
+ /* Unknown bits are rejected even when there are no bytes. */
+ assert(!term_feed(t, "", 0, 1u << 1));
+
+ assert(term_feed(t, "\nz", 2, TERM_FEED_ONLCR));
+ assert(tt_cursor_x(t) == 1);
+ assert(tt_cursor_y(t) == 1);
+ term_free(t);
+}
+
+static void test_feed_null_buffer(void)
+{
+ struct term *t = tt_new(10, 3);
+ assert(t);
+ assert(term_feed(t, "abc", 3, 0));
+
+ assert(!term_feed(t, NULL, 1, 0));
+ expect_unchanged(t, 3, 0, 'a');
+ assert(!term_feed(t, NULL, 4096, TERM_FEED_ONLCR));
+ expect_unchanged(t, 3, 0, 'a');
+
+ /* A zero length is a no-op, so a NULL buffer is fine. */
+ assert(term_feed(t, NULL, 0, 0));
+ assert(term_feed(t, "", 0, TERM_FEED_ONLCR));
+ expect_unchanged(t, 3, 0, 'a');
+
+ assert(!term_feed(NULL, "a", 1, 0));
+ assert(!term_feed(NULL, NULL, 0, 0));
+ term_free(t);
+}
+
+static void test_new_rejects_bad_options(void)
+{
+ struct term_options opt;
+
+ memset(&opt, 0, sizeof(opt));
+ opt.size.x = -1;
+ assert(!term_new(&opt));
+
+ memset(&opt, 0, sizeof(opt));
+ opt.size.y = -1;
+ assert(!term_new(&opt));
+
+ memset(&opt, 0, sizeof(opt));
+ opt.size.x = TERM_MAX_COLS + 1;
+ assert(!term_new(&opt));
+
+ memset(&opt, 0, sizeof(opt));
+ opt.scrollback = -1;
+ assert(!term_new(&opt));
+
+ memset(&opt, 0, sizeof(opt));
+ opt.cursor_style = (enum term_cursor_style)3;
+ assert(!term_new(&opt));
+
+ memset(&opt, 0, sizeof(opt));
+ opt.cursor_style = (enum term_cursor_style)-1;
+ assert(!term_new(&opt));
+}
+
+static void test_new_accepts_defaults(void)
+{
+ struct term_options opt;
+
+ memset(&opt, 0, sizeof(opt));
+ struct term *t = term_new(&opt);
+ assert(t);
+ assert(tt_cols(t) == 80);
+ assert(tt_rows(t) == 24);
+ assert(term_cursor(t).style == TERM_CURSOR_BLOCK);
+ term_free(t);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.size.x = TERM_MAX_COLS;
+ opt.size.y = 1;
+ opt.cursor_style = TERM_CURSOR_BAR;
+ t = term_new(&opt);
+ assert(t);
+ assert(tt_cols(t) == TERM_MAX_COLS);
+ assert(tt_rows(t) == 1);
+ assert(term_cursor(t).style == TERM_CURSOR_BAR);
+ term_free(t);
+}
+
+static void test_resize_rejects_too_wide(void)
+{
+ struct term *t = tt_new(10, 3);
+ assert(t);
+ assert(term_feed(t, "abc", 3, 0));
+
+ assert(!tt_resize(t, TERM_MAX_COLS + 1, 3));
+ assert(tt_cols(t) == 10);
+ assert(tt_rows(t) == 3);
+ expect_unchanged(t, 3, 0, 'a');
+
+ assert(!tt_resize(t, 10, 0));
+ assert(!tt_resize(t, 0, 3));
+ assert(tt_cols(t) == 10);
+ assert(tt_rows(t) == 3);
+
+ assert(tt_resize(t, TERM_MAX_COLS, 3));
+ assert(tt_cols(t) == TERM_MAX_COLS);
+ assert(tt_rows(t) == 3);
+ term_free(t);
+}
+
+int main(void)
+{
+ test_feed_unknown_flag();
+ test_feed_null_buffer();
+ test_new_rejects_bad_options();
+ test_new_accepts_defaults();
+ test_resize_rejects_too_wide();
+ return 0;
+}
diff --git a/tests/dcs_test.c b/tests/dcs_test.c
index 0e67f78..d9455be 100644
--- a/tests/dcs_test.c
+++ b/tests/dcs_test.c
@@ -176,8 +176,8 @@ static void test_xtgettcap(void)
{
struct term *t = new_term(80, 24);
assert_tcap(t, "kind", "\033[1;2B");
- assert_tcap(t, "TN", "wsap_term");
- assert_tcap(t, "name", "wsap_term");
+ assert_tcap(t, "TN", "term");
+ assert_tcap(t, "name", "term");
assert_tcap(t, "Co", "16777216");
assert_tcap(t, "colors", "16777216");
assert_tcap(t, "RGB", "8/8/8");
@@ -230,7 +230,7 @@ static void test_xtgettcap_multi(void)
snprintf(req, sizeof(req), "\033P+q%s;%s\033\\", hk, htn);
snprintf(want, sizeof(want), "\033P1+r%s=%s;%s=%s\033\\",
- hk, hv, htn, "777361705f7465726d");
+ hk, hv, htn, "7465726d");
response_len = 0;
write_seq(t, req);
expect_response(want);
@@ -383,7 +383,51 @@ static void test_xtversion(void)
struct term *t = new_term(80, 24);
response_len = 0;
write_seq(t, "\033[>0q");
- expect_response("\033P>|wsap_term\033\\");
+ expect_response("\033P>|term\033\\");
+ term_free(t);
+}
+
+/* term_options.name feeds XTVERSION and the TN and name capabilities. */
+static void test_host_name(void)
+{
+ struct term_options opt = {
+ .size = { .x = 80, .y = 24 },
+ .scrollback = TEST_SCROLLBACK,
+ .name = "myterm",
+ .callbacks = { .reply = respond },
+ };
+ struct term *t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|myterm\033\\");
+ assert_tcap(t, "TN", "myterm");
+ assert_tcap(t, "name", "myterm");
+
+ /* A DCS +p sets the capability value. The XTVERSION name does not change. */
+ response_len = 0;
+ write_seq(t, "\033P+p6f74686572\033\\");
+ expect_no_response();
+ assert_tcap(t, "TN", "other");
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|myterm\033\\");
+ term_free(t);
+
+ /* NULL and "" both select the default. */
+ opt.name = "";
+ t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|term\033\\");
+ term_free(t);
+ opt.name = NULL;
+ t = term_new(&opt);
+ assert(t);
+ response_len = 0;
+ write_seq(t, "\033[>0q");
+ expect_response("\033P>|term\033\\");
term_free(t);
}
@@ -467,6 +511,7 @@ int main(void)
test_synchronized_output();
test_ignored_dcs();
test_xtversion();
+ test_host_name();
test_bel_terminator();
test_dcs_long_query();
test_dcs_abort_c0();
diff --git a/tests/gstore_test.c b/tests/gstore_test.c
index 637b136..ea582f3 100644
--- a/tests/gstore_test.c
+++ b/tests/gstore_test.c
@@ -32,7 +32,7 @@ static void test_direct_unit(void)
/* Cluster returns a handle. */
uint32_t c1[2] = { 'e', 0x0301 };
uint32_t h1 = term_gstore_intern(g, c1, 2);
- assert(grapheme_is_handle(h1));
+ assert(term_grapheme_is_handle(h1));
assert(term_gstore_count(g) == 1);
/* Identical cluster deduplicates to the same handle. */
@@ -43,17 +43,17 @@ static void test_direct_unit(void)
/* Different cluster gets a distinct handle. */
uint32_t c2[2] = { 'a', 0x0300 };
uint32_t h2 = term_gstore_intern(g, c2, 2);
- assert(grapheme_is_handle(h2));
+ assert(term_grapheme_is_handle(h2));
assert(h2 != h1);
assert(term_gstore_count(g) == 2);
/* Expand both handles. */
- uint32_t out[GRAPHEME_MAX_LEN] = {0};
- int n = term_gstore_expand(g, h1, out, GRAPHEME_MAX_LEN);
+ 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, GRAPHEME_MAX_LEN);
+ n = term_gstore_expand(g, h2, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(out[0] == 'a' && out[1] == 0x0300);
@@ -63,20 +63,20 @@ static void test_direct_unit(void)
assert(term_gstore_count(g) == 1);
/* h1 is still valid. */
- n = term_gstore_expand(g, h1, out, GRAPHEME_MAX_LEN);
+ 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, GRAPHEME_MAX_LEN);
+ 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(grapheme_is_handle(h3));
+ assert(term_grapheme_is_handle(h3));
assert(term_gstore_count(g) == 2);
- n = term_gstore_expand(g, h3, out, GRAPHEME_MAX_LEN);
+ n = term_gstore_expand(g, h3, out, TERM_GRAPHEME_MAX_LEN);
assert(n == 3);
assert(out[0] == 'o' && out[1] == 0x0308 && out[2] == 0x0301);
@@ -105,8 +105,8 @@ static void test_two_terminals_isolation(void)
const struct term_cell *row = term_snapshot_row(snap, 0);
assert(row[0].text & TERM_TEXT_CLUSTER);
- uint32_t cps[GRAPHEME_MAX_LEN] = {0};
- int n = term_snapshot_cluster(snap, row[0].text, cps, GRAPHEME_MAX_LEN);
+ 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);
@@ -143,16 +143,16 @@ static void test_screen_clusters_survive_sweep(void)
const struct term_cell *row = term_snapshot_row(snap, 0);
assert(row[0].text & TERM_TEXT_CLUSTER);
- uint32_t cps[GRAPHEME_MAX_LEN] = {0};
- int n = term_snapshot_cluster(snap, row[0].text, cps, GRAPHEME_MAX_LEN);
+ 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, GRAPHEME_MAX_LEN);
+ 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, GRAPHEME_MAX_LEN);
+ n = term_snapshot_cluster(snap, row[2].text, cps, TERM_GRAPHEME_MAX_LEN);
assert(n == 2);
assert(cps[0] == 'c' && cps[1] == 0x0302);
@@ -183,8 +183,8 @@ static void test_more_than_65536_clusters(void)
size_t len = 0;
buf[len++] = base;
- len += (size_t)utf8_encode(cp1, buf + len);
- len += (size_t)utf8_encode(cp2, buf + len);
+ len += (size_t)term_utf8_encode(cp1, buf + len);
+ len += (size_t)term_utf8_encode(cp2, buf + len);
buf[len++] = '\r';
buf[len++] = '\n';
@@ -204,8 +204,8 @@ static void test_more_than_65536_clusters(void)
const struct term_cell *row = term_snapshot_row(snap, y);
assert(row[0].text & TERM_TEXT_CLUSTER);
- uint32_t cps[GRAPHEME_MAX_LEN] = {0};
- int n = term_snapshot_cluster(snap, row[0].text, cps, GRAPHEME_MAX_LEN);
+ 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). */
diff --git a/tests/header_test.sh b/tests/header_test.sh
index 4ba091b..9fd1041 100644
--- a/tests/header_test.sh
+++ b/tests/header_test.sh
@@ -1,25 +1,70 @@
#!/usr/bin/env bash
-# A host includes term.h and nothing else. The header compiles alone as
-# strict C11 and as C++, with only include/term on the include path.
+# A host includes <term/term.h> and nothing else. The package puts only the
+# parent of term/ on the include path. The headers compile alone as strict
+# C11 and as C++.
set -euo pipefail
cd "${1:-$(dirname "$0")/..}"
cc=${CC:-cc}
cxx=${CXX:-c++}
status=0
-echo '#include "term.h"' | "$cc" -std=c11 -Wall -Wextra -Wpedantic -Werror \
- -Iinclude/term -x c -fsyntax-only - || { echo "term.h: not valid C11" >&2; status=1; }
-if command -v "$cxx" >/dev/null; then
- echo '#include "term.h"' | "$cxx" -std=c++17 -Wall -Wextra -Wpedantic -Werror \
- -Iinclude/term -x c++ -fsyntax-only - || { echo "term.h: not valid C++" >&2; status=1; }
-fi
+
+# check LABEL: compile the code on stdin as C11 and as C++, with include/
+# on the path.
+check() {
+ local label=$1 src
+ src=$(cat)
+ printf '%s\n' "$src" | "$cc" -std=c11 -Wall -Wextra -Wpedantic -Werror \
+ -Iinclude -x c -fsyntax-only - || { echo "$label: not valid C11" >&2; status=1; }
+ if command -v "$cxx" >/dev/null; then
+ printf '%s\n' "$src" | "$cxx" -std=c++17 -Wall -Wextra -Wpedantic -Werror \
+ -Iinclude -x c++ -fsyntax-only - || { echo "$label: not valid C++" >&2; status=1; }
+ fi
+}
+
+check term.h <<'EOF'
+#include <term/term.h>
+EOF
# Apple headers define wide before a host includes the Unicode API.
-unicode_header=$'typedef struct wide { int value; } wide;\n#include "unicode/width.h"'
-echo "$unicode_header" | "$cc" -std=c11 -Wall -Wextra -Wpedantic -Werror \
- -Iinclude/term -x c -fsyntax-only - || { echo "unicode/width.h: conflicts with host types" >&2; status=1; }
-if command -v "$cxx" >/dev/null; then
- echo "$unicode_header" | "$cxx" -std=c++17 -Wall -Wextra -Wpedantic -Werror \
- -Iinclude/term -x c++ -fsyntax-only - || { echo "unicode/width.h: conflicts with C++ host types" >&2; status=1; }
-fi
+check "unicode/width.h: conflicts with host types" <<'EOF'
+typedef struct wide { int value; } wide;
+#include <term/unicode/width.h>
+EOF
+
+# A host that uses the names the Unicode API had before the term_ and
+# TERM_ prefixes. The API must not clash with them in either order.
+check "unicode API: host names declared first" <<'EOF'
+int unicode_width(int);
+static int in_ranges;
+static int zero_width;
+static int utf8d;
+struct unicode_range { int lo; };
+#define UTF8_ACCEPT 7
+#define GRAPHEME_MAX_LEN 3
+#define IN_TABLE 1
+#define UNICODE_DATA_VERSION "host"
+#define UTF_8_H
+#include <term/unicode/utf8.h>
+#include <term/unicode/width.h>
+#include <term/unicode/grapheme.h>
+int host_use(void)
+{
+ return in_ranges + zero_width + utf8d + unicode_width(1) + UTF8_ACCEPT + GRAPHEME_MAX_LEN;
+}
+EOF
+
+check "unicode API: host names declared after" <<'EOF'
+#include <term/unicode/utf8.h>
+#include <term/unicode/width.h>
+#include <term/unicode/grapheme.h>
+int unicode_width(int);
+static int in_ranges;
+#define UTF8_ACCEPT 7
+#define GRAPHEME_MAX_LEN 3
+int host_use(void)
+{
+ return in_ranges + unicode_width(1) + UTF8_ACCEPT + GRAPHEME_MAX_LEN;
+}
+EOF
exit $status
diff --git a/tests/hist_test.c b/tests/hist_test.c
index d772c5a..691ae3c 100644
--- a/tests/hist_test.c
+++ b/tests/hist_test.c
@@ -129,9 +129,9 @@ static void test_clusters_roundtrip(void)
struct cell out[2];
assert(term_hist_decode_segment(&h, 0, 0, 2, out, g) == 2);
- assert(grapheme_is_handle(out[0].c));
- uint32_t got[GRAPHEME_MAX_LEN];
- assert(term_gstore_expand(g, out[0].c, got, GRAPHEME_MAX_LEN) == 3);
+ assert(term_grapheme_is_handle(out[0].c));
+ uint32_t got[TERM_GRAPHEME_MAX_LEN];
+ assert(term_gstore_expand(g, out[0].c, got, TERM_GRAPHEME_MAX_LEN) == 3);
assert(got[0] == cps[0] && got[1] == cps[1] && got[2] == cps[2]);
assert(out[1].c == 'X');
@@ -623,9 +623,9 @@ static void test_fuzz_rows(void)
continue;
}
assert(a->fg == b->fg && a->flags == b->flags);
- if (grapheme_is_handle(a->c)) {
- uint32_t got[GRAPHEME_MAX_LEN];
- assert(term_gstore_expand(g, b->c, got, GRAPHEME_MAX_LEN) == 2 && got[1] == 0x0301);
+ if (term_grapheme_is_handle(a->c)) {
+ uint32_t got[TERM_GRAPHEME_MAX_LEN];
+ assert(term_gstore_expand(g, b->c, got, TERM_GRAPHEME_MAX_LEN) == 2 && got[1] == 0x0301);
} else {
assert(a->c == b->c);
}
diff --git a/tests/meson.build b/tests/meson.build
index 69f3c49..4fda075 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -30,6 +30,7 @@ term_tests = [
['term-resize-failure-test', files('resize_failure_test.c'), [dl_dep], ['extlib']],
['dcs-test', files('dcs_test.c'), [], []],
['osc-test', files('osc_test.c'), [], []],
+ ['api-args-test', files('api_args_test.c'), [], []],
]
test_helpers_inc = include_directories('helpers')
@@ -76,20 +77,20 @@ term_amalgamation = custom_target(
term_amalgamation_lib = static_library(
'term-amalgamation',
term_amalgamation[0],
- dependencies: [m_dep],
+ dependencies: [m_dep, thread_dep],
override_options: ['c_std=c11', 'warning_level=3', 'werror=true'],
build_by_default: false,
)
foreach entry : term_tests
if entry[0] in ['term-test', 'spec-test', 'view-test', 'dcs-test', 'osc-test',
'history-text-test', 'term-input-test', 'model-test', 'model-row-test',
- 'resize-property-test']
+ 'resize-property-test', 'api-args-test']
test(entry[0] + '-amalgamation',
executable(entry[0] + '-amalgamation', entry[1],
c_args: test_c_args,
include_directories: [term_inc, test_helpers_inc],
link_with: term_amalgamation_lib,
- dependencies: [m_dep] + entry[2],
+ dependencies: [m_dep, thread_dep] + entry[2],
build_by_default: false),
env: test_env, suite: ['unit', 'amalgamation'], timeout: 180)
endif
@@ -117,6 +118,17 @@ if cc.has_link_argument('-Wl,--wrap=realloc') and cc.has_link_argument('-Wl,--wr
suite: 'unit',
)
endif
+
+# The project version comes in as a define, so the header macros cannot
+# drift from meson.build.
+version_test = executable('version-test', 'version_test.c',
+ c_args: test_c_args + ['-DTERM_TEST_PROJECT_VERSION="@0@"'.format(meson.project_version())],
+ dependencies: [term_internal_dep],
+)
+test('version-test', version_test,
+ env: test_env,
+ suite: 'unit',
+)
test('term-cross-test', bash,
args: [files('cross_test.sh'), meson.project_source_root()],
suite: 'portability',
diff --git a/tests/model.c b/tests/model.c
index 8dead6c..6c9f288 100644
--- a/tests/model.c
+++ b/tests/model.c
@@ -31,7 +31,7 @@
#define STR_KEEP 1024
struct cell {
- uint32_t cp[GRAPHEME_MAX_LEN];
+ uint32_t cp[TERM_GRAPHEME_MAX_LEN];
uint8_t n, width;
uint16_t attrs;
term_color fg, bg;
@@ -72,6 +72,7 @@ enum { GROUND, ESC, CSI, STR, STR_ESC, CHARSET, HASH, DOCS };
enum { OSC, DCS, IGNORE };
struct model {
+ char name[64]; /* XTVERSION reply */
int cols, rows, limit;
struct row **scr;
struct row **hist;
@@ -98,7 +99,7 @@ struct model {
bool tabs[MAX_TABS];
struct keyboard keyboard[2];
/* The last printed glyph, for REP and cluster joins. */
- uint32_t last[GRAPHEME_MAX_LEN];
+ uint32_t last[TERM_GRAPHEME_MAX_LEN];
int last_n;
int prompt; /* screen row of the last OSC 133;A, or -1 */
unsigned flags;
@@ -158,7 +159,7 @@ static bool pristine(const struct model *m)
static bool is_ri(uint32_t cp)
{
- return cp - 0x1F1E6u <= 0x19u && grapheme_regional_indicator(cp);
+ return cp - 0x1F1E6u <= 0x19u && term_grapheme_regional_indicator(cp);
}
/* ---- replies ---- */
@@ -417,13 +418,13 @@ static void fill_gap(struct row *r, int x)
static uint16_t presentation(const uint32_t *cps, int n)
{
- bool pictographic = unicode_extended_pictographic(cps[0]);
+ bool pictographic = term_unicode_extended_pictographic(cps[0]);
bool emoji = false;
for (int i = 1; i < n; i++) {
if (cps[i] == 0xFE0F) return TERM_ATTR_EMOJI;
if (cps[i] == 0xFE0E) return TERM_ATTR_TEXT;
- if (cps[i] == 0x20E3 || grapheme_regional_indicator(cps[i]) ||
- (pictographic && (cps[i] == 0x200D || grapheme_emoji_modifier(cps[i]) ||
+ if (cps[i] == 0x20E3 || term_grapheme_regional_indicator(cps[i]) ||
+ (pictographic && (cps[i] == 0x200D || term_grapheme_emoji_modifier(cps[i]) ||
(cps[i] >= 0xE0020 && cps[i] <= 0xE007F))))
emoji = true;
}
@@ -443,7 +444,7 @@ static void put_glyph(struct model *m, const uint32_t *cps, int n, uint16_t attr
{
if (n > 1)
attrs = (uint16_t)((attrs & ~(TERM_ATTR_EMOJI | TERM_ATTR_TEXT)) | presentation(cps, n));
- int width = unicode_width(cps[0]) == 2 ? 2 : 1;
+ int width = term_unicode_width(cps[0]) == 2 ? 2 : 1;
if (attrs & TERM_ATTR_EMOJI) width = 2;
if (attrs & TERM_ATTR_TEXT) width = 1;
if (m->pending) wrap_to_next_row(m);
@@ -495,14 +496,14 @@ static void put_glyph(struct model *m, const uint32_t *cps, int n, uint16_t attr
static bool joins(uint32_t cp, int width)
{
if (cp < 0x300) return false;
- return width == 0 || grapheme_emoji_modifier(cp) ||
- (width == 1 && unicode_spacing_mark(cp));
+ return width == 0 || term_grapheme_emoji_modifier(cp) ||
+ (width == 1 && term_unicode_spacing_mark(cp));
}
static bool cluster_joins(const uint32_t *cps, int n, uint32_t cp, int width)
{
if (joins(cp, width)) return true;
- if (cps[n - 1] == 0x200D) return unicode_extended_pictographic(cp);
+ if (cps[n - 1] == 0x200D) return term_unicode_extended_pictographic(cp);
return n == 1 && is_ri(cp) && is_ri(cps[0]);
}
@@ -527,10 +528,10 @@ static bool extend_cluster(struct model *m, uint32_t cp, int width)
return joins(cp, width);
if (!cluster_joins(prev->cp, prev->n, cp, width))
return false;
- if (prev->n >= GRAPHEME_MAX_LEN)
+ if (prev->n >= TERM_GRAPHEME_MAX_LEN)
return true;
struct cell old = *prev;
- uint32_t cps[GRAPHEME_MAX_LEN];
+ uint32_t cps[TERM_GRAPHEME_MAX_LEN];
memcpy(cps, old.cp, (size_t)old.n * sizeof(*cps));
cps[old.n] = cp;
int n = old.n + 1;
@@ -590,7 +591,7 @@ static void print_cp(struct model *m, uint32_t cp)
emit(m, cp);
return;
}
- int width = unicode_width(cp);
+ int width = term_unicode_width(cp);
if (!extend_cluster(m, cp, width))
emit(m, cp);
}
@@ -1290,7 +1291,7 @@ static void resize_plain(struct model *m, int cols, int rows)
static bool resize(struct model *m, int cols, int rows)
{
- if (cols > TERM_MAX_COLS) cols = TERM_MAX_COLS;
+ if (cols > TERM_MAX_COLS) return false;
if (cols == m->cols && rows == m->rows) return true;
if (cols > MAX_TABS) return false;
m->top = 0;
@@ -1625,7 +1626,7 @@ static void csi_dispatch(struct model *m, unsigned char final)
break;
case 'q':
if (m->prefix == '>' && !m->inter && (!m->nparam || !m->param[0])) {
- reply(m, "\033P>|wsap_term\033\\");
+ reply(m, "\033P>|%s\033\\", m->name);
} else if (!m->prefix && m->inter == ' ') {
int ps = m->nparam ? m->param[0] : 0;
if (ps == 0) {
@@ -1971,10 +1972,11 @@ static void feed_byte(struct model *m, unsigned char c)
/* ---- public ---- */
-struct model *model_new(struct term_vector2i size, int history)
+struct model *model_new(struct term_vector2i size, int history, const char *name)
{
struct model *m = calloc(1, sizeof(*m));
if (!m) abort();
+ snprintf(m->name, sizeof(m->name), "%s", name && *name ? name : "term");
m->cols = size.x < 1 ? 80 : size.x;
m->rows = size.y < 1 ? 24 : size.y;
m->limit = history_limit(history);
diff --git a/tests/model.h b/tests/model.h
index 9a4316f..ca2bf96 100644
--- a/tests/model.h
+++ b/tests/model.h
@@ -19,7 +19,7 @@
#include <stdint.h>
struct model_cell {
- uint32_t cp[GRAPHEME_MAX_LEN];
+ uint32_t cp[TERM_GRAPHEME_MAX_LEN];
uint8_t n; /* code points; 0 for the right half of a wide cell */
uint8_t width; /* 1, 2 for a wide cell, 0 for its right half */
uint16_t attrs; /* TERM_ATTR_* */
@@ -28,8 +28,9 @@ struct model_cell {
struct model;
-/* history is the history limit, as in term_options.scrollback. */
-struct model *model_new(struct term_vector2i size, int history);
+/* history is the history limit, as in term_options.scrollback. name is
+ * as in term_options.name. */
+struct model *model_new(struct term_vector2i size, int history, const char *name);
void model_free(struct model *m);
void model_feed(struct model *m, const char *s, size_t len, unsigned flags);
bool model_resize(struct model *m, struct term_vector2i size);
diff --git a/tests/model_row_test.c b/tests/model_row_test.c
index 7afca05..ad4ac2c 100644
--- a/tests/model_row_test.c
+++ b/tests/model_row_test.c
@@ -5,7 +5,7 @@
int main(void)
{
- struct model *m = model_new((struct term_vector2i){ .x = 10, .y = 3 }, 10);
+ struct model *m = model_new((struct term_vector2i){ .x = 10, .y = 3 }, 10, NULL);
model_feed(m, "A\r\nB\r\nC\r\nD", strlen("A\r\nB\r\nC\r\nD"), 0);
assert(model_history(m) == 1);
assert(model_row(m, -1) == NULL);
diff --git a/tests/model_test.c b/tests/model_test.c
index 395097e..d3988c6 100644
--- a/tests/model_test.c
+++ b/tests/model_test.c
@@ -122,8 +122,8 @@ static void dump_all(struct model *m, struct term *t, struct term_snapshot *snap
term_snapshot_update(snap, t);
const struct term_cell *row = term_snapshot_row(snap, r < total ? 0 : r - total);
for (int x = 0; x < size.x; x++) {
- uint32_t cp[GRAPHEME_MAX_LEN] = {0};
- if (row[x].text) term_snapshot_cluster(snap, row[x].text, cp, GRAPHEME_MAX_LEN);
+ uint32_t cp[TERM_GRAPHEME_MAX_LEN] = {0};
+ if (row[x].text) term_snapshot_cluster(snap, row[x].text, cp, TERM_GRAPHEME_MAX_LEN);
fputc(glyph(cp[0]), log);
}
} else {
@@ -201,9 +201,9 @@ static const char *compare(struct model *m, struct term *t, struct term_snapshot
const struct model_cell *want_row = model_row(m, retained);
for (int x = 0; x < size.x; x++) {
const struct model_cell *e = &want_row[x];
- uint32_t cps[GRAPHEME_MAX_LEN] = {0};
+ uint32_t cps[TERM_GRAPHEME_MAX_LEN] = {0};
int n = row[x].text ? term_snapshot_cluster(snap, row[x].text, cps,
- GRAPHEME_MAX_LEN) : 0;
+ TERM_GRAPHEME_MAX_LEN) : 0;
if (n == e->n && row[x].width == e->width && row[x].attrs == e->attrs &&
row[x].fg == e->fg && row[x].bg == e->bg &&
!memcmp(cps, e->cp, (size_t)n * sizeof(*cps)))
@@ -211,9 +211,9 @@ static const char *compare(struct model *m, struct term *t, struct term_snapshot
if (log) {
fprintf(log, "retained row %d (offset %d):\n term: ", retained, offset);
for (int k = 0; k < size.x; k++) {
- uint32_t cp[GRAPHEME_MAX_LEN] = {0};
+ uint32_t cp[TERM_GRAPHEME_MAX_LEN] = {0};
if (row[k].text)
- term_snapshot_cluster(snap, row[k].text, cp, GRAPHEME_MAX_LEN);
+ term_snapshot_cluster(snap, row[k].text, cp, TERM_GRAPHEME_MAX_LEN);
fputc(glyph(cp[0]), log);
}
fprintf(log, "\n model: ");
@@ -256,7 +256,7 @@ static int run(const struct script *s, enum chunking chunks, uint64_t chunk_seed
.callbacks = { .ud = &got, .reply = on_reply },
};
struct term *t = term_new(&opt);
- struct model *m = model_new(s->size, s->history);
+ struct model *m = model_new(s->size, s->history, NULL);
struct term_snapshot *snap = term_snapshot_new();
if (!t || !snap) abort();
uint64_t saved_rng = rng;
diff --git a/tests/osc_test.c b/tests/osc_test.c
index 41a9247..9d08157 100644
--- a/tests/osc_test.c
+++ b/tests/osc_test.c
@@ -104,6 +104,35 @@ static void reset_captures(void)
notify_count = 0;
}
+/* The notification default is term_options.name. A DCS +p does not change it. */
+static void test_host_name_notify(void)
+{
+ struct term_options opt = {
+ .size = { .x = 80, .y = 24 },
+ .scrollback = TEST_SCROLLBACK,
+ .name = "myterm",
+ .callbacks = { .notify = capture_notify },
+ };
+ struct term *t = term_new(&opt);
+ assert(t);
+ reset_captures();
+ write_seq(t, "\033]9;hello\007");
+ assert(notify_count == 1);
+ assert(!strcmp(notify_title, "myterm"));
+ assert(!strcmp(notify_body, "hello"));
+
+ write_seq(t, "\033P+p6f74686572\033\\");
+ reset_captures();
+ write_seq(t, "\033]9;again\007");
+ assert(notify_count == 1);
+ assert(!strcmp(notify_title, "myterm"));
+
+ write_seq(t, "\033]777;notify;Title;Body\007");
+ assert(notify_count == 2);
+ assert(!strcmp(notify_title, "Title"));
+ term_free(t);
+}
+
static void test_osc52(void)
{
struct term *t = osc_test_term();
@@ -289,7 +318,7 @@ static void test_osc_misc(void)
write_seq(t, "\033]9;hello there\033\\");
assert(notify_count == 1);
- assert(!strcmp(notify_title, "wsap_term"));
+ assert(!strcmp(notify_title, "term"));
assert(!strcmp(notify_body, "hello there"));
write_seq(t, "\033]9;4;3;50\007");
assert(notify_count == 1);
@@ -450,6 +479,7 @@ int main(void)
test_osc8();
test_sgr_reset_keeps_link();
test_osc_misc();
+ test_host_name_notify();
test_osc_colors();
test_osc_legacy_titles();
test_palette_and_theme();
diff --git a/tests/term_test.c b/tests/term_test.c
index 66dbd96..235e9df 100644
--- a/tests/term_test.c
+++ b/tests/term_test.c
@@ -4,6 +4,7 @@
#include <assert.h>
#include <limits.h>
#include <pthread.h>
+#include <sched.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
@@ -1440,6 +1441,55 @@ static void test_snapshot_follows_new_terminal(void)
term_snapshot_free(s);
}
+/* The getters take a NULL snapshot and an out-of-range row like a NULL
+ * terminal: they return the empty value. The view keeps its address. */
+static void test_snapshot_null_and_range(void)
+{
+ uint32_t cps[4] = { 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA };
+ assert(term_snapshot_view(NULL) == NULL);
+ assert(term_snapshot_row(NULL, 0) == NULL);
+ assert(term_snapshot_row_key(NULL, 0) == 0);
+ struct term_span none = term_snapshot_row_selection(NULL, 0);
+ assert(none.begin == 0 && none.end == 0);
+ assert(term_snapshot_cluster(NULL, 'a', cps, 4) == 0);
+ assert(term_snapshot_cluster(NULL, TERM_TEXT_CLUSTER, cps, 4) == 0);
+ assert(cps[0] == 0xAAAA);
+
+ struct term *t = tt_new(4, 2);
+ tt_write(t, "e\xcc\x81", 3);
+ struct term_snapshot *s = term_snapshot_new();
+ assert(term_snapshot_update(s, t));
+
+ const struct term_view *view = term_snapshot_view(s);
+ const uint32_t *palette = view->palette;
+ assert(term_snapshot_update(s, t));
+ assert(term_snapshot_view(s) == view && view->palette == palette);
+
+ assert(term_snapshot_row(s, -1) == NULL);
+ assert(term_snapshot_row(s, 2) == NULL);
+ assert(term_snapshot_row_key(s, -1) == 0);
+ assert(term_snapshot_row_key(s, 2) == 0);
+ struct term_span off = term_snapshot_row_selection(s, 2);
+ assert(off.begin == 0 && off.end == 0);
+
+ /* A cluster cell gives its count with no buffer, and with cap 0 it
+ * writes nothing. */
+ uint32_t text = term_snapshot_row(s, 0)[0].text;
+ assert(text & TERM_TEXT_CLUSTER);
+ assert(term_snapshot_cluster(s, text, NULL, 0) == 2);
+ assert(term_snapshot_cluster(s, text, cps, 0) == 2);
+ assert(cps[0] == 0xAAAA);
+ assert(term_snapshot_cluster(s, text, NULL, 4) == 2);
+ /* A plain code point is one code point, and cap 0 writes nothing. */
+ assert(term_snapshot_cluster(s, 'a', NULL, 0) == 1);
+ assert(term_snapshot_cluster(s, 'a', cps, 0) == 1);
+ assert(cps[0] == 0xAAAA);
+ assert(term_snapshot_cluster(s, 'a', cps, 4) == 1 && cps[0] == 'a');
+
+ term_snapshot_free(s);
+ term_free(t);
+}
+
static void test_string_payloads(void)
{
static const char *prefixes[] = {"\033]0;", "\033]7;file://host/", "\033P"};
@@ -1485,6 +1535,32 @@ static void test_string_payloads(void)
}
}
+/* term_new() refuses a name that a DCS reply could echo unsafely. */
+static void test_host_name_rejected(void)
+{
+ static const char *const bad[] = {
+ "\033x", "a\tb", "\177", "\300", "a\nb",
+ };
+ char longest[65];
+ memset(longest, 'a', 64);
+ longest[64] = 0;
+ char fits[64];
+ memset(fits, 'b', 63);
+ fits[63] = 0;
+
+ for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
+ struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = bad[i] };
+ assert(!term_new(&opt));
+ }
+ struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = longest };
+ assert(!term_new(&opt));
+
+ opt.name = fits;
+ struct term *t = term_new(&opt);
+ assert(t);
+ term_free(t);
+}
+
static void test_newline_policy_survives_reset(void)
{
static const char *const resets[] = { "\033c", "\033[!p", "\033[20l" };
@@ -1762,21 +1838,22 @@ struct concurrent_worker_args {
_Atomic bool done;
};
+static const char concurrent_stream[] =
+ "The quick brown fox jumps over the lazy dog.\r\n"
+ "\033[31mRed text\033[0m and \033[32mgreen text\033[0m.\r\n"
+ "Some unicode: 漢字, ひらがな, and emojis: 😀🚀🎉\r\n"
+ "\033[2J\033[HCleared and reset\r\n"
+ "Line 1\r\nLine 2\r\nLine 3\r\nLine 4\r\nLine 5\r\n"
+ "\033]7;file:///tmp/dir\007\033]22;pointer\007"
+ "\033]8;;https://example.com\007link\033]8;;\007\r\n";
+
static void *concurrent_feed_worker(void *arg)
{
struct concurrent_worker_args *w = arg;
- static const char stream[] =
- "The quick brown fox jumps over the lazy dog.\r\n"
- "\033[31mRed text\033[0m and \033[32mgreen text\033[0m.\r\n"
- "Some unicode: 漢字, ひらがな, and emojis: 😀🚀🎉\r\n"
- "\033[2J\033[HCleared and reset\r\n"
- "Line 1\r\nLine 2\r\nLine 3\r\nLine 4\r\nLine 5\r\n"
- "\033]7;file:///tmp/dir\007\033]22;pointer\007"
- "\033]8;;https://example.com\007link\033]8;;\007\r\n";
- size_t len = strlen(stream);
+ size_t len = strlen(concurrent_stream);
for (int i = 0; i < 2000; i++) {
- term_feed(w->t, stream, len, 0);
+ term_feed(w->t, concurrent_stream, len, 0);
}
atomic_store(&w->done, true);
return NULL;
@@ -1784,10 +1861,11 @@ static void *concurrent_feed_worker(void *arg)
static void test_concurrent_feed_and_snapshot(void)
{
- struct term *t = term_new(&(struct term_options){
+ struct term_options opt = {
.size = { .x = 80, .y = 24 },
.scrollback = 1000,
- });
+ };
+ struct term *t = term_new(&opt);
assert(t != NULL);
struct concurrent_worker_args w = { .t = t, .done = false };
@@ -1828,13 +1906,104 @@ static void test_concurrent_feed_and_snapshot(void)
bool ok = term_snapshot_update(snap, t);
assert(ok);
+ /* The reader changed only the view and the selection. The screen and
+ * the history must match a terminal that got the same bytes on one
+ * thread. */
+ struct term *ref = term_new(&opt);
+ assert(ref != NULL);
+ for (int i = 0; i < 2000; i++)
+ tt_write(ref, concurrent_stream, strlen(concurrent_stream));
+ assert(tt_cols(t) == tt_cols(ref) && tt_rows(t) == tt_rows(ref));
+ assert(tt_history(t) == tt_history(ref));
+ assert(tt_cursor_x(t) == tt_cursor_x(ref) && tt_cursor_y(t) == tt_cursor_y(ref));
+ char *got = malloc((size_t)tt_cols(t) + 1);
+ char *want = malloc((size_t)tt_cols(ref) + 1);
+ assert(got && want);
+ for (int y = 0; y < tt_rows(t); y++) {
+ row_text(t, y, got);
+ row_text(ref, y, want);
+ assert(!strcmp(got, want));
+ }
+ free(got);
+ free(want);
+ char *text = term_history_text(t, 1000);
+ char *text_ref = term_history_text(ref, 1000);
+ assert(text && text_ref && !strcmp(text, text_ref));
+ term_text_free(text);
+ term_text_free(text_ref);
+
+ term_free(ref);
term_snapshot_free(snap);
term_free(t);
}
+static void test_reserved_options_must_be_zero(void)
+{
+ struct term_options opt = { .size = { .x = 10, .y = 2 } };
+ struct term *t = term_new(&opt);
+ assert(t != NULL);
+ term_free(t);
+
+ size_t words = sizeof(opt.reserved) / sizeof(opt.reserved[0]);
+ for (size_t i = 0; i < words; i++) {
+ opt.reserved[i] = 1;
+ assert(term_new(&opt) == NULL);
+ opt.reserved[i] = 0;
+ }
+ opt.reserved[words - 1] = 0x80000000u;
+ assert(term_new(&opt) == NULL);
+ opt.reserved[words - 1] = 0;
+ t = term_new(&opt);
+ assert(t != NULL);
+ term_free(t);
+}
+
+struct lock_probe {
+ struct term *t;
+ _Atomic bool entered;
+};
+
+static void *lock_probe_worker(void *arg)
+{
+ struct lock_probe *p = arg;
+ term_lock(p->t);
+ atomic_store(&p->entered, true);
+ term_unlock(p->t);
+ return NULL;
+}
+
+/* term_lock() nests on the owning thread. Another thread waits until the
+ * last term_unlock(). */
+static void test_lock_reentrant_across_threads(void)
+{
+ struct term *t = term_new(&(struct term_options){
+ .size = { .x = 80, .y = 24 },
+ });
+ assert(t != NULL);
+
+ term_lock(t);
+ term_lock(t);
+ assert(tt_cols(t) == 80);
+ term_unlock(t);
+
+ struct lock_probe p = { .t = t, .entered = false };
+ pthread_t th;
+ assert(pthread_create(&th, NULL, lock_probe_worker, &p) == 0);
+ for (int i = 0; i < 1000; i++)
+ sched_yield();
+ assert(!atomic_load(&p.entered));
+ term_unlock(t);
+
+ assert(pthread_join(th, NULL) == 0);
+ assert(atomic_load(&p.entered));
+ term_free(t);
+}
+
int main(void)
{
+ test_reserved_options_must_be_zero();
test_concurrent_feed_and_snapshot();
+ test_lock_reentrant_across_threads();
test_grow_past_reservation();
test_skipped_cells_read_blank();
test_csi_text_chunks();
@@ -1859,17 +2028,19 @@ int main(void)
test_alt_shrink_then_flood();
test_alt_erase_scrollback();
test_snapshot_follows_new_terminal();
+ test_snapshot_null_and_range();
test_bulk_rows_survive_edits();
-
- assert(!strcmp(UNICODE_DATA_VERSION, "18.0.0"));
- assert(!strcmp(term_unicode_version(), UNICODE_DATA_VERSION));
- assert(unicode_width('A') == 1);
- assert(unicode_width(0x0301) == 0);
- assert(unicode_width(0x754C) == 2);
- assert(unicode_width(0x1FAE0) == 2);
- assert(unicode_emoji_presentation(0x1FAE0));
- assert(unicode_width(0x1FAEB) == 2);
- assert(unicode_emoji_presentation(0x1FAEB));
+ test_host_name_rejected();
+
+ assert(!strcmp(TERM_UNICODE_DATA_VERSION, "18.0.0"));
+ assert(!strcmp(term_unicode_version(), TERM_UNICODE_DATA_VERSION));
+ assert(term_unicode_width('A') == 1);
+ assert(term_unicode_width(0x0301) == 0);
+ assert(term_unicode_width(0x754C) == 2);
+ assert(term_unicode_width(0x1FAE0) == 2);
+ assert(term_unicode_emoji_presentation(0x1FAE0));
+ assert(term_unicode_width(0x1FAEB) == 2);
+ assert(term_unicode_emoji_presentation(0x1FAEB));
{
/* A Jamo Extended-B vowel draws inside its leading consonant. */
struct term *t = term_new(&(struct term_options){ .size = { .x = 10, .y = 2 } });
diff --git a/tests/unicode/unicode_test.c b/tests/unicode/unicode_test.c
index 7fc6e3e..e3cf5a7 100644
--- a/tests/unicode/unicode_test.c
+++ b/tests/unicode/unicode_test.c
@@ -16,17 +16,17 @@ static void test_utf8_decode_one(void)
for (size_t i = 0; i < sizeof(valid) / sizeof(*valid); i++) {
uint32_t cp = 0xdeadbeef;
for (int n = 0; n < valid[i].len; n++) {
- int r = utf8_decode_one(valid[i].bytes, n, &cp);
+ int r = term_utf8_decode_one(valid[i].bytes, n, &cp);
assert(r == 0);
assert(cp == 0xdeadbeef);
}
- int r = utf8_decode_one(valid[i].bytes, valid[i].len, &cp);
+ int r = term_utf8_decode_one(valid[i].bytes, valid[i].len, &cp);
assert(r == valid[i].len);
assert(cp == valid[i].cp);
char followed[8];
memcpy(followed, valid[i].bytes, valid[i].len);
memcpy(followed + valid[i].len, "next", 4);
- r = utf8_decode_one(followed, valid[i].len + 4, &cp);
+ r = term_utf8_decode_one(followed, valid[i].len + 4, &cp);
assert(r == valid[i].len);
assert(cp == valid[i].cp);
}
@@ -38,7 +38,7 @@ static void test_utf8_decode_one(void)
};
for (size_t i = 0; i < sizeof(invalid) / sizeof(*invalid); i++) {
uint32_t cp = 0xdeadbeef;
- int r = utf8_decode_one(invalid[i], strlen(invalid[i]), &cp);
+ int r = term_utf8_decode_one(invalid[i], strlen(invalid[i]), &cp);
assert(r == 0);
assert(cp == 0xdeadbeef);
}
@@ -48,24 +48,24 @@ static void test_scalar_roundtrips(void)
{
for (uint32_t cp = 0; cp <= 0x10ffff; cp++) {
if (cp >= 0xd800 && cp <= 0xdfff) continue;
- char bytes[UTF8_MAX_BYTES];
- int len = utf8_encode(cp, bytes);
- uint32_t scalar = 0, streamed = 0, state = UTF8_ACCEPT;
- int r = utf8_decode_one(bytes, (size_t)len, &scalar);
+ char bytes[TERM_UTF8_MAX_BYTES];
+ int len = term_utf8_encode(cp, bytes);
+ uint32_t scalar = 0, streamed = 0, state = TERM_UTF8_ACCEPT;
+ int r = term_utf8_decode_one(bytes, (size_t)len, &scalar);
assert(r == len);
assert(scalar == cp);
for (int i = 0; i < len; i++) {
- utf8_decode(&state, &streamed, (unsigned char)bytes[i]);
- if (i + 1 < len) assert(state != UTF8_ACCEPT && state != UTF8_REJECT);
+ term_utf8_decode(&state, &streamed, (unsigned char)bytes[i]);
+ if (i + 1 < len) assert(state != TERM_UTF8_ACCEPT && state != TERM_UTF8_REJECT);
}
- assert(state == UTF8_ACCEPT && streamed == cp);
+ assert(state == TERM_UTF8_ACCEPT && streamed == cp);
}
static const uint32_t invalid[] = {0xd800, 0xdfff, 0x110000, UINT32_MAX};
for (size_t i = 0; i < sizeof(invalid) / sizeof(*invalid); i++) {
- char bytes[UTF8_MAX_BYTES];
- int len = utf8_encode(invalid[i], bytes);
+ char bytes[TERM_UTF8_MAX_BYTES];
+ int len = term_utf8_encode(invalid[i], bytes);
uint32_t cp = 0;
- int r = utf8_decode_one(bytes, len, &cp);
+ int r = term_utf8_decode_one(bytes, len, &cp);
assert(len == 3 && r == len && cp == 0xfffd);
}
}
@@ -74,26 +74,26 @@ static void test_utf8_sanitize(void)
{
char dst[64];
- utf8_sanitize(dst, sizeof(dst), "hello \xe2\x82\xac\xf0\x9f\x98\x80");
+ term_utf8_sanitize(dst, sizeof(dst), "hello \xe2\x82\xac\xf0\x9f\x98\x80");
assert(!strcmp(dst, "hello \xe2\x82\xac\xf0\x9f\x98\x80"));
- utf8_sanitize(dst, sizeof(dst), "a\xff\x80" "b\xed\xa0\x80" "c\xe2\x82");
+ term_utf8_sanitize(dst, sizeof(dst), "a\xff\x80" "b\xed\xa0\x80" "c\xe2\x82");
assert(!strcmp(dst, "abc"));
- utf8_sanitize(dst, sizeof(dst), "");
+ term_utf8_sanitize(dst, sizeof(dst), "");
assert(!strcmp(dst, ""));
- utf8_sanitize(dst, sizeof(dst), "\xff\xfe");
+ term_utf8_sanitize(dst, sizeof(dst), "\xff\xfe");
assert(!strcmp(dst, ""));
- utf8_sanitize(dst, 6, "ab\xe2\x82\xac" "cd");
+ term_utf8_sanitize(dst, 6, "ab\xe2\x82\xac" "cd");
assert(!strcmp(dst, "ab\xe2\x82\xac"));
- utf8_sanitize(dst, 5, "ab\xe2\x82\xac" "cd");
+ term_utf8_sanitize(dst, 5, "ab\xe2\x82\xac" "cd");
assert(!strcmp(dst, "abcd"));
memset(dst, 'x', sizeof(dst));
- utf8_sanitize(dst, 4, "abcdef");
+ term_utf8_sanitize(dst, 4, "abcdef");
assert(!strcmp(dst, "abc"));
memset(dst, 'x', sizeof(dst));
- utf8_sanitize(dst, 0, "abc");
+ term_utf8_sanitize(dst, 0, "abc");
assert(dst[0] == 'x');
}
@@ -101,30 +101,30 @@ static void test_utf8_sanitize(void)
* stays unused. */
static void test_ground_invalid_subparts(void)
{
- uint32_t state = UTF8_ACCEPT, codep = 0, cp = 0xdead;
- int n = utf8_decode_ground(&state, &codep, "\xe2\x80\x1b\xac", 4, &cp);
+ uint32_t state = TERM_UTF8_ACCEPT, codep = 0, cp = 0xdead;
+ int n = term_utf8_decode_ground(&state, &codep, "\xe2\x80\x1b\xac", 4, &cp);
assert(n == 2);
assert(cp == 0xFFFD);
- assert(state == UTF8_ACCEPT);
- n = utf8_decode_ground(&state, &codep, "\xac", 1, &cp);
+ assert(state == TERM_UTF8_ACCEPT);
+ n = term_utf8_decode_ground(&state, &codep, "\xac", 1, &cp);
assert(n == 1);
assert(cp == 0xFFFD);
- assert(state == UTF8_ACCEPT);
- n = utf8_decode_ground(&state, &codep, "\xe2\x82z", 3, &cp);
+ assert(state == TERM_UTF8_ACCEPT);
+ n = term_utf8_decode_ground(&state, &codep, "\xe2\x82z", 3, &cp);
assert(n == 2);
assert(cp == 0xFFFD);
- assert(state == UTF8_ACCEPT);
- n = utf8_decode_ground(&state, &codep, "\xe2\x82", 2, &cp);
+ assert(state == TERM_UTF8_ACCEPT);
+ n = term_utf8_decode_ground(&state, &codep, "\xe2\x82", 2, &cp);
assert(n == 2);
assert(cp == 0);
- assert(state != UTF8_ACCEPT);
+ assert(state != TERM_UTF8_ACCEPT);
- state = UTF8_ACCEPT;
+ state = TERM_UTF8_ACCEPT;
codep = 0;
- n = utf8_decode_ground(&state, &codep, "\xe2\x82\xac", 3, &cp);
+ n = term_utf8_decode_ground(&state, &codep, "\xe2\x82\xac", 3, &cp);
assert(n == 3);
assert(cp == 0x20ac);
- assert(state == UTF8_ACCEPT);
+ assert(state == TERM_UTF8_ACCEPT);
}
int main(void)
diff --git a/tests/version_test.c b/tests/version_test.c
new file mode 100644
index 0000000..0eb889e
--- /dev/null
+++ b/tests/version_test.c
@@ -0,0 +1,27 @@
+#include "term/term.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+/* The project version from meson.build. The build defines it. */
+#ifndef TERM_TEST_PROJECT_VERSION
+#error "define TERM_TEST_PROJECT_VERSION as the project version, such as \"0.1.0\""
+#endif
+
+int main(void)
+{
+ char text[32];
+ snprintf(text, sizeof(text), "%d.%d.%d",
+ TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH);
+ assert(!strcmp(text, TERM_TEST_PROJECT_VERSION));
+
+ int major, minor, patch;
+ assert(sscanf(TERM_TEST_PROJECT_VERSION, "%d.%d.%d", &major, &minor, &patch) == 3);
+ assert(TERM_VERSION_CODE == TERM_VERSION(major, minor, patch));
+ assert(term_version() == TERM_VERSION_CODE);
+
+ assert(TERM_VERSION(0, 2, 0) > TERM_VERSION(0, 1, 255));
+ assert(TERM_VERSION(1, 0, 0) > TERM_VERSION(0, 255, 255));
+ return 0;
+}