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