coreutils / tests /nl /tests_for_proc_text.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
/* 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();
}