#include "../../unity/unity.h" #include #include #include #include #include #include #include /* Unity hooks */ void setUp(void) { /* Reset relevant globals to safe defaults between tests */ in_stream = NULL; have_read_stdin = false; /* Preserve other program globals at their defaults; set per-test as needed */ } void tearDown(void) { /* Ensure any open stream is closed to not leak across tests */ if (in_stream) { check_and_close(0); } } /* Helper: create a temporary file with provided content; returns heap-allocated path */ static char* create_temp_file_with_content(const char* content) { const char* tmpdir = getenv("TMPDIR"); if (!tmpdir || !*tmpdir) tmpdir = "/tmp"; size_t need = strlen(tmpdir) + 1 + strlen("od_utXXXXXX") + 1; char* tmpl = (char*)malloc(need); TEST_ASSERT_NOT_NULL(tmpl); snprintf(tmpl, need, "%s/%s", tmpdir, "od_utXXXXXX"); int fd = mkstemp(tmpl); TEST_ASSERT_MESSAGE(fd >= 0, "mkstemp failed creating temporary file"); ssize_t len = (ssize_t)strlen(content); if (len > 0) { ssize_t w = write(fd, content, (size_t)len); TEST_ASSERT_EQUAL_INT64((int64_t)len, (int64_t)w); } close(fd); return tmpl; /* caller must unlink() and free() */ } /* Helper: initialize reading from first file 'path_cur', with remaining files list 'rest' */ static void init_stream_and_file_list(const char* path_cur, const char* const* rest) { /* Open current file and position file_list to the next (so file_list[-1] is current) */ FILE* f = fopen(path_cur, (O_BINARY ? "rb" : "r")); TEST_ASSERT_NOT_NULL_MESSAGE(f, "Failed to open initial file"); in_stream = f; input_filename = path_cur; /* Build a contiguous array: arr[0] = current, arr[1..k] = rest..., arr[k+1] = NULL. Then set file_list = &arr[1], so file_list[-1] == current. */ /* Count rest length */ size_t k = 0; if (rest) { while (rest[k] != NULL) k++; } const char** arr = (const char**)malloc((k + 2) * sizeof(const char*)); TEST_ASSERT_NOT_NULL(arr); arr[0] = path_cur; for (size_t i = 0; i < k; i++) arr[i + 1] = rest[i]; arr[k + 1] = NULL; file_list = arr + 1; /* Note: we malloc'd the array. We'll free it in the test after we're done using file_list. */ } /* Helper: free the malloc'd file_list array created by init_stream_and_file_list */ static void free_file_list_array(void) { /* file_list points into arr + 1; free the base pointer (file_list - 1) */ if (file_list) { const char** base = file_list - 1; free((void*)base); file_list = NULL; } } /* Test 1: read exact N bytes from a single file */ static void test_read_block_single_file_exact(void) { bytes_per_block = 16; /* ensure n <= bytes_per_block */ char* p1 = create_temp_file_with_content("ABCDEFGH"); const char* rest[] = { NULL }; init_stream_and_file_list(p1, rest); char buf[16]; memset(buf, 0, sizeof(buf)); idx_t got = 0; bool ok = read_block(4, buf, &got); TEST_ASSERT_TRUE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)4, (uint64_t)got); TEST_ASSERT_EQUAL_INT(0, memcmp(buf, "ABCD", 4)); /* Cleanup */ check_and_close(0); free_file_list_array(); unlink(p1); free(p1); } /* Test 2: read across file boundary into the next file */ static void test_read_block_cross_file_boundary(void) { bytes_per_block = 16; char* p1 = create_temp_file_with_content("ABC"); /* 3 bytes */ char* p2 = create_temp_file_with_content("DEFG"); /* 4 bytes */ const char* rest[] = { p2, NULL }; init_stream_and_file_list(p1, rest); char buf[16]; memset(buf, 0, sizeof(buf)); idx_t got = 0; /* Request 5 bytes: should read 3 from p1 and 2 from p2 */ bool ok = read_block(5, buf, &got); TEST_ASSERT_TRUE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)5, (uint64_t)got); TEST_ASSERT_EQUAL_INT(0, memcmp(buf, "ABCDE", 5)); /* Subsequent call should continue from p2 (offset 2), reading next 2 bytes "FG" */ char buf2[8]; memset(buf2, 0, sizeof(buf2)); idx_t got2 = 0; ok = read_block(2, buf2, &got2); TEST_ASSERT_TRUE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)2, (uint64_t)got2); TEST_ASSERT_EQUAL_INT(0, memcmp(buf2, "FG", 2)); /* Cleanup */ check_and_close(0); free_file_list_array(); unlink(p1); unlink(p2); free(p1); free(p2); } /* Test 3: partial read at end-of-input and subsequent call returns zero bytes */ static void test_read_block_partial_then_eof_then_zero_on_next_call(void) { bytes_per_block = 16; char* p1 = create_temp_file_with_content("HI"); /* 2 bytes */ const char* rest[] = { NULL }; init_stream_and_file_list(p1, rest); char buf[16]; memset(buf, 0, sizeof(buf)); idx_t got = 0; /* Request 5 bytes: only 2 available in total */ bool ok = read_block(5, buf, &got); TEST_ASSERT_TRUE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)2, (uint64_t)got); TEST_ASSERT_EQUAL_INT(0, memcmp(buf, "HI", 2)); /* Next call: in_stream should be NULL; expect 0 bytes returned, ok==true */ char buf2[8]; memset(buf2, 0xAA, sizeof(buf2)); /* fill to detect modification */ idx_t got2 = 0; ok = read_block(3, buf2, &got2); TEST_ASSERT_TRUE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)0, (uint64_t)got2); /* Do not assert on buf2 contents, only that it wasn't written (contract says don't modify block) */ /* Cleanup */ /* in_stream is already NULL after previous call, but call to close is safe */ check_and_close(0); free_file_list_array(); unlink(p1); free(p1); } /* Test 4: failure to open the next file causes read_block to return false, while still returning the bytes already read */ static void test_read_block_next_file_open_failure(void) { bytes_per_block = 16; char* p1 = create_temp_file_with_content("A"); /* 1 byte */ /* Nonexistent path; choose a path unlikely to exist */ const char* bad_path = "/no_such_od_test_file_path_hopefully_123456789"; const char* rest[] = { bad_path, NULL }; init_stream_and_file_list(p1, rest); char buf[8]; memset(buf, 0, sizeof(buf)); idx_t got = 0; /* Request 2 bytes: will read 1 from p1; next file fails to open => ok == false */ bool ok = read_block(2, buf, &got); TEST_ASSERT_FALSE(ok); TEST_ASSERT_EQUAL_UINT64((uint64_t)1, (uint64_t)got); TEST_ASSERT_EQUAL_INT(0, memcmp(buf, "A", 1)); /* Cleanup (in_stream should be NULL by now) */ check_and_close(0); free_file_list_array(); unlink(p1); free(p1); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_read_block_single_file_exact); RUN_TEST(test_read_block_cross_file_boundary); RUN_TEST(test_read_block_partial_then_eof_then_zero_on_next_call); RUN_TEST(test_read_block_next_file_open_failure); return UNITY_END(); }