File size: 6,814 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include "../../unity/unity.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>

/* 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();
}