#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Access to internal globals and function since this file is included into the same translation unit after the function definitions. */ /* Prototypes of tested function are not needed; we can call it directly: static void parse_patterns (int argc, int start, char **argv); */ /* Helper: run parse_patterns in a child process, capturing stderr, and return the child's exit status. Does not use Unity assertions in the child. */ static int run_parse_patterns_in_child(int argc, int start, char **argv, char *errbuf, size_t errbufsz) { int pipefd[2]; if (pipe(pipefd) != 0) return -1; fflush(stdout); fflush(stderr); pid_t pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); return -1; } if (pid == 0) { /* Child: redirect stderr to pipe write end. */ (void)dup2(pipefd[1], STDERR_FILENO); close(pipefd[0]); close(pipefd[1]); /* Ensure clean control state for this invocation. */ /* control_used is visible; controls pointer is reused by xpalloc. */ extern idx_t control_used; extern char **global_argv; control_used = 0; global_argv = argv; /* Call the function. If it returns, exit success. */ parse_patterns(argc, start, argv); _exit(0); } /* Parent: read child's stderr and wait for it. */ close(pipefd[1]); ssize_t total = 0; while (total < (ssize_t)errbufsz - 1) { ssize_t n = read(pipefd[0], errbuf + total, errbufsz - 1 - total); if (n > 0) total += n; else break; } errbuf[total] = '\0'; close(pipefd[0]); int status = 0; if (waitpid(pid, &status, 0) < 0) return -1; if (WIFEXITED(status)) return WEXITSTATUS(status); else if (WIFSIGNALED(status)) return 128 + WTERMSIG(status); else return -1; } void setUp(void) { /* Reset state used by parse_patterns that is safe to reset between tests. */ extern idx_t control_used; control_used = 0; /* Align global_argv to avoid null dereferences in error paths that might use it (though we avoid such checks in parent without forking). */ extern char **global_argv; global_argv = NULL; } void tearDown(void) { /* Nothing specific to do. */ } /* Test: regexp patterns with and without ignore, including offset parsing. */ void test_parse_patterns_regexp_with_offset_and_ignore(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char *argv[] = { "/foo/", "%bar%+3" }; int argc = 2; int start = 0; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(2, control_used); /* First: /foo/ */ TEST_ASSERT_TRUE(controls[0].regexpr); TEST_ASSERT_FALSE(controls[0].ignore); TEST_ASSERT_EQUAL_INT(0, controls[0].offset); TEST_ASSERT_EQUAL_INT(0, controls[0].argnum); /* Second: %bar%+3 */ TEST_ASSERT_TRUE(controls[1].regexpr); TEST_ASSERT_TRUE(controls[1].ignore); TEST_ASSERT_EQUAL_INT(3, controls[1].offset); TEST_ASSERT_EQUAL_INT(1, controls[1].argnum); } /* Test: numeric pattern with integer repeat count {3}. */ void test_parse_patterns_numeric_with_repeat_integer(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char rep[] = "{3}"; char *argv[] = { "5", rep }; int argc = 2; int start = 0; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(1, control_used); TEST_ASSERT_FALSE(controls[0].regexpr); TEST_ASSERT_EQUAL_INT64(5, controls[0].lines_required); TEST_ASSERT_FALSE(controls[0].repeat_forever); TEST_ASSERT_EQUAL_INT64(3, controls[0].repeat); } /* Test: numeric pattern with star repeat count {*}. */ void test_parse_patterns_repeat_star(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char rep[] = "{*}"; char *argv[] = { "6", rep }; int argc = 2; int start = 0; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(1, control_used); TEST_ASSERT_FALSE(controls[0].regexpr); TEST_ASSERT_EQUAL_INT64(6, controls[0].lines_required); TEST_ASSERT_TRUE(controls[0].repeat_forever); } /* Test: equal line number is allowed (warns but nonfatal). */ void test_parse_patterns_equal_line_ok_warning(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char *argv[] = { "6" }; /* Equal to previous test's last numeric value. */ int argc = 1; int start = 0; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(1, control_used); TEST_ASSERT_FALSE(controls[0].regexpr); TEST_ASSERT_EQUAL_INT64(6, controls[0].lines_required); } /* Test: argnum indices correspond to original argv indices when start > 0. */ void test_parse_patterns_argnum_indices(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char *argv[] = { "FILE", "IGNORED", "/x/", "10" }; int argc = 4; int start = 2; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(2, control_used); TEST_ASSERT_TRUE(controls[0].regexpr); TEST_ASSERT_EQUAL_INT(2, controls[0].argnum); TEST_ASSERT_FALSE(controls[1].regexpr); TEST_ASSERT_EQUAL_INT(3, controls[1].argnum); TEST_ASSERT_EQUAL_INT64(10, controls[1].lines_required); } /* Test: regexp pattern followed by integer repeat {2}. */ void test_parse_patterns_regex_with_repeat_integer(void) { extern struct control *controls; extern idx_t control_used; extern char **global_argv; char rep[] = "{2}"; char *argv[] = { "/baz/", rep }; int argc = 2; int start = 0; global_argv = argv; parse_patterns(argc, start, argv); TEST_ASSERT_EQUAL_INT64(1, control_used); TEST_ASSERT_TRUE(controls[0].regexpr); TEST_ASSERT_FALSE(controls[0].repeat_forever); TEST_ASSERT_EQUAL_INT64(2, controls[0].repeat); } /* Error case: zero line number -> exit failure. */ void test_parse_patterns_zero_line_error(void) { char *argv[] = { "0" }; char errbuf[1024]; int status = run_parse_patterns_in_child(1, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } /* Error case: decreasing line numbers -> exit failure. */ void test_parse_patterns_decreasing_line_error(void) { char *argv[] = { "5", "3" }; char errbuf[1024]; int status = run_parse_patterns_in_child(2, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } /* Error case: invalid non-number pattern -> exit failure. */ void test_parse_patterns_invalid_pattern_non_number_error(void) { char *argv[] = { "abc" }; char errbuf[1024]; int status = run_parse_patterns_in_child(1, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } /* Error case: missing closing delimiter in regexp "/abc" -> exit failure. */ void test_parse_patterns_missing_closing_delimiter_error(void) { char *argv[] = { "/abc" }; char errbuf[1024]; int status = run_parse_patterns_in_child(1, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } /* Error case: invalid regular expression "/[/" -> exit failure. */ void test_parse_patterns_invalid_regex_error(void) { char *argv[] = { "/[/" }; char errbuf[1024]; int status = run_parse_patterns_in_child(1, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } /* Error case: malformed repeat count missing '}' -> exit failure. */ void test_parse_patterns_repeat_missing_right_brace_error(void) { /* Use a mutable buffer for the malformed repeat. */ char badrep[] = "{3"; char *argv[] = { "1", badrep }; char errbuf[1024]; int status = run_parse_patterns_in_child(2, 0, argv, errbuf, sizeof errbuf); TEST_ASSERT_NOT_EQUAL(0, status); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_parse_patterns_regexp_with_offset_and_ignore); RUN_TEST(test_parse_patterns_numeric_with_repeat_integer); RUN_TEST(test_parse_patterns_repeat_star); RUN_TEST(test_parse_patterns_equal_line_ok_warning); RUN_TEST(test_parse_patterns_argnum_indices); RUN_TEST(test_parse_patterns_regex_with_repeat_integer); RUN_TEST(test_parse_patterns_zero_line_error); RUN_TEST(test_parse_patterns_decreasing_line_error); RUN_TEST(test_parse_patterns_invalid_pattern_non_number_error); RUN_TEST(test_parse_patterns_missing_closing_delimiter_error); RUN_TEST(test_parse_patterns_invalid_regex_error); RUN_TEST(test_parse_patterns_repeat_missing_right_brace_error); return UNITY_END(); }