coreutils / tests /paste /tests_for_paste_serial.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
/* The following globals/functions are defined in the program source (paste.c)
and are available here since this file is included into the same TU:
- static bool paste_serial(size_t, char **)
- static int collapse_escapes(const char *)
- static bool have_read_stdin;
- static unsigned char line_delim;
- static char *delims;
- static char const *delim_end;
*/
static char *create_temp_file_with_bytes(const void *data, size_t len)
{
char tmpl[] = "/tmp/paste_serial_test_XXXXXX";
int fd = mkstemp(tmpl);
if (fd < 0)
return NULL;
ssize_t written = 0;
const char *p = (const char *)data;
while ((size_t)written < len)
{
ssize_t r = write(fd, p + written, len - (size_t)written);
if (r < 0)
{
int saved = errno;
close(fd);
unlink(tmpl);
errno = saved;
return NULL;
}
written += r;
}
if (close(fd) != 0)
{
int saved = errno;
unlink(tmpl);
errno = saved;
return NULL;
}
/* Caller must free the returned path and unlink when done. */
return strdup(tmpl);
}
static char *create_temp_file_with_str(const char *s)
{
return create_temp_file_with_bytes(s, strlen(s));
}
/* Capture stdout while invoking paste_serial(nfiles, files).
Returns 0 on success; nonzero on failure to set up capturing.
The out_buf is malloc'ed and must be free()'d by caller.
Important: This function does not perform any Unity assertions while
stdout is redirected. */
static int run_and_capture(char **files, size_t nfiles,
char **out_buf, size_t *out_len,
bool *ok_ret)
{
if (!out_buf || !out_len || !ok_ret)
return -1;
fflush(stdout);
int saved_stdout = dup(STDOUT_FILENO);
if (saved_stdout < 0)
return -1;
FILE *tmp = tmpfile();
if (!tmp)
{
int saved = errno;
close(saved_stdout);
errno = saved;
return -1;
}
if (dup2(fileno(tmp), STDOUT_FILENO) < 0)
{
int saved = errno;
fclose(tmp);
close(saved_stdout);
errno = saved;
return -1;
}
/* Call the function under test. */
bool ok = paste_serial(nfiles, files);
/* Flush and read captured output. */
fflush(stdout);
long endpos = ftell(tmp);
if (endpos < 0)
{
/* Restore stdout before returning. */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(tmp);
return -1;
}
size_t len = (size_t)endpos;
if (fseek(tmp, 0, SEEK_SET) != 0)
{
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(tmp);
return -1;
}
char *buf = NULL;
if (len > 0)
{
buf = (char *)malloc(len);
if (!buf)
{
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(tmp);
return -1;
}
size_t rd = fread(buf, 1, len, tmp);
if (rd != len)
{
free(buf);
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(tmp);
return -1;
}
}
else
{
/* Ensure we return a non-NULL pointer even for zero length? Not necessary.
We'll return NULL with out_len==0. */
}
/* Restore stdout. */
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
fclose(tmp);
*out_buf = buf;
*out_len = len;
*ok_ret = ok;
return 0;
}
static void assert_bytes_equal(const void *expected, size_t expected_len,
const void *actual, size_t actual_len)
{
TEST_ASSERT_EQUAL_size_t_MESSAGE(expected_len, actual_len, "Output length mismatch");
if (expected_len > 0)
{
TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, expected_len, "Output content mismatch");
}
}
void setUp(void) {
/* Reset global state for each test. */
have_read_stdin = false;
line_delim = '\n';
/* Default delimiter to TAB, as per paste. */
collapse_escapes("\t");
}
void tearDown(void) {
/* Nothing to clean specifically; test cases unlink their temp files. */
}
/* 1) Basic serial paste with default TAB delimiter. */
void test_paste_serial_basic_tab(void)
{
char *f = create_temp_file_with_str("a\nb\nc\n");
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "a\tb\tc\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 2) File without trailing newline still ends with newline in output. */
void test_paste_serial_no_trailing_newline(void)
{
char *f = create_temp_file_with_str("a\nb\nc");
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "a\tb\tc\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 3) Empty file should produce just a newline. */
void test_paste_serial_empty_file_outputs_newline(void)
{
char *f = create_temp_file_with_str("");
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 4) Multiple files processed serially, outputs concatenated with newline after each file. */
void test_paste_serial_multiple_files(void)
{
char *f1 = create_temp_file_with_str("x\ny\n");
char *f2 = create_temp_file_with_str("1\n2\n");
TEST_ASSERT_NOT_NULL(f1);
TEST_ASSERT_NOT_NULL(f2);
char *files[] = { f1, f2 };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 2, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "x\ty\n1\t2\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f1); unlink(f2);
free(f1); free(f2);
}
/* 5) Custom delimiters cycle correctly. */
void test_paste_serial_custom_delims_cycle(void)
{
/* Use ",;" cycling: a,b;c,d\n for four lines. */
collapse_escapes(",;");
char *f = create_temp_file_with_str("a\nb\nc\nd\n");
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "a,b;c,d\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 6) EMPTY_DELIM (\0) means no delimiter inserted between lines. */
void test_paste_serial_empty_delimiter(void)
{
collapse_escapes("\\0");
char *f = create_temp_file_with_str("a\nb\nc\n");
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "abc\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 7) Zero-terminated records without trailing NUL: outputs newline at end. */
void test_paste_serial_zero_terminated_no_trailing_nul(void)
{
line_delim = '\0';
collapse_escapes("\t");
const char data[] = { 'a', '\0', 'b', '\0', 'c' }; /* no trailing NUL */
char *f = create_temp_file_with_bytes(data, sizeof(data));
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char expected[] = { 'a', '\t', 'b', '\t', 'c', '\n' };
assert_bytes_equal(expected, sizeof(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 8) Zero-terminated records with trailing NUL: no final newline (last char equals line_delim). */
void test_paste_serial_zero_terminated_with_trailing_nul(void)
{
line_delim = '\0';
collapse_escapes("\t");
const char data[] = { 'a', '\0', 'b', '\0' }; /* trailing NUL */
char *f = create_temp_file_with_bytes(data, sizeof(data));
TEST_ASSERT_NOT_NULL(f);
char *files[] = { f };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char expected[] = { 'a', '\t', 'b', '\0' }; /* no final newline */
assert_bytes_equal(expected, sizeof(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 9) Reading from standard input via "-" works. */
void test_paste_serial_stdin_input(void)
{
/* Reset line delimiter and delimiters. */
line_delim = '\n';
collapse_escapes("\t");
char *f = create_temp_file_with_str("p\nq\n");
TEST_ASSERT_NOT_NULL(f);
/* Redirect stdin from file f. */
int saved_stdin = dup(STDIN_FILENO);
TEST_ASSERT_TRUE_MESSAGE(saved_stdin >= 0, "Failed to save stdin");
int fd = open(f, O_RDONLY);
TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "Failed to open temp file for stdin");
TEST_ASSERT_TRUE_MESSAGE(dup2(fd, STDIN_FILENO) >= 0, "Failed to redirect stdin");
close(fd);
char *dash = "-";
char *files[] = { dash };
char *out = NULL;
size_t out_len = 0;
bool ok = false;
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
/* Restore stdin before any assertions that may output. */
dup2(saved_stdin, STDIN_FILENO);
close(saved_stdin);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_TRUE(ok);
const char *expected = "p\tq\n";
assert_bytes_equal(expected, strlen(expected), out, out_len);
free(out);
unlink(f);
free(f);
}
/* 10) Nonexistent file: returns false and writes nothing to stdout. */
void test_paste_serial_open_error_returns_false(void)
{
char *missing = (char *)"__definitely_missing_paste_serial_test__";
char *files[] = { missing };
char *out = NULL;
size_t out_len = 0;
bool ok = true; /* expect false */
int rc = run_and_capture(files, 1, &out, &out_len, &ok);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_FALSE(ok);
TEST_ASSERT_EQUAL_size_t(0, out_len);
/* out may be NULL when out_len==0; that's fine. */
free(out);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_paste_serial_basic_tab);
RUN_TEST(test_paste_serial_no_trailing_newline);
RUN_TEST(test_paste_serial_empty_file_outputs_newline);
RUN_TEST(test_paste_serial_multiple_files);
RUN_TEST(test_paste_serial_custom_delims_cycle);
RUN_TEST(test_paste_serial_empty_delimiter);
RUN_TEST(test_paste_serial_zero_terminated_no_trailing_nul);
RUN_TEST(test_paste_serial_zero_terminated_with_trailing_nul);
RUN_TEST(test_paste_serial_stdin_input);
RUN_TEST(test_paste_serial_open_error_returns_false);
return UNITY_END();
}