diff options
| -rw-r--r-- | benchmarks/term_write_bench.c | 5 | ||||
| -rw-r--r-- | include/term/term.h | 52 | ||||
| -rw-r--r-- | include/term/term_input.h | 6 | ||||
| -rw-r--r-- | src/meson.build | 6 | ||||
| -rw-r--r-- | src/screen.c | 22 | ||||
| -rw-r--r-- | src/snapshot.c | 14 | ||||
| -rw-r--r-- | src/term.c | 5 | ||||
| -rw-r--r-- | src/text.c | 14 | ||||
| -rw-r--r-- | tests/api_args_test.c | 140 | ||||
| -rw-r--r-- | tests/meson.build | 14 | ||||
| -rw-r--r-- | tests/model.c | 2 | ||||
| -rw-r--r-- | tests/term_test.c | 72 | ||||
| -rw-r--r-- | tests/version_test.c | 27 |
13 files changed, 343 insertions, 36 deletions
diff --git a/benchmarks/term_write_bench.c b/benchmarks/term_write_bench.c index 50853f6..7649ed8 100644 --- a/benchmarks/term_write_bench.c +++ b/benchmarks/term_write_bench.c @@ -152,7 +152,10 @@ int main(int argc, char **argv) .size = { .x = cols_val, .y = 40 }, .scrollback = BENCH_SCROLLBACK, }); - if (!t) return EXIT_FAILURE; + if (!t) { + fprintf(stderr, "term_new failed: TERM_COLS must be 0 to %d\n", TERM_MAX_COLS); + return EXIT_FAILURE; + } /* manyline has bare LFs: each one also returns the cursor to * column 0, as a tty with ONLCR does. */ unsigned flags = !strcmp(cases[c].name, "manyline") ? TERM_FEED_ONLCR : 0; diff --git a/include/term/term.h b/include/term/term.h index aa1a3d7..9d5a882 100644 --- a/include/term/term.h +++ b/include/term/term.h @@ -41,6 +41,16 @@ struct term_snapshot; #define TERM_SCROLLBACK_DEFAULT 10000 /* + * The version of this header. A host compares TERM_VERSION_CODE with + * term_version() to check that the library matches the header. + */ +#define TERM_VERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) +#define TERM_VERSION_MAJOR 0 +#define TERM_VERSION_MINOR 1 +#define TERM_VERSION_PATCH 0 +#define TERM_VERSION_CODE TERM_VERSION(TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH) + +/* * Colors. A term_color is either a direct color (alpha 0xFF, then RGB) or a * palette slot: an index 0-255, the default foreground, or the default * background. A slot resolves when a snapshot is painted, so a theme change @@ -139,9 +149,14 @@ struct term_callbacks { void (*trace)(void *ud, const char *line); }; -/* Options for term_new(). An all-zero value is valid. */ +/* + * Options for term_new(). An all-zero value is valid. A size component of 0 + * selects 80 columns or 24 rows. A negative component, or columns above + * TERM_MAX_COLS, makes term_new() fail. A negative scrollback fails too. + * cursor_style must be a value of enum term_cursor_style. + */ struct term_options { - struct term_vector2i size; /* columns and rows; each is at least 1 */ + struct term_vector2i size; /* columns and rows */ int scrollback; /* history lines; 0 selects TERM_SCROLLBACK_DEFAULT */ struct term_theme theme; enum term_cursor_style cursor_style; @@ -154,12 +169,16 @@ struct term_options { */ const char *name; struct term_callbacks callbacks; + /* Must be zero. term_new() returns NULL if any word is nonzero, so a + * later version can give a word a meaning. */ + uint32_t reserved[8]; }; -/* NULL when memory runs out. */ /* The Unicode version of the width tables, such as "18.0.0". A host can * pass it to programs as UNICODE_VERSION. */ TERM_API const char *term_unicode_version(void); +/* The TERM_VERSION_CODE the library was built with. */ +TERM_API unsigned term_version(void); /* The length of the prefix of buf made of bytes in [0x20, hi). With hi * 0x7f, it finds a run of printable ASCII in UTF-8 text, so a host can @@ -172,6 +191,7 @@ TERM_API size_t term_scan_printable_span(const char *buf, size_t len, unsigned c * and comparisons. */ TERM_API const char *term_isa(void); +/* NULL when memory runs out or an option is invalid. */ TERM_API struct term *term_new(const struct term_options *opt); TERM_API void term_free(struct term *t); @@ -182,8 +202,9 @@ TERM_API void term_free(struct term *t); TERM_API void term_lock(struct term *t); TERM_API void term_unlock(struct term *t); -/* Change the size in cells. Returns false when memory runs out; the terminal - * is then unchanged. The screen reflows to the new width. */ +/* Change the size in cells. Returns false, and the terminal is unchanged, + * when a component is below 1, when cols is above TERM_MAX_COLS, or when + * memory runs out. The screen reflows to the new width. */ TERM_API bool term_resize(struct term *t, struct term_vector2i size); /* Replace the theme. A different theme also drops the colors that the @@ -204,7 +225,12 @@ enum term_feed_flags { TERM_FEED_ONLCR = 1u << 0, }; -TERM_API void term_feed(struct term *t, const char *bytes, size_t len, unsigned flags); +/* + * Feed bytes from the program. Returns false, and changes nothing, when t is + * NULL, when bytes is NULL and len is not 0, or when flags has a bit that is + * not in enum term_feed_flags. A len of 0 is a no-op that returns true. + */ +TERM_API bool term_feed(struct term *t, const char *bytes, size_t len, unsigned flags); /* Report a focus change. The terminal replies only in focus-event mode. */ TERM_API void term_focus(struct term *t, bool focused); @@ -415,11 +441,16 @@ struct term_view { const uint32_t *palette; /* TERM_PALETTE_SIZE ARGB entries */ }; +/* The view of s, NULL for a NULL s. The view and view->palette keep their + * address until term_snapshot_free(); their contents change on each update. */ TERM_API const struct term_view *term_snapshot_view(const struct term_snapshot *s); -/* The cells of viewport row y: view->size.x entries. */ +/* The cells of viewport row y: view->size.x entries. NULL when y is outside + * the viewport or s is NULL. An update can move the cells, so the pointer is + * valid only until the next term_snapshot_update() or term_snapshot_free() of s. */ TERM_API const struct term_cell *term_snapshot_row(const struct term_snapshot *s, int y); /* An identity for the content of row y. Two rows with the same key hold the - * same cells, also when the content moved to another row. */ + * same cells, also when the content moved to another row. 0 when y is outside + * the viewport or s is NULL. */ TERM_API uint64_t term_snapshot_row_key(const struct term_snapshot *s, int y); /* Number of rows in history above the screen. 0 if alternate screen or no history. */ @@ -430,10 +461,13 @@ TERM_API int term_history_rows(const struct term *t); * an empty string; NULL means allocation failed or t is NULL. Release the * result with term_text_free(). */ TERM_API char *term_history_text(struct term *t, int max_rows); +/* The selected columns of viewport row y, end exclusive. An empty span when y + * is outside the viewport or s is NULL. */ TERM_API struct term_span term_snapshot_row_selection(const struct term_snapshot *s, int y); /* The code points of a cluster cell. Returns the number of code points and - * writes at most cap of them. */ + * writes at most cap of them. Writes nothing when cps is NULL or cap is 0. + * Returns 0 when s is NULL. */ TERM_API int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t *cps, int cap); /* The ARGB value of a color in a view. */ diff --git a/include/term/term_input.h b/include/term/term_input.h index c317bea..19d9355 100644 --- a/include/term/term_input.h +++ b/include/term/term_input.h @@ -1,5 +1,5 @@ -#ifndef TERM_INPUT_TYPES_H -#define TERM_INPUT_TYPES_H +#ifndef TERM_INPUT_H +#define TERM_INPUT_H #include <stdbool.h> #include <stddef.h> @@ -199,4 +199,4 @@ struct term_input_result { } #endif -#endif /* TERM_INPUT_TYPES_H */ +#endif /* TERM_INPUT_H */ diff --git a/src/meson.build b/src/meson.build index 694ee0e..7223caa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,9 @@ subdir('osc') +# The SONAME carries the major version. An ABI break bumps it, so an old +# binary keeps the old library. +term_soversion = meson.project_version().split('.')[0] + # The x86-64 SIMD kernels pick their level at run time (see simd.h), so the # library needs no ISA flags. -march in c_args sets the lowest level. term_lib = both_libraries('term', @@ -23,6 +27,8 @@ term_lib = both_libraries('term', include_directories: term_private_inc, dependencies: [m_dep], gnu_symbol_visibility: 'hidden', + version: meson.project_version(), + soversion: term_soversion, install: true ) diff --git a/src/screen.c b/src/screen.c index e728cec..0857f17 100644 --- a/src/screen.c +++ b/src/screen.c @@ -808,12 +808,11 @@ void term_screen_set_alternate(struct term *t, bool enabled) } } +/* The caller has checked that requested is not negative. */ static int scrollback_lines(int requested) { if (requested == 0) return TERM_SCROLLBACK_DEFAULT; - if (requested < 0) - return 0; return requested > 1000000 ? 1000000 : requested; } @@ -858,13 +857,18 @@ struct term *term_new(const struct term_options *opt) static const struct term_options defaults; if (!opt) opt = &defaults; + for (size_t i = 0; i < sizeof(opt->reserved) / sizeof(opt->reserved[0]); i++) + if (opt->reserved[i]) + return NULL; + if (opt->size.x < 0 || opt->size.y < 0 || opt->size.x > TERM_MAX_COLS) + return NULL; + if (opt->scrollback < 0 || (unsigned)opt->cursor_style > TERM_CURSOR_BAR) + return NULL; const char *name = opt->name && *opt->name ? opt->name : "term"; if (!name_valid(name)) return NULL; - int cols = opt->size.x < 1 ? 80 : opt->size.x; - int rows = opt->size.y < 1 ? 24 : opt->size.y; - if (cols > TERM_MAX_COLS) - cols = TERM_MAX_COLS; + int cols = opt->size.x == 0 ? 80 : opt->size.x; + int rows = opt->size.y == 0 ? 24 : opt->size.y; static _Atomic uint64_t next_serial = 1; struct term *t = calloc(1, sizeof(*t)); @@ -910,7 +914,7 @@ struct term *term_new(const struct term_options *opt) t->cur_fg = TERM_COLOR_FG; t->cur_bg = TERM_COLOR_BG; t->cursor_visible = true; - t->default_cursor_style = opt->cursor_style > TERM_CURSOR_BAR ? TERM_CURSOR_BLOCK : opt->cursor_style; + t->default_cursor_style = opt->cursor_style; t->default_cursor_blink = !opt->cursor_steady; t->cursor_style = t->default_cursor_style; t->cursor_blink = t->default_cursor_blink; @@ -1538,10 +1542,8 @@ bool term_resize(struct term *t, struct term_vector2i size) return false; int cols = size.x; int rows = size.y; - if (cols < 1 || rows < 1) + if (cols < 1 || rows < 1 || cols > TERM_MAX_COLS) return false; - if (cols > TERM_MAX_COLS) - cols = TERM_MAX_COLS; term_write_begin(t); if (cols == t->cols && rows == t->rows) { term_write_end(t); diff --git a/src/snapshot.c b/src/snapshot.c index 8dbb498..c578f18 100644 --- a/src/snapshot.c +++ b/src/snapshot.c @@ -314,34 +314,36 @@ bool term_snapshot_update(struct term_snapshot *s, struct term *t) const struct term_view *term_snapshot_view(const struct term_snapshot *s) { - return &s->view; + return s ? &s->view : NULL; } const struct term_cell *term_snapshot_row(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return NULL; return s->cells + (size_t)y * s->view.size.x; } uint64_t term_snapshot_row_key(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return 0; return s->keys[y]; } struct term_span term_snapshot_row_selection(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return (struct term_span){ 0 }; return s->selection[y]; } int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t *cps, int cap) { + if (!s) + return 0; if (!(text & TERM_TEXT_CLUSTER)) { - if (cap > 0) + if (cps && cap > 0) cps[0] = text; return 1; } @@ -350,7 +352,7 @@ int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t return 0; int n = (int)s->arena[off]; int copy = n < cap ? n : cap; - if (copy > 0) + if (cps && copy > 0) memcpy(cps, &s->arena[off + 1], (size_t)copy * sizeof(*cps)); return n; } @@ -8,6 +8,11 @@ const char *term_unicode_version(void) return UNICODE_DATA_VERSION; } +unsigned term_version(void) +{ + return TERM_VERSION_CODE; +} + void term_lock(struct term *t) { term_write_begin(t); @@ -190,10 +190,11 @@ static inline bool try_text_run(struct term *t, const char *buf, size_t len, return wrote; } +/* The feed flags this build knows. term_feed() rejects any other bit. */ +#define TERM_FEED_KNOWN TERM_FEED_ONLCR + static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigned flags) { - if (!t || !buf || len == 0) - return; t->transport_newline = (flags & TERM_FEED_ONLCR) != 0; uint16_t cur_flags = term_current_flags(t); @@ -366,13 +367,16 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne } } -void term_feed(struct term *t, const char *buf, size_t len, unsigned flags) +bool term_feed(struct term *t, const char *buf, size_t len, unsigned flags) { - if (!t || !buf || len == 0) - return; + if (!t || (!buf && len > 0) || (flags & ~TERM_FEED_KNOWN)) + return false; + if (len == 0) + return true; term_write_begin(t); term_feed_inner(t, buf, len, flags); term_write_end(t); + return true; } void term_text_repeat(struct term *t, int count) 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/meson.build b/tests/meson.build index 69f3c49..629f6d5 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') @@ -83,7 +84,7 @@ term_amalgamation_lib = static_library( 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, @@ -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 b538ba1..d212acb 100644 --- a/tests/model.c +++ b/tests/model.c @@ -1291,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; diff --git a/tests/term_test.c b/tests/term_test.c index e4ffb27..51544ef 100644 --- a/tests/term_test.c +++ b/tests/term_test.c @@ -1440,6 +1440,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"}; @@ -1858,8 +1907,30 @@ static void test_concurrent_feed_and_snapshot(void) 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); +} + int main(void) { + test_reserved_options_must_be_zero(); test_concurrent_feed_and_snapshot(); test_grow_past_reservation(); test_skipped_cells_read_blank(); @@ -1885,6 +1956,7 @@ 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(); test_host_name_rejected(); 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; +} |
