#include "../../unity/unity.h" #include #include #include #include #include /* The following globals and functions are defined earlier in the same TU (nl.c): - FORMAT_RIGHT_NOLZ - body_type, header_type, footer_type, current_type - body_regex, header_regex, footer_regex - body_fastmap, header_fastmap, footer_fastmap - current_regex - separator_str, print_no_line_fmt - starting_line_number, page_incr, reset_numbers, blank_join - lineno_width, lineno_format - line_no, line_no_overflow - section_del, header_del, body_del, footer_del - header_del_len, body_del_len, footer_del_len - process_file(FILE *fp) */ static char *testutil_build_no_line_fmt(int width, const char *sep) { size_t seplen = strlen(sep); size_t len = (size_t)width + seplen; char *s = (char *)malloc(len + 1); if (!s) return NULL; memset(s, ' ', (size_t)width); memcpy(s + width, sep, seplen); s[len] = '\0'; return s; } /* Helper to run process_file on input string and capture stdout into a newly allocated string. Returns NULL on error. No Unity asserts inside. */ static char *testutil_run_and_capture(const char *input) { FILE *in = tmpfile(); if (!in) return NULL; size_t inlen = strlen(input); if (inlen > 0 && fwrite(input, 1, inlen, in) != inlen) { fclose(in); return NULL; } fflush(in); rewind(in); FILE *out = tmpfile(); if (!out) { fclose(in); return NULL; } int stdout_fd = fileno(stdout); int saved_fd = dup(stdout_fd); if (saved_fd < 0) { fclose(in); fclose(out); return NULL; } fflush(stdout); if (dup2(fileno(out), stdout_fd) < 0) { close(saved_fd); fclose(in); fclose(out); return NULL; } /* Call the target function while stdout is redirected. */ process_file(in); /* Finish writing and prepare to read captured output. */ fflush(stdout); /* Read captured data. */ long sz = 0; if (fseek(out, 0, SEEK_END) == 0) { sz = ftell(out); if (sz < 0) sz = 0; fseek(out, 0, SEEK_SET); } char *buf = (char *)malloc((size_t)sz + 1); if (!buf) { /* Restore stdout before returning. */ dup2(saved_fd, stdout_fd); close(saved_fd); fclose(in); fclose(out); return NULL; } size_t nread = 0; if (sz > 0) { nread = fread(buf, 1, (size_t)sz, out); } buf[nread] = '\0'; /* Restore stdout and clean up. */ dup2(saved_fd, stdout_fd); close(saved_fd); fclose(in); fclose(out); return buf; } void setUp(void) { /* Standardized environment for each test. */ separator_str = "|"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; starting_line_number = 1; page_incr = 1; reset_numbers = true; blank_join = 1; /* Default numbering types. */ body_type = "t"; /* number only nonempty lines */ header_type = "n"; /* number no lines (can be overridden per test) */ footer_type = "n"; /* Set current type to body and default regex (unused for 't'). */ current_type = body_type; current_regex = &body_regex; /* Initialize regex buffers to a sane state. */ body_regex.buffer = NULL; body_regex.allocated = 0; body_regex.fastmap = body_fastmap; body_regex.translate = NULL; header_regex.buffer = NULL; header_regex.allocated = 0; header_regex.fastmap = header_fastmap; header_regex.translate = NULL; footer_regex.buffer = NULL; footer_regex.allocated = 0; footer_regex.fastmap = footer_fastmap; footer_regex.translate = NULL; /* Disable section matching by default. */ section_del = DEFAULT_SECTION_DELIMITERS; header_del = NULL; body_del = NULL; footer_del = NULL; header_del_len = 0; body_del_len = 0; footer_del_len = 0; /* Reset numbering state. */ line_no = starting_line_number; line_no_overflow = false; /* Build no-line format. */ if (print_no_line_fmt) { free(print_no_line_fmt); print_no_line_fmt = NULL; } print_no_line_fmt = testutil_build_no_line_fmt(lineno_width, separator_str); } void tearDown(void) { if (print_no_line_fmt) { free(print_no_line_fmt); print_no_line_fmt = NULL; } } /* Test 1: Default body numbering 't' – number only nonempty lines. */ void test_process_file_numbers_nonempty_lines(void) { const char *input = "alpha\n\nbeta\n"; char *out = testutil_run_and_capture(input); TEST_ASSERT_NOT_NULL(out); const char *expected = " 1|alpha\n" " |\n" " 2|beta\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test 2: All-lines numbering 'a' with blank_join = 2. Should number only every second consecutive blank line. */ void test_process_file_all_lines_with_blank_join(void) { current_type = "a"; blank_join = 2; /* Rebuild no-line format in case separator/width changed in prior tests. */ if (print_no_line_fmt) { free(print_no_line_fmt); } print_no_line_fmt = testutil_build_no_line_fmt(lineno_width, separator_str); const char *input = "\n\n\n"; char *out = testutil_run_and_capture(input); TEST_ASSERT_NOT_NULL(out); const char *expected = " |\n" " 1|\n" " |\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test 3: Section delimiters switch numbering modes and reset numbers. Configure custom delimiters with a common section_del prefix "%%". */ void test_process_file_section_delimiters(void) { /* Configure delimiters. */ section_del = "%%"; header_del = "%%H"; body_del = "%%B"; footer_del = "%%F"; header_del_len = strlen(header_del); body_del_len = strlen(body_del); footer_del_len = strlen(footer_del); /* Use 'a' for headers (number all), 't' for body (nonempty). */ header_type = "a"; body_type = "t"; current_type = body_type; /* start in body */ /* Rebuild no-line format */ if (print_no_line_fmt) { free(print_no_line_fmt); } print_no_line_fmt = testutil_build_no_line_fmt(lineno_width, separator_str); const char *input = "b1\n%%H\nh1\n%%B\nb2\n"; char *out = testutil_run_and_capture(input); TEST_ASSERT_NOT_NULL(out); /* Expected behavior: - "b1" in body 't' numbered as 1 - "%%H" produces a single newline and resets numbering - "h1" in header 'a' numbered as 1 - "%%B" produces a single newline and resets numbering - "b2" in body 't' numbered as 1 */ const char *expected = " 1|b1\n" "\n" " 1|h1\n" "\n" " 1|b2\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } /* Test 4: Regex-based numbering 'p' – number lines matching a pattern. Pattern "x" numbers lines containing 'x'. */ void test_process_file_regex_numbering(void) { /* Compile a simple GNU regex pattern into body_regex. */ body_regex.buffer = NULL; body_regex.allocated = 0; body_regex.fastmap = body_fastmap; body_regex.translate = NULL; /* Configure regex syntax as in build_type_arg for 'p'. */ re_syntax_options = RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES; const char *pattern = "x"; const char *errmsg = re_compile_pattern(pattern, strlen(pattern), &body_regex); TEST_ASSERT_NULL_MESSAGE(errmsg, "Failed to compile regex pattern"); current_type = "p"; current_regex = &body_regex; /* Rebuild no-line format */ if (print_no_line_fmt) { free(print_no_line_fmt); } print_no_line_fmt = testutil_build_no_line_fmt(lineno_width, separator_str); const char *input = "ax\nb\nx\n"; char *out = testutil_run_and_capture(input); TEST_ASSERT_NOT_NULL(out); const char *expected = " 1|ax\n" " |b\n" " 2|x\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_process_file_numbers_nonempty_lines); RUN_TEST(test_process_file_all_lines_with_blank_join); RUN_TEST(test_process_file_section_delimiters); RUN_TEST(test_process_file_regex_numbering); return UNITY_END(); }