diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/api_args_test.c | 140 | ||||
| -rw-r--r-- | tests/dcs_test.c | 53 | ||||
| -rw-r--r-- | tests/gstore_test.c | 38 | ||||
| -rw-r--r-- | tests/header_test.sh | 75 | ||||
| -rw-r--r-- | tests/hist_test.c | 12 | ||||
| -rw-r--r-- | tests/meson.build | 18 | ||||
| -rw-r--r-- | tests/model.c | 34 | ||||
| -rw-r--r-- | tests/model.h | 7 | ||||
| -rw-r--r-- | tests/model_row_test.c | 2 | ||||
| -rw-r--r-- | tests/model_test.c | 14 | ||||
| -rw-r--r-- | tests/osc_test.c | 32 | ||||
| -rw-r--r-- | tests/term_test.c | 215 | ||||
| -rw-r--r-- | tests/unicode/unicode_test.c | 68 | ||||
| -rw-r--r-- | tests/version_test.c | 27 |
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; +} |
