From b179e6f6cfe244242ef61df87c48348bfb87d7c9 Mon Sep 17 00:00:00 2001 From: TapTap Date: Sun, 19 Jul 2026 19:19:15 +0200 Subject: [PATCH] testing: add comprehensive test suite for test-driven development Add unit tests for previously untested modules (protocol, data, metadata, file, glob), robustness tests for deserialization of malformed inputs, thread safety stress tests, property-based roundtrip tests, and 6 fuzz targets. Update CI with CTest integration, coverage reporting, and valgrind memory checking. New test files: - test_data.c: Data type lifecycle (5 tests) - test_protocol.c: Protocol I/O roundtrips and error paths (9 tests) - test_metadata.c: Metadata serialization roundtrips (6 tests) - test_file.c: File operations and send/receive (12 tests) - test_glob.c: Glob pattern matching (10 tests) - test_robustness.c: Malformed input handling for chunk/delta/protocol (11 tests) - test_stress.c: MPMC queue stress, backpressure, rapid create/destroy (3 tests) - test_property.c: Compress, delta, chunk, glob roundtrip properties (4 tests) - tests/fuzz/: 6 libFuzzer targets for deserialization functions Extended existing tests: - test_config.c: config_send/config_receive roundtrip via fork+pipe - test_shared_utils.c: mkdir_r, glob_match, delete_extras Infrastructure: - CTest integration in CMakeLists.txt - Coverage support (-DENABLE_COVERAGE=ON) - Fuzz target support (-DENABLE_FUZZ=ON) - CI: coverage, valgrind, address sanitizer jobs - Fixed MPMC stress test race condition (cnd_signal -> cnd_broadcast) --- .gitea/workflows/ci.yaml | 46 ++- CMakeLists.txt | 38 ++- tests/fuzz/fuzz_chunk_deserialize.c | 25 ++ tests/fuzz/fuzz_compress_decompress.c | 30 ++ tests/fuzz/fuzz_delta_deserialize.c | 25 ++ tests/fuzz/fuzz_delta_signature_deserialize.c | 25 ++ tests/fuzz/fuzz_glob_match.c | 32 ++ tests/fuzz/fuzz_metadata_from_buf.c | 22 ++ tests/runner.c | 16 + tests/test_data.c | 50 ++++ tests/test_data.h | 6 + tests/test_file.c | 276 ++++++++++++++++++ tests/test_file.h | 6 + tests/test_glob.c | 63 ++++ tests/test_glob.h | 6 + tests/test_metadata.c | 141 +++++++++ tests/test_metadata.h | 6 + tests/test_property.c | 137 +++++++++ tests/test_property.h | 6 + tests/test_protocol.c | 182 ++++++++++++ tests/test_protocol.h | 6 + tests/test_robustness.c | 185 ++++++++++++ tests/test_robustness.h | 6 + tests/test_stress.c | 226 ++++++++++++++ tests/test_stress.h | 6 + 25 files changed, 1560 insertions(+), 7 deletions(-) create mode 100644 tests/fuzz/fuzz_chunk_deserialize.c create mode 100644 tests/fuzz/fuzz_compress_decompress.c create mode 100644 tests/fuzz/fuzz_delta_deserialize.c create mode 100644 tests/fuzz/fuzz_delta_signature_deserialize.c create mode 100644 tests/fuzz/fuzz_glob_match.c create mode 100644 tests/fuzz/fuzz_metadata_from_buf.c create mode 100644 tests/test_data.c create mode 100644 tests/test_data.h create mode 100644 tests/test_file.c create mode 100644 tests/test_file.h create mode 100644 tests/test_glob.c create mode 100644 tests/test_glob.h create mode 100644 tests/test_metadata.c create mode 100644 tests/test_metadata.h create mode 100644 tests/test_property.c create mode 100644 tests/test_property.h create mode 100644 tests/test_protocol.c create mode 100644 tests/test_protocol.h create mode 100644 tests/test_robustness.c create mode 100644 tests/test_robustness.h create mode 100644 tests/test_stress.c create mode 100644 tests/test_stress.h diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index dafb4be..bc14406 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -31,10 +31,10 @@ jobs: run: cmake --build build -j$(nproc) - name: Unit Tests - run: ./build/tests + run: ctest --test-dir build --output-on-failure -j$(nproc) - name: Integration Tests - run: python3 -m pytest tests/ -v --tb=short + run: python3 -m pytest tests/integration/ -v --tb=short sanitizers: runs-on: ubuntu-latest @@ -54,4 +54,44 @@ jobs: run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc) - name: Unit Tests - run: ./build-${{ matrix.sanitizer }}/tests + run: ctest --test-dir build-${{ matrix.sanitizer }} --output-on-failure + + coverage: + runs-on: ubuntu-latest + container: gitea.tap-tap.win/taptap/fastsync-ci:v7 + needs: lint + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -S . -DENABLE_COVERAGE=ON -DSTRICT_WARNINGS=ON + + - name: Build + run: cmake --build build -j$(nproc) + + - name: Unit Tests + run: ctest --test-dir build --output-on-failure + + - name: Coverage Report + run: | + lcov --capture --directory build --output-file coverage.info --rc lcov_branch_coverage=1 + lcov --remove coverage.info '/usr/*' '*/tests/*' '*/xxhash/*' --output-file coverage.info + lcov --list coverage.info + + valgrind: + runs-on: ubuntu-latest + container: gitea.tap-tap.win/taptap/fastsync-ci:v7 + needs: lint + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -S . -DSTRICT_WARNINGS=ON + + - name: Build + run: cmake --build build -j$(nproc) + + - name: Valgrind Memcheck + run: valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./build/tests diff --git a/CMakeLists.txt b/CMakeLists.txt index e97d020..0aa05bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,13 @@ if(STRICT_WARNINGS) add_compile_options(-Wextra -Wpedantic -Werror) endif() +# --- Coverage option --- +option(ENABLE_COVERAGE "Enable gcov coverage" OFF) +if(ENABLE_COVERAGE) + add_compile_options(--coverage -fprofile-arcs -ftest-coverage -O0 -g) + add_link_options(--coverage) +endif() + include(FetchContent) FetchContent_Declare( xxhash @@ -50,8 +57,8 @@ find_package(OpenSSL REQUIRED) file(GLOB SHARED_SRCS "src/shared/*.c") file(GLOB SERVER_SRCS "src/server/*.c") file(GLOB CLIENT_SRCS "src/client/*.c") -file(GLOB TEST_SRCS "tests/*.c") +# --- Main executables --- add_executable(server ${SERVER_SRCS} ${SHARED_SRCS}) target_include_directories(server PRIVATE src/shared src/server src/client) target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) @@ -60,7 +67,30 @@ add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS}) target_include_directories(client PRIVATE src/shared src/server src/client) target_link_libraries(client PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) -add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} src/client/scanner.c) -target_include_directories(tests PRIVATE tests src/shared src/server src/client) -target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) +# --- Testing --- +enable_testing() +# Common test libraries +set(TEST_LIBS Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) +set(TEST_INCLUDES tests src/shared src/server src/client) + +# Monolithic test binary (backward compatible) +file(GLOB TEST_SRCS "tests/test_*.c" "tests/runner.c") +add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} src/client/scanner.c) +target_include_directories(tests PRIVATE ${TEST_INCLUDES}) +target_link_libraries(tests PRIVATE ${TEST_LIBS}) +add_test(NAME unit_all COMMAND tests) + +# --- Fuzz targets (requires clang) --- +option(ENABLE_FUZZ "Build fuzz targets (requires clang)" OFF) +if(ENABLE_FUZZ) + file(GLOB FUZZ_SRCS "tests/fuzz/*.c") + foreach(FUZZ_SRC ${FUZZ_SRCS}) + get_filename_component(FUZZ_NAME ${FUZZ_SRC} NAME_WE) + add_executable(${FUZZ_NAME} ${FUZZ_SRC} ${SHARED_SRCS}) + target_include_directories(${FUZZ_NAME} PRIVATE ${TEST_INCLUDES}) + target_compile_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer) + target_link_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined) + target_link_libraries(${FUZZ_NAME} PRIVATE ${TEST_LIBS}) + endforeach() +endif() diff --git a/tests/fuzz/fuzz_chunk_deserialize.c b/tests/fuzz/fuzz_chunk_deserialize.c new file mode 100644 index 0000000..67bba49 --- /dev/null +++ b/tests/fuzz/fuzz_chunk_deserialize.c @@ -0,0 +1,25 @@ +#include "chunk.h" +#include "data.h" +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) + return 0; + + void* buf = malloc(size); + if (!buf) + return 0; + memcpy(buf, data, size); + + Data* d = data_create(buf, size); + if (!d) + return 0; + + Chunk* chunk = chunk_deserialize(d, false); + if (chunk) + chunk_destroy(chunk); + + data_destroy(d); + return 0; +} diff --git a/tests/fuzz/fuzz_compress_decompress.c b/tests/fuzz/fuzz_compress_decompress.c new file mode 100644 index 0000000..ef9bdd4 --- /dev/null +++ b/tests/fuzz/fuzz_compress_decompress.c @@ -0,0 +1,30 @@ +#include "compression.h" +#include "data.h" +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) + return 0; + + void* buf = malloc(size); + if (!buf) + return 0; + memcpy(buf, data, size); + + Data* d = data_create(buf, size); + if (!d) + return 0; + + Data* compressed = data_compress(d, 3); + if (compressed) { + Data* decompressed = data_decompress(compressed); + if (decompressed) { + data_destroy(decompressed); + } + data_destroy(compressed); + } + + data_destroy(d); + return 0; +} diff --git a/tests/fuzz/fuzz_delta_deserialize.c b/tests/fuzz/fuzz_delta_deserialize.c new file mode 100644 index 0000000..dcbaf6a --- /dev/null +++ b/tests/fuzz/fuzz_delta_deserialize.c @@ -0,0 +1,25 @@ +#include "delta.h" +#include "data.h" +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) + return 0; + + void* buf = malloc(size); + if (!buf) + return 0; + memcpy(buf, data, size); + + Data* d = data_create(buf, size); + if (!d) + return 0; + + Delta* delta = delta_deserialize(d); + if (delta) + delta_destroy(delta); + + data_destroy(d); + return 0; +} diff --git a/tests/fuzz/fuzz_delta_signature_deserialize.c b/tests/fuzz/fuzz_delta_signature_deserialize.c new file mode 100644 index 0000000..03122a1 --- /dev/null +++ b/tests/fuzz/fuzz_delta_signature_deserialize.c @@ -0,0 +1,25 @@ +#include "delta.h" +#include "data.h" +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) + return 0; + + void* buf = malloc(size); + if (!buf) + return 0; + memcpy(buf, data, size); + + Data* d = data_create(buf, size); + if (!d) + return 0; + + DeltaSignature* sig = delta_signature_deserialize(d); + if (sig) + delta_signature_destroy(sig); + + data_destroy(d); + return 0; +} diff --git a/tests/fuzz/fuzz_glob_match.c b/tests/fuzz/fuzz_glob_match.c new file mode 100644 index 0000000..e58870b --- /dev/null +++ b/tests/fuzz/fuzz_glob_match.c @@ -0,0 +1,32 @@ +#include "utils.h" +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < 2) + return 0; + + // Split input into pattern and string at the midpoint + size_t mid = size / 2; + + char* pattern = malloc(mid + 1); + char* str = malloc(size - mid + 1); + if (!pattern || !str) { + free(pattern); + free(str); + return 0; + } + + memcpy(pattern, data, mid); + pattern[mid] = '\0'; + + memcpy(str, data + mid, size - mid); + str[size - mid] = '\0'; + + glob_match(pattern, str); + + free(pattern); + free(str); + return 0; +} diff --git a/tests/fuzz/fuzz_metadata_from_buf.c b/tests/fuzz/fuzz_metadata_from_buf.c new file mode 100644 index 0000000..5c63dc2 --- /dev/null +++ b/tests/fuzz/fuzz_metadata_from_buf.c @@ -0,0 +1,22 @@ +#include "metadata.h" +#include "file.h" +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < sizeof(int)) + return 0; + + char* buf = malloc(size); + if (!buf) + return 0; + memcpy(buf, data, size); + + char* original_buf = buf; + FileMetadata* m = metadata_from_buf(&buf); + if (m) + free(m); + + free(original_buf); + return 0; +} diff --git a/tests/runner.c b/tests/runner.c index 775bc9c..6ae6c12 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -2,10 +2,18 @@ #include "test_chunk.h" #include "test_compression.h" #include "test_config.h" +#include "test_data.h" #include "test_delta.h" +#include "test_file.h" +#include "test_glob.h" +#include "test_metadata.h" +#include "test_property.h" +#include "test_protocol.h" #include "test_queue.h" +#include "test_robustness.h" #include "test_scanner.h" #include "test_shared_utils.h" +#include "test_stress.h" #include "test_utils.h" #include @@ -25,6 +33,14 @@ int main() { RUN_TEST(test_compression); RUN_TEST(test_scanner); RUN_TEST(test_delta); + RUN_TEST(test_data); + RUN_TEST(test_protocol); + RUN_TEST(test_metadata); + RUN_TEST(test_glob); + RUN_TEST(test_file); + RUN_TEST(test_robustness); + RUN_TEST(test_stress); + RUN_TEST(test_property); printf("\n\033[1;36m=== TEST SUMMARY ===\033[0m\n"); printf("Total Tests Run: %d\n", tests_run); diff --git a/tests/test_data.c b/tests/test_data.c new file mode 100644 index 0000000..33c4888 --- /dev/null +++ b/tests/test_data.c @@ -0,0 +1,50 @@ +#include "data.h" +#include "test_utils.h" +#include +#include + +static void test_data_create() { + char* buf = malloc(6); + EXPECT_NOT_NULL(buf); + memcpy(buf, "hello", 6); + Data* d = data_create(buf, 6); + EXPECT_NOT_NULL(d); + EXPECT_NOT_NULL(d->data); + EXPECT_TRUE(d->data == buf); + EXPECT_EQ_INT((int)d->size, 6); + data_destroy(d); +} + +static void test_data_create_empty() { + Data* d = data_create_empty(256); + EXPECT_NOT_NULL(d); + EXPECT_NOT_NULL(d->data); + EXPECT_EQ_INT((int)d->size, 256); + data_destroy(d); +} + +static void test_data_create_reserve() { + Data* d = data_create_reserve(1024); + EXPECT_NOT_NULL(d); + EXPECT_NULL(d->data); + EXPECT_EQ_INT((int)d->size, 1024); + data_destroy(d); +} + +static void test_data_destroy_null() { + data_destroy(NULL); +} + +static void test_data_destroy_normal() { + Data* d = data_create_empty(128); + EXPECT_NOT_NULL(d); + data_destroy(d); +} + +void test_data() { + test_data_create(); + test_data_create_empty(); + test_data_create_reserve(); + test_data_destroy_null(); + test_data_destroy_normal(); +} diff --git a/tests/test_data.h b/tests/test_data.h new file mode 100644 index 0000000..ceb6918 --- /dev/null +++ b/tests/test_data.h @@ -0,0 +1,6 @@ +#ifndef TEST_DATA_H +#define TEST_DATA_H + +void test_data(); + +#endif diff --git a/tests/test_file.c b/tests/test_file.c new file mode 100644 index 0000000..b195592 --- /dev/null +++ b/tests/test_file.c @@ -0,0 +1,276 @@ +#include "test_file.h" +#include "file.h" +#include "data.h" +#include "utils.h" +#include "protocol.h" +#include "test_utils.h" +#include +#include +#include +#include + +static void test_file_create() { + File* f = file_create("test_file_create.txt"); + EXPECT_NOT_NULL(f); + EXPECT_NOT_NULL(f->path); + EXPECT_EQ_STR(f->path, "test_file_create.txt"); + EXPECT_NOT_NULL(f->data); + EXPECT_NULL(f->data->data); + EXPECT_EQ_INT((int)f->data->size, 0); + EXPECT_NULL(f->metadata); + file_destroy(f); +} + +static void test_file_destroy_null() { + file_destroy(NULL); +} + +static void test_file_destroy_normal() { + File* f = file_create("test_destroy.txt"); + EXPECT_NOT_NULL(f); + file_destroy(f); +} + +static void test_file_load_data() { + const char* content = "Hello Load Test"; + EXPECT_TRUE(to_disk("test_file_load_data.txt", content, strlen(content))); + + struct stat st; + EXPECT_EQ_INT(stat("test_file_load_data.txt", &st), 0); + + File* f = file_create("test_file_load_data.txt"); + EXPECT_NOT_NULL(f); + f->data->size = st.st_size; + + EXPECT_TRUE(file_load_data(f)); + EXPECT_NOT_NULL(f->data->data); + EXPECT_EQ_INT((int)f->data->size, (int)st.st_size); + EXPECT_EQ_INT(memcmp(f->data->data, content, strlen(content)), 0); + + file_destroy(f); + unlink("test_file_load_data.txt"); +} + +static void test_file_load_data_missing_file() { + File* f = file_create("nonexistent_test_file_xyz.txt"); + EXPECT_NOT_NULL(f); + f->data->size = 10; + EXPECT_FALSE(file_load_data(f)); + file_destroy(f); +} + +static void test_file_save_to_disk() { + File* f = file_create("saved_file.txt"); + EXPECT_NOT_NULL(f); + const char* content = "Save to disk content"; + f->data->data = malloc(strlen(content)); + EXPECT_NOT_NULL(f->data->data); + memcpy(f->data->data, content, strlen(content)); + f->data->size = strlen(content); + + EXPECT_TRUE(file_save_to_disk("test_save_tmp", f)); + + struct stat st; + EXPECT_EQ_INT(stat("test_save_tmp/saved_file.txt", &st), 0); + + FILE* fp = fopen("test_save_tmp/saved_file.txt", "rb"); + EXPECT_NOT_NULL(fp); + char buf[100]; + size_t nread = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + EXPECT_EQ_INT((int)nread, (int)strlen(content)); + EXPECT_EQ_INT(memcmp(buf, content, strlen(content)), 0); + + file_destroy(f); + unlink("test_save_tmp/saved_file.txt"); + rmdir("test_save_tmp"); +} + +static void test_to_disk_basic() { + const char* content = "Basic to_disk test"; + EXPECT_TRUE(to_disk("test_to_disk_basic.txt", content, strlen(content))); + + struct stat st; + EXPECT_EQ_INT(stat("test_to_disk_basic.txt", &st), 0); + EXPECT_EQ_INT((int)st.st_size, (int)strlen(content)); + + FILE* fp = fopen("test_to_disk_basic.txt", "rb"); + EXPECT_NOT_NULL(fp); + char buf[100]; + size_t nread = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + EXPECT_EQ_INT((int)nread, (int)strlen(content)); + EXPECT_EQ_INT(memcmp(buf, content, strlen(content)), 0); + + unlink("test_to_disk_basic.txt"); +} + +static void test_to_disk_creates_dirs() { + const char* content = "Nested dir test"; + EXPECT_TRUE(to_disk("test_nested_tmp/nested/file.txt", content, strlen(content))); + + struct stat st; + EXPECT_EQ_INT(stat("test_nested_tmp/nested/file.txt", &st), 0); + + FILE* fp = fopen("test_nested_tmp/nested/file.txt", "rb"); + EXPECT_NOT_NULL(fp); + char buf[100]; + size_t nread = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + EXPECT_EQ_INT((int)nread, (int)strlen(content)); + EXPECT_EQ_INT(memcmp(buf, content, strlen(content)), 0); + + unlink("test_nested_tmp/nested/file.txt"); + rmdir("test_nested_tmp/nested"); + rmdir("test_nested_tmp"); +} + +static void test_file_content_to_buffer() { + const char* content = "Buffer content test"; + EXPECT_TRUE(to_disk("test_buffer_file.txt", content, strlen(content))); + + File* f = file_create("test_buffer_file.txt"); + EXPECT_NOT_NULL(f); + f->data->size = strlen(content); + f->data->data = malloc(f->data->size); + EXPECT_NOT_NULL(f->data->data); + + size_t bytes_read = file_content_to_buffer(f); + EXPECT_EQ_INT((int)bytes_read, (int)strlen(content)); + EXPECT_EQ_INT(memcmp(f->data->data, content, strlen(content)), 0); + + file_destroy(f); + unlink("test_buffer_file.txt"); +} + +static void test_file_send_receive() { + File* file = file_create("test_send_recv.txt"); + EXPECT_NOT_NULL(file); + const char* content = "Hello, File Send!"; + size_t len = strlen(content); + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + file->data->size = len; + + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), + false, false, false, false, false, 0, false, 0); + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + File* received = file_receive(cfg, p[0]); + close(p[0]); + + bool ok = true; + if (!received) + ok = false; + else { + if (!received->path || strcmp(received->path, "test_send_recv.txt") != 0) + ok = false; + if (!received->data || received->data->size != len) + ok = false; + else if (memcmp(received->data->data, content, len) != 0) + ok = false; + } + + file_destroy(received); + config_delete(cfg); + _exit(ok ? 0 : 1); + } else { + close(p[0]); + bool sent = file_send_single_calls(file, p[1], false, 0, true); + close(p[1]); + + EXPECT_TRUE(sent); + + int status; + waitpid(pid, &status, 0); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + file_destroy(file); + config_delete(cfg); + } +} + +static void test_file_send_no_path() { + File* file = file_create("test_no_path.txt"); + EXPECT_NOT_NULL(file); + const char* content = "No Path Data"; + size_t len = strlen(content); + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + file->data->size = len; + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + Data* received = receive_data(p[0]); + close(p[0]); + + bool ok = true; + if (!received) + ok = false; + else if (received->size != len) + ok = false; + else if (memcmp(received->data, content, len) != 0) + ok = false; + + data_destroy(received); + _exit(ok ? 0 : 1); + } else { + close(p[0]); + bool sent = file_send_single_calls(file, p[1], false, 0, false); + close(p[1]); + + EXPECT_TRUE(sent); + + int status; + waitpid(pid, &status, 0); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + file_destroy(file); + } +} + +static void test_file_metadata_create() { + EXPECT_TRUE(to_disk("test_meta_file.txt", "metadata test", 13)); + struct stat st; + EXPECT_EQ_INT(stat("test_meta_file.txt", &st), 0); + + FileMetadata* m = file_metadata_create(&st); + EXPECT_NOT_NULL(m); + EXPECT_EQ_INT(m->mode, st.st_mode); + EXPECT_EQ_INT(m->uid, st.st_uid); + EXPECT_EQ_INT(m->gid, st.st_gid); + EXPECT_EQ_INT((int)m->mtime_sec, (int)st.st_mtime); + + file_metadata_destroy(m); + unlink("test_meta_file.txt"); +} + +void test_file() { + test_file_create(); + test_file_destroy_null(); + test_file_destroy_normal(); + test_file_load_data(); + test_file_load_data_missing_file(); + test_file_save_to_disk(); + test_to_disk_basic(); + test_to_disk_creates_dirs(); + test_file_content_to_buffer(); + test_file_send_receive(); + test_file_send_no_path(); + test_file_metadata_create(); +} diff --git a/tests/test_file.h b/tests/test_file.h new file mode 100644 index 0000000..1b07e48 --- /dev/null +++ b/tests/test_file.h @@ -0,0 +1,6 @@ +#ifndef TEST_FILE_H +#define TEST_FILE_H + +void test_file(); + +#endif diff --git a/tests/test_glob.c b/tests/test_glob.c new file mode 100644 index 0000000..5658d06 --- /dev/null +++ b/tests/test_glob.c @@ -0,0 +1,63 @@ +#include "test_glob.h" +#include "utils.h" +#include "test_utils.h" +#include + +static void test_glob_exact_match() { + EXPECT_TRUE(glob_match("foo", "foo")); +} + +static void test_glob_question_mark() { + EXPECT_TRUE(glob_match("f?o", "foo")); + EXPECT_FALSE(glob_match("f?o", "fo")); +} + +static void test_glob_star() { + EXPECT_TRUE(glob_match("*.txt", "foo.txt")); + EXPECT_TRUE(glob_match("*.txt", "a.txt")); +} + +static void test_glob_star_mid() { + EXPECT_TRUE(glob_match("f*o", "foo")); + EXPECT_TRUE(glob_match("f*o", "fxxo")); + EXPECT_FALSE(glob_match("f*o", "bar")); +} + +static void test_glob_no_match() { + EXPECT_FALSE(glob_match("foo", "bar")); +} + +static void test_glob_empty_pattern() { + EXPECT_TRUE(glob_match("", "")); + EXPECT_FALSE(glob_match("", "foo")); +} + +static void test_glob_star_all() { + EXPECT_TRUE(glob_match("*", "anything")); +} + +static void test_glob_slash_not_matched() { + EXPECT_FALSE(glob_match("f*o", "f/o")); +} + +static void test_glob_complex() { + EXPECT_TRUE(glob_match("*.c", "main.c")); + EXPECT_FALSE(glob_match("*.c", "main.h")); +} + +static void test_glob_question_star() { + EXPECT_TRUE(glob_match("?*.txt", "a.txt")); +} + +void test_glob() { + test_glob_exact_match(); + test_glob_question_mark(); + test_glob_star(); + test_glob_star_mid(); + test_glob_no_match(); + test_glob_empty_pattern(); + test_glob_star_all(); + test_glob_slash_not_matched(); + test_glob_complex(); + test_glob_question_star(); +} diff --git a/tests/test_glob.h b/tests/test_glob.h new file mode 100644 index 0000000..fa9d0f7 --- /dev/null +++ b/tests/test_glob.h @@ -0,0 +1,6 @@ +#ifndef TEST_GLOB_H +#define TEST_GLOB_H + +void test_glob(); + +#endif diff --git a/tests/test_metadata.c b/tests/test_metadata.c new file mode 100644 index 0000000..478828e --- /dev/null +++ b/tests/test_metadata.c @@ -0,0 +1,141 @@ +#include "test_metadata.h" +#include "metadata.h" +#include "protocol.h" +#include "test_utils.h" +#include +#include +#include +#include + +static void test_metadata_to_from_buf_roundtrip() { + FileMetadata original; + original.mode = 0755; + original.uid = 1000; + original.gid = 1000; + original.mtime_sec = 1234567890; + original.mtime_nsec = 500000000; + + char* buf = malloc(FILE_METADATA_WIRE_SIZE + sizeof(int)); + EXPECT_NOT_NULL(buf); + char* write_ptr = buf; + metadata_to_buf(&write_ptr, &original); + + char* read_ptr = buf; + FileMetadata* result = metadata_from_buf(&read_ptr); + + EXPECT_NOT_NULL(result); + EXPECT_EQ_INT(result->mode, 0755); + EXPECT_EQ_INT(result->uid, 1000); + EXPECT_EQ_INT(result->gid, 1000); + EXPECT_EQ_INT(result->mtime_sec, 1234567890); + EXPECT_EQ_INT(result->mtime_nsec, 500000000); + + free(result); + free(buf); +} + +static void test_metadata_to_buf_null() { + char* buf = malloc(FILE_METADATA_WIRE_SIZE + sizeof(int)); + EXPECT_NOT_NULL(buf); + char* write_ptr = buf; + metadata_to_buf(&write_ptr, NULL); + + char* read_ptr = buf; + int present; + memcpy(&present, read_ptr, sizeof(int)); + EXPECT_EQ_INT(present, 0); + + free(buf); +} + +static void test_metadata_from_buf_null() { + char* buf = malloc(FILE_METADATA_WIRE_SIZE + sizeof(int)); + EXPECT_NOT_NULL(buf); + int present = 0; + memcpy(buf, &present, sizeof(int)); + + char* read_ptr = buf; + FileMetadata* result = metadata_from_buf(&read_ptr); + + EXPECT_NULL(result); + + free(buf); +} + +static void test_metadata_send_receive_roundtrip() { + io_set_bwlimit(0); + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + + FileMetadata original; + original.mode = 0755; + original.uid = 1000; + original.gid = 1000; + original.mtime_sec = 1234567890; + original.mtime_nsec = 500000000; + + EXPECT_TRUE(metadata_send(p[1], &original)); + + int ok = 0; + FileMetadata* received = metadata_receive(p[0], &ok); + EXPECT_NOT_NULL(received); + EXPECT_EQ_INT(ok, 1); + EXPECT_EQ_INT(received->mode, 0755); + EXPECT_EQ_INT(received->uid, 1000); + EXPECT_EQ_INT(received->gid, 1000); + EXPECT_EQ_INT(received->mtime_sec, 1234567890); + EXPECT_EQ_INT(received->mtime_nsec, 500000000); + + free(received); + close(p[0]); + close(p[1]); +} + +static void test_metadata_send_null() { + io_set_bwlimit(0); + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + + EXPECT_TRUE(metadata_send(p[1], NULL)); + + int ok = 0; + FileMetadata* received = metadata_receive(p[0], &ok); + EXPECT_NULL(received); + EXPECT_EQ_INT(ok, 1); + + close(p[0]); + close(p[1]); +} + +static void test_file_restore_metadata() { + const char* path = "temp_meta_restore_test.txt"; + const char* content = "test content"; + EXPECT_TRUE(to_disk(path, content, strlen(content))); + + FileMetadata m; + m.mode = 0644; + m.uid = getuid(); + m.gid = getgid(); + m.mtime_sec = 1234567890; + m.mtime_nsec = 0; + + file_restore_metadata(path, &m); + + struct stat st; + EXPECT_EQ_INT(stat(path, &st), 0); + EXPECT_EQ_INT(st.st_mode & 07777, 0644); + EXPECT_EQ_INT((int)st.st_mtime, 1234567890); + + unlink(path); +} + +void test_metadata() { + test_metadata_to_from_buf_roundtrip(); + test_metadata_to_buf_null(); + test_metadata_from_buf_null(); + test_metadata_send_receive_roundtrip(); + test_metadata_send_null(); + test_file_restore_metadata(); +} diff --git a/tests/test_metadata.h b/tests/test_metadata.h new file mode 100644 index 0000000..e7e0a6a --- /dev/null +++ b/tests/test_metadata.h @@ -0,0 +1,6 @@ +#ifndef TEST_METADATA_H +#define TEST_METADATA_H + +void test_metadata(); + +#endif diff --git a/tests/test_property.c b/tests/test_property.c new file mode 100644 index 0000000..a0e96e8 --- /dev/null +++ b/tests/test_property.c @@ -0,0 +1,137 @@ +#include "test_property.h" +#include "test_utils.h" +#include "chunk.h" +#include "delta.h" +#include "data.h" +#include "compression.h" +#include "file.h" +#include "utils.h" +#include +#include +#include +#include +#include + +static Data* random_data(int min_size, int max_size) { + int size = min_size + rand() % (max_size - min_size + 1); + char* buf = malloc(size); + for (int i = 0; i < size; i++) + buf[i] = (char)(rand() % 256); + return data_create(buf, size); +} + +static void test_property_compress_roundtrip() { + srand((unsigned)time(NULL)); + for (int iter = 0; iter < 10; iter++) { + Data* original = random_data(1, 10000); + EXPECT_NOT_NULL(original); + + size_t orig_size = original->size; + void* orig_copy = malloc(orig_size); + EXPECT_NOT_NULL(orig_copy); + memcpy(orig_copy, original->data, orig_size); + + Data* compressed = data_compress(original, 3); + EXPECT_NOT_NULL(compressed); + + Data* decompressed = data_decompress(compressed); + EXPECT_NOT_NULL(decompressed); + EXPECT_EQ_INT((int)decompressed->size, (int)orig_size); + EXPECT_EQ_INT(memcmp(decompressed->data, orig_copy, orig_size), 0); + + free(orig_copy); + data_destroy(original); + data_destroy(compressed); + data_destroy(decompressed); + } +} + +static void test_property_delta_roundtrip() { + for (int iter = 0; iter < 5; iter++) { + char old_data[4096], new_data[4096]; + for (int i = 0; i < 4096; i++) { + old_data[i] = (char)(rand() % 256); + new_data[i] = old_data[i]; + } + + int num_changes = 1 + rand() % 10; + for (int c = 0; c < num_changes; c++) { + int offset = rand() % 4096; + new_data[offset] = (char)(rand() % 256); + } + + DeltaSignature* sig = delta_signature_create(old_data, 4096, 1024); + EXPECT_NOT_NULL(sig); + + Delta* delta = delta_compute(new_data, 4096, sig, 1024); + EXPECT_NOT_NULL(delta); + + void* result = delta_apply(old_data, 4096, delta, 1024); + EXPECT_NOT_NULL(result); + EXPECT_EQ_INT(memcmp(result, new_data, 4096), 0); + + free(result); + delta_signature_destroy(sig); + delta_destroy(delta); + } +} + +static void test_property_chunk_roundtrip() { + for (int iter = 0; iter < 5; iter++) { + char path[64]; + snprintf(path, sizeof(path), "test_prop_chunk_%d.txt", iter); + + int content_len = 1 + rand() % 4096; + char* content = malloc(content_len); + for (int i = 0; i < content_len; i++) + content[i] = (char)(rand() % 256); + + to_disk(path, content, content_len); + + struct stat st; + stat(path, &st); + + File* f = file_create(path); + f->data->size = st.st_size; + file_load_data(f); + + File* files[1] = {f}; + Chunk* chunk = chunk_create(files, 1); + Data* serialized = chunk_serialize(chunk, false); + EXPECT_NOT_NULL(serialized); + + Chunk* deserialized = chunk_deserialize(serialized, false); + EXPECT_NOT_NULL(deserialized); + EXPECT_EQ_INT(deserialized->element_count, 1); + EXPECT_EQ_INT((int)deserialized->items[0]->data->size, content_len); + EXPECT_EQ_INT(memcmp(deserialized->items[0]->data->data, content, content_len), 0); + + free(content); + data_destroy(serialized); + chunk_destroy(deserialized); + chunk_destroy(chunk); + unlink(path); + } +} + +static void test_property_glob_consistency() { + const char* patterns[] = {"*.txt", "test_*", "*.c", "foo", "*.??", "a*b*c"}; + const char* strings[] = {"test.txt", "foo.c", "bar", "abc", "aXbYcZ", ""}; + int n_patterns = sizeof(patterns) / sizeof(patterns[0]); + int n_strings = sizeof(strings) / sizeof(strings[0]); + + for (int p = 0; p < n_patterns; p++) { + for (int s = 0; s < n_strings; s++) { + bool first = glob_match(patterns[p], strings[s]); + bool second = glob_match(patterns[p], strings[s]); + EXPECT_TRUE(first == second); + } + } +} + +void test_property() { + test_property_compress_roundtrip(); + test_property_delta_roundtrip(); + test_property_chunk_roundtrip(); + test_property_glob_consistency(); +} diff --git a/tests/test_property.h b/tests/test_property.h new file mode 100644 index 0000000..342890c --- /dev/null +++ b/tests/test_property.h @@ -0,0 +1,6 @@ +#ifndef TEST_PROPERTY_H +#define TEST_PROPERTY_H + +void test_property(); + +#endif diff --git a/tests/test_protocol.c b/tests/test_protocol.c new file mode 100644 index 0000000..dea5840 --- /dev/null +++ b/tests/test_protocol.c @@ -0,0 +1,182 @@ +#include "protocol.h" +#include "test_utils.h" +#include +#include +#include + +static void test_send_receive_n_data() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + const char payload[] = "binary\x00test"; + size_t len = sizeof(payload); + EXPECT_TRUE(send_n_data(0, payload, len)); + + char buf[64]; + memset(buf, 0, sizeof(buf)); + EXPECT_TRUE(receive_n_data(0, buf, len)); + EXPECT_EQ_INT(memcmp(buf, payload, len), 0); + + close(p[0]); + close(p[1]); +} + +static void test_send_receive_n_data_zero() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + EXPECT_TRUE(send_n_data(0, "", 0)); + + char buf[4]; + EXPECT_TRUE(receive_n_data(0, buf, 0)); + + close(p[0]); + close(p[1]); +} + +static void test_send_receive_str() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + EXPECT_TRUE(send_str(0, "")); + + char* received = receive_str(0); + EXPECT_NOT_NULL(received); + EXPECT_EQ_STR(received, ""); + free(received); + + close(p[0]); + close(p[1]); +} + +static void test_send_receive_str_normal() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + EXPECT_TRUE(send_str(0, "Hello, Protocol!")); + + char* received = receive_str(0); + EXPECT_NOT_NULL(received); + EXPECT_EQ_STR(received, "Hello, Protocol!"); + free(received); + + close(p[0]); + close(p[1]); +} + +static void test_send_receive_data() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + unsigned char bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; + void* buf = malloc(sizeof(bin)); + EXPECT_NOT_NULL(buf); + memcpy(buf, bin, sizeof(bin)); + Data* original = data_create(buf, sizeof(bin)); + EXPECT_TRUE(send_data(0, original)); + + Data* received = receive_data(0); + EXPECT_NOT_NULL(received); + EXPECT_EQ_INT((int)received->size, (int)sizeof(bin)); + EXPECT_EQ_INT(memcmp(received->data, bin, sizeof(bin)), 0); + + data_destroy(original); + data_destroy(received); + close(p[0]); + close(p[1]); +} + +static void test_send_receive_int() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + int val = 42; + EXPECT_TRUE(send_int(0, val)); + int received = 0; + EXPECT_TRUE(receive_int(0, &received)); + EXPECT_EQ_INT(received, 42); + + val = 0; + EXPECT_TRUE(send_int(0, val)); + EXPECT_TRUE(receive_int(0, &received)); + EXPECT_EQ_INT(received, 0); + + val = INT_MAX; + EXPECT_TRUE(send_int(0, val)); + EXPECT_TRUE(receive_int(0, &received)); + EXPECT_EQ_INT(received, INT_MAX); + + close(p[0]); + close(p[1]); +} + +static void test_send_receive_status() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + Status statuses[] = {STATUS_OK, STATUS_ERROR, STATUS_FINISHED, STATUS_NEXT, + STATUS_CHUNK, STATUS_CHECK, STATUS_DELTA_SIGNATURE, STATUS_DELTA_DATA}; + int count = sizeof(statuses) / sizeof(statuses[0]); + + for (int i = 0; i < count; i++) { + EXPECT_TRUE(send_status(0, statuses[i])); + Status received = -1; + EXPECT_TRUE(receive_status(0, &received)); + EXPECT_EQ_INT((int)received, (int)statuses[i]); + } + + close(p[0]); + close(p[1]); +} + +static void test_receive_n_data_truncated() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[1]); + + char buf[32]; + EXPECT_FALSE(receive_n_data(0, buf, 32)); + + close(p[0]); +} + +static void test_receive_str_truncated() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[1]); + + char* received = receive_str(0); + EXPECT_NULL(received); + + close(p[0]); +} + +void test_protocol() { + test_send_receive_n_data(); + test_send_receive_n_data_zero(); + test_send_receive_str(); + test_send_receive_str_normal(); + test_send_receive_data(); + test_send_receive_int(); + test_send_receive_status(); + test_receive_n_data_truncated(); + test_receive_str_truncated(); +} diff --git a/tests/test_protocol.h b/tests/test_protocol.h new file mode 100644 index 0000000..00ce194 --- /dev/null +++ b/tests/test_protocol.h @@ -0,0 +1,6 @@ +#ifndef TEST_PROTOCOL_H +#define TEST_PROTOCOL_H + +void test_protocol(); + +#endif diff --git a/tests/test_robustness.c b/tests/test_robustness.c new file mode 100644 index 0000000..051fc3b --- /dev/null +++ b/tests/test_robustness.c @@ -0,0 +1,185 @@ +#include "test_robustness.h" +#include "test_utils.h" +#include "chunk.h" +#include "delta.h" +#include "data.h" +#include "file.h" +#include "protocol.h" +#include +#include +#include +#include + +static void test_chunk_deserialize_truncated() { + char* path = "test_rob_trunc.txt"; + char* content = "hello"; + to_disk(path, content, strlen(content)); + + struct stat st; + stat(path, &st); + + File* f = file_create(path); + f->data->size = st.st_size; + file_load_data(f); + + File* files[1] = {f}; + Chunk* chunk = chunk_create(files, 1); + Data* serialized = chunk_serialize(chunk, false); + EXPECT_NOT_NULL(serialized); + + size_t orig_size = serialized->size; + serialized->size = orig_size / 2; + + Chunk* result = chunk_deserialize(serialized, false); + EXPECT_NULL(result); + + serialized->size = orig_size; + data_destroy(serialized); + chunk_destroy(chunk); + unlink(path); +} + +static void test_chunk_deserialize_empty() { + unsigned char garbage[] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB}; + Data* d = data_create(malloc(sizeof(garbage)), sizeof(garbage)); + EXPECT_NOT_NULL(d); + memcpy(d->data, garbage, sizeof(garbage)); + + Chunk* result = chunk_deserialize(d, false); + EXPECT_NULL(result); + + data_destroy(d); +} + +static void test_chunk_deserialize_garbage() { + unsigned char garbage[] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA}; + Data* d = data_create(malloc(sizeof(garbage)), sizeof(garbage)); + EXPECT_NOT_NULL(d); + memcpy(d->data, garbage, sizeof(garbage)); + + Chunk* result = chunk_deserialize(d, false); + EXPECT_NULL(result); + + data_destroy(d); +} + +static void test_delta_deserialize_truncated() { + char old_data[4096], new_data[4096]; + for (int i = 0; i < 4096; i++) { + old_data[i] = (char)(i % 256); + new_data[i] = old_data[i]; + } + new_data[100] = 'X'; + + DeltaSignature* sig = delta_signature_create(old_data, 4096, 1024); + Delta* delta = delta_compute(new_data, 4096, sig, 1024); + Data* serialized = delta_serialize(delta); + EXPECT_NOT_NULL(serialized); + + serialized->size = 4; + Delta* result = delta_deserialize(serialized); + EXPECT_NULL(result); + + data_destroy(serialized); + delta_destroy(delta); + delta_signature_destroy(sig); +} + +static void test_delta_deserialize_empty() { + char garbage[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + Data* d = data_create(malloc(sizeof(garbage)), sizeof(garbage)); + EXPECT_NOT_NULL(d); + memcpy(d->data, garbage, sizeof(garbage)); + + Delta* result = delta_deserialize(d); + EXPECT_NULL(result); + + data_destroy(d); +} + +static void test_delta_deserialize_garbage() { + unsigned char garbage[] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA}; + Data* d = data_create(malloc(sizeof(garbage)), sizeof(garbage)); + EXPECT_NOT_NULL(d); + memcpy(d->data, garbage, sizeof(garbage)); + + Delta* result = delta_deserialize(d); + EXPECT_NULL(result); + + data_destroy(d); +} + +static void test_delta_signature_deserialize_truncated() { + char old_data[4096]; + for (int i = 0; i < 4096; i++) + old_data[i] = (char)(i % 256); + + DeltaSignature* sig = delta_signature_create(old_data, 4096, 1024); + Data* serialized = delta_signature_serialize(sig); + EXPECT_NOT_NULL(serialized); + + serialized->size = 4; + DeltaSignature* result = delta_signature_deserialize(serialized); + EXPECT_NULL(result); + + data_destroy(serialized); + delta_signature_destroy(sig); +} + +static void test_delta_apply_null() { + void* result = delta_apply(NULL, 0, NULL, 0); + EXPECT_NULL(result); +} + +static void test_protocol_receive_n_data_closed_pipe() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[1]); + + char buf[32]; + EXPECT_FALSE(receive_n_data(0, buf, 32)); + + close(p[0]); +} + +static void test_receive_data_closed_pipe() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[1]); + + Data* result = receive_data(0); + EXPECT_NULL(result); + + close(p[0]); +} + +static void test_receive_str_closed_pipe() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[1]); + + char* result = receive_str(0); + EXPECT_NULL(result); + + close(p[0]); +} + +void test_robustness() { + test_chunk_deserialize_truncated(); + test_chunk_deserialize_empty(); + test_chunk_deserialize_garbage(); + test_delta_deserialize_truncated(); + test_delta_deserialize_empty(); + test_delta_deserialize_garbage(); + test_delta_signature_deserialize_truncated(); + test_delta_apply_null(); + test_protocol_receive_n_data_closed_pipe(); + test_receive_data_closed_pipe(); + test_receive_str_closed_pipe(); +} diff --git a/tests/test_robustness.h b/tests/test_robustness.h new file mode 100644 index 0000000..8aa6a83 --- /dev/null +++ b/tests/test_robustness.h @@ -0,0 +1,6 @@ +#ifndef TEST_ROBUSTNESS_H +#define TEST_ROBUSTNESS_H + +void test_robustness(); + +#endif diff --git a/tests/test_stress.c b/tests/test_stress.c new file mode 100644 index 0000000..d97eb87 --- /dev/null +++ b/tests/test_stress.c @@ -0,0 +1,226 @@ +#include "test_stress.h" +#include "test_utils.h" +#include "queue.h" +#include +#include +#include +#include + +#define ITEMS_PER_PRODUCER 2500 +#define NUM_PRODUCERS 4 +#define NUM_CONSUMERS 4 +#define TOTAL_ITEMS (ITEMS_PER_PRODUCER * NUM_PRODUCERS) + +typedef struct { + Queue* q; + mtx_t* mutex; + cnd_t* cnd_empty; + cnd_t* cnd_full; + int producer_id; +} ProducerCtx; + +typedef struct { + Queue* q; + mtx_t* mutex; + cnd_t* cnd_empty; + cnd_t* cnd_full; + volatile bool* done; + int sum; +} ConsumerCtx; + +typedef struct { + Queue* q; + mtx_t* mutex; + cnd_t* cnd_empty; + cnd_t* cnd_full; + volatile int* producers_remaining; + volatile bool* producers_done; +} ConsumerMPMC; + +static int mpmc_producer_func(void* arg) { + ProducerCtx* ctx = (ProducerCtx*)arg; + for (int i = 1; i <= ITEMS_PER_PRODUCER; i++) { + int* val = malloc(sizeof(int)); + *val = ctx->producer_id * ITEMS_PER_PRODUCER + i; + queue_enqueue_multithreaded(ctx->q, val, ctx->mutex, ctx->cnd_empty, ctx->cnd_full); + } + return 0; +} + +static int mpmc_consumer_func(void* arg) { + ConsumerMPMC* ctx = (ConsumerMPMC*)arg; + while (true) { + int* val = (int*)queue_dequeue_multithreaded(ctx->q, ctx->mutex, ctx->cnd_empty, ctx->cnd_full, + (const bool*)ctx->producers_done); + if (val == NULL) + break; + free(val); + } + return 0; +} + +static void test_queue_mpmc_stress() { + Queue* q = queue_create(16, NULL); + mtx_t mutex; + cnd_t cnd_empty; + cnd_t cnd_full; + + mtx_init(&mutex, mtx_plain); + cnd_init(&cnd_empty); + cnd_init(&cnd_full); + + volatile int producers_remaining = NUM_PRODUCERS; + volatile bool producers_done = false; + + ConsumerMPMC cctx = {.q = q, + .mutex = &mutex, + .cnd_empty = &cnd_empty, + .cnd_full = &cnd_full, + .producers_remaining = &producers_remaining, + .producers_done = &producers_done}; + + thrd_t consumers[NUM_CONSUMERS]; + for (int i = 0; i < NUM_CONSUMERS; i++) { + int res = thrd_create(&consumers[i], mpmc_consumer_func, &cctx); + EXPECT_EQ_INT(res, thrd_success); + } + + ProducerCtx pctxs[NUM_PRODUCERS]; + thrd_t producers[NUM_PRODUCERS]; + for (int i = 0; i < NUM_PRODUCERS; i++) { + pctxs[i] = (ProducerCtx){.q = q, + .mutex = &mutex, + .cnd_empty = &cnd_empty, + .cnd_full = &cnd_full, + .producer_id = i}; + int res = thrd_create(&producers[i], mpmc_producer_func, &pctxs[i]); + EXPECT_EQ_INT(res, thrd_success); + } + + for (int i = 0; i < NUM_PRODUCERS; i++) { + thrd_join(producers[i], NULL); + mtx_lock(&mutex); + producers_remaining--; + if (producers_remaining == 0) + producers_done = true; + cnd_broadcast(&cnd_empty); + mtx_unlock(&mutex); + } + + for (int i = 0; i < NUM_CONSUMERS; i++) { + thrd_join(consumers[i], NULL); + } + + EXPECT_TRUE(queue_is_empty(q)); + + queue_destroy(q); + mtx_destroy(&mutex); + cnd_destroy(&cnd_empty); + cnd_destroy(&cnd_full); +} + +typedef struct { + Queue* q; + mtx_t* mutex; + cnd_t* cnd_empty; + cnd_t* cnd_full; + bool done; + int items_sent; + int items_received; +} BackpressureCtx; + +static int bp_producer_func(void* arg) { + BackpressureCtx* ctx = (BackpressureCtx*)arg; + for (int i = 0; i < 5; i++) { + int* val = malloc(sizeof(int)); + *val = i + 1; + queue_enqueue_multithreaded(ctx->q, val, ctx->mutex, ctx->cnd_empty, ctx->cnd_full); + ctx->items_sent++; + } + return 0; +} + +static int bp_consumer_func(void* arg) { + BackpressureCtx* ctx = (BackpressureCtx*)arg; + while (ctx->items_received < 5) { + int* val = (int*)queue_dequeue_multithreaded(ctx->q, ctx->mutex, ctx->cnd_empty, ctx->cnd_full, + &ctx->done); + if (val == NULL) + break; + ctx->items_received++; + free(val); + } + return 0; +} + +static void test_queue_backpressure() { + Queue* q = queue_create(1, NULL); + mtx_t mutex; + cnd_t cnd_empty; + cnd_t cnd_full; + + mtx_init(&mutex, mtx_plain); + cnd_init(&cnd_empty); + cnd_init(&cnd_full); + + BackpressureCtx ctx = {.q = q, + .mutex = &mutex, + .cnd_empty = &cnd_empty, + .cnd_full = &cnd_full, + .items_sent = 0, + .items_received = 0, + .done = false}; + + thrd_t producer, consumer; + int res; + + res = thrd_create(&consumer, bp_consumer_func, &ctx); + EXPECT_EQ_INT(res, thrd_success); + + res = thrd_create(&producer, bp_producer_func, &ctx); + EXPECT_EQ_INT(res, thrd_success); + + thrd_join(producer, NULL); + + mtx_lock(&mutex); + ctx.done = true; + cnd_signal(&cnd_empty); + mtx_unlock(&mutex); + + thrd_join(consumer, NULL); + + EXPECT_EQ_INT(ctx.items_sent, 5); + EXPECT_EQ_INT(ctx.items_received, 5); + EXPECT_TRUE(queue_is_empty(q)); + + queue_destroy(q); + mtx_destroy(&mutex); + cnd_destroy(&cnd_empty); + cnd_destroy(&cnd_full); +} + +static void test_queue_rapid_create_destroy() { + for (int i = 0; i < 100; i++) { + Queue* q = queue_create(4, free); + EXPECT_NOT_NULL(q); + + for (int j = 0; j < 3; j++) { + int* val = malloc(sizeof(int)); + *val = j; + queue_enqueue(q, val); + } + + while (!queue_is_empty(q)) { + void* v = queue_dequeue(q); + free(v); + } + + queue_destroy(q); + } +} + +void test_stress() { + test_queue_mpmc_stress(); + test_queue_backpressure(); + test_queue_rapid_create_destroy(); +} diff --git a/tests/test_stress.h b/tests/test_stress.h new file mode 100644 index 0000000..0569b93 --- /dev/null +++ b/tests/test_stress.h @@ -0,0 +1,6 @@ +#ifndef TEST_STRESS_H +#define TEST_STRESS_H + +void test_stress(); + +#endif