mirror of
https://github.com/m1ngsama/TNT.git
synced 2026-02-08 08:54:05 +00:00
Compare commits
2 commits
4b158ede18
...
25a277ab27
| Author | SHA1 | Date | |
|---|---|---|---|
| 25a277ab27 | |||
| 2535d8bfd4 |
7 changed files with 557 additions and 2 deletions
45
.github/workflows/deploy.yml
vendored
Normal file
45
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libssh-dev
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
- name: Build with AddressSanitizer
|
||||
run: make asan
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
make test
|
||||
cd tests
|
||||
./test_security_features.sh
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy to production
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
script: |
|
||||
cd /home/admin/repo/tnt
|
||||
git pull origin main
|
||||
make clean && make release
|
||||
cp tnt /home/admin/tnt/tnt
|
||||
sudo systemctl restart tnt
|
||||
|
|
@ -805,8 +805,9 @@ void* client_handle_session(void *arg) {
|
|||
int n = ssh_channel_read_timeout(client->channel, buf, 1, 0, 30000); /* 30 sec timeout */
|
||||
|
||||
if (n == SSH_AGAIN) {
|
||||
/* Timeout - check if channel is still alive */
|
||||
if (!ssh_channel_is_open(client->channel)) {
|
||||
/* Timeout - send keepalive to prevent NAT/firewall timeout */
|
||||
if (!ssh_channel_is_open(client->channel) ||
|
||||
ssh_send_keepalive(client->session) != SSH_OK) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
|
|
|
|||
30
tests/unit/Makefile
Normal file
30
tests/unit/Makefile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Unit Tests Makefile
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -std=c11 -I../../include
|
||||
LDFLAGS = -pthread
|
||||
|
||||
# Source files
|
||||
UTF8_SRC = ../../src/utf8.c
|
||||
MESSAGE_SRC = ../../src/message.c
|
||||
|
||||
TESTS = test_utf8 test_message
|
||||
|
||||
.PHONY: all clean run
|
||||
|
||||
all: $(TESTS)
|
||||
|
||||
test_utf8: test_utf8.c $(UTF8_SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
test_message: test_message.c $(MESSAGE_SRC) $(UTF8_SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
run: all
|
||||
@echo "=== Running UTF-8 Tests ==="
|
||||
./test_utf8
|
||||
@echo ""
|
||||
@echo "=== Running Message Tests ==="
|
||||
./test_message
|
||||
|
||||
clean:
|
||||
rm -f $(TESTS) *.o test_messages.log
|
||||
BIN
tests/unit/test_message
Executable file
BIN
tests/unit/test_message
Executable file
Binary file not shown.
240
tests/unit/test_message.c
Normal file
240
tests/unit/test_message.c
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/* Unit tests for message functions */
|
||||
#include "../../include/message.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define TEST(name) static void test_##name()
|
||||
#define RUN_TEST(name) do { \
|
||||
printf("Running %s... ", #name); \
|
||||
test_##name(); \
|
||||
printf("✓\n"); \
|
||||
tests_passed++; \
|
||||
} while(0)
|
||||
|
||||
static int tests_passed = 0;
|
||||
static const char *test_log = "test_messages.log";
|
||||
|
||||
/* Helper: Clean up test log file */
|
||||
static void cleanup_test_log(void) {
|
||||
unlink(test_log);
|
||||
}
|
||||
|
||||
/* Helper: Create test log with N messages */
|
||||
static void create_test_log(int count) {
|
||||
FILE *fp = fopen(test_log, "w");
|
||||
assert(fp != NULL);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
fprintf(fp, "2026-02-08T10:00:%02d+08:00|user%d|Test message %d\n",
|
||||
i, i, i);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Test message initialization */
|
||||
TEST(message_init) {
|
||||
message_init();
|
||||
/* No assertion needed, just ensure it doesn't crash */
|
||||
}
|
||||
|
||||
/* Test loading from empty file */
|
||||
TEST(message_load_empty) {
|
||||
cleanup_test_log();
|
||||
|
||||
/* Temporarily override LOG_FILE */
|
||||
FILE *fp = fopen(test_log, "w");
|
||||
fclose(fp);
|
||||
|
||||
message_t *messages = NULL;
|
||||
/* Can't easily override LOG_FILE constant, so this is a documentation test */
|
||||
|
||||
cleanup_test_log();
|
||||
}
|
||||
|
||||
/* Test message format */
|
||||
TEST(message_format_basic) {
|
||||
message_t msg;
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "testuser");
|
||||
strcpy(msg.content, "Hello World");
|
||||
|
||||
char buffer[512];
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
|
||||
/* Should contain timestamp, username, and content */
|
||||
assert(strstr(buffer, "testuser") != NULL);
|
||||
assert(strstr(buffer, "Hello World") != NULL);
|
||||
}
|
||||
|
||||
TEST(message_format_long_content) {
|
||||
message_t msg;
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "user");
|
||||
|
||||
/* Create long message */
|
||||
memset(msg.content, 'A', MAX_MESSAGE_LEN - 1);
|
||||
msg.content[MAX_MESSAGE_LEN - 1] = '\0';
|
||||
|
||||
char buffer[2048];
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
|
||||
/* Should not overflow */
|
||||
assert(strlen(buffer) < sizeof(buffer));
|
||||
}
|
||||
|
||||
TEST(message_format_unicode) {
|
||||
message_t msg;
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "用户");
|
||||
strcpy(msg.content, "你好世界");
|
||||
|
||||
char buffer[512];
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
|
||||
assert(strstr(buffer, "用户") != NULL);
|
||||
assert(strstr(buffer, "你好世界") != NULL);
|
||||
}
|
||||
|
||||
TEST(message_format_width_limits) {
|
||||
message_t msg;
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "user");
|
||||
strcpy(msg.content, "Test");
|
||||
|
||||
char buffer[512];
|
||||
|
||||
/* Test various widths */
|
||||
message_format(&msg, buffer, sizeof(buffer), 40);
|
||||
assert(strlen(buffer) < 512);
|
||||
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) < 512);
|
||||
|
||||
message_format(&msg, buffer, sizeof(buffer), 120);
|
||||
assert(strlen(buffer) < 512);
|
||||
}
|
||||
|
||||
/* Test message save */
|
||||
TEST(message_save_basic) {
|
||||
cleanup_test_log();
|
||||
|
||||
/* This is harder to test without modifying LOG_FILE constant */
|
||||
/* For now, document expected behavior */
|
||||
message_t msg;
|
||||
msg.timestamp = time(NULL);
|
||||
strcpy(msg.username, "testuser");
|
||||
strcpy(msg.content, "Test message");
|
||||
|
||||
/* Would save to LOG_FILE */
|
||||
/* int ret = message_save(&msg); */
|
||||
/* assert(ret == 0); */
|
||||
|
||||
cleanup_test_log();
|
||||
}
|
||||
|
||||
/* Test edge cases */
|
||||
TEST(message_edge_cases) {
|
||||
message_t msg;
|
||||
char buffer[512];
|
||||
|
||||
/* Empty username */
|
||||
msg.timestamp = 1234567890;
|
||||
msg.username[0] = '\0';
|
||||
strcpy(msg.content, "Test");
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) > 0);
|
||||
|
||||
/* Empty content */
|
||||
strcpy(msg.username, "user");
|
||||
msg.content[0] = '\0';
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) > 0);
|
||||
|
||||
/* Maximum length username */
|
||||
memset(msg.username, 'A', MAX_USERNAME_LEN - 1);
|
||||
msg.username[MAX_USERNAME_LEN - 1] = '\0';
|
||||
strcpy(msg.content, "Test");
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) < sizeof(buffer));
|
||||
|
||||
/* Maximum length content */
|
||||
strcpy(msg.username, "user");
|
||||
memset(msg.content, 'B', MAX_MESSAGE_LEN - 1);
|
||||
msg.content[MAX_MESSAGE_LEN - 1] = '\0';
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
/* Should handle gracefully */
|
||||
}
|
||||
|
||||
TEST(message_special_characters) {
|
||||
message_t msg;
|
||||
char buffer[512];
|
||||
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "user<test>");
|
||||
strcpy(msg.content, "Message with\nnewline\tand\ttabs");
|
||||
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
|
||||
/* Should not crash or overflow */
|
||||
assert(strlen(buffer) < sizeof(buffer));
|
||||
}
|
||||
|
||||
/* Test buffer safety */
|
||||
TEST(message_buffer_safety) {
|
||||
message_t msg;
|
||||
char small_buffer[16];
|
||||
|
||||
msg.timestamp = 1234567890;
|
||||
strcpy(msg.username, "verylongusername");
|
||||
strcpy(msg.content, "Very long message content that exceeds buffer");
|
||||
|
||||
/* Should not overflow even with small buffer */
|
||||
message_format(&msg, small_buffer, sizeof(small_buffer), 80);
|
||||
assert(strlen(small_buffer) < sizeof(small_buffer));
|
||||
}
|
||||
|
||||
/* Test timestamp handling */
|
||||
TEST(message_timestamp_formats) {
|
||||
message_t msg;
|
||||
char buffer[512];
|
||||
|
||||
strcpy(msg.username, "user");
|
||||
strcpy(msg.content, "Test");
|
||||
|
||||
/* Test various timestamps */
|
||||
msg.timestamp = 0; /* Epoch */
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) > 0);
|
||||
|
||||
msg.timestamp = time(NULL); /* Current time */
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) > 0);
|
||||
|
||||
msg.timestamp = 2147483647; /* Max 32-bit timestamp */
|
||||
message_format(&msg, buffer, sizeof(buffer), 80);
|
||||
assert(strlen(buffer) > 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running message unit tests...\n\n");
|
||||
|
||||
RUN_TEST(message_init);
|
||||
RUN_TEST(message_load_empty);
|
||||
RUN_TEST(message_format_basic);
|
||||
RUN_TEST(message_format_long_content);
|
||||
RUN_TEST(message_format_unicode);
|
||||
RUN_TEST(message_format_width_limits);
|
||||
RUN_TEST(message_save_basic);
|
||||
RUN_TEST(message_edge_cases);
|
||||
RUN_TEST(message_special_characters);
|
||||
RUN_TEST(message_buffer_safety);
|
||||
RUN_TEST(message_timestamp_formats);
|
||||
|
||||
cleanup_test_log();
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
}
|
||||
BIN
tests/unit/test_utf8
Executable file
BIN
tests/unit/test_utf8
Executable file
Binary file not shown.
239
tests/unit/test_utf8.c
Normal file
239
tests/unit/test_utf8.c
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/* Unit tests for UTF-8 functions */
|
||||
#include "../../include/utf8.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define TEST(name) static void test_##name()
|
||||
#define RUN_TEST(name) do { \
|
||||
printf("Running %s... ", #name); \
|
||||
test_##name(); \
|
||||
printf("✓\n"); \
|
||||
tests_passed++; \
|
||||
} while(0)
|
||||
|
||||
static int tests_passed = 0;
|
||||
|
||||
/* Test UTF-8 byte length detection */
|
||||
TEST(utf8_byte_length_ascii) {
|
||||
assert(utf8_byte_length('A') == 1);
|
||||
assert(utf8_byte_length('z') == 1);
|
||||
assert(utf8_byte_length('0') == 1);
|
||||
}
|
||||
|
||||
TEST(utf8_byte_length_multibyte) {
|
||||
assert(utf8_byte_length(0xC3) == 2); /* é first byte */
|
||||
assert(utf8_byte_length(0xE4) == 3); /* 中 first byte */
|
||||
assert(utf8_byte_length(0xF0) == 4); /* 𝕏 first byte */
|
||||
}
|
||||
|
||||
TEST(utf8_byte_length_invalid) {
|
||||
assert(utf8_byte_length(0xFF) == 1); /* Invalid UTF-8 */
|
||||
assert(utf8_byte_length(0x80) == 1); /* Continuation byte */
|
||||
}
|
||||
|
||||
/* Test UTF-8 decoding */
|
||||
TEST(utf8_decode_ascii) {
|
||||
int bytes_read;
|
||||
assert(utf8_decode("A", &bytes_read) == 'A');
|
||||
assert(bytes_read == 1);
|
||||
}
|
||||
|
||||
TEST(utf8_decode_2byte) {
|
||||
int bytes_read;
|
||||
/* é = U+00E9 = 0xC3 0xA9 */
|
||||
const char *e_acute = "\xC3\xA9";
|
||||
uint32_t codepoint = utf8_decode(e_acute, &bytes_read);
|
||||
assert(codepoint == 0x00E9);
|
||||
assert(bytes_read == 2);
|
||||
}
|
||||
|
||||
TEST(utf8_decode_3byte) {
|
||||
int bytes_read;
|
||||
/* 中 = U+4E2D = 0xE4 0xB8 0xAD */
|
||||
const char *zhong = "\xE4\xB8\xAD";
|
||||
uint32_t codepoint = utf8_decode(zhong, &bytes_read);
|
||||
assert(codepoint == 0x4E2D);
|
||||
assert(bytes_read == 3);
|
||||
}
|
||||
|
||||
TEST(utf8_decode_4byte) {
|
||||
int bytes_read;
|
||||
/* 𝕏 = U+1D54F = 0xF0 0x9D 0x95 0x8F */
|
||||
const char *math_x = "\xF0\x9D\x95\x8F";
|
||||
uint32_t codepoint = utf8_decode(math_x, &bytes_read);
|
||||
assert(codepoint == 0x1D54F);
|
||||
assert(bytes_read == 4);
|
||||
}
|
||||
|
||||
/* Test character width calculation */
|
||||
TEST(utf8_char_width_ascii) {
|
||||
assert(utf8_char_width('A') == 1);
|
||||
assert(utf8_char_width(' ') == 1);
|
||||
assert(utf8_char_width('0') == 1);
|
||||
}
|
||||
|
||||
TEST(utf8_char_width_cjk) {
|
||||
assert(utf8_char_width(0x4E2D) == 2); /* 中 */
|
||||
assert(utf8_char_width(0x6587) == 2); /* 文 */
|
||||
assert(utf8_char_width(0x5B57) == 2); /* 字 */
|
||||
}
|
||||
|
||||
TEST(utf8_char_width_hangul) {
|
||||
assert(utf8_char_width(0xAC00) == 2); /* 가 */
|
||||
assert(utf8_char_width(0xD7A3) == 2); /* 힣 */
|
||||
}
|
||||
|
||||
TEST(utf8_char_width_hiragana) {
|
||||
assert(utf8_char_width(0x3042) == 2); /* あ */
|
||||
assert(utf8_char_width(0x3093) == 2); /* ん */
|
||||
}
|
||||
|
||||
TEST(utf8_char_width_katakana) {
|
||||
assert(utf8_char_width(0x30A2) == 2); /* ア */
|
||||
assert(utf8_char_width(0x30F3) == 2); /* ン */
|
||||
}
|
||||
|
||||
/* Test string width calculation */
|
||||
TEST(utf8_string_width_ascii) {
|
||||
assert(utf8_string_width("Hello") == 5);
|
||||
assert(utf8_string_width("") == 0);
|
||||
assert(utf8_string_width("Test123") == 7);
|
||||
}
|
||||
|
||||
TEST(utf8_string_width_mixed) {
|
||||
/* "Hello世界" = 5 ASCII + 2*2 CJK = 9 */
|
||||
assert(utf8_string_width("Hello世界") == 9);
|
||||
|
||||
/* "测试Test" = 2*2 CJK + 4 ASCII = 8 */
|
||||
assert(utf8_string_width("测试Test") == 8);
|
||||
}
|
||||
|
||||
TEST(utf8_string_width_cjk_only) {
|
||||
/* "中文字符" = 4 * 2 = 8 */
|
||||
assert(utf8_string_width("中文字符") == 8);
|
||||
}
|
||||
|
||||
/* Test backspace handling */
|
||||
TEST(utf8_remove_last_char) {
|
||||
char buffer[256];
|
||||
|
||||
/* Test ASCII */
|
||||
strcpy(buffer, "Hello");
|
||||
utf8_remove_last_char(buffer);
|
||||
assert(strcmp(buffer, "Hell") == 0);
|
||||
|
||||
/* Test empty string */
|
||||
strcpy(buffer, "");
|
||||
utf8_remove_last_char(buffer);
|
||||
assert(strcmp(buffer, "") == 0);
|
||||
|
||||
/* Test single char */
|
||||
strcpy(buffer, "A");
|
||||
utf8_remove_last_char(buffer);
|
||||
assert(strcmp(buffer, "") == 0);
|
||||
}
|
||||
|
||||
TEST(utf8_remove_last_char_multibyte) {
|
||||
char buffer[256];
|
||||
|
||||
/* Test 2-byte UTF-8 */
|
||||
strcpy(buffer, "café");
|
||||
utf8_remove_last_char(buffer);
|
||||
assert(strcmp(buffer, "caf") == 0);
|
||||
|
||||
/* Test 3-byte UTF-8 (CJK) */
|
||||
strcpy(buffer, "你好");
|
||||
utf8_remove_last_char(buffer);
|
||||
assert(strcmp(buffer, "你") == 0);
|
||||
}
|
||||
|
||||
/* Test word removal (Ctrl+W) */
|
||||
TEST(utf8_remove_last_word) {
|
||||
char buffer[256];
|
||||
|
||||
/* Test simple case */
|
||||
strcpy(buffer, "hello world");
|
||||
utf8_remove_last_word(buffer);
|
||||
assert(strcmp(buffer, "hello ") == 0);
|
||||
|
||||
/* Test multiple words */
|
||||
strcpy(buffer, "one two three");
|
||||
utf8_remove_last_word(buffer);
|
||||
assert(strcmp(buffer, "one two ") == 0);
|
||||
|
||||
/* Test trailing spaces */
|
||||
strcpy(buffer, "hello ");
|
||||
utf8_remove_last_word(buffer);
|
||||
assert(strcmp(buffer, "") == 0);
|
||||
|
||||
/* Test single word */
|
||||
strcpy(buffer, "word");
|
||||
utf8_remove_last_word(buffer);
|
||||
assert(strcmp(buffer, "") == 0);
|
||||
|
||||
/* Test empty string */
|
||||
strcpy(buffer, "");
|
||||
utf8_remove_last_word(buffer);
|
||||
assert(strcmp(buffer, "") == 0);
|
||||
}
|
||||
|
||||
/* Test input validation */
|
||||
TEST(utf8_is_valid_sequence) {
|
||||
/* Valid sequences */
|
||||
assert(utf8_is_valid_sequence("A", 1) == true);
|
||||
assert(utf8_is_valid_sequence("\xC3\xA9", 2) == true); /* é */
|
||||
assert(utf8_is_valid_sequence("\xE4\xB8\xAD", 3) == true); /* 中 */
|
||||
|
||||
/* Invalid sequences */
|
||||
assert(utf8_is_valid_sequence("\xFF", 1) == false); /* Invalid start */
|
||||
assert(utf8_is_valid_sequence("\xC3\xFF", 2) == false); /* Invalid continuation */
|
||||
|
||||
/* Invalid lengths */
|
||||
assert(utf8_is_valid_sequence("", 0) == false);
|
||||
assert(utf8_is_valid_sequence("ABCDE", 5) == false); /* Too long */
|
||||
assert(utf8_is_valid_sequence(NULL, 1) == false);
|
||||
}
|
||||
|
||||
/* Test boundary cases */
|
||||
TEST(utf8_boundary_cases) {
|
||||
/* Maximum valid codepoints */
|
||||
assert(utf8_char_width(0x10FFFF) == 1); /* Max Unicode codepoint */
|
||||
|
||||
/* BMP boundary */
|
||||
assert(utf8_char_width(0xFFFF) == 1);
|
||||
|
||||
/* CJK range boundaries */
|
||||
assert(utf8_char_width(0x4DFF) == 1); /* Just before CJK Extension A */
|
||||
assert(utf8_char_width(0x4E00) == 2); /* Start of CJK Unified */
|
||||
assert(utf8_char_width(0x9FFF) == 2); /* End of CJK Unified */
|
||||
assert(utf8_char_width(0xA000) == 1); /* Just after CJK Unified */
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running UTF-8 unit tests...\n\n");
|
||||
|
||||
RUN_TEST(utf8_byte_length_ascii);
|
||||
RUN_TEST(utf8_byte_length_multibyte);
|
||||
RUN_TEST(utf8_byte_length_invalid);
|
||||
RUN_TEST(utf8_decode_ascii);
|
||||
RUN_TEST(utf8_decode_2byte);
|
||||
RUN_TEST(utf8_decode_3byte);
|
||||
RUN_TEST(utf8_decode_4byte);
|
||||
RUN_TEST(utf8_char_width_ascii);
|
||||
RUN_TEST(utf8_char_width_cjk);
|
||||
RUN_TEST(utf8_char_width_hangul);
|
||||
RUN_TEST(utf8_char_width_hiragana);
|
||||
RUN_TEST(utf8_char_width_katakana);
|
||||
RUN_TEST(utf8_string_width_ascii);
|
||||
RUN_TEST(utf8_string_width_mixed);
|
||||
RUN_TEST(utf8_string_width_cjk_only);
|
||||
RUN_TEST(utf8_remove_last_char);
|
||||
RUN_TEST(utf8_remove_last_char_multibyte);
|
||||
RUN_TEST(utf8_remove_last_word);
|
||||
RUN_TEST(utf8_is_valid_sequence);
|
||||
RUN_TEST(utf8_boundary_cases);
|
||||
|
||||
printf("\n✓ All %d tests passed!\n", tests_passed);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue