coreutils / tests /fmt /tests_for_put_line.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>
#include <stdbool.h>
/* We are included into the fmt source, so we can use:
- set_prefix
- put_line
- WORD type
- globals: prefix, prefix_length, prefix_indent, tabs, last_line_length
*/
void setUp(void) {
/* Nothing to do */
}
void tearDown(void) {
/* Nothing to do */
}
/* Helper: append characters to a dynamic buffer */
static void append_char(char **buf, size_t *len, size_t *cap, char c) {
if (*len + 1 >= *cap) {
size_t ncap = (*cap == 0 ? 128 : (*cap * 2));
char *nbuf = (char *)realloc(*buf, ncap);
if (!nbuf) {
/* In a test environment, abort if OOM */
fprintf(stderr, "OOM in test helper\n");
abort();
}
*buf = nbuf;
*cap = ncap;
}
(*buf)[(*len)++] = c;
}
/* Helper: append a C-string */
static void append_str(char **buf, size_t *len, size_t *cap, const char *s) {
while (*s) {
append_char(buf, len, cap, *s++);
}
}
/* Helper: emulate put_space behavior to build expected output.
Advance from current column 'col' to 'target' emitting tabs/spaces based on 'use_tabs'.
Updates 'col' and appends to buffer. */
static void append_space_run(char **buf, size_t *len, size_t *cap,
int *col, int target, bool use_tabs) {
if (target <= *col) return;
int out_column = *col;
int space_target = target;
if (use_tabs) {
int tab_target = (space_target / 8) * 8;
if (out_column + 1 < tab_target) {
while (out_column < tab_target) {
append_char(buf, len, cap, '\t');
out_column = (out_column / 8 + 1) * 8;
}
}
}
while (out_column < space_target) {
append_char(buf, len, cap, ' ');
out_column++;
}
*col = out_column;
}
/* Construct expected output and expected last_line_length for a given scenario.
words_texts: array of const char*
spaces: array of inter-word spaces (spaces[i] applied after words i, except last word ignored)
nwords: number of words
prefix_c_str: original prefix string to pass to set_prefix (leading/trailing spaces trimmed)
prefix_ind: prefix_indent
indent: target indent for first word
use_tabs: whether tabs==true for spacing
Returns a newly allocated string containing expected output (caller frees),
and writes expected last-line column into *out_expected_len. */
static char *build_expected_output(const char *trimmed_prefix, int prefix_len,
int prefix_ind, int indent,
const char **words_texts, const int *spaces, size_t nwords,
bool use_tabs, int *out_expected_len) {
char *buf = NULL;
size_t len = 0, cap = 0;
int col = 0;
/* prefix_indent spaces/tabs */
append_space_run(&buf, &len, &cap, &col, prefix_ind, use_tabs);
/* prefix text */
if (trimmed_prefix && *trimmed_prefix) {
append_str(&buf, &len, &cap, trimmed_prefix);
col += prefix_len;
}
/* spaces/tabs to indent */
append_space_run(&buf, &len, &cap, &col, indent, use_tabs);
/* words with inter-word spaces */
for (size_t i = 0; i < nwords; i++) {
const char *w = words_texts[i];
append_str(&buf, &len, &cap, w);
col += (int)strlen(w);
if (i + 1 < nwords) {
int target = col + spaces[i];
append_space_run(&buf, &len, &cap, &col, target, use_tabs);
}
}
/* newline */
append_char(&buf, &len, &cap, '\n');
append_char(&buf, &len, &cap, '\0'); /* null-terminate */
if (out_expected_len) *out_expected_len = col;
return buf;
}
/* Capture stdout during put_line call, compare output and last_line_length.
Returns NULL on success, or a malloc'd error message on failure. */
static char *run_put_line_case(WORD *start_word, size_t nwords,
int indent,
const char *prefix_original,
int prefix_ind,
bool use_tabs) {
/* Prepare globals: use set_prefix to derive prefix/prefix_length from the provided string.
We must pass a mutable buffer because set_prefix trims trailing spaces in place. */
char *error_msg = NULL;
char *prefix_buf = NULL;
size_t pblen = strlen(prefix_original);
prefix_buf = (char *)malloc(pblen + 1);
if (!prefix_buf) {
error_msg = strdup("OOM allocating prefix buffer");
return error_msg;
}
memcpy(prefix_buf, prefix_original, pblen + 1);
/* Save old globals to restore */
const char *old_prefix = prefix;
int old_prefix_length = prefix_length;
int old_prefix_indent = prefix_indent;
bool old_tabs = tabs;
set_prefix(prefix_buf);
prefix_indent = prefix_ind;
tabs = use_tabs;
/* Build expected output based on current trimmed prefix and prefix_length */
const char *wtexts_stack[64];
int wspaces_stack[64];
if (nwords > 64) {
free(prefix_buf);
prefix = old_prefix; prefix_length = old_prefix_length; prefix_indent = old_prefix_indent; tabs = old_tabs;
return strdup("Too many words for test helper");
}
for (size_t i = 0; i < nwords; i++) {
wtexts_stack[i] = start_word[i].text;
wspaces_stack[i] = start_word[i].space;
}
int expected_len = 0;
char *expected = build_expected_output(prefix, prefix_length, prefix_indent, indent,
wtexts_stack, wspaces_stack, nwords, use_tabs,
&expected_len);
if (!expected) {
free(prefix_buf);
prefix = old_prefix; prefix_length = old_prefix_length; prefix_indent = old_prefix_indent; tabs = old_tabs;
return strdup("Failed to build expected output");
}
/* Setup stdout capture */
fflush(stdout);
int saved_stdout_fd = dup(fileno(stdout));
if (saved_stdout_fd < 0) {
free(prefix_buf);
free(expected);
prefix = old_prefix; prefix_length = old_prefix_length; prefix_indent = old_prefix_indent; tabs = old_tabs;
char buf[128];
snprintf(buf, sizeof buf, "dup stdout failed: %s", strerror(errno));
return strdup(buf);
}
FILE *tmp = tmpfile();
if (!tmp) {
free(prefix_buf);
free(expected);
close(saved_stdout_fd);
char buf[128];
snprintf(buf, sizeof buf, "tmpfile failed: %s", strerror(errno));
return strdup(buf);
}
if (dup2(fileno(tmp), fileno(stdout)) < 0) {
free(prefix_buf);
free(expected);
fclose(tmp);
close(saved_stdout_fd);
char buf[128];
snprintf(buf, sizeof buf, "dup2 to stdout failed: %s", strerror(errno));
return strdup(buf);
}
/* Ensure start_word->next_break is set to end (one past last) */
start_word->next_break = start_word + nwords;
/* Call the function under test */
put_line(start_word, indent);
/* Flush and read capture */
fflush(stdout);
fseek(tmp, 0, SEEK_END);
long sz = ftell(tmp);
if (sz < 0) sz = 0;
fseek(tmp, 0, SEEK_SET);
char *got = (char *)malloc((size_t)sz + 1);
if (!got) {
/* Restore stdout before returning */
dup2(saved_stdout_fd, fileno(stdout));
close(saved_stdout_fd);
fclose(tmp);
free(prefix_buf);
free(expected);
return strdup("OOM allocating capture buffer");
}
size_t rd = fread(got, 1, (size_t)sz, tmp);
got[rd] = '\0';
/* Restore stdout */
dup2(saved_stdout_fd, fileno(stdout));
close(saved_stdout_fd);
fclose(tmp);
/* Compare */
if (strcmp(got, expected) != 0) {
size_t emlen = strlen(expected);
size_t gmlen = strlen(got);
/* Build a readable diff message */
size_t msgcap = emlen + gmlen + 256;
error_msg = (char *)malloc(msgcap);
if (!error_msg) {
error_msg = strdup("Mismatch and OOM building error message");
} else {
snprintf(error_msg, msgcap,
"Output mismatch.\nExpected(%zu): [%s]\nGot (%zu): [%s]",
emlen, expected, gmlen, got);
}
free(got);
free(expected);
free(prefix_buf);
/* Restore globals */
prefix = old_prefix; prefix_length = old_prefix_length; prefix_indent = old_prefix_indent; tabs = old_tabs;
return error_msg;
}
/* Check last_line_length */
if (last_line_length != expected_len) {
char bufmsg[128];
snprintf(bufmsg, sizeof bufmsg,
"last_line_length mismatch: expected %d, got %d",
expected_len, last_line_length);
error_msg = strdup(bufmsg);
}
free(got);
free(expected);
free(prefix_buf);
/* Restore globals */
prefix = old_prefix; prefix_length = old_prefix_length; prefix_indent = old_prefix_indent; tabs = old_tabs;
return error_msg;
}
/* Initialize a WORD */
static void init_word(WORD *w, const char *text, int space_after) {
w->text = text;
w->length = (int)strlen(text);
w->space = space_after;
w->paren = 0;
w->period = 0;
w->punct = 0;
w->final = 0;
w->line_length = 0;
w->best_cost = 0;
w->next_break = NULL;
}
/* Tests */
static void test_put_line_single_word_no_prefix_no_tabs_impl(void) {
WORD arr[2];
init_word(&arr[0], "Hello", 1);
/* next_break set inside helper */
char *err = run_put_line_case(&arr[0], 1, /*indent*/0,
"", /*prefix_indent*/0, /*tabs*/false);
if (err) {
TEST_FAIL_MESSAGE(err);
free(err);
}
}
void test_put_line_single_word_no_prefix_no_tabs(void) {
test_put_line_single_word_no_prefix_no_tabs_impl();
}
static void test_put_line_multiple_words_with_prefix_spaces_impl(void) {
WORD arr[3];
init_word(&arr[0], "A", 1);
init_word(&arr[1], "BB", 1); /* trailing space ignored for last word */
/* prefix with lead/trailing spaces -> trimmed prefix "##" */
char *err = run_put_line_case(&arr[0], 2, /*indent*/8,
" ## ", /*prefix_indent*/2, /*tabs*/false);
if (err) {
TEST_FAIL_MESSAGE(err);
free(err);
}
}
void test_put_line_multiple_words_with_prefix_spaces(void) {
test_put_line_multiple_words_with_prefix_spaces_impl();
}
static void test_put_line_tabs_for_indent_impl(void) {
WORD arr[3];
init_word(&arr[0], "X", 1);
init_word(&arr[1], "Y", 1);
char *err = run_put_line_case(&arr[0], 2, /*indent*/16,
"", /*prefix_indent*/0, /*tabs*/true);
if (err) {
TEST_FAIL_MESSAGE(err);
free(err);
}
}
void test_put_line_tabs_for_indent(void) {
test_put_line_tabs_for_indent_impl();
}
static void test_put_line_mixed_prefix_indent_tabs_and_spaces_impl(void) {
WORD arr[3];
init_word(&arr[0], "Hi", 2);
init_word(&arr[1], "There", 1);
char *err = run_put_line_case(&arr[0], 2, /*indent*/18,
"", /*prefix_indent*/3, /*tabs*/true);
if (err) {
TEST_FAIL_MESSAGE(err);
free(err);
}
}
void test_put_line_mixed_prefix_indent_tabs_and_spaces(void) {
test_put_line_mixed_prefix_indent_tabs_and_spaces_impl();
}
static void test_put_line_no_extra_space_after_prefix_impl(void) {
WORD arr[2];
init_word(&arr[0], "Y", 1);
/* prefix "X", indent equals prefix length -> no extra spaces before word */
char *err = run_put_line_case(&arr[0], 1, /*indent*/1,
"X", /*prefix_indent*/0, /*tabs*/false);
if (err) {
TEST_FAIL_MESSAGE(err);
free(err);
}
}
void test_put_line_no_extra_space_after_prefix(void) {
test_put_line_no_extra_space_after_prefix_impl();
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_put_line_single_word_no_prefix_no_tabs);
RUN_TEST(test_put_line_multiple_words_with_prefix_spaces);
RUN_TEST(test_put_line_tabs_for_indent);
RUN_TEST(test_put_line_mixed_prefix_indent_tabs_and_spaces);
RUN_TEST(test_put_line_no_extra_space_after_prefix);
return UNITY_END();
}