|
|
#include "../../unity/unity.h" |
|
|
|
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <stdbool.h> |
|
|
#include <stdint.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <sys/stat.h> |
|
|
#include <errno.h> |
|
|
#include <limits.h> |
|
|
|
|
|
|
|
|
extern bool print_headers; |
|
|
extern bool presume_input_pipe; |
|
|
extern bool have_read_stdin; |
|
|
extern char line_end; |
|
|
|
|
|
|
|
|
static bool head_file (char const *filename, uintmax_t n_units, bool count_lines, |
|
|
bool elide_from_end); |
|
|
|
|
|
|
|
|
static char* create_temp_file_with_content(const void* data, size_t len) |
|
|
{ |
|
|
char tmpl[] = "/tmp/head_file_test_XXXXXX"; |
|
|
int fd = mkstemp(tmpl); |
|
|
if (fd < 0) |
|
|
{ |
|
|
|
|
|
TEST_FAIL_MESSAGE("mkstemp failed creating temporary file"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
ssize_t written = 0; |
|
|
if (len) |
|
|
{ |
|
|
written = write(fd, data, len); |
|
|
if (written < 0 || (size_t)written != len) |
|
|
{ |
|
|
close(fd); |
|
|
unlink(tmpl); |
|
|
TEST_FAIL_MESSAGE("Failed to write expected content to temporary file"); |
|
|
return NULL; |
|
|
} |
|
|
} |
|
|
|
|
|
if (close(fd) != 0) |
|
|
{ |
|
|
unlink(tmpl); |
|
|
TEST_FAIL_MESSAGE("Failed to close temporary file descriptor"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
char* path = (char*)malloc(strlen(tmpl) + 1); |
|
|
TEST_ASSERT_NOT_NULL(path); |
|
|
strcpy(path, tmpl); |
|
|
return path; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static char* capture_head_file_output(const char* filename, |
|
|
uintmax_t n_units, |
|
|
bool count_lines, |
|
|
bool elide_from_end, |
|
|
size_t* out_len, |
|
|
bool* out_ok) |
|
|
{ |
|
|
if (!out_len || !out_ok) |
|
|
return NULL; |
|
|
|
|
|
*out_len = 0; |
|
|
*out_ok = false; |
|
|
|
|
|
fflush(stdout); |
|
|
int saved_stdout = dup(fileno(stdout)); |
|
|
if (saved_stdout < 0) |
|
|
{ |
|
|
TEST_FAIL_MESSAGE("dup(stdout) failed"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
char outtmpl[] = "/tmp/head_file_out_XXXXXX"; |
|
|
int outfd = mkstemp(outtmpl); |
|
|
if (outfd < 0) |
|
|
{ |
|
|
close(saved_stdout); |
|
|
TEST_FAIL_MESSAGE("mkstemp failed for capture file"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
if (dup2(outfd, fileno(stdout)) < 0) |
|
|
{ |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
close(saved_stdout); |
|
|
TEST_FAIL_MESSAGE("dup2 to redirect stdout failed"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
bool ok = head_file(filename, n_units, count_lines, elide_from_end); |
|
|
|
|
|
fflush(stdout); |
|
|
off_t endpos = lseek(outfd, 0, SEEK_END); |
|
|
if (endpos < 0) |
|
|
{ |
|
|
|
|
|
dup2(saved_stdout, fileno(stdout)); |
|
|
close(saved_stdout); |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
TEST_FAIL_MESSAGE("lseek on capture fd failed"); |
|
|
return NULL; |
|
|
} |
|
|
if (lseek(outfd, 0, SEEK_SET) < 0) |
|
|
{ |
|
|
dup2(saved_stdout, fileno(stdout)); |
|
|
close(saved_stdout); |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
TEST_FAIL_MESSAGE("lseek rewind on capture fd failed"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
size_t len = (size_t)endpos; |
|
|
char* buf = (char*)malloc(len + 1); |
|
|
if (!buf) |
|
|
{ |
|
|
dup2(saved_stdout, fileno(stdout)); |
|
|
close(saved_stdout); |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
TEST_FAIL_MESSAGE("malloc failed for capture buffer"); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
size_t total = 0; |
|
|
while (total < len) |
|
|
{ |
|
|
ssize_t r = read(outfd, buf + total, len - total); |
|
|
if (r < 0) |
|
|
{ |
|
|
free(buf); |
|
|
dup2(saved_stdout, fileno(stdout)); |
|
|
close(saved_stdout); |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
TEST_FAIL_MESSAGE("read on capture fd failed"); |
|
|
return NULL; |
|
|
} |
|
|
if (r == 0) |
|
|
break; |
|
|
total += (size_t)r; |
|
|
} |
|
|
buf[len] = '\0'; |
|
|
|
|
|
|
|
|
dup2(saved_stdout, fileno(stdout)); |
|
|
close(saved_stdout); |
|
|
close(outfd); |
|
|
unlink(outtmpl); |
|
|
|
|
|
*out_len = len; |
|
|
*out_ok = ok; |
|
|
return buf; |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
print_headers = false; |
|
|
presume_input_pipe = false; |
|
|
have_read_stdin = false; |
|
|
line_end = '\n'; |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void test_head_file_bytes_first_N(void) |
|
|
{ |
|
|
const char* content = "abcdef\n12345\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output(path, 5, false, false, &out_len, &ok); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64(5, (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY("abcde", out, 5); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_lines_first_N(void) |
|
|
{ |
|
|
const char* content = "line1\nline2\nline3\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output(path, 2, true, false, &out_len, &ok); |
|
|
|
|
|
const char* expected = "line1\nline2\n"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_bytes_zero_prints_nothing(void) |
|
|
{ |
|
|
const char* content = "abcdef"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output(path, 0, false, false, &out_len, &ok); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64(0, (uint64_t)out_len); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_lines_zero_prints_nothing(void) |
|
|
{ |
|
|
const char* content = "a\nb\nc\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output(path, 0, true, false, &out_len, &ok); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64(0, (uint64_t)out_len); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_elide_bytes_seekable(void) |
|
|
{ |
|
|
const char* content = "abcdef"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
|
|
|
char* out = capture_head_file_output(path, 2, false, true, &out_len, &ok); |
|
|
|
|
|
const char* expected = "abcd"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_elide_lines_seekable(void) |
|
|
{ |
|
|
const char* content = "a\nb\nc\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
|
|
|
char* out = capture_head_file_output(path, 1, true, true, &out_len, &ok); |
|
|
|
|
|
const char* expected = "a\nb\n"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_elide_bytes_pipe(void) |
|
|
{ |
|
|
const char* content = "abcdef"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
bool prev_presume = presume_input_pipe; |
|
|
presume_input_pipe = true; |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
|
|
|
char* out = capture_head_file_output(path, 3, false, true, &out_len, &ok); |
|
|
|
|
|
presume_input_pipe = prev_presume; |
|
|
|
|
|
const char* expected = "abc"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_elide_lines_pipe(void) |
|
|
{ |
|
|
const char* content = "a\nb\nc\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
bool prev_presume = presume_input_pipe; |
|
|
presume_input_pipe = true; |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
|
|
|
char* out = capture_head_file_output(path, 2, true, true, &out_len, &ok); |
|
|
|
|
|
presume_input_pipe = prev_presume; |
|
|
|
|
|
const char* expected = "a\n"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
static void test_head_file_stdin_bytes(void) |
|
|
{ |
|
|
const char* content = "hello\nworld\n"; |
|
|
int p[2]; |
|
|
TEST_ASSERT_EQUAL_INT(0, pipe(p)); |
|
|
|
|
|
|
|
|
ssize_t w = write(p[1], content, strlen(content)); |
|
|
TEST_ASSERT_EQUAL_INT((int)strlen(content), (int)w); |
|
|
close(p[1]); |
|
|
|
|
|
int saved_stdin = dup(STDIN_FILENO); |
|
|
TEST_ASSERT_TRUE(saved_stdin >= 0); |
|
|
TEST_ASSERT_TRUE(dup2(p[0], STDIN_FILENO) >= 0); |
|
|
close(p[0]); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output("-", 5, false, false, &out_len, &ok); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE(dup2(saved_stdin, STDIN_FILENO) >= 0); |
|
|
close(saved_stdin); |
|
|
|
|
|
const char* expected = "hello"; |
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64((uint64_t)strlen(expected), (uint64_t)out_len); |
|
|
TEST_ASSERT_EQUAL_MEMORY(expected, out, strlen(expected)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
static void test_head_file_nonexistent_error(void) |
|
|
{ |
|
|
|
|
|
const char* path = "/tmp/this_file_should_not_exist_head_test"; |
|
|
|
|
|
unlink(path); |
|
|
|
|
|
bool ok = head_file(path, 1, false, false); |
|
|
TEST_ASSERT_FALSE(ok); |
|
|
} |
|
|
|
|
|
static void test_head_file_elide_infinite(void) |
|
|
{ |
|
|
const char* content = "abc\ndef\n"; |
|
|
char* path = create_temp_file_with_content(content, strlen(content)); |
|
|
|
|
|
size_t out_len = 0; |
|
|
bool ok = false; |
|
|
char* out = capture_head_file_output(path, UINTMAX_MAX, false, true, &out_len, &ok); |
|
|
|
|
|
TEST_ASSERT_TRUE(ok); |
|
|
TEST_ASSERT_EQUAL_UINT64(0, (uint64_t)out_len); |
|
|
|
|
|
free(out); |
|
|
unlink(path); |
|
|
free(path); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
|
|
|
RUN_TEST(test_head_file_bytes_first_N); |
|
|
RUN_TEST(test_head_file_lines_first_N); |
|
|
RUN_TEST(test_head_file_bytes_zero_prints_nothing); |
|
|
RUN_TEST(test_head_file_lines_zero_prints_nothing); |
|
|
RUN_TEST(test_head_file_elide_bytes_seekable); |
|
|
RUN_TEST(test_head_file_elide_lines_seekable); |
|
|
RUN_TEST(test_head_file_elide_bytes_pipe); |
|
|
RUN_TEST(test_head_file_elide_lines_pipe); |
|
|
RUN_TEST(test_head_file_stdin_bytes); |
|
|
RUN_TEST(test_head_file_nonexistent_error); |
|
|
RUN_TEST(test_head_file_elide_infinite); |
|
|
|
|
|
return UNITY_END(); |
|
|
} |