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)
This commit is contained in:
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -31,18 +31,18 @@ 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
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
needs: lint
|
||||
strategy:
|
||||
matrix:
|
||||
sanitizer: [address]
|
||||
sanitizer: [address, undefined]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -57,14 +57,14 @@ jobs:
|
||||
run: ln -sf build-${{ matrix.sanitizer }} build
|
||||
|
||||
- name: Unit Tests
|
||||
run: ./build-${{ matrix.sanitizer }}/tests
|
||||
run: ctest --test-dir build-${{ matrix.sanitizer }} --output-on-failure
|
||||
|
||||
- name: Integration Tests
|
||||
run: LSAN_OPTIONS=suppressions=.lsan-suppressions.txt python3 -m pytest tests/ -v --tb=short
|
||||
|
||||
clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -83,3 +83,59 @@ jobs:
|
||||
else
|
||||
echo "clang-tidy: no issues found"
|
||||
fi
|
||||
|
||||
fuzz-build:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure (clang + fuzz)
|
||||
run: CC=clang CXX=clang++ cmake -B build-fuzz -S . -DENABLE_FUZZ=ON
|
||||
|
||||
- name: Build fuzz targets
|
||||
run: cmake --build build-fuzz -j$(nproc)
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure
|
||||
run: cmake -B build -S . -DENABLE_COVERAGE=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 --branch-coverage
|
||||
lcov --remove coverage.info '/usr/*' '*/tests/*' '*/_deps/*' --output-file coverage.info --branch-coverage --ignore-errors unused
|
||||
lcov --list coverage.info
|
||||
|
||||
valgrind:
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v9
|
||||
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=definite --error-exitcode=1 ./build/tests
|
||||
env:
|
||||
FASTSYNC_UNDER_VALGRIND: "1"
|
||||
|
||||
+34
-4
@@ -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()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "chunk.h"
|
||||
#include "data.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "compression.h"
|
||||
#include "data.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "delta.h"
|
||||
#include "data.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "delta.h"
|
||||
#include "data.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "utils.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "metadata.h"
|
||||
#include "file.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "data.h"
|
||||
#include "test_utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_DATA_H
|
||||
#define TEST_DATA_H
|
||||
|
||||
void test_data();
|
||||
|
||||
#endif
|
||||
@@ -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 <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_FILE_H
|
||||
#define TEST_FILE_H
|
||||
|
||||
void test_file();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "test_glob.h"
|
||||
#include "utils.h"
|
||||
#include "test_utils.h"
|
||||
#include <string.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_GLOB_H
|
||||
#define TEST_GLOB_H
|
||||
|
||||
void test_glob();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "test_metadata.h"
|
||||
#include "metadata.h"
|
||||
#include "protocol.h"
|
||||
#include "test_utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_METADATA_H
|
||||
#define TEST_METADATA_H
|
||||
|
||||
void test_metadata();
|
||||
|
||||
#endif
|
||||
@@ -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 <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_PROPERTY_H
|
||||
#define TEST_PROPERTY_H
|
||||
|
||||
void test_property();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
#include "protocol.h"
|
||||
#include "test_utils.h"
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_PROTOCOL_H
|
||||
#define TEST_PROTOCOL_H
|
||||
|
||||
void test_protocol();
|
||||
|
||||
#endif
|
||||
@@ -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 <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_ROBUSTNESS_H
|
||||
#define TEST_ROBUSTNESS_H
|
||||
|
||||
void test_robustness();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,226 @@
|
||||
#include "test_stress.h"
|
||||
#include "test_utils.h"
|
||||
#include "queue.h"
|
||||
#include <threads.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_STRESS_H
|
||||
#define TEST_STRESS_H
|
||||
|
||||
void test_stress();
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user