|
|
#include "../../unity/unity.h" |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <sys/stat.h> |
|
|
#include <errno.h> |
|
|
#include <stdint.h> |
|
|
#include <limits.h> |
|
|
#include <stdbool.h> |
|
|
|
|
|
|
|
|
extern char line_end; |
|
|
extern bool print_headers; |
|
|
extern bool presume_input_pipe; |
|
|
|
|
|
|
|
|
static int make_temp_file_with_content(const char* tag, const void* data, size_t len, |
|
|
char* out_path, size_t out_path_size) |
|
|
{ |
|
|
char tmpl[PATH_MAX]; |
|
|
snprintf(tmpl, sizeof(tmpl), "/tmp/head_test_%s_XXXXXX", tag ? tag : "tmp"); |
|
|
if (out_path && out_path_size > 0) |
|
|
out_path[0] = '\0'; |
|
|
|
|
|
int wfd = mkstemp(tmpl); |
|
|
if (wfd < 0) |
|
|
return -1; |
|
|
|
|
|
ssize_t wr = 0; |
|
|
const char* p = (const char*)data; |
|
|
size_t left = len; |
|
|
while (left > 0) { |
|
|
ssize_t n = write(wfd, p + wr, left); |
|
|
if (n < 0) { |
|
|
close(wfd); |
|
|
unlink(tmpl); |
|
|
return -1; |
|
|
} |
|
|
wr += n; |
|
|
left -= n; |
|
|
} |
|
|
|
|
|
if (lseek(wfd, 0, SEEK_SET) < 0) { } |
|
|
close(wfd); |
|
|
|
|
|
if (out_path && out_path_size > 0) |
|
|
snprintf(out_path, out_path_size, "%s", tmpl); |
|
|
|
|
|
int rfd = open(tmpl, O_RDONLY); |
|
|
if (rfd < 0) { |
|
|
unlink(tmpl); |
|
|
return -1; |
|
|
} |
|
|
return rfd; |
|
|
} |
|
|
|
|
|
|
|
|
typedef struct { |
|
|
int saved_stdout_fd; |
|
|
int cap_fd; |
|
|
char path[PATH_MAX]; |
|
|
bool active; |
|
|
} capture_ctx_t; |
|
|
|
|
|
static bool capture_stdout_begin(capture_ctx_t* ctx, const char* tag) |
|
|
{ |
|
|
if (!ctx) return false; |
|
|
memset(ctx, 0, sizeof(*ctx)); |
|
|
ctx->saved_stdout_fd = dup(STDOUT_FILENO); |
|
|
if (ctx->saved_stdout_fd < 0) return false; |
|
|
|
|
|
char tmpl[PATH_MAX]; |
|
|
snprintf(tmpl, sizeof(tmpl), "/tmp/head_cap_%s_XXXXXX", tag ? tag : "out"); |
|
|
int fd = mkstemp(tmpl); |
|
|
if (fd < 0) { |
|
|
close(ctx->saved_stdout_fd); |
|
|
return false; |
|
|
} |
|
|
snprintf(ctx->path, sizeof(ctx->path), "%s", tmpl); |
|
|
|
|
|
fflush(stdout); |
|
|
if (dup2(fd, STDOUT_FILENO) < 0) { |
|
|
close(fd); |
|
|
unlink(ctx->path); |
|
|
close(ctx->saved_stdout_fd); |
|
|
return false; |
|
|
} |
|
|
ctx->cap_fd = fd; |
|
|
ctx->active = true; |
|
|
return true; |
|
|
} |
|
|
|
|
|
static bool capture_stdout_end(capture_ctx_t* ctx, char** out_buf, size_t* out_len) |
|
|
{ |
|
|
if (!ctx || !ctx->active) return false; |
|
|
|
|
|
|
|
|
fflush(stdout); |
|
|
fsync(STDOUT_FILENO); |
|
|
|
|
|
|
|
|
off_t end = lseek(ctx->cap_fd, 0, SEEK_END); |
|
|
if (end < 0) end = 0; |
|
|
if (lseek(ctx->cap_fd, 0, SEEK_SET) < 0) { } |
|
|
|
|
|
size_t len = (size_t)end; |
|
|
char* buf = (char*)malloc(len + 1); |
|
|
if (!buf) { |
|
|
|
|
|
dup2(ctx->saved_stdout_fd, STDOUT_FILENO); |
|
|
close(ctx->saved_stdout_fd); |
|
|
close(ctx->cap_fd); |
|
|
unlink(ctx->path); |
|
|
ctx->active = false; |
|
|
return false; |
|
|
} |
|
|
|
|
|
size_t rd_total = 0; |
|
|
while (rd_total < len) { |
|
|
ssize_t n = read(ctx->cap_fd, buf + rd_total, len - rd_total); |
|
|
if (n < 0) { |
|
|
free(buf); |
|
|
dup2(ctx->saved_stdout_fd, STDOUT_FILENO); |
|
|
close(ctx->saved_stdout_fd); |
|
|
close(ctx->cap_fd); |
|
|
unlink(ctx->path); |
|
|
ctx->active = false; |
|
|
return false; |
|
|
} |
|
|
if (n == 0) break; |
|
|
rd_total += n; |
|
|
} |
|
|
buf[rd_total] = '\0'; |
|
|
|
|
|
|
|
|
dup2(ctx->saved_stdout_fd, STDOUT_FILENO); |
|
|
close(ctx->saved_stdout_fd); |
|
|
close(ctx->cap_fd); |
|
|
unlink(ctx->path); |
|
|
ctx->active = false; |
|
|
|
|
|
if (out_buf) *out_buf = buf; else free(buf); |
|
|
if (out_len) *out_len = rd_total; |
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
line_end = '\n'; |
|
|
print_headers = false; |
|
|
presume_input_pipe = false; |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
static bool head (char const *filename, int fd, uintmax_t n_units, bool count_lines, |
|
|
bool elide_from_end); |
|
|
|
|
|
|
|
|
void test_head_headers_first_call_prints_banner_and_content(void) |
|
|
{ |
|
|
const char* content = "12345\n67890\n"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("hdr", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
|
|
|
unlink(path); |
|
|
|
|
|
print_headers = true; |
|
|
line_end = '\n'; |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE_MESSAGE(capture_stdout_begin(&cap, "hdr"), "Failed to start capture"); |
|
|
bool ok = head("file1", fd, 1, true, false); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE_MESSAGE(capture_stdout_end(&cap, &out, &out_len), "Failed to finish capture"); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
const char* expected = "==> file1 <==\n12345\n"; |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_bytes_basic(void) |
|
|
{ |
|
|
const char* content = "abcdef"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("bytes", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "bytes")); |
|
|
bool ok = head("bytes_file", fd, 3, false, false); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
const char* expected = "abc"; |
|
|
TEST_ASSERT_EQUAL_size_t(3, out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_lines_basic_newline(void) |
|
|
{ |
|
|
const char* content = "a\nb\nc\n"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("lines", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
line_end = '\n'; |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "lines")); |
|
|
bool ok = head("lines_file", fd, 2, true, false); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
const char* expected = "a\nb\n"; |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_bytes_elide_end_seekable(void) |
|
|
{ |
|
|
const char* content = "abcdef"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("elideb", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "elideb")); |
|
|
bool ok = head("eb_file", fd, 2, false, true); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_size_t(4, out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp("abcd", out, 4)); |
|
|
free(out); |
|
|
|
|
|
|
|
|
lseek(fd, 0, SEEK_SET); |
|
|
capture_ctx_t cap2; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap2, "elideb2")); |
|
|
ok = head("eb_file", fd, 99, false, true); |
|
|
out = NULL; out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap2, &out, &out_len)); |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_size_t(0, out_len); |
|
|
free(out); |
|
|
|
|
|
close(fd); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_lines_elide_end_pipe(void) |
|
|
{ |
|
|
const char* content = "a\nb\nc\nd\n"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("elidel", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
presume_input_pipe = true; |
|
|
line_end = '\n'; |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "elidel")); |
|
|
bool ok = head("elidel_file", fd, 2, true, true); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
const char* expected = "a\nb\n"; |
|
|
TEST_ASSERT_EQUAL_size_t(strlen(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_lines_zero_terminated(void) |
|
|
{ |
|
|
const unsigned char data[] = { 'a','a','\0','b','b','b','b','\0','c','c','c','\0' }; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("nul", data, sizeof(data), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
line_end = '\0'; |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "nul")); |
|
|
bool ok = head("nul_file", fd, 2, true, false); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
const unsigned char expected[] = { 'a','a','\0','b','b','b','b','\0' }; |
|
|
TEST_ASSERT_EQUAL_size_t(sizeof(expected), out_len); |
|
|
TEST_ASSERT_EQUAL_INT(0, memcmp(expected, out, out_len)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_infinite_elide_returns_true_and_no_output(void) |
|
|
{ |
|
|
const char* content = "data\n"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("inf", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
capture_ctx_t cap; |
|
|
TEST_ASSERT_TRUE(capture_stdout_begin(&cap, "inf")); |
|
|
bool ok = head("inf_file", fd, UINTMAX_MAX, true, true); |
|
|
char* out = NULL; size_t out_len = 0; |
|
|
TEST_ASSERT_TRUE(capture_stdout_end(&cap, &out, &out_len)); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_size_t(0, out_len); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_head_read_error_returns_false(void) |
|
|
{ |
|
|
const char* content = "error"; |
|
|
char path[PATH_MAX]; |
|
|
int fd = make_temp_file_with_content("err", content, strlen(content), path, sizeof(path)); |
|
|
TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp file"); |
|
|
unlink(path); |
|
|
|
|
|
close(fd); |
|
|
|
|
|
bool ok = head("err_file", fd, 3, false, false); |
|
|
TEST_ASSERT_FALSE(ok); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
|
|
|
RUN_TEST(test_head_headers_first_call_prints_banner_and_content); |
|
|
RUN_TEST(test_head_bytes_basic); |
|
|
RUN_TEST(test_head_lines_basic_newline); |
|
|
RUN_TEST(test_head_bytes_elide_end_seekable); |
|
|
RUN_TEST(test_head_lines_elide_end_pipe); |
|
|
RUN_TEST(test_head_lines_zero_terminated); |
|
|
RUN_TEST(test_head_infinite_elide_returns_true_and_no_output); |
|
|
RUN_TEST(test_head_read_error_returns_false); |
|
|
return UNITY_END(); |
|
|
} |