#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Note: - This test file is included into the nl source, so it has access to all internal globals and functions, including proc_text, line_buf, etc. - Do not use Unity assertions while stdout is redirected. */ /* Forward declare the target function for clarity (it is visible as it's in the same TU). */ /* static void proc_text(void); -- not redeclared to avoid conflicts */ /* Helper: begin capturing stdout to a temporary file. */ typedef struct { int saved_fd; int tmp_fd; char path[64]; } capture_ctx; static int begin_capture(capture_ctx *ctx) { strcpy(ctx->path, "/tmp/nl_cap_XXXXXX"); ctx->tmp_fd = mkstemp(ctx->path); if (ctx->tmp_fd < 0) return -1; ctx->saved_fd = dup(fileno(stdout)); if (ctx->saved_fd < 0) { close(ctx->tmp_fd); unlink(ctx->path); return -1; } fflush(stdout); if (dup2(ctx->tmp_fd, fileno(stdout)) < 0) { close(ctx->tmp_fd); close(ctx->saved_fd); unlink(ctx->path); return -1; } return 0; } /* Helper: end capturing stdout and return the captured output as a malloc'd string. */ static char *end_capture(capture_ctx *ctx) { fflush(stdout); /* Restore stdout first, then read and clean up. */ dup2(ctx->saved_fd, fileno(stdout)); close(ctx->saved_fd); /* Read back file */ struct stat st; if (fstat(ctx->tmp_fd, &st) != 0) { close(ctx->tmp_fd); unlink(ctx->path); return NULL; } off_t sz = st.st_size; if (lseek(ctx->tmp_fd, 0, SEEK_SET) < 0) { close(ctx->tmp_fd); unlink(ctx->path); return NULL; } char *buf = (char *)malloc((size_t)sz + 1); if (!buf) { close(ctx->tmp_fd); unlink(ctx->path); return NULL; } ssize_t rd = read(ctx->tmp_fd, buf, (size_t)sz); if (rd < 0) { free(buf); close(ctx->tmp_fd); unlink(ctx->path); return NULL; } buf[rd] = '\0'; close(ctx->tmp_fd); unlink(ctx->path); return buf; } /* Helper: set the global line buffer to the given content (must include '\n'). Returns pointer that must be freed by caller after proc_text call. */ static char *set_line(const char *s) { size_t len = strlen(s); char *copy = (char *)malloc(len); memcpy(copy, s, len); line_buf.buffer = copy; line_buf.length = len; return copy; } /* Helper: call proc_text on provided content and capture output. */ static char *call_and_capture_proc_text(const char *content) { capture_ctx ctx; if (begin_capture(&ctx) != 0) { return NULL; } char *mem = set_line(content); /* Do not use Unity asserts while redirected */ proc_text(); char *out = end_capture(&ctx); free(mem); return out; } /* Reset the static blank_lines inside proc_text to a known state. We do this by forcing an 'a' mode call on a non-empty line, which sets blank_lines = 0. Output is discarded. */ static void reset_proc_text_static_state(void) { const char *saved_type = current_type; intmax_t saved_blank_join = blank_join; char const *saved_sep = separator_str; char const *saved_fmt = lineno_format; int saved_width = lineno_width; char const *saved_no_line_fmt = print_no_line_fmt; intmax_t saved_line_no = line_no; intmax_t saved_incr = page_incr; current_type = "a"; blank_join = 2; /* >1 to ensure the path sets blank_lines=0 for non-empty line */ separator_str = "|"; lineno_format = FORMAT_RIGHT_NOLZ; lineno_width = 2; print_no_line_fmt = ""; line_no = 1; page_incr = 1; capture_ctx ctx; if (begin_capture(&ctx) == 0) { char *mem = set_line("R\n"); proc_text(); /* does a numbered non-empty line -> blank_lines becomes 0 */ char *d = end_capture(&ctx); free(mem); free(d); } /* restore */ current_type = saved_type; blank_join = saved_blank_join; separator_str = saved_sep; lineno_format = saved_fmt; lineno_width = saved_width; print_no_line_fmt = saved_no_line_fmt; line_no = saved_line_no; page_incr = saved_incr; } void setUp(void) { /* Set deterministic defaults; reset internal static state in proc_text */ separator_str = "|"; lineno_format = FORMAT_RIGHT_NOLZ; lineno_width = 3; page_incr = 1; line_no = 1; line_no_overflow = false; print_no_line_fmt = "U"; current_type = "n"; blank_join = 1; reset_proc_text_static_state(); } void tearDown(void) { /* Nothing to clean specifically */ } /* Test: 'a' mode with blank_join == 1 numbers all lines, including blank. */ static void test_proc_text_a_blank_join_1(void) { current_type = "a"; blank_join = 1; separator_str = "|"; lineno_width = 3; lineno_format = FORMAT_RIGHT_NOLZ; print_no_line_fmt = "<>"; line_no = 7; page_incr = 1; char *out1 = call_and_capture_proc_text("X\n"); TEST_ASSERT_NOT_NULL(out1); TEST_ASSERT_EQUAL_STRING(" 7|X\n", out1); free(out1); TEST_ASSERT_EQUAL_INT64(8, line_no); char *out2 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(out2); TEST_ASSERT_EQUAL_STRING(" 8|\n", out2); free(out2); TEST_ASSERT_EQUAL_INT64(9, line_no); } /* Test: 'a' mode with blank_join > 1 groups consecutive blank lines. For blank_join=3 and starting line_no=1: - first blank -> unnumbered - second blank -> unnumbered - third blank -> numbered " 1|" - fourth blank -> unnumbered Then a non-empty line resets the blank counter and is numbered " 2|". */ static void test_proc_text_a_blank_join_gt1_sequence(void) { current_type = "a"; blank_join = 3; separator_str = "|"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; print_no_line_fmt = "U"; line_no = 1; page_incr = 1; char *o1 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o1); TEST_ASSERT_EQUAL_STRING("U\n", o1); free(o1); TEST_ASSERT_EQUAL_INT64(1, line_no); char *o2 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o2); TEST_ASSERT_EQUAL_STRING("U\n", o2); free(o2); TEST_ASSERT_EQUAL_INT64(1, line_no); char *o3 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o3); TEST_ASSERT_EQUAL_STRING(" 1|\n", o3); free(o3); TEST_ASSERT_EQUAL_INT64(2, line_no); char *o4 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o4); TEST_ASSERT_EQUAL_STRING("U\n", o4); free(o4); TEST_ASSERT_EQUAL_INT64(2, line_no); char *o5 = call_and_capture_proc_text("abc\n"); TEST_ASSERT_NOT_NULL(o5); TEST_ASSERT_EQUAL_STRING(" 2|abc\n", o5); free(o5); TEST_ASSERT_EQUAL_INT64(3, line_no); } /* Test: 't' mode numbers only non-empty lines. */ static void test_proc_text_t_type(void) { current_type = "t"; print_no_line_fmt = "X"; separator_str = "|"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; line_no = 42; page_incr = 1; char *o1 = call_and_capture_proc_text("hello\n"); TEST_ASSERT_NOT_NULL(o1); TEST_ASSERT_EQUAL_STRING("42|hello\n", o1); free(o1); TEST_ASSERT_EQUAL_INT64(43, line_no); char *o2 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o2); TEST_ASSERT_EQUAL_STRING("X\n", o2); free(o2); /* line_no unchanged for unnumbered line */ TEST_ASSERT_EQUAL_INT64(43, line_no); } /* Test: 'n' mode numbers no lines. */ static void test_proc_text_n_type(void) { current_type = "n"; print_no_line_fmt = "Z"; separator_str = "|"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; line_no = 5; page_incr = 1; char *o1 = call_and_capture_proc_text("q\n"); TEST_ASSERT_NOT_NULL(o1); TEST_ASSERT_EQUAL_STRING("Zq\n", o1); free(o1); TEST_ASSERT_EQUAL_INT64(5, line_no); char *o2 = call_and_capture_proc_text("\n"); TEST_ASSERT_NOT_NULL(o2); TEST_ASSERT_EQUAL_STRING("Z\n", o2); free(o2); TEST_ASSERT_EQUAL_INT64(5, line_no); } /* Test: 'p' mode numbers only lines matching regex. Use the program's regex engine and buffers. */ static void test_proc_text_p_type_regex(void) { /* Prepare regex: "foo" */ current_type = "p"; current_regex = &body_regex; /* Initialize pattern buffer similarly to build_type_arg. */ body_regex.buffer = NULL; body_regex.allocated = 0; body_regex.fastmap = body_fastmap; body_regex.translate = NULL; re_syntax_options = RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES; const char *errmsg = re_compile_pattern("foo", strlen("foo"), &body_regex); TEST_ASSERT_NULL_MESSAGE(errmsg, "Failed to compile regex pattern"); print_no_line_fmt = "N"; separator_str = "|"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; line_no = 5; page_incr = 1; char *o1 = call_and_capture_proc_text("foo\n"); TEST_ASSERT_NOT_NULL(o1); TEST_ASSERT_EQUAL_STRING(" 5|foo\n", o1); free(o1); TEST_ASSERT_EQUAL_INT64(6, line_no); char *o2 = call_and_capture_proc_text("bar\n"); TEST_ASSERT_NOT_NULL(o2); TEST_ASSERT_EQUAL_STRING("Nbar\n", o2); free(o2); TEST_ASSERT_EQUAL_INT64(6, line_no); } /* Test: page_incr affects line_no increment. */ static void test_proc_text_page_incr(void) { current_type = "a"; blank_join = 1; separator_str = "|"; lineno_width = 3; lineno_format = FORMAT_RIGHT_NOLZ; print_no_line_fmt = ""; line_no = 10; page_incr = 10; char *o = call_and_capture_proc_text("X\n"); TEST_ASSERT_NOT_NULL(o); TEST_ASSERT_EQUAL_STRING(" 10|X\n", o); free(o); TEST_ASSERT_EQUAL_INT64(20, line_no); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_proc_text_a_blank_join_1); RUN_TEST(test_proc_text_a_blank_join_gt1_sequence); RUN_TEST(test_proc_text_t_type); RUN_TEST(test_proc_text_n_type); RUN_TEST(test_proc_text_p_type_regex); RUN_TEST(test_proc_text_page_incr); return UNITY_END(); }