#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include /* The test file is included directly into the same translation unit as the coreutils cat implementation, so we can access its internal static symbols: - infile - input_desc - newlines2 - pending_cr - line_buf, line_num_print, line_num_start, line_num_end - cat(...) */ static const char line_buf_init_template[] = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', '\t', '\0' }; /* Helper to reset global numbering buffer and pointers to their initial state. */ static void reset_line_numbering_state(void) { /* line_buf has size LINE_COUNTER_BUF_LEN in the program. */ 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; } /* Helper: run cat() with given input and flags, capturing stdout into a malloc'd buffer. Returns output buffer (NUL-terminated for convenience) and sets *out_len. On error, returns NULL and sets *out_len = 0. This helper ensures stdout is restored before returning (to satisfy Unity rules). */ 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; } /* Write input and close writer to signal EOF. */ 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]); /* Redirect stdout to capture output. */ 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]); /* stdout now refers to this writer; close the extra fd */ /* Set globals required by cat(). */ infile = "test-input"; input_desc = inpipe[0]; /* Prepare buffers: inbuf needs +1 for sentinel; outbuf big enough (we use outsize*4 + 1024) */ idx_t inbuf_len = insize + 1; if (inbuf_len < 2) inbuf_len = 2; /* ensure minimal space for sentinel */ char *inbuf = (char *)malloc((size_t)inbuf_len); if (!inbuf) { /* Restore stdout before returning. */ 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; } /* Call the function under test. */ bool ok = cat(inbuf, insize, outbuf, outsize, show_nonprinting, show_tabs, number, number_nonblank, show_ends, squeeze_blank); /* Close input read end now that cat returned. */ close(inpipe[0]); /* Restore stdout before doing any assertions or reading result. */ dup2(saved_stdout, STDOUT_FILENO); close(saved_stdout); /* Collect output. */ 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]); /* NUL-terminate for convenience; the data is textual in our tests. */ if (result) result[*out_len] = '\0'; free(inbuf); free(outbuf); /* If the function indicated failure, treat as error (but we've already restored stdout). */ if (!ok) { free(result); *out_len = 0; return NULL; } return result; } void setUp(void) { /* Reset persistent global state that cat() uses across invocations. */ newlines2 = 0; pending_cr = false; reset_line_numbering_state(); /* Provide a default infile name for error reporting, though errors not expected. */ infile = "test-input"; } void tearDown(void) { /* Nothing to clean up globally. */ } /* Helper to format numbering prefix exactly like coreutils cat: width 6, then a tab. */ static void fmt_num_prefix(int n, char *dst, size_t dstlen) { /* Enough room for 6 digits + tab + NUL */ snprintf(dst, dstlen, "%6d\t", n); } /* Test 1: Basic pass-through with no options. */ 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); } /* Test 2: Show tabs (-T). */ 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); } /* Test 3: Show ends (-E) basic. */ 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); } /* Test 4: Show ends with CRLF where CR and LF are in the same input buffer. */ 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; /* insize large enough to read all at once so CR and LF are in the same buffer */ 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); } /* Test 5: Show ends with CRLF split across input buffers (pending_cr path). */ 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; /* Small insize ensures first read sees 'A\r' and sentinel, then next read sees '\n'. */ 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); } /* Test 6: Number all lines (-n). */ 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); } /* Test 7: Number nonblank (-b) with -n present: only non-empty lines numbered. */ 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); /* Expect: a\n \n \n b\n \n */ 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); } /* Test 8: Squeeze blank (-s) collapses multiple empty lines into one. */ 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); } /* Test 9: Show nonprinting (-v) for control, DEL, and high-bit chars; tabs preserved when -T not set. */ void test_cat_show_nonprinting_basic(void) { unsigned char in[] = { 0x01, 0x09, 0x7F, 0xC8, '\n' }; /* SOH, TAB, DEL, 200, NL */ 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); } /* Test 10: Show nonprinting (-v) with show tabs (-T): tabs rendered as ^I. */ 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(); }