#include "../../unity/unity.h" #include #include #include #include #include #include /* The test file is included into the same translation unit as dd.c, so we can access file-scope static variables and functions directly. */ /* Helper to capture and restore stderr output. */ static FILE *cap_file = NULL; static int saved_stderr_fd = -1; static void start_capture_stderr(void) { fflush(stderr); saved_stderr_fd = dup(STDERR_FILENO); TEST_ASSERT_TRUE_MESSAGE(saved_stderr_fd >= 0, "dup(STDERR_FILENO) failed"); cap_file = tmpfile(); TEST_ASSERT_NOT_NULL_MESSAGE(cap_file, "tmpfile() failed"); int cap_fd = fileno(cap_file); TEST_ASSERT_TRUE_MESSAGE(cap_fd >= 0, "fileno(tmpfile) failed"); int rc = dup2(cap_fd, STDERR_FILENO); TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "dup2 to redirect stderr failed"); } static char *finish_capture_stderr(void) { fflush(stderr); TEST_ASSERT_NOT_NULL_MESSAGE(cap_file, "No capture active"); long size; if (fseek(cap_file, 0, SEEK_END) != 0) TEST_FAIL_MESSAGE("fseek end failed"); size = ftell(cap_file); if (size < 0) TEST_FAIL_MESSAGE("ftell failed"); if (fseek(cap_file, 0, SEEK_SET) != 0) TEST_FAIL_MESSAGE("fseek set failed"); char *buf = (char *)malloc((size_t)size + 1); TEST_ASSERT_NOT_NULL_MESSAGE(buf, "malloc failed"); size_t n = fread(buf, 1, (size_t)size, cap_file); buf[n] = '\0'; /* Restore stderr */ int rc = dup2(saved_stderr_fd, STDERR_FILENO); TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "dup2 restore stderr failed"); close(saved_stderr_fd); saved_stderr_fd = -1; fclose(cap_file); cap_file = NULL; return buf; } /* Count newline characters in a C string. */ static int count_newlines(const char *s) { int c = 0; for (; *s; ++s) if (*s == '\n') ++c; return c; } /* Reset the global state relevant to print_stats. */ static void reset_print_stats_state(void) { /* status_level enum values are visible from included dd.c */ status_level = STATUS_DEFAULT; progress_len = 0; r_full = 0; r_partial = 0; w_full = 0; w_partial = 0; r_truncate = 0; w_bytes = 0; reported_w_bytes = -1; } void setUp(void) { setlocale(LC_ALL, "C"); reset_print_stats_state(); } void tearDown(void) { /* Ensure capture is not left active between tests */ if (cap_file) { char *discard = finish_capture_stderr(); free(discard); } } /* Test: STATUS_NONE -> no output and progress_len unchanged */ void test_print_stats_status_none_no_output_no_progress_reset(void) { status_level = STATUS_NONE; progress_len = 5; /* simulate prior progress output */ r_full = 1; r_partial = 2; w_full = 3; w_partial = 4; r_truncate = 1; start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); TEST_ASSERT_EQUAL_INT(0, (int)strlen(out)); TEST_ASSERT_EQUAL_INT(5, progress_len); /* unchanged */ free(out); } /* Test: basic records lines with STATUS_NOXFER and no truncate */ void test_print_stats_basic_records_output_no_truncate(void) { status_level = STATUS_NOXFER; progress_len = 0; r_full = 2; r_partial = 3; w_full = 4; w_partial = 5; r_truncate = 0; start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); const char *expected = "2+3 records in\n4+5 records out\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test: truncate singular message */ void test_print_stats_truncate_singular(void) { status_level = STATUS_NOXFER; r_truncate = 1; start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); const char *expected = "0+0 records in\n0+0 records out\n1 truncated record\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test: truncate plural message */ void test_print_stats_truncate_plural(void) { status_level = STATUS_NOXFER; r_truncate = 2; start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); const char *expected = "0+0 records in\n0+0 records out\n2 truncated records\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test: when progress_len > 0 and not STATUS_NONE, prepend newline and reset progress_len */ void test_print_stats_progress_len_newline_and_reset(void) { status_level = STATUS_NOXFER; /* avoid print_xfer_stats */ progress_len = 10; /* pending progress line */ start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); TEST_ASSERT_TRUE_MESSAGE(out[0] == '\n', "Expected leading newline when progress_len > 0"); const char *rest = out + 1; const char *expected_rest = "0+0 records in\n0+0 records out\n"; TEST_ASSERT_EQUAL_STRING(expected_rest, rest); TEST_ASSERT_EQUAL_INT(0, progress_len); /* reset */ free(out); } /* Test: non-NOXFER status invokes print_xfer_stats (observed via reported_w_bytes) and adds a third line */ void test_print_stats_calls_print_xfer_stats_and_updates_reported(void) { status_level = STATUS_DEFAULT; /* triggers print_xfer_stats */ progress_len = 0; r_truncate = 0; w_bytes = 12345; reported_w_bytes = -1; /* Make start_time close to now to keep print_xfer_stats deterministic enough. We don't assert the exact text, only newline count and side effect. */ start_time = gethrxtime(); start_capture_stderr(); print_stats(); char *out = finish_capture_stderr(); TEST_ASSERT_EQUAL_INT64((int64_t)w_bytes, (int64_t)reported_w_bytes); int nl = count_newlines(out); TEST_ASSERT_GREATER_OR_EQUAL_INT(3, nl); /* at least records-in, records-out, and xfer stats */ free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_print_stats_status_none_no_output_no_progress_reset); RUN_TEST(test_print_stats_basic_records_output_no_truncate); RUN_TEST(test_print_stats_truncate_singular); RUN_TEST(test_print_stats_truncate_plural); RUN_TEST(test_print_stats_progress_len_newline_and_reset); RUN_TEST(test_print_stats_calls_print_xfer_stats_and_updates_reported); return UNITY_END(); }