coreutils / tests /fmt /tests_for_fmt.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 <errno.h>
/* We are included into the same translation unit as fmt and its statics. */
/* Use the existing internal functions/variables declared above. */
/* Forward declare the target to silence potential warnings in some compilers. */
static bool fmt (FILE *f, char const *file);
static void set_prefix (char *p);
/* Helpers local to tests. Keep names unique to avoid collisions. */
static void test_set_width(int w)
{
/* max_width and goal_width are static globals in the program. */
/* Goal width defaults to 93% of width (100 - LEEWAY). */
max_width = w;
goal_width = (int)((long long)w * (100 - LEEWAY) / 100);
if (goal_width <= 0) goal_width = w; /* Fallback safety */
}
static void test_reset_options(void)
{
/* Reset option flags to defaults. */
crown = false;
tagged = false;
split = false;
uniform = false;
/* Reset prefix state. When prefix_length==0, get_prefix ignores prefix. */
prefix = "";
prefix_full_length = 0;
prefix_lead_space = 0;
prefix_length = 0;
/* Reset dynamic state that could influence formatting. */
in_column = 0;
out_column = 0;
tabs = false;
prefix_indent = 0;
first_indent = 0;
other_indent = 0;
next_char = '\0';
next_prefix_indent = 0;
last_line_length = 0;
}
/* Redirect stdout to a temporary file, run fmt on the provided input,
restore stdout, and return the captured output as a heap-allocated string.
The function fills *out_ok with fmt's return value. */
static char *run_fmt_and_capture(const char *input, const char *file_label, bool *out_ok)
{
/* Prepare input file for fmt. It will be closed by fmt on return. */
FILE *in = tmpfile();
if (!in) return NULL;
size_t in_len = strlen(input);
if (in_len && fwrite(input, 1, in_len, in) != in_len) {
fclose(in);
return NULL;
}
fflush(in);
rewind(in);
/* Redirect stdout to a tmpfile. */
FILE *cap = tmpfile();
if (!cap) {
fclose(in);
return NULL;
}
int saved_stdout_fd = dup(fileno(stdout));
if (saved_stdout_fd < 0) {
fclose(in);
fclose(cap);
return NULL;
}
fflush(stdout);
if (dup2(fileno(cap), fileno(stdout)) < 0) {
fclose(in);
fclose(cap);
close(saved_stdout_fd);
return NULL;
}
/* Call fmt while stdout is redirected. Do not use Unity asserts here. */
bool ok = fmt(in, file_label);
/* Flush and restore stdout. */
fflush(stdout);
if (dup2(saved_stdout_fd, fileno(stdout)) < 0) {
/* Fatal; but attempt to continue. */
}
close(saved_stdout_fd);
/* Read captured output into a buffer. */
fflush(cap);
fseek(cap, 0, SEEK_END);
long cap_len = ftell(cap);
if (cap_len < 0) cap_len = 0;
fseek(cap, 0, SEEK_SET);
char *buf = (char *)malloc((size_t)cap_len + 1);
if (!buf) {
fclose(cap);
return NULL;
}
size_t nread = fread(buf, 1, (size_t)cap_len, cap);
buf[nread] = '\0';
fclose(cap);
if (out_ok) *out_ok = ok;
return buf;
}
/* Normalize a text to space-separated words (single spaces, no newlines),
used to compare content ignoring line breaks and spacing variations. */
static char *normalize_words(const char *s)
{
size_t len = strlen(s);
/* Worst-case allocate same length; normalization won't expand size. */
char *out = (char *)malloc(len + 1);
if (!out) return NULL;
size_t oi = 0;
int in_word = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)s[i];
int is_ws = (c == ' ' || c == '\t' || c == '\n' || c == '\r');
if (!is_ws) {
out[oi++] = c;
in_word = 1;
} else {
if (in_word) {
out[oi++] = ' ';
in_word = 0;
}
}
}
/* Remove trailing space if any. */
if (oi > 0 && out[oi-1] == ' ')
oi--;
out[oi] = '\0';
return out;
}
/* Count the length in columns of a single line (no tabs expected in tests). */
static size_t line_visible_length(const char *start, const char *end)
{
/* end points just past '\n' or end-of-buffer; count visible chars before '\n'. */
size_t n = 0;
for (const char *p = start; p < end; p++) {
if (*p == '\n') break;
n++;
}
return n;
}
/* Unity setUp/tearDown */
void setUp(void) {
test_reset_options();
test_set_width(80); /* Safe default; tests set as needed. */
}
void tearDown(void) {
/* nothing */
}
/* Tests */
void test_fmt_basic_no_wrap(void)
{
test_reset_options();
test_set_width(80);
const char *input =
"Hello world\n"
"Second line\n"
"\n"
"Third paragraph line\n";
bool ok = false;
char *out = run_fmt_and_capture(input, "basic_no_wrap", &ok);
TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output");
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed for simple input");
/* Expect unchanged (since lines are short and no prefix). */
TEST_ASSERT_EQUAL_STRING(input, out);
free(out);
}
void test_fmt_wrap_lines_constraints(void)
{
test_reset_options();
test_set_width(10); /* small width to force wrapping */
const char *input = "one two three four five\n";
bool ok = false;
char *out = run_fmt_and_capture(input, "wrap_constraints", &ok);
TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output");
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed");
/* Verify that no output line exceeds max_width, and words preserved. */
const char *p = out;
while (*p) {
const char *line_end = strchr(p, '\n');
if (!line_end) line_end = p + strlen(p);
size_t vis = line_visible_length(p, line_end);
TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE((unsigned)max_width, (unsigned)max_width, "sanity");
TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE((unsigned)max_width, (unsigned)vis);
/* Correction: The assertion should ensure vis <= max_width */
TEST_ASSERT_TRUE_MESSAGE(vis <= (size_t)max_width, "A line exceeds max_width");
p = (*line_end == '\n') ? line_end + 1 : line_end;
}
/* Normalize words and compare with expected words sequence. */
char *norm_out = normalize_words(out);
char *norm_in = normalize_words(input);
TEST_ASSERT_NOT_NULL(norm_out);
TEST_ASSERT_NOT_NULL(norm_in);
TEST_ASSERT_EQUAL_STRING_MESSAGE(norm_in, norm_out, "Words/content changed by formatting");
free(norm_out);
free(norm_in);
free(out);
}
void test_fmt_prefix_trimming_and_reattach(void)
{
test_reset_options();
test_set_width(80);
/* Use a prefix with leading/trailing spaces: " > " */
char *pf = (char *)malloc(8);
TEST_ASSERT_NOT_NULL(pf);
strcpy(pf, " > ");
set_prefix(pf);
const char *input =
" >Hello\n"
" >world\n"
"\n";
bool ok = false;
char *out = run_fmt_and_capture(input, "prefix_trim", &ok);
/* After capture, reset prefix state and free buffer to avoid dangling. */
prefix = "";
prefix_full_length = 0;
prefix_lead_space = 0;
prefix_length = 0;
free(pf);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed with prefixed lines");
/* The trimmed prefix should be reattached (no trailing spaces emitted). */
const char *expected =
" >Hello\n"
" >world\n"
"\n";
TEST_ASSERT_EQUAL_STRING(expected, out);
free(out);
}
void test_fmt_returns_false_on_read_error(void)
{
test_reset_options();
test_set_width(80);
/* Create a readable FILE* and then close its underlying FD to induce read error. */
FILE *in = tmpfile();
TEST_ASSERT_NOT_NULL(in);
const char *input = "data\n";
fwrite(input, 1, strlen(input), in);
fflush(in);
rewind(in);
int fd = fileno(in);
/* Close the underlying file descriptor. Further reads should set ferror. */
close(fd);
bool ok = fmt(in, "read_error_expected");
/* fmt should detect the error and return false. */
TEST_ASSERT_FALSE_MESSAGE(ok, "fmt should return false on read error");
}
/* main */
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_fmt_basic_no_wrap);
RUN_TEST(test_fmt_wrap_lines_constraints);
RUN_TEST(test_fmt_prefix_trimming_and_reattach);
RUN_TEST(test_fmt_returns_false_on_read_error);
return UNITY_END();
}