#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Unity setUp/tearDown */ void setUp(void) { /* no-op */ } void tearDown(void) { /* no-op */ } /* Helper: read all from fd into malloc'd buffer, return NUL-terminated string */ static char *read_all_into_string(int fd) { size_t cap = 1024; size_t len = 0; char *buf = (char *)malloc(cap); if (!buf) return NULL; for (;;) { if (len + 512 > cap) { size_t ncap = cap * 2; char *nbuf = (char *)realloc(buf, ncap); if (!nbuf) { free(buf); return NULL; } cap = ncap; buf = nbuf; } ssize_t n = read(fd, buf + len, 512); if (n < 0) { if (errno == EINTR) continue; break; } if (n == 0) break; len += (size_t)n; } buf[len] = '\0'; return buf; } /* Helper: capture stderr while executing a callback that writes to stderr. */ static char *capture_stderr_and_call(void (*cb)(void)) { int pipefd[2]; if (pipe(pipefd) != 0) { return NULL; } int saved_stderr = dup(STDERR_FILENO); if (saved_stderr < 0) { close(pipefd[0]); close(pipefd[1]); return NULL; } /* Redirect stderr to pipe write end */ if (dup2(pipefd[1], STDERR_FILENO) < 0) { close(pipefd[0]); close(pipefd[1]); close(saved_stderr); return NULL; } /* We can close this extra descriptor; fd 2 now points to the write end */ close(pipefd[1]); /* Call the provided function that triggers output to stderr */ cb(); /* Ensure all stdio buffers to stderr are flushed before restoring */ fflush(stderr); /* Restore stderr */ dup2(saved_stderr, STDERR_FILENO); close(saved_stderr); /* Read captured output */ char *out = read_all_into_string(pipefd[0]); close(pipefd[0]); return out; } /* Simple substring check */ static bool str_contains(const char *haystack, const char *needle) { if (!haystack || !needle) return false; return strstr(haystack, needle) != NULL; } /* Access the static variable progress_len and static function diagnose directly, since this test file is included in the same translation unit as dd.c. */ extern int progress_len; /* declared in dd.c as static; available via inclusion */ /* diagnose is defined earlier in the same translation unit */ /* Helper to call diagnose with varargs via a closure-like wrapper */ static int g_errnum; static const char *g_fmt; static const char *g_sarg; static int g_iarg; static void call_diagnose_s_i(void) { diagnose(g_errnum, g_fmt, g_sarg, g_iarg); } static void call_diagnose_s(void) { diagnose(g_errnum, g_fmt, g_sarg); } static void call_diagnose_literal(void) { diagnose(g_errnum, "Literal message"); } void test_diagnose_inserts_newline_when_progress_pending(void) { progress_len = 5; /* simulate an in-progress status line */ g_errnum = 0; g_fmt = "Test %s"; g_sarg = "NL"; char *out = capture_stderr_and_call(call_diagnose_s); TEST_ASSERT_NOT_NULL(out); /* First character should be a newline due to pending progress */ TEST_ASSERT_TRUE_MESSAGE(out[0] == '\n', "diagnose should prepend a newline when progress_len>0"); /* It should contain our formatted message */ TEST_ASSERT_TRUE(str_contains(out, "Test NL")); /* progress_len must reset to 0 */ TEST_ASSERT_EQUAL_INT(0, progress_len); free(out); } void test_diagnose_no_leading_newline_without_progress(void) { progress_len = 0; g_errnum = 0; g_fmt = "Hello %s"; g_sarg = "World"; char *out = capture_stderr_and_call(call_diagnose_s); TEST_ASSERT_NOT_NULL(out); /* Should not start with a newline when no progress pending */ TEST_ASSERT_TRUE_MESSAGE(out[0] != '\n', "diagnose should not prepend newline when progress_len==0"); TEST_ASSERT_TRUE(str_contains(out, "Hello World")); TEST_ASSERT_EQUAL_INT(0, progress_len); free(out); } void test_diagnose_includes_errno_message_when_errnum_nonzero(void) { progress_len = 0; g_errnum = EINVAL; g_fmt = "Bad option %s"; g_sarg = "--foo"; const char *errtxt = strerror(g_errnum); char *out = capture_stderr_and_call(call_diagnose_s); TEST_ASSERT_NOT_NULL(out); /* Check our message passed through */ TEST_ASSERT_TRUE(str_contains(out, "Bad option --foo")); /* Check errno text is included (localized string acceptable) */ TEST_ASSERT_TRUE_MESSAGE(str_contains(out, errtxt), "diagnose should include strerror text when errnum != 0"); free(out); } void test_diagnose_varargs_formatting_with_int_and_string(void) { progress_len = 0; g_errnum = 0; g_fmt = "Value:%d;Name:%s"; g_sarg = "Z"; g_iarg = 123; char *out = capture_stderr_and_call(call_diagnose_s_i); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(str_contains(out, "Value:123;Name:Z")); free(out); } void test_diagnose_resets_progress_len_even_on_simple_message(void) { progress_len = 2; g_errnum = 0; char *out = capture_stderr_and_call(call_diagnose_literal); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_INT(0, progress_len); TEST_ASSERT_TRUE(str_contains(out, "Literal message")); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_diagnose_inserts_newline_when_progress_pending); RUN_TEST(test_diagnose_no_leading_newline_without_progress); RUN_TEST(test_diagnose_includes_errno_message_when_errnum_nonzero); RUN_TEST(test_diagnose_varargs_formatting_with_int_and_string); RUN_TEST(test_diagnose_resets_progress_len_even_on_simple_message); return UNITY_END(); }