coreutils / tests /cat /tests_for_cat.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#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>
/* 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:
<p1>a\n
\n
\n
<p2>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();
}