coreutils / tests /nl /tests_for_nl_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 <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
/* 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();
}