#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* The test file is included within dd.c, so we can access its internal symbols and helper functions/state directly (e.g., cache_round, globals). */ /* Helper: get system page size into the program's global page_size. */ static void tests_set_pagesize(void) { if (page_size == 0) { #if defined(_SC_PAGESIZE) long ps = sysconf(_SC_PAGESIZE); if (ps <= 0) ps = 4096; page_size = (idx_t) ps; #else page_size = 4096; #endif } } /* Helper: reset cache_round pending for a given fd to 0 without invoking invalidate_cache (so no syscalls or output_offset changes). */ static void tests_reset_pending(int fd) { off_t rem = cache_round(fd, 0); if (rem != 0) { /* Bring the accumulated length up to the next IO_BUFSIZE multiple. */ off_t need = (off_t)IO_BUFSIZE - rem; (void) cache_round(fd, need); } } /* Helper: create an anonymous temporary file, sized to multiple pages. */ static int tests_make_temp_file(off_t size) { char tmpl[] = "/tmp/dd_invalidate_cache_testXXXXXX"; int fd = mkstemp(tmpl); if (fd >= 0) { /* Unlink so it is removed when closed. */ unlink(tmpl); if (size > 0) { if (ftruncate(fd, size) != 0) { /* Best effort; ignore error. */ } } } return fd; } /* Helper: redirect stdout to fd; returns saved original stdout fd via *saved. */ static int tests_redirect_stdout(int new_fd, int *saved) { int s = dup(STDOUT_FILENO); if (s < 0) return -1; if (dup2(new_fd, STDOUT_FILENO) < 0) { close(s); return -1; } *saved = s; return 0; } /* Helper: restore stdout from saved fd. */ static void tests_restore_stdout(int saved) { if (saved >= 0) { (void) dup2(saved, STDOUT_FILENO); close(saved); } } void setUp(void) { tests_set_pagesize(); /* Provide sane defaults for globals used by invalidate_cache */ i_nocache_eof = false; o_nocache_eof = false; input_seekable = true; input_offset = 0; /* Reset rounding state for both stdin and stdout. */ tests_reset_pending(STDIN_FILENO); tests_reset_pending(STDOUT_FILENO); } void tearDown(void) { /* Nothing persistent to clean. */ } /* 1) Small len on stdin should early-return true (no advisory), and advance internal pending by len. */ void test_invalidate_cache_stdin_small_len_no_advise(void) { tests_reset_pending(STDIN_FILENO); input_seekable = true; input_offset = 12345; /* Arbitrary non-negative; shouldn't be used here. */ off_t before = cache_round(STDIN_FILENO, 0); TEST_ASSERT_EQUAL_INT64(0, (long long)before); bool ret = invalidate_cache(STDIN_FILENO, (off_t)1); TEST_ASSERT_TRUE(ret); off_t after = cache_round(STDIN_FILENO, 0); TEST_ASSERT_EQUAL_INT64(1, (long long)after); } /* 2) len==0, no pending, and nocache_eof==false => early return true. */ void test_invalidate_cache_stdout_noop_eof_when_nocache_eof_false(void) { tests_reset_pending(STDOUT_FILENO); o_nocache_eof = false; bool ret = invalidate_cache(STDOUT_FILENO, (off_t)0); TEST_ASSERT_TRUE(ret); } /* 3) On stdout backed by a regular file: - accumulate a non-zero pending via a small len (no advisory expected), - with o_nocache_eof=true and len==0, we expect an advisory attempt, returning true if posix_fadvise is available, else false/ENOTSUP, - then perform a full-chunk advisory with len==IO_BUFSIZE. */ void test_invalidate_cache_stdout_eof_pending_and_full_chunk(void) { int tmpfd = tests_make_temp_file((off_t)(page_size * 4)); TEST_ASSERT_MESSAGE(tmpfd >= 0, "Failed to create temporary file"); int saved_out = -1; int redir = tests_redirect_stdout(tmpfd, &saved_out); TEST_ASSERT_MESSAGE(redir == 0, "Failed to redirect stdout"); /* Do not use Unity asserts while stdout is redirected beyond this point. */ bool err = false; const char *err_msg = NULL; /* Ensure pending==0 for stdout. */ tests_reset_pending(STDOUT_FILENO); if (cache_round(STDOUT_FILENO, 0) != 0) { err = true; err_msg = "Pending not reset to 0"; } /* Small len: should early-return true and set pending to IO_BUFSIZE-1. */ bool r_small = invalidate_cache(STDOUT_FILENO, (off_t)(IO_BUFSIZE - 1)); off_t pend1 = cache_round(STDOUT_FILENO, 0); if (!r_small || pend1 != (off_t)(IO_BUFSIZE - 1)) { err = true; err_msg = "Small len did not set pending as expected"; } /* EOF advisory with nocache_eof=true and non-zero pending. */ o_nocache_eof = true; errno = 0; bool r_eof = invalidate_cache(STDOUT_FILENO, (off_t)0); int e_eof = errno; /* Clear pending back to 0 via direct rounding (no advisory). */ (void) cache_round(STDOUT_FILENO, (off_t)1); if (cache_round(STDOUT_FILENO, 0) != 0) { err = true; err_msg = "Failed to clear pending to 0"; } /* Full-chunk advisory. */ errno = 0; bool r_full = invalidate_cache(STDOUT_FILENO, (off_t)IO_BUFSIZE); int e_full = errno; /* Restore stdout before any Unity asserts. */ tests_restore_stdout(saved_out); close(tmpfd); if (err) TEST_FAIL_MESSAGE(err_msg); #if HAVE_POSIX_FADVISE TEST_ASSERT_TRUE_MESSAGE(r_eof, "EOF nocache advisory should succeed (posix_fadvise)"); TEST_ASSERT_TRUE_MESSAGE(r_full, "Full-chunk advisory should succeed (posix_fadvise)"); /* On success, coreutils sets errno to adv_ret (0). */ TEST_ASSERT_EQUAL_INT_MESSAGE(0, e_eof, "errno not 0 after EOF advisory"); TEST_ASSERT_EQUAL_INT_MESSAGE(0, e_full, "errno not 0 after full advisory"); #else TEST_ASSERT_FALSE_MESSAGE(r_eof, "EOF nocache advisory should fail without posix_fadvise"); TEST_ASSERT_FALSE_MESSAGE(r_full, "Full-chunk advisory should fail without posix_fadvise"); TEST_ASSERT_EQUAL_INT_MESSAGE(ENOTSUP, e_eof, "errno should be ENOTSUP without posix_fadvise (EOF)"); TEST_ASSERT_EQUAL_INT_MESSAGE(ENOTSUP, e_full, "errno should be ENOTSUP without posix_fadvise (full)"); #endif } /* 4) stdin not seekable: expect false and errno=ESPIPE when len yields clen>0. */ void test_invalidate_cache_stdin_not_seekable(void) { tests_reset_pending(STDIN_FILENO); input_seekable = false; errno = 0; bool ret = invalidate_cache(STDIN_FILENO, (off_t)IO_BUFSIZE); int e = errno; TEST_ASSERT_FALSE(ret); TEST_ASSERT_EQUAL_INT(ESPIPE, e); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_invalidate_cache_stdin_small_len_no_advise); RUN_TEST(test_invalidate_cache_stdout_noop_eof_when_nocache_eof_false); RUN_TEST(test_invalidate_cache_stdout_eof_pending_and_full_chunk); RUN_TEST(test_invalidate_cache_stdin_not_seekable); return UNITY_END(); }