summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-15 12:38:54 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-15 12:38:54 +0300
commit6ee9eb37d8e616a972fb29b3fbdbc33991aa844f (patch)
tree5b257df9f1301f00ca5de49de60c56be45c67274 /tests
parentcaf7d446446a0af0b30886a6c4ee5851aab553e2 (diff)
parente3d8fe4854a7ef7ff0637688cda9e65feb680122 (diff)
Merge branch 'wt/lock' into api-hardening
# Conflicts: # src/screen.c # tests/term_test.c
Diffstat (limited to 'tests')
-rw-r--r--tests/meson.build4
-rw-r--r--tests/term_test.c96
2 files changed, 86 insertions, 14 deletions
diff --git a/tests/meson.build b/tests/meson.build
index 629f6d5..4fda075 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -77,7 +77,7 @@ 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,
)
@@ -90,7 +90,7 @@ foreach entry : term_tests
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
diff --git a/tests/term_test.c b/tests/term_test.c
index 51544ef..62b827f 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>
@@ -1837,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;
@@ -1859,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 };
@@ -1903,6 +1906,33 @@ 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);
}
@@ -1928,10 +1958,52 @@ static void test_reserved_options_must_be_zero(void)
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();