#include "../../unity/unity.h" #include #include #include #include #include #include #include /* We rely on inclusion into the od translation unit so we can access internal globals and functions directly. */ /* Prototypes for internal helpers used from od source (visible here due to inclusion): */ static bool dump_strings(void); static bool open_next_file(void); /* Internal globals from od we will set up in tests. */ extern int address_base; /* default 8 */ extern int address_pad_len; /* default 7 */ extern bool flag_dump_strings; /* controls buffering path in open_next_file */ extern intmax_t n_bytes_to_skip; /* starting address */ extern intmax_t end_offset; /* termination offset or -1 */ extern idx_t string_min; /* minimum printable run length */ extern FILE *in_stream; /* current input stream */ extern char const *const *file_list; /* list of file names */ extern void (*format_address)(intmax_t, char); static void format_address_std (intmax_t, char); /* from od */ /* ---------- Test utilities ---------- */ /* Create a temporary file with the given bytes. Returns malloc'd path string. */ static char *testutil_make_temp_file(const unsigned char *data, size_t len) { char tmpl[] = "od_test_XXXXXX"; int fd = mkstemp(tmpl); TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp failed"); ssize_t written = 0; while ((size_t)written < len) { ssize_t w = write(fd, data + written, len - written); TEST_ASSERT_TRUE_MESSAGE(w >= 0, "write failed"); written += w; } int rc = close(fd); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "close temp file failed"); /* Return a copy so caller can free after unlink */ char *ret = (char *)malloc(strlen(tmpl) + 1); TEST_ASSERT_NOT_NULL(ret); strcpy(ret, tmpl); return ret; } /* Build a heap-allocated, NULL-terminated file list for od's globals. */ static const char **testutil_build_file_list(char **paths, size_t n) { const char **list = (const char **)malloc((n + 1) * sizeof(*list)); TEST_ASSERT_NOT_NULL(list); for (size_t i = 0; i < n; i++) list[i] = paths[i]; list[n] = NULL; return list; } /* Capture stdout while running dump_strings. Return malloc'd buffer with output. */ static char *testutil_capture_dump_strings(bool *ok_out) { /* Save current stdout */ int saved_fd = dup(fileno(stdout)); TEST_ASSERT_TRUE_MESSAGE(saved_fd >= 0, "dup(stdout) failed"); FILE *cap = tmpfile(); TEST_ASSERT_NOT_NULL_MESSAGE(cap, "tmpfile failed"); int cap_fd = fileno(cap); TEST_ASSERT_TRUE_MESSAGE(cap_fd >= 0, "fileno(tmpfile) failed"); fflush(stdout); int rc = dup2(cap_fd, fileno(stdout)); TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "dup2 to stdout failed"); /* Call function under test without using Unity assertions while redirected */ bool ok = dump_strings(); fflush(stdout); /* Restore stdout before doing any TEST_ASSERTs */ rc = dup2(saved_fd, fileno(stdout)); TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "restore stdout failed"); close(saved_fd); /* Read captured output */ long sz; int fseek_rc = fseek(cap, 0, SEEK_END); TEST_ASSERT_EQUAL_INT_MESSAGE(0, fseek_rc, "fseek end failed"); sz = ftell(cap); TEST_ASSERT_TRUE_MESSAGE(sz >= 0, "ftell failed"); fseek_rc = fseek(cap, 0, SEEK_SET); TEST_ASSERT_EQUAL_INT_MESSAGE(0, fseek_rc, "fseek set failed"); char *buf = (char *)malloc((size_t)sz + 1); TEST_ASSERT_NOT_NULL(buf); size_t rd = fread(buf, 1, (size_t)sz, cap); TEST_ASSERT_EQUAL_UINT_MESSAGE((size_t)sz, rd, "fread captured output failed"); buf[sz] = '\0'; fclose(cap); if (ok_out) *ok_out = ok; return buf; } /* Common setup before each test */ void setUp(void) { /* Reset key globals to known defaults for each test */ in_stream = NULL; n_bytes_to_skip = 0; end_offset = -1; string_min = 0; flag_dump_strings = true; /* ensure open_next_file doesn't force unbuffered */ address_base = 8; address_pad_len = 7; format_address = format_address_std; } void tearDown(void) { /* Nothing to clean globally here */ } /* ---------- Tests ---------- */ /* Basic: single NUL-terminated string, length >= string_min */ void test_dump_strings_basic_nul_terminated(void) { const unsigned char data[] = { 'a','b','c','\0' }; char *path = testutil_make_temp_file(data, sizeof data); char *paths_arr[] = { path }; const char **list = testutil_build_file_list(paths_arr, 1); file_list = list; /* Configure parameters */ string_min = 3; n_bytes_to_skip = 0; end_offset = -1; /* Open first file */ bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("0000000 abc\n", out); /* Cleanup */ free(out); unlink(path); free(path); free((void*)list); } /* Strings shorter than string_min are ignored */ void test_dump_strings_minlen_not_met(void) { const unsigned char data[] = { 'a','b','\0' }; /* length 2 before NUL */ char *path = testutil_make_temp_file(data, sizeof data); char *paths_arr[] = { path }; const char **list = testutil_build_file_list(paths_arr, 1); file_list = list; string_min = 3; n_bytes_to_skip = 0; end_offset = -1; bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("", out); free(out); unlink(path); free(path); free((void*)list); } /* End offset termination without NUL, string should print if >= string_min */ void test_dump_strings_end_offset_termination(void) { const unsigned char data[] = { 'a','b','c','d','e','f' }; /* 6 bytes */ char *path = testutil_make_temp_file(data, sizeof data); char *paths_arr[] = { path }; const char **list = testutil_build_file_list(paths_arr, 1); file_list = list; string_min = 3; n_bytes_to_skip = 0; end_offset = 6; /* terminate exactly after 6 bytes */ bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("0000000 abcdef\n", out); free(out); unlink(path); free(path); free((void*)list); } /* Non-zero starting address via n_bytes_to_skip affects printed address */ void test_dump_strings_start_offset_address(void) { const unsigned char data[] = { 'a','b','c','\0' }; char *path = testutil_make_temp_file(data, sizeof data); char *paths_arr[] = { path }; const char **list = testutil_build_file_list(paths_arr, 1); file_list = list; string_min = 3; n_bytes_to_skip = 2; /* start address offset */ end_offset = -1; bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("0000002 abc\n", out); free(out); unlink(path); free(path); free((void*)list); } /* String spanning across two files: "hello" + "\0" */ void test_dump_strings_across_files(void) { const unsigned char data1[] = { 'h','e','l','l','o' }; const unsigned char data2[] = { '\0' }; char *path1 = testutil_make_temp_file(data1, sizeof data1); char *path2 = testutil_make_temp_file(data2, sizeof data2); char *paths_arr[] = { path1, path2 }; const char **list = testutil_build_file_list(paths_arr, 2); file_list = list; string_min = 3; n_bytes_to_skip = 0; end_offset = -1; bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("0000000 hello\n", out); free(out); unlink(path1); unlink(path2); free(path1); free(path2); free((void*)list); } /* Multiple strings within a single file */ void test_dump_strings_multiple_strings(void) { const unsigned char data[] = { 'a','b','c','\0', 'x','y','z','\0' }; char *path = testutil_make_temp_file(data, sizeof data); char *paths_arr[] = { path }; const char **list = testutil_build_file_list(paths_arr, 1); file_list = list; string_min = 3; n_bytes_to_skip = 0; end_offset = -1; bool open_ok = open_next_file(); TEST_ASSERT_TRUE(open_ok); bool ok = false; char *out = testutil_capture_dump_strings(&ok); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("0000000 abc\n0000004 xyz\n", out); free(out); unlink(path); free(path); free((void*)list); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_dump_strings_basic_nul_terminated); RUN_TEST(test_dump_strings_minlen_not_met); RUN_TEST(test_dump_strings_end_offset_termination); RUN_TEST(test_dump_strings_start_offset_address); RUN_TEST(test_dump_strings_across_files); RUN_TEST(test_dump_strings_multiple_strings); return UNITY_END(); }