#include "../../unity/unity.h" #include #include #include #include #include #include /* 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(); }