From b33e9eeee1cec7983828eff9de1691ba97e15502 Mon Sep 17 00:00:00 2001 From: AnmiTaliDev Date: Fri, 3 Jul 2026 11:40:06 +0500 Subject: [PATCH] ci: Add GitHub Actions build and test workflow --- .github/workflows/ci.yml | 26 +++++++++++++ README.md | 8 ++++ meson.build | 1 + src/meson.build | 15 ++++++-- tests/meson.build | 11 ++++++ tests/test_buffer.c | 80 ++++++++++++++++++++++++++++++++++++++++ tests/test_editor.c | 64 ++++++++++++++++++++++++++++++++ tests/test_encoding.c | 24 ++++++++++++ tests/test_filetype.c | 18 +++++++++ tests/test_search.c | 35 ++++++++++++++++++ 10 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/meson.build create mode 100644 tests/test_buffer.c create mode 100644 tests/test_editor.c create mode 100644 tests/test_encoding.c create mode 100644 tests/test_filetype.c create mode 100644 tests/test_search.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f548d56 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install -y meson ninja-build gcc + + - name: Configure (release) + run: meson setup build --buildtype=release + + - name: Build + run: ninja -C build + + - name: Test + run: meson test -C build --print-errorlogs diff --git a/README.md b/README.md index f5babd3..c6459b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Daf +[![CI](https://github.com/AnmiTaliDev/daf/actions/workflows/ci.yml/badge.svg)](https://github.com/AnmiTaliDev/daf/actions/workflows/ci.yml) + Daf (Hebrew `דף` — "sheet/page") is a simple console text editor written in C. ## Planned Features @@ -17,6 +19,12 @@ ninja -C build The binary is produced at `build/src/daf`. +Run the test suite with: + +```sh +meson test -C build +``` + ## Usage ```sh diff --git a/meson.build b/meson.build index 3dc6d15..cb93c59 100644 --- a/meson.build +++ b/meson.build @@ -13,3 +13,4 @@ project( add_project_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c') subdir('src') +subdir('tests') diff --git a/src/meson.build b/src/meson.build index 64467da..9ea56c4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -daf_sources = files( +daf_lib_sources = files( 'common.c', 'terminal.c', 'encoding.c', @@ -8,11 +8,20 @@ daf_sources = files( 'render.c', 'input.c', 'editor.c', - 'main.c', +) + +daf_inc = include_directories('.') + +daf_lib = static_library( + 'daf_lib', + daf_lib_sources, + include_directories: daf_inc, ) executable( 'daf', - daf_sources, + 'main.c', + include_directories: daf_inc, + link_with: daf_lib, install: true, ) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..c6c546d --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,11 @@ +test_names = ['buffer', 'search', 'encoding', 'filetype', 'editor'] + +foreach name : test_names + test_exe = executable( + 'test_' + name, + 'test_' + name + '.c', + include_directories: daf_inc, + link_with: daf_lib, + ) + test(name, test_exe) +endforeach diff --git a/tests/test_buffer.c b/tests/test_buffer.c new file mode 100644 index 0000000..265c20c --- /dev/null +++ b/tests/test_buffer.c @@ -0,0 +1,80 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include "buffer.h" + +static void test_insert_and_delete(void) +{ + buffer_t buf; + buffer_init(&buf); + + buffer_insert_bytes(&buf, 0, 0, "hello", 5); + assert(buf.lines[0].len == 5); + assert(memcmp(buf.lines[0].chars, "hello", 5) == 0); + + buffer_delete_range(&buf, 0, 1, 0, 3); + assert(buf.lines[0].len == 3); + assert(memcmp(buf.lines[0].chars, "hlo", 3) == 0); + + buffer_free(&buf); +} + +static void test_split_and_join(void) +{ + buffer_t buf; + buffer_init(&buf); + + buffer_insert_bytes(&buf, 0, 0, "abcdef", 6); + buffer_split_line(&buf, 0, 3); + assert(buf.num_lines == 2); + assert(buf.lines[0].len == 3); + assert(memcmp(buf.lines[0].chars, "abc", 3) == 0); + assert(buf.lines[1].len == 3); + assert(memcmp(buf.lines[1].chars, "def", 3) == 0); + + buffer_join_lines(&buf, 0); + assert(buf.num_lines == 1); + assert(buf.lines[0].len == 6); + assert(memcmp(buf.lines[0].chars, "abcdef", 6) == 0); + + buffer_free(&buf); +} + +static void test_load_save_roundtrip(void) +{ + const char *path = "test_buffer_roundtrip.tmp"; + + buffer_t buf; + buffer_init(&buf); + buffer_insert_bytes(&buf, 0, 0, "line one", 8); + buffer_split_line(&buf, 0, 8); + buffer_insert_bytes(&buf, 1, 0, "line two", 8); + + assert(buffer_save_as(&buf, path) == 0); + buffer_free(&buf); + + buffer_t loaded; + buffer_init(&loaded); + assert(buffer_load(&loaded, path) == 0); + assert(loaded.num_lines == 2); + assert(memcmp(loaded.lines[0].chars, "line one", 8) == 0); + assert(memcmp(loaded.lines[1].chars, "line two", 8) == 0); + assert(loaded.dirty == 0); + assert(loaded.trailing_newline == 1); + + buffer_free(&loaded); + remove(path); +} + +int main(void) +{ + test_insert_and_delete(); + test_split_and_join(); + test_load_save_roundtrip(); + return 0; +} diff --git a/tests/test_editor.c b/tests/test_editor.c new file mode 100644 index 0000000..37ffd8d --- /dev/null +++ b/tests/test_editor.c @@ -0,0 +1,64 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "editor.h" + +static void set_line(editor_t *ed, size_t row, const char *text) +{ + buffer_insert_bytes(&ed->buf, row, 0, text, strlen(text)); +} + +static void test_smart_home(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, " int x;"); + + ed.cx = 10; + editor_move_home(&ed, false); + assert(ed.cx == 4); + + editor_move_home(&ed, false); + assert(ed.cx == 0); + + editor_free(&ed); +} + +static void test_selection_and_tab_indent(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, " int x;"); + + ed.cy = 0; + ed.cx = 4; + editor_move_right(&ed, true); + editor_move_right(&ed, true); + editor_move_right(&ed, true); + + assert(editor_has_selection(&ed)); + size_t r1; + size_t c1; + size_t r2; + size_t c2; + editor_selection_range(&ed, &r1, &c1, &r2, &c2); + assert(r1 == 0 && c1 == 4 && r2 == 0 && c2 == 7); + + editor_handle_tab(&ed, false); + editor_selection_range(&ed, &r1, &c1, &r2, &c2); + assert(c1 == 5 && c2 == 8); + assert(memcmp(ed.buf.lines[0].chars + c1, "int", 3) == 0); + + editor_free(&ed); +} + +int main(void) +{ + test_smart_home(); + test_selection_and_tab_indent(); + return 0; +} diff --git a/tests/test_encoding.c b/tests/test_encoding.c new file mode 100644 index 0000000..edaf4d9 --- /dev/null +++ b/tests/test_encoding.c @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "encoding.h" + +int main(void) +{ + const unsigned char ascii[] = "hello world"; + assert(encoding_detect(ascii, sizeof(ascii) - 1) == ENCODING_ASCII); + + const unsigned char utf8[] = {'h', 'i', ' ', 0xD0, 0xBF, 0xD1, 0x80}; + assert(encoding_detect(utf8, sizeof(utf8)) == ENCODING_UTF8); + + const unsigned char bom[] = {0xEF, 0xBB, 0xBF, 'h', 'i'}; + assert(encoding_detect(bom, sizeof(bom)) == ENCODING_UTF8_BOM); + + const unsigned char invalid[] = {0xFF, 0xFE}; + assert(encoding_detect(invalid, sizeof(invalid)) == ENCODING_UNKNOWN); + + return 0; +} diff --git a/tests/test_filetype.c b/tests/test_filetype.c new file mode 100644 index 0000000..1b3a1f5 --- /dev/null +++ b/tests/test_filetype.c @@ -0,0 +1,18 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "filetype.h" + +int main(void) +{ + assert(strcmp(filetype_detect("main.c"), "C") == 0); + assert(strcmp(filetype_detect("app.py"), "Python") == 0); + assert(strcmp(filetype_detect("path/to/file.MD"), "Markdown") == 0); + assert(strcmp(filetype_detect("noext"), "Plain Text") == 0); + assert(strcmp(filetype_detect(".hidden"), "Plain Text") == 0); + return 0; +} diff --git a/tests/test_search.c b/tests/test_search.c new file mode 100644 index 0000000..5e32260 --- /dev/null +++ b/tests/test_search.c @@ -0,0 +1,35 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "buffer.h" +#include "search.h" + +int main(void) +{ + buffer_t buf; + buffer_init(&buf); + buffer_insert_bytes(&buf, 0, 0, "foo bar", 7); + buffer_split_line(&buf, 0, 7); + buffer_insert_bytes(&buf, 1, 0, "baz foo", 7); + + size_t row = 0; + size_t col = 0; + + assert(search_find_forward(&buf, 0, 0, "foo", &row, &col)); + assert(row == 0 && col == 0); + + assert(search_find_forward(&buf, 0, 1, "foo", &row, &col)); + assert(row == 1 && col == 4); + + assert(search_find_forward(&buf, 1, 5, "foo", &row, &col)); + assert(row == 0 && col == 0); + + assert(!search_find_forward(&buf, 0, 0, "missing", &row, &col)); + assert(!search_find_forward(&buf, 0, 0, "", &row, &col)); + + buffer_free(&buf); + return 0; +}