diff --git a/tests/runner.c b/tests/runner.c index 402af49..0bba0f1 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -1,11 +1,13 @@ #include "test_array_list.h" #include "test_chunk.h" +#include "test_client_cli.h" #include "test_compression.h" #include "test_config.h" #include "test_data.h" #include "test_delta.h" #include "test_file.h" #include "test_file_sendfile.h" +#include "test_fuzz_smoke.h" #include "test_glob.h" #include "test_log.h" #include "test_metadata.h" @@ -15,6 +17,7 @@ #include "test_queue.h" #include "test_robustness.h" #include "test_scanner.h" +#include "test_server.h" #include "test_shared_utils.h" #include "test_stress.h" #include "test_transport_tcp.h" @@ -53,6 +56,9 @@ int main() { RUN_TEST(test_transport_tcp); RUN_TEST(test_transport_ssh); RUN_TEST(test_transport_tls); + RUN_TEST(test_client_cli); + RUN_TEST(test_server); + RUN_TEST(test_fuzz_smoke); printf("\n\033[1;36m=== TEST SUMMARY ===\033[0m\n"); printf("Total Tests Run: %d\n", tests_run); diff --git a/tests/test_client_cli.c b/tests/test_client_cli.c new file mode 100644 index 0000000..888bbab --- /dev/null +++ b/tests/test_client_cli.c @@ -0,0 +1,80 @@ +#include "test_client_cli.h" +#include "config.h" +#include "test_utils.h" +#include "utils.h" +#include +#include +#include + +/* Test basic config creation matching client_cli startup */ +static void test_cli_default_config() { + Config* cfg = config_create(str_dup("1.0"), NULL, NULL, false, false, false, false, false, 5, + false, 0); + EXPECT_NOT_NULL(cfg); + EXPECT_NULL(cfg->send_directory); + EXPECT_NULL(cfg->receive_root_directory); + EXPECT_EQ_INT(cfg->compression_level, 5); + config_delete(cfg); +} + +/* Test that --archive sets compression, multithreading, and metadata */ +static void test_cli_archive_flags() { + Config* cfg = config_create(str_dup("1.0"), NULL, NULL, false, false, false, false, false, 5, + false, 0); + EXPECT_NOT_NULL(cfg); + + cfg->use_compression = true; + cfg->use_multithreading = true; + cfg->use_metadata = true; + + EXPECT_TRUE(cfg->use_compression); + EXPECT_TRUE(cfg->use_multithreading); + EXPECT_TRUE(cfg->use_metadata); + + config_delete(cfg); +} + +/* Test that --dry-run sets dry_run flag */ +static void test_cli_dry_run() { + Config* cfg = config_create(str_dup("1.0"), NULL, NULL, false, false, false, false, false, 5, + false, 0); + cfg->dry_run = true; + EXPECT_TRUE(cfg->dry_run); + config_delete(cfg); +} + +/* Test that --delete sets use_delete */ +static void test_cli_delete_flag() { + Config* cfg = config_create(str_dup("1.0"), NULL, NULL, false, false, false, false, false, 5, + false, 0); + cfg->use_delete = true; + EXPECT_TRUE(cfg->use_delete); + config_delete(cfg); +} + +/* Test exclude pattern handling */ +static void test_cli_exclude_patterns() { + Config* cfg = config_create(str_dup("1.0"), NULL, NULL, false, false, false, false, false, 5, + false, 0); + EXPECT_NOT_NULL(cfg); + + cfg->exclude_patterns = malloc(2 * sizeof(char*)); + EXPECT_NOT_NULL(cfg->exclude_patterns); + cfg->exclude_patterns[0] = str_dup("*.log"); + cfg->exclude_patterns[1] = str_dup("tmp/"); + cfg->exclude_count = 2; + + EXPECT_EQ_STR(cfg->exclude_patterns[0], "*.log"); + EXPECT_EQ_STR(cfg->exclude_patterns[1], "tmp/"); + EXPECT_EQ_INT(cfg->exclude_count, 2); + + config_delete(cfg); +} + +void test_client_cli() { + test_cli_default_config(); + test_cli_archive_flags(); + test_cli_dry_run(); + test_cli_delete_flag(); + test_cli_exclude_patterns(); +} diff --git a/tests/test_client_cli.h b/tests/test_client_cli.h new file mode 100644 index 0000000..daacba5 --- /dev/null +++ b/tests/test_client_cli.h @@ -0,0 +1,6 @@ +#ifndef TEST_CLIENT_CLI_H +#define TEST_CLIENT_CLI_H + +void test_client_cli(); + +#endif diff --git a/tests/test_config.c b/tests/test_config.c index 2dad898..d46939d 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -1,10 +1,16 @@ #include "test_config.h" #include "config.h" #include "multiprocessing.h" +#include "protocol.h" #include "queue.h" #include "test_utils.h" #include "utils.h" #include +#include +#include +#include +#include +#include static void test_config_lifecycle() { Config* cfg = config_create(str_dup("1.0"), str_dup("/src"), str_dup("/dst"), true, true, false, @@ -89,6 +95,104 @@ static void test_pipeline_receiver_lifecycle() { pipeline_context_receiver_destroy(pcr); } +/* Test config_send/config_receive round-trip over a socketpair */ +static void test_config_send_receive() { + Config* send_cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/send/src"), + str_dup("/send/dst"), true, true, true, true, true, 5, false, + 1024); + EXPECT_NOT_NULL(send_cfg); + + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + io_set_fds(p[0], p[0]); + Config* recv_cfg = config_receive(p[0]); + close(p[0]); + + bool ok = true; + if (!recv_cfg) ok = false; + else { + if (strcmp(recv_cfg->version, PROTOCOL_VERSION) != 0) ok = false; + if (strcmp(recv_cfg->send_directory, "/send/src") != 0) ok = false; + if (strcmp(recv_cfg->receive_root_directory, "/send/dst") != 0) ok = false; + if (!recv_cfg->save_to_disk) ok = false; + if (!recv_cfg->use_multithreading) ok = false; + if (!recv_cfg->use_chunk_serialization) ok = false; + if (recv_cfg->compression_level != 5) ok = false; + if (recv_cfg->chunk_size != 1024) ok = false; + } + config_delete(recv_cfg); + _exit(ok ? 0 : 1); + } else { + close(p[0]); + io_set_fds(p[1], p[1]); + bool sent = config_send(p[1], send_cfg); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + config_delete(send_cfg); + + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +/* Test config_send/receive version mismatch rejection */ +static void test_config_send_receive_version_mismatch() { + Config* cfg = config_create(str_dup("0.0"), str_dup("/src"), str_dup("/dst"), false, false, false, + false, false, 0, false, 0); + + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + io_set_fds(p[0], p[0]); + Config* recv = config_receive(p[0]); + close(p[0]); + _exit(recv == NULL ? 0 : 1); + } else { + close(p[0]); + io_set_fds(p[1], p[1]); + bool sent = config_send(p[1], cfg); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + config_delete(cfg); + + /* config_send should return false because config_receive sends STATUS_ERROR */ + EXPECT_FALSE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +/* Test is_remote_dest edge cases */ +static void test_is_remote_dest() { + EXPECT_TRUE(is_remote_dest("user@host:/path")); + EXPECT_TRUE(is_remote_dest("host:/path")); + EXPECT_TRUE(is_remote_dest("user@192.168.1.1:/remote/path")); + EXPECT_FALSE(is_remote_dest(NULL)); + EXPECT_FALSE(is_remote_dest("")); + EXPECT_FALSE(is_remote_dest(":")); + EXPECT_FALSE(is_remote_dest("/local/path")); + EXPECT_FALSE(is_remote_dest("relative/path")); + EXPECT_TRUE(is_remote_dest("C:/windows/path")); + EXPECT_FALSE(is_remote_dest("noslash")); + EXPECT_FALSE(is_remote_dest("/")); + EXPECT_TRUE(is_remote_dest("host:")); + EXPECT_TRUE(is_remote_dest("user@host:")); +} + void test_config() { test_config_lifecycle(); test_config_ssh_dest(); @@ -96,4 +200,9 @@ void test_config() { test_config_ssh_dest_no_user(); test_pipeline_sender_lifecycle(); test_pipeline_receiver_lifecycle(); + if (!is_running_under_valgrind()) { + test_config_send_receive(); + test_config_send_receive_version_mismatch(); + } + test_is_remote_dest(); } diff --git a/tests/test_file.c b/tests/test_file.c index 25862a5..81678da 100644 --- a/tests/test_file.c +++ b/tests/test_file.c @@ -261,6 +261,132 @@ static void test_file_metadata_create() { unlink("test_meta_file.txt"); } +static void test_file_save_to_disk_path_traversal() { + File* f = file_create("../etc/passwd"); + EXPECT_NOT_NULL(f); + const char* content = "should not save"; + 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_FALSE(file_save_to_disk("/tmp", f, NULL)); + file_destroy(f); +} + +static void test_file_save_to_disk_deep_traversal() { + File* f = file_create("subdir/../../etc/passwd"); + EXPECT_NOT_NULL(f); + const char* content = "should not save"; + 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_FALSE(file_save_to_disk("/tmp", f, NULL)); + file_destroy(f); +} + +static void test_file_send_single_calls_compression() { + const char* content = "Hello, Compressed File Transfer!"; + size_t len = strlen(content); + + File* file = file_create("test_send_comp.txt"); + EXPECT_NOT_NULL(file); + 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, true, false, 3, 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 (strcmp(received->path, "test_send_comp.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, 3, true); + close(p[1]); + int status; + waitpid(pid, &status, 0); + file_destroy(file); + config_delete(cfg); + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void test_file_send_single_calls_metadata() { + const char* content = "File with metadata"; + size_t len = strlen(content); + EXPECT_TRUE(to_disk("test_meta_send.txt", content, len)); + + struct stat st; + EXPECT_EQ_INT(stat("test_meta_send.txt", &st), 0); + + File* file = file_create("test_meta_send.txt"); + EXPECT_NOT_NULL(file); + file->data->size = len; + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + file->metadata = file_metadata_create(&st); + EXPECT_NOT_NULL(file->metadata); + + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false, + false, false, false, true, 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 (strcmp(received->path, "test_meta_send.txt") != 0) ok = false; + if (!received->data || received->data->size != len) ok = false; + else if (memcmp(received->data->data, content, len) != 0) ok = false; + if (!received->metadata) 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], true, 0, true); + close(p[1]); + int status; + waitpid(pid, &status, 0); + file_destroy(file); + config_delete(cfg); + unlink("test_meta_send.txt"); + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + void test_file() { test_file_create(); test_file_destroy_null(); @@ -271,14 +397,13 @@ void test_file() { test_to_disk_basic(); test_to_disk_creates_dirs(); test_file_content_to_buffer(); + test_file_save_to_disk_path_traversal(); + test_file_save_to_disk_deep_traversal(); if (!is_running_under_valgrind()) { - // Fork tests are skipped under valgrind because the parent process runs - // orders of magnitude slower than the child (parent is instrumented, child - // is not), which causes pipe-based protocol handshake timeouts. The parent - // process itself has zero valgrind errors -- the failures are all in the - // forked children where inherited allocations are reported as leaks. test_file_send_receive(); test_file_send_no_path(); + test_file_send_single_calls_compression(); + test_file_send_single_calls_metadata(); } test_file_metadata_create(); } diff --git a/tests/test_fuzz_smoke.c b/tests/test_fuzz_smoke.c new file mode 100644 index 0000000..34b8923 --- /dev/null +++ b/tests/test_fuzz_smoke.c @@ -0,0 +1,158 @@ +#include "test_fuzz_smoke.h" +#include "chunk.h" +#include "compression.h" +#include "data.h" +#include "delta.h" +#include "metadata.h" +#include "test_utils.h" +#include "utils.h" +#include +#include +#include +#include + +/* Smoke test for chunk_deserialize fuzz target */ +static void test_fuzz_chunk_deserialize() { + File* file = file_create("fuzz_test.txt"); + EXPECT_NOT_NULL(file); + const char* content = "fuzz data"; + file->data->data = malloc(strlen(content)); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, strlen(content)); + file->data->size = strlen(content); + + File* chunk_files[] = {file}; + Chunk* chunk = chunk_create(chunk_files, 1); + EXPECT_NOT_NULL(chunk); + + 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); + + chunk_destroy(deserialized); + data_destroy(serialized); + chunk_destroy(chunk); +} + +/* Smoke test for compress/decompress fuzz target */ +static void test_fuzz_compress_decompress() { + /* Must use malloc'd buffers since data_destroy calls free(data->data) */ + const char* test_data = "Hello, compression fuzzing!"; + size_t len = strlen(test_data); + void* buf = malloc(len); + EXPECT_NOT_NULL(buf); + memcpy(buf, test_data, len); + + Data* original = data_create(buf, len); + EXPECT_NOT_NULL(original); + + 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)len); + EXPECT_EQ_INT(memcmp(decompressed->data, test_data, len), 0); + + data_destroy(decompressed); + data_destroy(compressed); + data_destroy(original); +} + +/* Smoke test for delta_deserialize fuzz target */ +static void test_fuzz_delta_deserialize() { + const char* old_data_str = "Hello, World!"; + const char* new_data_str = "Hello, Delta!"; + size_t old_len = strlen(old_data_str); + size_t new_len = strlen(new_data_str); + + DeltaSignature* sig = delta_signature_create((void*)old_data_str, old_len, 64); + EXPECT_NOT_NULL(sig); + + Delta* delta = delta_compute((void*)new_data_str, new_len, sig, 64); + EXPECT_NOT_NULL(delta); + + Data* serialized = delta_serialize(delta); + EXPECT_NOT_NULL(serialized); + + Delta* deserialized = delta_deserialize(serialized); + EXPECT_NOT_NULL(deserialized); + + delta_destroy(deserialized); + data_destroy(serialized); + delta_destroy(delta); + delta_signature_destroy(sig); +} + +/* Smoke test for metadata_from_buf fuzz target */ +static void test_fuzz_metadata_from_buf() { + EXPECT_TRUE(to_disk("fuzz_meta_test.txt", "metadata test", 13)); + + struct stat st; + EXPECT_EQ_INT(stat("fuzz_meta_test.txt", &st), 0); + + FileMetadata* meta = file_metadata_create(&st); + EXPECT_NOT_NULL(meta); + + size_t meta_buf_size = sizeof(int32_t) + FILE_METADATA_WIRE_SIZE; + char* meta_buf = malloc(meta_buf_size); + EXPECT_NOT_NULL(meta_buf); + char* meta_ptr = meta_buf; + metadata_to_buf(&meta_ptr, meta); + + char* buf_copy = meta_buf; + FileMetadata* deserialized = metadata_from_buf(&buf_copy); + EXPECT_NOT_NULL(deserialized); + EXPECT_EQ_INT((int)deserialized->mode, (int)meta->mode); + EXPECT_EQ_INT((int)deserialized->mtime_sec, (int)meta->mtime_sec); + + file_metadata_destroy(deserialized); + free(meta_buf); + file_metadata_destroy(meta); + unlink("fuzz_meta_test.txt"); +} + +/* Smoke test for delta_signature_deserialize fuzz target */ +static void test_fuzz_delta_signature_deserialize() { + const char* data_str = "Test data for signature"; + size_t len = strlen(data_str); + + DeltaSignature* sig = delta_signature_create((void*)data_str, len, 64); + EXPECT_NOT_NULL(sig); + + Data* serialized = delta_signature_serialize(sig); + EXPECT_NOT_NULL(serialized); + + DeltaSignature* deserialized = delta_signature_deserialize(serialized); + EXPECT_NOT_NULL(deserialized); + EXPECT_EQ_INT((int)deserialized->block_size, 64); + + delta_signature_destroy(deserialized); + data_destroy(serialized); + delta_signature_destroy(sig); +} + +/* Smoke test for glob_match fuzz target */ +static void test_fuzz_glob_match() { + EXPECT_TRUE(glob_match("*.txt", "file.txt")); + EXPECT_FALSE(glob_match("*.txt", "file.TXT")); + EXPECT_FALSE(glob_match("*.txt", "file.c")); + EXPECT_TRUE(glob_match("data?", "data1")); + EXPECT_TRUE(glob_match("data?", "dataX")); + EXPECT_FALSE(glob_match("data?", "data12")); + EXPECT_TRUE(glob_match("src/**/*.c", "src/main.c")); + EXPECT_TRUE(glob_match("**/test*.py", "src/tests/test_foo.py")); + EXPECT_FALSE(glob_match("*.md", "readme.txt")); +} + +void test_fuzz_smoke() { + test_fuzz_chunk_deserialize(); + test_fuzz_compress_decompress(); + test_fuzz_delta_deserialize(); + test_fuzz_metadata_from_buf(); + test_fuzz_delta_signature_deserialize(); + test_fuzz_glob_match(); +} diff --git a/tests/test_fuzz_smoke.h b/tests/test_fuzz_smoke.h new file mode 100644 index 0000000..5699f45 --- /dev/null +++ b/tests/test_fuzz_smoke.h @@ -0,0 +1,6 @@ +#ifndef TEST_FUZZ_SMOKE_H +#define TEST_FUZZ_SMOKE_H + +void test_fuzz_smoke(); + +#endif diff --git a/tests/test_multiprocessing.c b/tests/test_multiprocessing.c index 741bcf0..759cf2a 100644 --- a/tests/test_multiprocessing.c +++ b/tests/test_multiprocessing.c @@ -1,10 +1,18 @@ #include "test_multiprocessing.h" #include "multiprocessing.h" #include "config.h" +#include "protocol.h" #include "queue.h" #include "utils.h" #include "test_utils.h" +#include #include +#include +#include +#include +#include +#include +#include /* Test pipeline_context_sender_create/destroy with valid arguments */ static void test_sender_create_destroy() { @@ -92,10 +100,85 @@ static void test_receiver_fd_zero() { pipeline_context_receiver_destroy(ctx); } +/* Test that receive_thread completes cleanly when sent FINISHED immediately */ +static void test_receive_thread_finished() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/src"), str_dup("/tmp/dst"), + true, false, false, false, false, 0, false, 0); + + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + io_set_fds(p[0], p[0]); + + Queue* q = queue_create(5, file_destroy); + EXPECT_NOT_NULL(q); + PipelineContextReceiver* ctx = pipeline_context_receiver_create(cfg, q, p[0]); + EXPECT_NOT_NULL(ctx); + + int ret = receive_thread(ctx); + + pipeline_context_receiver_destroy(ctx); + close(p[0]); + _exit(ret == thrd_success ? 0 : 1); + } else { + close(p[0]); + io_set_fds(p[1], p[1]); + + send_status(p[1], STATUS_FINISHED); + + int status; + waitpid(pid, &status, 0); + close(p[1]); + config_delete(cfg); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +/* Test that write_thread completes when queue signals done */ +static void test_write_thread_done() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/src"), str_dup("/tmp/dst"), + false, false, false, false, false, 0, false, 0); + + Queue* q = queue_create(5, file_destroy); + EXPECT_NOT_NULL(q); + + PipelineContextReceiver* ctx = pipeline_context_receiver_create(cfg, q, 0); + EXPECT_NOT_NULL(ctx); + + /* Mark as done so write_thread exits immediately */ + ctx->receiver_done = true; + + thrd_t writer; + int ret = thrd_create(&writer, write_thread, ctx); + EXPECT_EQ_INT(ret, thrd_success); + + int result; + thrd_join(writer, &result); + EXPECT_EQ_INT(result, thrd_success); + + /* Clean up manually (pipeline_context_receiver_destroy would double-free) */ + mtx_destroy(&ctx->mutex); + cnd_destroy(&ctx->condition_not_full); + cnd_destroy(&ctx->condition_not_empty); + free(ctx); + queue_destroy(q); + config_delete(cfg); +} + void test_multiprocessing() { test_sender_create_destroy(); test_receiver_create_destroy(); test_sender_queue_capacities(); test_sender_zero_capacity(); test_receiver_fd_zero(); + if (!is_running_under_valgrind()) { + test_receive_thread_finished(); + } + test_write_thread_done(); } diff --git a/tests/test_server.c b/tests/test_server.c new file mode 100644 index 0000000..8accc37 --- /dev/null +++ b/tests/test_server.c @@ -0,0 +1,151 @@ +#include "test_server.h" +#include "config.h" +#include "file.h" +#include "protocol.h" +#include "test_utils.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include + +/* Include server.c but rename main to avoid conflict with test runner's main */ +#define main server_main_ +#include "server.c" +#undef main + +/* Test receive_files with immediate FINISHED status */ +static void test_receive_files_finished() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/src"), str_dup("/tmp/dst"), + false, false, false, false, false, 0, false, 0); + EXPECT_NOT_NULL(cfg); + + /* Use socketpair for full-duplex communication */ + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + /* Child: run receive_files - needs both directions */ + close(p[1]); + io_set_fds(p[0], p[0]); + int ret = receive_files(cfg, p[0]); + close(p[0]); + config_delete(cfg); + _exit(ret == 0 ? 0 : 1); + } else { + /* Parent: send FINISHED */ + close(p[0]); + io_set_fds(p[1], p[1]); + send_status(p[1], STATUS_FINISHED); + Status resp; + receive_status(p[1], &resp); + + int status; + waitpid(pid, &status, 0); + close(p[1]); + config_delete(cfg); + + EXPECT_EQ_INT(resp, STATUS_OK); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +/* Test receive_files with STATUS_NEXT + file data */ +static void test_receive_files_single_file() { + const char* content = "Hello from server test!"; + size_t len = strlen(content); + + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/src"), str_dup("/tmp/dst"), + false, false, false, false, false, 0, false, 0); + EXPECT_NOT_NULL(cfg); + + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + io_set_fds(p[0], p[0]); + int ret = receive_files(cfg, p[0]); + close(p[0]); + config_delete(cfg); + _exit(ret == 0 ? 0 : 1); + } else { + close(p[0]); + io_set_fds(p[1], p[1]); + + send_status(p[1], STATUS_NEXT); + + File* file = file_create("test_server_file.txt"); + EXPECT_NOT_NULL(file); + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + file->data->size = len; + + send_str(p[1], file->path); + send_data(p[1], file->data); + file_destroy(file); + + send_status(p[1], STATUS_FINISHED); + Status resp; + receive_status(p[1], &resp); + + int status; + waitpid(pid, &status, 0); + close(p[1]); + config_delete(cfg); + + EXPECT_EQ_INT(resp, STATUS_OK); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +/* Test receive_files with STATUS_ABORT */ +static void test_receive_files_abort() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/src"), str_dup("/tmp/dst"), + false, false, false, false, false, 0, false, 0); + EXPECT_NOT_NULL(cfg); + + int p[2]; + EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + io_set_fds(p[0], p[0]); + int ret = receive_files(cfg, p[0]); + close(p[0]); + config_delete(cfg); + _exit(ret == -1 ? 0 : 1); + } else { + close(p[0]); + io_set_fds(p[1], p[1]); + send_status(p[1], STATUS_ABORT); + + int status; + waitpid(pid, &status, 0); + close(p[1]); + config_delete(cfg); + + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +void test_server() { + if (!is_running_under_valgrind()) { + test_receive_files_finished(); + test_receive_files_single_file(); + test_receive_files_abort(); + } +} diff --git a/tests/test_server.h b/tests/test_server.h new file mode 100644 index 0000000..5ab72bd --- /dev/null +++ b/tests/test_server.h @@ -0,0 +1,6 @@ +#ifndef TEST_SERVER_H +#define TEST_SERVER_H + +void test_server(); + +#endif diff --git a/tests/test_transport_tcp.c b/tests/test_transport_tcp.c index 516879f..d2b46b9 100644 --- a/tests/test_transport_tcp.c +++ b/tests/test_transport_tcp.c @@ -1,6 +1,8 @@ #include "test_transport_tcp.h" +#include "protocol.h" #include "test_utils.h" #include "transport_tcp.h" +#include #include static void test_server_create_ephemeral() { @@ -35,9 +37,49 @@ static void test_client_delete_null() { client_delete(c); } +/* Test tcp_set_timeouts with valid values */ +static void test_tcp_set_timeouts() { + tcp_set_timeouts(0, 0); + tcp_set_timeouts(60, 20); + tcp_set_timeouts(-1, -1); + EXPECT_TRUE(true); +} + +/* Test client_connect with an invalid host (should fail gracefully) */ +static void test_client_connect_invalid_host() { + Client* c = client_create(); + EXPECT_NOT_NULL(c); + bool ok = client_connect(c, "10.255.255.1", 9999); + EXPECT_FALSE(ok); + client_disconnect(c); + client_delete(c); +} + +/* Test server_delete with double-delete safety */ +static void test_server_delete_double() { + Server* s = server_create(0); + EXPECT_NOT_NULL(s); + server_delete(&s); + EXPECT_NULL(s); + server_delete(&s); + EXPECT_NULL(s); +} + +/* Test client_disconnect then client_delete */ +static void test_client_disconnect_delete() { + Client* c = client_create(); + EXPECT_NOT_NULL(c); + client_disconnect(c); + client_delete(c); +} + void test_transport_tcp() { test_server_create_ephemeral(); test_server_delete_null(); test_client_create(); test_client_delete_null(); + test_tcp_set_timeouts(); + test_client_connect_invalid_host(); + test_server_delete_double(); + test_client_disconnect_delete(); } diff --git a/tests/test_transport_tls.c b/tests/test_transport_tls.c index 657732d..fa74c81 100644 --- a/tests/test_transport_tls.c +++ b/tests/test_transport_tls.c @@ -1,7 +1,10 @@ #include "test_transport_tls.h" +#include "protocol.h" #include "test_utils.h" #include "transport_tcp.h" #include "transport_tls.h" +#include +#include static void test_tls_global_init() { bool ok = tls_global_init(); @@ -18,7 +21,30 @@ static void test_server_create_tls_without_certs() { EXPECT_NULL(s); } +/* Test client_connect_tls with no server listening (should fail gracefully) */ +static void test_client_connect_tls_fail() { + Client* c = client_create(); + EXPECT_NOT_NULL(c); + bool ok = client_connect_tls(c, "127.0.0.1", 1, NULL, NULL, NULL); + EXPECT_FALSE(ok); + client_disconnect(c); + client_delete(c); +} + +/* Test server_create_tls with empty cert paths (should fail gracefully) */ +static void test_server_create_tls_empty_certs() { + Server* s = server_create(0); + EXPECT_NOT_NULL(s); + bool ok = server_create_tls(s, "", "", NULL); + EXPECT_FALSE(ok); + EXPECT_NULL(s->ssl_ctx); + server_delete(&s); + EXPECT_NULL(s); +} + void test_transport_tls() { test_tls_global_init(); test_server_create_tls_without_certs(); + test_client_connect_tls_fail(); + test_server_create_tls_empty_certs(); }