|
|
#include "../../unity/unity.h" |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <stdbool.h> |
|
|
#include <unistd.h> |
|
|
#include <fcntl.h> |
|
|
#include <errno.h> |
|
|
#include <stdio.h> |
|
|
#include <sys/types.h> |
|
|
#include <sys/stat.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char line_buf_init_template[] = { |
|
|
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', |
|
|
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', |
|
|
'\t', '\0' |
|
|
}; |
|
|
|
|
|
|
|
|
static void reset_line_numbering_state(void) { |
|
|
|
|
|
memcpy(line_buf, line_buf_init_template, sizeof(line_buf_init_template)); |
|
|
line_num_print = line_buf + LINE_COUNTER_BUF_LEN - 8; |
|
|
line_num_start = line_buf + LINE_COUNTER_BUF_LEN - 3; |
|
|
line_num_end = line_buf + LINE_COUNTER_BUF_LEN - 3; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char* run_cat_and_capture(const unsigned char *input, size_t input_len, |
|
|
idx_t insize, idx_t outsize, |
|
|
bool show_nonprinting, bool show_tabs, |
|
|
bool number, bool number_nonblank, |
|
|
bool show_ends, bool squeeze_blank, |
|
|
size_t *out_len) |
|
|
{ |
|
|
*out_len = 0; |
|
|
int inpipe[2]; |
|
|
if (pipe(inpipe) != 0) { |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
|
|
|
ssize_t wr = write(inpipe[1], input, input_len); |
|
|
if (wr < 0 || (size_t)wr != input_len) { |
|
|
close(inpipe[0]); |
|
|
close(inpipe[1]); |
|
|
return NULL; |
|
|
} |
|
|
close(inpipe[1]); |
|
|
|
|
|
|
|
|
int outpipe[2]; |
|
|
if (pipe(outpipe) != 0) { |
|
|
close(inpipe[0]); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
int saved_stdout = dup(STDOUT_FILENO); |
|
|
if (saved_stdout < 0) { |
|
|
close(inpipe[0]); |
|
|
close(outpipe[0]); |
|
|
close(outpipe[1]); |
|
|
return NULL; |
|
|
} |
|
|
if (dup2(outpipe[1], STDOUT_FILENO) < 0) { |
|
|
close(inpipe[0]); |
|
|
close(outpipe[0]); |
|
|
close(outpipe[1]); |
|
|
close(saved_stdout); |
|
|
return NULL; |
|
|
} |
|
|
close(outpipe[1]); |
|
|
|
|
|
|
|
|
infile = "test-input"; |
|
|
input_desc = inpipe[0]; |
|
|
|
|
|
|
|
|
idx_t inbuf_len = insize + 1; |
|
|
if (inbuf_len < 2) inbuf_len = 2; |
|
|
char *inbuf = (char *)malloc((size_t)inbuf_len); |
|
|
if (!inbuf) { |
|
|
|
|
|
dup2(saved_stdout, STDOUT_FILENO); |
|
|
close(saved_stdout); |
|
|
close(inpipe[0]); |
|
|
close(outpipe[0]); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
idx_t outbuf_len = outsize * 4 + 1024; |
|
|
if (outbuf_len < outsize + 16) outbuf_len = outsize + 16; |
|
|
char *outbuf = (char *)malloc((size_t)outbuf_len); |
|
|
if (!outbuf) { |
|
|
dup2(saved_stdout, STDOUT_FILENO); |
|
|
close(saved_stdout); |
|
|
close(inpipe[0]); |
|
|
close(outpipe[0]); |
|
|
free(inbuf); |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
|
|
|
bool ok = cat(inbuf, insize, outbuf, outsize, |
|
|
show_nonprinting, show_tabs, number, number_nonblank, |
|
|
show_ends, squeeze_blank); |
|
|
|
|
|
|
|
|
close(inpipe[0]); |
|
|
|
|
|
|
|
|
dup2(saved_stdout, STDOUT_FILENO); |
|
|
close(saved_stdout); |
|
|
|
|
|
|
|
|
char *result = NULL; |
|
|
size_t cap = 0; |
|
|
for (;;) { |
|
|
char buf[4096]; |
|
|
ssize_t r = read(outpipe[0], buf, sizeof buf); |
|
|
if (r < 0) { |
|
|
free(inbuf); |
|
|
free(outbuf); |
|
|
close(outpipe[0]); |
|
|
free(result); |
|
|
return NULL; |
|
|
} |
|
|
if (r == 0) break; |
|
|
if (*out_len + (size_t)r + 1 > cap) { |
|
|
size_t new_cap = (*out_len + (size_t)r + 1) * 2; |
|
|
char *tmp = (char *)realloc(result, new_cap); |
|
|
if (!tmp) { |
|
|
free(inbuf); |
|
|
free(outbuf); |
|
|
close(outpipe[0]); |
|
|
free(result); |
|
|
return NULL; |
|
|
} |
|
|
result = tmp; |
|
|
cap = new_cap; |
|
|
} |
|
|
memcpy(result + *out_len, buf, (size_t)r); |
|
|
*out_len += (size_t)r; |
|
|
} |
|
|
close(outpipe[0]); |
|
|
|
|
|
|
|
|
if (result) result[*out_len] = '\0'; |
|
|
|
|
|
free(inbuf); |
|
|
free(outbuf); |
|
|
|
|
|
|
|
|
if (!ok) { |
|
|
free(result); |
|
|
*out_len = 0; |
|
|
return NULL; |
|
|
} |
|
|
|
|
|
return result; |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
newlines2 = 0; |
|
|
pending_cr = false; |
|
|
reset_line_numbering_state(); |
|
|
|
|
|
infile = "test-input"; |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
static void fmt_num_prefix(int n, char *dst, size_t dstlen) { |
|
|
|
|
|
snprintf(dst, dstlen, "%6d\t", n); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_basic_passthrough(void) { |
|
|
const char *in = "Hello\tWorld\nSecond line\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, false, false, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(in, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_tabs(void) { |
|
|
const char *in = "a\tb\n\t\n"; |
|
|
const char *expected = "a^Ib\n^I\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, true, false, false, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_ends_basic(void) { |
|
|
const char *in = "line1\n\nline2\n"; |
|
|
const char *expected = "line1$\n$\nline2$\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, false, false, true, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_ends_crlf_in_buffer(void) { |
|
|
const unsigned char in[] = { 'A', '\r', '\n', 'B', '\n' }; |
|
|
const char *expected = "A^M$\nB$\n"; |
|
|
size_t out_len = 0; |
|
|
|
|
|
char *out = run_cat_and_capture(in, sizeof(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, false, false, true, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_ends_crlf_across_boundary(void) { |
|
|
const unsigned char in[] = { 'A', '\r', '\n' }; |
|
|
const char *expected = "A^M$\n"; |
|
|
size_t out_len = 0; |
|
|
|
|
|
char *out = run_cat_and_capture(in, sizeof(in), |
|
|
(idx_t)2, (idx_t)4096, |
|
|
false, false, false, false, true, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_number_all_lines(void) { |
|
|
const char *in = "a\n\nb\n"; |
|
|
char expected[256]; |
|
|
char p1[16], p2[16], p3[16]; |
|
|
fmt_num_prefix(1, p1, sizeof p1); |
|
|
fmt_num_prefix(2, p2, sizeof p2); |
|
|
fmt_num_prefix(3, p3, sizeof p3); |
|
|
snprintf(expected, sizeof expected, "%s%s\n%s\n%s%s\n", p1, "a", p2, p3, "b"); |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, true, false, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_number_nonblank(void) { |
|
|
const char *in = "a\n\n\nb\n\n"; |
|
|
char expected[256]; |
|
|
char p1[16], p2[16]; |
|
|
fmt_num_prefix(1, p1, sizeof p1); |
|
|
fmt_num_prefix(2, p2, sizeof p2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
snprintf(expected, sizeof expected, "%s%s\n\n\n%s%s\n\n", p1, "a", p2, "b"); |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, true, true, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_squeeze_blank(void) { |
|
|
const char *in = "a\n\n\n\nb\n\n"; |
|
|
const char *expected = "a\n\nb\n\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
false, false, false, false, false, true, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_nonprinting_basic(void) { |
|
|
unsigned char in[] = { 0x01, 0x09, 0x7F, 0xC8, '\n' }; |
|
|
const char *expected = "^A\t^?M-H\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture(in, sizeof(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
true, false, false, false, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
|
|
|
void test_cat_show_nonprinting_with_tabs(void) { |
|
|
const char *in = "\t\n"; |
|
|
const char *expected = "^I\n"; |
|
|
size_t out_len = 0; |
|
|
char *out = run_cat_and_capture((const unsigned char *)in, strlen(in), |
|
|
(idx_t)64, (idx_t)4096, |
|
|
true, true, false, false, false, false, &out_len); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_STRING(expected, out); |
|
|
free(out); |
|
|
} |
|
|
|
|
|
int main(void) { |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_cat_basic_passthrough); |
|
|
RUN_TEST(test_cat_show_tabs); |
|
|
RUN_TEST(test_cat_show_ends_basic); |
|
|
RUN_TEST(test_cat_show_ends_crlf_in_buffer); |
|
|
RUN_TEST(test_cat_show_ends_crlf_across_boundary); |
|
|
RUN_TEST(test_cat_number_all_lines); |
|
|
RUN_TEST(test_cat_number_nonblank); |
|
|
RUN_TEST(test_cat_squeeze_blank); |
|
|
RUN_TEST(test_cat_show_nonprinting_basic); |
|
|
RUN_TEST(test_cat_show_nonprinting_with_tabs); |
|
|
return UNITY_END(); |
|
|
} |