coreutils / tests /du /tests_for_process_file.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <limits.h>
/* The following globals and functions come from du.c and its includes:
- static bool process_file (FTS *fts, FTSENT *ent);
- struct di_set *di_files; (static in du.c, but we can assign to it)
- di_set_alloc()
- many option globals like opt_all, apparent_size, etc.
- xfts_open(), fts_read(), fts_close(), FTS_xxx flags
- duinfo_init(&tot_dui), tot_dui, prev_level, max_depth, etc.
*/
/* Helpers for filesystem setup/teardown and stdout capture */
static char g_tmpdir[PATH_MAX];
static void reset_du_state_defaults(void)
{
/* Ensure predictable printing and accounting. */
apparent_size = true; /* Use st_size in bytes */
opt_count_all = false;
hash_all = false;
opt_all = false;
opt_nul_terminate_output = false;
print_grand_total = false;
opt_separate_dirs = false;
max_depth = IDX_MAX;
opt_threshold = 0;
human_output_opts = 0;
opt_inodes = false;
opt_time = false;
time_type = time_mtime;
time_style = NULL;
time_format = NULL;
output_block_size = 1; /* bytes */
exclude = NULL;
/* Reset running totals and traversal level. */
duinfo_init(&tot_dui);
prev_level = 0;
/* Fresh hard-link detection set. */
di_files = di_set_alloc();
}
static int ensure_dir(const char *path)
{
if (mkdir(path, 0700) == 0) return 0;
if (errno == EEXIST) return 0;
return -1;
}
static int create_file_with_size(const char *path, size_t nbytes)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) return -1;
if (nbytes > 0) {
/* Write deterministic content */
size_t remaining = nbytes;
char buf[4096];
memset(buf, 'X', sizeof buf);
while (remaining > 0) {
size_t chunk = remaining < sizeof buf ? remaining : sizeof buf;
ssize_t w = write(fd, buf, chunk);
if (w < 0) { close(fd); return -1; }
remaining -= (size_t)w;
}
}
if (fsync(fd) != 0) { /* best-effort */ }
return close(fd);
}
static int remove_tree(const char *root)
{
char *argv[2];
argv[0] = (char *)root;
argv[1] = NULL;
FTS *fts = xfts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
if (!fts) return -1;
int ret = 0;
while (1) {
FTSENT *e = fts_read(fts);
if (!e) break;
switch (e->fts_info) {
case FTS_DP:
if (rmdir(e->fts_path) != 0) ret = -1;
break;
case FTS_F:
case FTS_SL:
case FTS_SLNONE:
case FTS_DEFAULT:
case FTS_NS:
case FTS_NSOK:
case FTS_ERR:
if (unlink(e->fts_path) != 0) ret = -1;
break;
default:
break;
}
}
if (fts_close(fts) != 0) ret = -1;
return ret;
}
/* Simple stdout capture using a temporary FILE backed by an anonymous file */
typedef struct {
int saved_fd;
FILE *tmpf;
} capture_ctx_t;
static int begin_capture_stdout(capture_ctx_t *ctx)
{
fflush(stdout);
ctx->saved_fd = dup(fileno(stdout));
if (ctx->saved_fd < 0) return -1;
ctx->tmpf = tmpfile();
if (!ctx->tmpf) { close(ctx->saved_fd); ctx->saved_fd = -1; return -1; }
if (dup2(fileno(ctx->tmpf), fileno(stdout)) < 0) {
fclose(ctx->tmpf);
close(ctx->saved_fd);
ctx->tmpf = NULL;
ctx->saved_fd = -1;
return -1;
}
return 0;
}
static char *end_capture_stdout(capture_ctx_t *ctx, size_t *out_len)
{
/* Restore stdout first, then read and assert afterwards. */
fflush(stdout);
dup2(ctx->saved_fd, fileno(stdout));
close(ctx->saved_fd);
ctx->saved_fd = -1;
fflush(ctx->tmpf);
long len;
char *buf = NULL;
if (fseek(ctx->tmpf, 0, SEEK_END) == 0) {
len = ftell(ctx->tmpf);
if (len < 0) len = 0;
if (fseek(ctx->tmpf, 0, SEEK_SET) == 0) {
buf = (char *)malloc((size_t)len + 1);
if (buf) {
size_t rd = fread(buf, 1, (size_t)len, ctx->tmpf);
buf[rd] = '\0';
if (out_len) *out_len = rd;
}
}
}
fclose(ctx->tmpf);
ctx->tmpf = NULL;
return buf;
}
/* Unity hooks */
void setUp(void)
{
/* Create a unique temporary directory for each test */
snprintf(g_tmpdir, sizeof g_tmpdir, "/tmp/du_test_%ld_XXXXXX", (long)getpid());
char *p = mkdtemp(g_tmpdir);
TEST_ASSERT_MESSAGE(p != NULL, "mkdtemp failed");
}
void tearDown(void)
{
/* Remove the entire temporary directory tree */
if (g_tmpdir[0]) {
remove_tree(g_tmpdir);
/* The root directory is removed by remove_tree via FTS_DP at the end */
g_tmpdir[0] = '\0';
}
}
/* Tests */
static FTS *open_fts_single(const char *path)
{
char *files[2];
files[0] = (char *)path; /* cast acceptable: xfts_open does not modify string literal */
files[1] = NULL;
return xfts_open(files, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
}
void test_process_file_regular_file_basic(void)
{
reset_du_state_defaults();
char path[PATH_MAX];
snprintf(path, sizeof path, "%s/file_basic.bin", g_tmpdir);
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 7));
FTS *fts = open_fts_single(path);
TEST_ASSERT_NOT_NULL(fts);
FTSENT *ent = fts_read(fts);
TEST_ASSERT_NOT_NULL(ent);
capture_ctx_t cap; memset(&cap, 0, sizeof cap);
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap));
/* Do not use TEST_ASSERT while stdout is redirected */
bool ok = process_file(fts, ent);
size_t out_len = 0;
char *out = end_capture_stdout(&cap, &out_len);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_NOT_NULL(out);
/* Expected exact output: "7\t<path>\n" */
char expected[PATH_MAX + 64];
snprintf(expected, sizeof expected, "7\t%s\n", path);
TEST_ASSERT_EQUAL_STRING(expected, out);
/* Check the grand total increased appropriately */
TEST_ASSERT_EQUAL_UINT64(7u, (unsigned long long)tot_dui.size);
free(out);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
}
void test_process_file_threshold_suppresses_output(void)
{
reset_du_state_defaults();
char path[PATH_MAX];
snprintf(path, sizeof path, "%s/file_thresh.bin", g_tmpdir);
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 10));
/* Set threshold above size so nothing is printed */
opt_threshold = 11; /* only print if >= 11 */
FTS *fts = open_fts_single(path);
TEST_ASSERT_NOT_NULL(fts);
FTSENT *ent = fts_read(fts);
TEST_ASSERT_NOT_NULL(ent);
capture_ctx_t cap; memset(&cap, 0, sizeof cap);
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap));
bool ok = process_file(fts, ent);
size_t out_len = 0;
char *out = end_capture_stdout(&cap, &out_len);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_EQUAL_UINT(0u, (unsigned int)out_len);
/* Totals should still be updated */
TEST_ASSERT_EQUAL_UINT64(10u, (unsigned long long)tot_dui.size);
free(out);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
}
void test_process_file_nul_terminated_and_inodes(void)
{
reset_du_state_defaults();
char path[PATH_MAX];
snprintf(path, sizeof path, "%s/file_inodes.bin", g_tmpdir);
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 123));
/* Configure to print inodes and with NUL terminator */
opt_inodes = true;
opt_nul_terminate_output = true;
FTS *fts = open_fts_single(path);
TEST_ASSERT_NOT_NULL(fts);
FTSENT *ent = fts_read(fts);
TEST_ASSERT_NOT_NULL(ent);
capture_ctx_t cap; memset(&cap, 0, sizeof cap);
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap));
bool ok = process_file(fts, ent);
size_t out_len = 0;
char *out = end_capture_stdout(&cap, &out_len);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_NOT_NULL(out);
/* Expect: "1\t<path>\0" (NUL terminator included in captured bytes) */
size_t expected_len = strlen("1\t") + strlen(path) + 1; /* +1 for NUL byte at end of line */
TEST_ASSERT_EQUAL_UINT((unsigned int)expected_len, (unsigned int)out_len);
/* Verify contents byte-by-byte since it contains NUL */
/* First part should be "1\t" */
TEST_ASSERT_EQUAL_INT('1', out[0]);
TEST_ASSERT_EQUAL_INT('\t', out[1]);
/* Next should be the path */
TEST_ASSERT_EQUAL_INT(0, strncmp(out + 2, path, strlen(path)));
/* Final byte should be NUL, not newline */
TEST_ASSERT_EQUAL_INT('\0', out[out_len - 1]);
free(out);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
}
void test_process_file_hash_all_skips_duplicate(void)
{
reset_du_state_defaults();
char path[PATH_MAX];
snprintf(path, sizeof path, "%s/file_dup.bin", g_tmpdir);
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(path, 3));
/* Enable hashing of all files so duplicates are skipped */
hash_all = true;
FTS *fts = open_fts_single(path);
TEST_ASSERT_NOT_NULL(fts);
FTSENT *ent = fts_read(fts);
TEST_ASSERT_NOT_NULL(ent);
capture_ctx_t cap; memset(&cap, 0, sizeof cap);
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap));
/* First call prints once */
bool ok1 = process_file(fts, ent);
/* Second call on the same entry should be skipped */
bool ok2 = process_file(fts, ent);
size_t out_len = 0;
char *out = end_capture_stdout(&cap, &out_len);
TEST_ASSERT_TRUE(ok1);
TEST_ASSERT_TRUE(ok2);
TEST_ASSERT_NOT_NULL(out);
/* Expect exactly one line ending with '\n' */
unsigned nl_count = 0;
for (size_t i = 0; i < out_len; i++) if (out[i] == '\n') nl_count++;
TEST_ASSERT_EQUAL_UINT(1u, nl_count);
/* Totals should reflect only a single accounting */
TEST_ASSERT_EQUAL_UINT64(3u, (unsigned long long)tot_dui.size);
free(out);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
}
void test_process_file_opt_all_prints_child_file_line(void)
{
reset_du_state_defaults();
/* Create a directory with a child file */
char dir[PATH_MAX];
snprintf(dir, sizeof dir, "%s/dirA", g_tmpdir);
TEST_ASSERT_EQUAL_INT(0, ensure_dir(dir));
char child[PATH_MAX];
snprintf(child, sizeof child, "%s/child.txt", dir);
TEST_ASSERT_EQUAL_INT(0, create_file_with_size(child, 42));
opt_all = true; /* ensure files at level > 0 are also printed */
char *files[2];
files[0] = dir;
files[1] = NULL;
FTS *fts = xfts_open(files, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
TEST_ASSERT_NOT_NULL(fts);
capture_ctx_t cap; memset(&cap, 0, sizeof cap);
TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&cap));
/* Traverse and call process_file on each entry */
FTSENT *ent;
while ((ent = fts_read(fts)) != NULL) {
(void)process_file(fts, ent);
}
size_t out_len = 0;
char *out = end_capture_stdout(&cap, &out_len);
TEST_ASSERT_NOT_NULL(out);
/* There should be a line for the child file: "42\t<child>\n" */
char expected[PATH_MAX + 64];
snprintf(expected, sizeof expected, "42\t%s\n", child);
TEST_ASSERT_NOT_NULL(strstr(out, expected));
free(out);
TEST_ASSERT_EQUAL_INT(0, fts_close(fts));
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_process_file_regular_file_basic);
RUN_TEST(test_process_file_threshold_suppresses_output);
RUN_TEST(test_process_file_nul_terminated_and_inodes);
RUN_TEST(test_process_file_hash_all_skips_duplicate);
RUN_TEST(test_process_file_opt_all_prints_child_file_line);
return UNITY_END();
}