#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* The test file is included into the same translation unit as nl.c, so we can access file-scope static variables and functions directly. */ /* Forward declaration of target (has internal linkage, but visible here because we are included in the same translation unit). */ static bool nl_file (char const *file); /* Globals from program, accessible here due to inclusion into same TU. */ extern char const *FORMAT_RIGHT_NOLZ; /* format const from program */ static void reset_lineno(void); /* static in program, visible here by inclusion */ /* Helper: make a print_no_line_fmt based on width and separator. */ static char* make_print_no_line_fmt_buf(int width, const char* sep) { size_t seplen = strlen(sep); char* s = (char*)malloc((size_t)width + seplen + 1); if (!s) return NULL; memset(s, ' ', (size_t)width); memcpy(s + width, sep, seplen); s[width + seplen] = '\0'; return s; } /* Helper: write content to an already-open fd, then rewind position. */ static int write_all(int fd, const char* data, size_t len) { size_t off = 0; while (off < len) { ssize_t w = write(fd, data + off, len - off); if (w < 0) return -1; off += (size_t)w; } return 0; } /* Helper: create a temp file with given content, return its path (malloc'd). Caller must free the returned path. */ static char* create_temp_file_with(const char* content) { char tmpl[] = "/tmp/nl_test_XXXXXX"; int fd = mkstemp(tmpl); if (fd < 0) return NULL; if (content && write_all(fd, content, strlen(content)) < 0) { close(fd); unlink(tmpl); return NULL; } /* Rewind the file for reading by nl_file. */ lseek(fd, 0, SEEK_SET); close(fd); char* path = strdup(tmpl); return path; } /* Helper: read entire file into malloc'd buffer; returns NUL-terminated string. */ static char* read_entire_file(const char* path) { FILE* f = fopen(path, "rb"); if (!f) return NULL; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } long sz = ftell(f); if (sz < 0) { fclose(f); return NULL; } if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; } char* buf = (char*)malloc((size_t)sz + 1); if (!buf) { fclose(f); return NULL; } size_t n = fread(buf, 1, (size_t)sz, f); buf[n] = '\0'; fclose(f); return buf; } /* Helper: redirect stdout to a temp file, returning saved stdout fd and path to out file. */ static int redirect_stdout_to_temp(char** out_path) { fflush(stdout); char tmpl[] = "/tmp/nl_out_XXXXXX"; int outfd = mkstemp(tmpl); if (outfd < 0) return -1; int saved = dup(STDOUT_FILENO); if (saved < 0) { close(outfd); unlink(tmpl); return -1; } if (dup2(outfd, STDOUT_FILENO) < 0) { close(outfd); close(saved); unlink(tmpl); return -1; } close(outfd); *out_path = strdup(tmpl); return saved; } /* Helper: redirect stderr to a temp file, returning saved stderr fd and path. */ static int redirect_stderr_to_temp(char** err_path) { fflush(stderr); char tmpl[] = "/tmp/nl_err_XXXXXX"; int errfd = mkstemp(tmpl); if (errfd < 0) return -1; int saved = dup(STDERR_FILENO); if (saved < 0) { close(errfd); unlink(tmpl); return -1; } if (dup2(errfd, STDERR_FILENO) < 0) { close(errfd); close(saved); unlink(tmpl); return -1; } close(errfd); *err_path = strdup(tmpl); return saved; } /* Helper: restore stdout from saved fd. */ static void restore_stdout(int saved_fd) { fflush(stdout); dup2(saved_fd, STDOUT_FILENO); close(saved_fd); } /* Helper: restore stderr from saved fd. */ static void restore_stderr(int saved_fd) { fflush(stderr); dup2(saved_fd, STDERR_FILENO); close(saved_fd); } /* Helper: redirect stdin from a file, returning saved stdin fd. */ static int redirect_stdin_from_file(const char* path) { int fd = open(path, O_RDONLY); if (fd < 0) return -1; int saved = dup(STDIN_FILENO); if (saved < 0) { close(fd); return -1; } if (dup2(fd, STDIN_FILENO) < 0) { close(fd); close(saved); return -1; } close(fd); return saved; } extern char const *separator_str; extern int lineno_width; extern char const *lineno_format; extern char *print_no_line_fmt; extern intmax_t starting_line_number; extern intmax_t page_incr; extern bool reset_numbers; extern char const *body_type; extern char const *header_type; extern char const *footer_type; extern char const *current_type; extern bool have_read_stdin; /* Keep track to free the allocated format string per test. */ static char* allocated_print_no_line_fmt = NULL; /* Initialize core globals before each test. */ void setUp(void) { /* Defaults tailored for tests */ separator_str = "\t"; lineno_width = 2; lineno_format = FORMAT_RIGHT_NOLZ; starting_line_number = 1; page_incr = 1; reset_numbers = true; /* Ensure default numbering style is body "t". */ body_type = "t"; header_type = "n"; footer_type = "n"; current_type = body_type; /* Build the "no number" prefix: spaces + separator. */ if (allocated_print_no_line_fmt) { free(allocated_print_no_line_fmt); allocated_print_no_line_fmt = NULL; } allocated_print_no_line_fmt = make_print_no_line_fmt_buf(lineno_width, separator_str); print_no_line_fmt = allocated_print_no_line_fmt; /* Set initial line number. */ reset_lineno(); /* Clear have_read_stdin for tests that inspect it. */ have_read_stdin = false; } void tearDown(void) { if (allocated_print_no_line_fmt) { free(allocated_print_no_line_fmt); allocated_print_no_line_fmt = NULL; print_no_line_fmt = NULL; } } /* Test 1: Simple numbering of a regular file with two non-empty lines. */ static void test_nl_file_numbers_simple_file(void) { const char* input = "line1\nline2\n"; char* in_path = create_temp_file_with(input); TEST_ASSERT_NOT_NULL(in_path); char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); /* Call under test while stdout is redirected: no Unity asserts here. */ bool ok = nl_file(in_path); /* Restore stdout before asserting. */ restore_stdout(saved_stdout); /* Read captured output and assert. */ char* out = read_entire_file(out_path); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); const char* expected = " 1\tline1\n 2\tline2\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); unlink(out_path); free(out_path); unlink(in_path); free(in_path); } /* Test 2: Behavior with blank lines in 't' mode (number only non-empty lines). */ static void test_nl_file_t_mode_blank_lines(void) { const char* input = "\nX\n\n"; char* in_path = create_temp_file_with(input); TEST_ASSERT_NOT_NULL(in_path); /* Ensure 't' mode. */ body_type = "t"; current_type = body_type; reset_lineno(); char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); bool ok = nl_file(in_path); restore_stdout(saved_stdout); char* out = read_entire_file(out_path); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); const char* expected = " \t\n 1\tX\n \t\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); unlink(out_path); free(out_path); unlink(in_path); free(in_path); } /* Test 3: Reading from stdin ("-") and have_read_stdin side-effect. */ static void test_nl_file_from_stdin_dash(void) { const char* input = "a\nb\n"; char* in_path = create_temp_file_with(input); TEST_ASSERT_NOT_NULL(in_path); /* Redirect stdin. */ int saved_stdin = redirect_stdin_from_file(in_path); TEST_ASSERT_TRUE(saved_stdin >= 0); char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); /* Call under test while stdout redirected. */ bool ok = nl_file("-"); /* Restore stdout and stdin before assertions. */ restore_stdout(saved_stdout); dup2(saved_stdin, STDIN_FILENO); close(saved_stdin); char* out = read_entire_file(out_path); TEST_ASSERT_TRUE(ok); TEST_ASSERT_TRUE(have_read_stdin); TEST_ASSERT_NOT_NULL(out); const char* expected = " 1\ta\n 2\tb\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); unlink(out_path); free(out_path); unlink(in_path); free(in_path); } /* Test 4: Nonexistent file should return false and not write to stdout. */ static void test_nl_file_nonexistent_returns_false(void) { const char* bad_path = "/tmp/this_file_should_not_exist_nl_test_XXXX"; /* Capture stdout (should be empty) and stderr (to avoid noisy output). */ char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); char* err_path = NULL; int saved_stderr = redirect_stderr_to_temp(&err_path); TEST_ASSERT_TRUE(saved_stderr >= 0); TEST_ASSERT_NOT_NULL(err_path); /* Call under test while redirected. */ bool ok = nl_file(bad_path); /* Restore I/O before asserting. */ restore_stdout(saved_stdout); restore_stderr(saved_stderr); /* stdout should be empty and ok should be false. */ char* out = read_entire_file(out_path); TEST_ASSERT_FALSE(ok); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_STRING("", out); free(out); unlink(out_path); free(out_path); unlink(err_path); free(err_path); } /* Test 5: Starting line number and increment respected. */ static void test_nl_file_starting_and_increment(void) { starting_line_number = 5; page_incr = 3; reset_numbers = true; reset_lineno(); /* Keep 't' mode. */ body_type = "t"; current_type = body_type; const char* input = "aa\nbb\n"; char* in_path = create_temp_file_with(input); TEST_ASSERT_NOT_NULL(in_path); char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); bool ok = nl_file(in_path); restore_stdout(saved_stdout); char* out = read_entire_file(out_path); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); const char* expected = " 5\taa\n 8\tbb\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); unlink(out_path); free(out_path); unlink(in_path); free(in_path); } /* Test 6: 'a' mode with blank_join > 1 numbers grouped blank lines appropriately. */ static void test_nl_file_a_mode_blank_join(void) { /* Switch to 'a' mode and set blank_join. */ body_type = "a"; current_type = body_type; extern intmax_t blank_join; /* declared in program */ blank_join = 2; reset_numbers = true; starting_line_number = 1; reset_lineno(); const char* input = "\n\n\nx\n\n"; char* in_path = create_temp_file_with(input); TEST_ASSERT_NOT_NULL(in_path); char* out_path = NULL; int saved_stdout = redirect_stdout_to_temp(&out_path); TEST_ASSERT_TRUE(saved_stdout >= 0); TEST_ASSERT_NOT_NULL(out_path); bool ok = nl_file(in_path); restore_stdout(saved_stdout); char* out = read_entire_file(out_path); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(out); /* Expected: blank -> no number second blank -> numbered "1" third blank -> no number 'x' -> numbered "2" final blank -> no number */ const char* expected = " \t\n" " 1\t\n" " \t\n" " 2\tx\n" " \t\n"; TEST_ASSERT_EQUAL_STRING(expected, out); free(out); unlink(out_path); free(out_path); unlink(in_path); free(in_path); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_nl_file_numbers_simple_file); RUN_TEST(test_nl_file_t_mode_blank_lines); RUN_TEST(test_nl_file_from_stdin_dash); RUN_TEST(test_nl_file_nonexistent_returns_false); RUN_TEST(test_nl_file_starting_and_increment); RUN_TEST(test_nl_file_a_mode_blank_join); return UNITY_END(); }