#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include /* The test file is included directly into dd.c, so all static symbols from dd (like process_signals, info_signal_count, status_level, progress_len, etc.) are visible here. We must not redeclare them. */ static char *capture_stderr_begin(int *saved_fd, int pipefd[2]) { if (pipe(pipefd) != 0) return NULL; fflush(stderr); *saved_fd = dup(STDERR_FILENO); if (*saved_fd < 0) return NULL; if (dup2(pipefd[1], STDERR_FILENO) < 0) return NULL; close(pipefd[1]); /* writer now at fd 2 */ return (char *)1; /* non-NULL indicates success */ } static char *capture_stderr_end(int saved_fd, int pipefd[2]) { /* Restore stderr */ fflush(stderr); if (dup2(saved_fd, STDERR_FILENO) >= 0) { close(saved_fd); } /* Read all from pipefd[0] */ char *buf = NULL; size_t cap = 0; size_t len = 0; for (;;) { char tmp[256]; ssize_t n = read(pipefd[0], tmp, sizeof(tmp)); if (n <= 0) break; if (len + (size_t)n + 1 > cap) { size_t ncap = cap ? cap * 2 : 512; while (ncap < len + (size_t)n + 1) ncap *= 2; char *nbuf = (char *)realloc(buf, ncap); if (!nbuf) { free(buf); buf = NULL; break; } buf = nbuf; cap = ncap; } memcpy(buf + len, tmp, (size_t)n); len += (size_t)n; buf[len] = '\0'; } close(pipefd[0]); if (!buf) { buf = (char *)malloc(1); if (buf) buf[0] = '\0'; } return buf; } void setUp(void) { /* Ensure no pending interrupt and default state before each test. */ /* These are static globals from dd.c; accessible here. */ extern sig_atomic_t volatile interrupt_signal; extern sig_atomic_t volatile info_signal_count; extern int status_level; extern int progress_len; interrupt_signal = 0; info_signal_count = 0; status_level = 3; /* STATUS_DEFAULT, will be overridden in tests as needed */ progress_len = 0; } void tearDown(void) { /* Nothing to clean */ } /* Test that info signals are all processed (count decremented to 0) and that no output occurs when status_level == STATUS_NONE. */ void test_process_signals_info_only_decrements_to_zero(void) { extern void process_signals(void); extern sig_atomic_t volatile info_signal_count; extern sig_atomic_t volatile interrupt_signal; extern int status_level; info_signal_count = 5; interrupt_signal = 0; status_level = 1; /* STATUS_NONE: suppress output */ process_signals(); TEST_ASSERT_EQUAL_INT_MESSAGE(0, (int)info_signal_count, "info_signal_count should be 0 after processing"); TEST_ASSERT_EQUAL_INT_MESSAGE(0, (int)interrupt_signal, "interrupt_signal should remain 0"); } /* Test that when info_signal_count is zero, process_signals is a no-op and produces no output (use STATUS_NOXFER to ensure print_stats would print if called). */ void test_process_signals_no_info_no_output(void) { extern void process_signals(void); extern sig_atomic_t volatile info_signal_count; extern int status_level; info_signal_count = 0; status_level = 2; /* STATUS_NOXFER: would print records if called */ int saved; int pf[2]; TEST_ASSERT_NOT_NULL_MESSAGE(capture_stderr_begin(&saved, pf), "Failed to start capturing stderr"); process_signals(); char *out = capture_stderr_end(saved, pf); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_UINT_MESSAGE(0, (unsigned)strlen(out), "No output expected when no info signals"); free(out); } /* Test that info signals cause print_stats to be called the correct number of times by checking the number of newline-terminated lines emitted to stderr when status_level == STATUS_NOXFER (which prints exactly two lines per call). */ void test_process_signals_info_only_prints_expected_lines(void) { extern void process_signals(void); extern sig_atomic_t volatile info_signal_count; extern int status_level; extern int progress_len; info_signal_count = 2; status_level = 2; /* STATUS_NOXFER */ progress_len = 0; /* ensure no leading newline from prior progress */ int saved; int pf[2]; TEST_ASSERT_NOT_NULL_MESSAGE(capture_stderr_begin(&saved, pf), "Failed to start capturing stderr"); process_signals(); char *out = capture_stderr_end(saved, pf); TEST_ASSERT_NOT_NULL(out); /* Count newlines; expect 2 lines per info signal -> 4 newlines total. */ size_t nl = 0; for (char *p = out; *p; ++p) if (*p == '\n') nl++; TEST_ASSERT_EQUAL_UINT_MESSAGE(4, nl, "Expected exactly two lines per info signal (total 4 newlines)"); free(out); } /* Test that if there was a pending progress line (progress_len > 0), print_stats first emits a newline before the records, when signaled. We assert the first byte of captured stderr is a newline. */ void test_process_signals_progress_line_newline_emitted_first(void) { extern void process_signals(void); extern sig_atomic_t volatile info_signal_count; extern int status_level; extern int progress_len; info_signal_count = 1; status_level = 2; /* STATUS_NOXFER */ progress_len = 7; /* simulate in-progress status line */ int saved; int pf[2]; TEST_ASSERT_NOT_NULL_MESSAGE(capture_stderr_begin(&saved, pf), "Failed to start capturing stderr"); process_signals(); char *out = capture_stderr_end(saved, pf); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE_MESSAGE(out[0] == '\n', "Expected leading newline when progress_len > 0"); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_process_signals_info_only_decrements_to_zero); RUN_TEST(test_process_signals_no_info_no_output); RUN_TEST(test_process_signals_info_only_prints_expected_lines); RUN_TEST(test_process_signals_progress_line_newline_emitted_first); return UNITY_END(); }