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