coreutils / tests /nl /tests_for_process_file.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 <errno.h>
/* 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();
}