coreutils / tests /numfmt /tests_for_process_line.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 <locale.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
/* Access to program internals (these are in the same translation unit). */
/* Globals from the program that we adjust in tests. */
extern struct field_range_pair *frp; /* declared in set-fields.h, included in the program */
/* Helper: capture output while calling process_line. */
typedef struct {
char *out; /* captured stdout string (malloc'd) */
int retval; /* return value from process_line */
int err; /* 0 on success, else nonzero */
} CapResult;
static CapResult capture_process_line_call(const char *input, bool newline)
{
CapResult r = {0};
/* Create a modifiable copy of input because process_line mutates it. */
char *mutable_line = NULL;
size_t inlen = strlen(input);
mutable_line = (char *)malloc(inlen + 1);
if (!mutable_line) { r.err = 1; return r; }
memcpy(mutable_line, input, inlen + 1);
/* Redirect stdout to a temporary file. */
FILE *tmp = tmpfile();
if (!tmp) { free(mutable_line); r.err = 2; return r; }
fflush(stdout);
int saved_fd = dup(fileno(stdout));
if (saved_fd < 0) { fclose(tmp); free(mutable_line); r.err = 3; return r; }
if (dup2(fileno(tmp), fileno(stdout)) < 0) {
close(saved_fd); fclose(tmp); free(mutable_line); r.err = 4; return r;
}
/* Call the target function while stdout is redirected. */
int rv = process_line(mutable_line, newline);
/* Flush and rewind to read captured output. */
fflush(stdout);
fflush(tmp);
fseek(tmp, 0, SEEK_END);
long sz = ftell(tmp);
if (sz < 0) sz = 0;
fseek(tmp, 0, SEEK_SET);
char *buf = (char *)malloc((size_t)sz + 1);
if (!buf) {
/* Restore stdout before returning. */
dup2(saved_fd, fileno(stdout));
close(saved_fd);
fclose(tmp);
free(mutable_line);
r.err = 5; return r;
}
size_t rd = fread(buf, 1, (size_t)sz, tmp);
buf[rd] = '\0';
/* Restore stdout. */
dup2(saved_fd, fileno(stdout));
close(saved_fd);
fclose(tmp);
r.out = buf;
r.retval = rv;
r.err = 0;
free(mutable_line);
return r;
}
/* Minimal environment setup helpers (operate on program globals). */
static void set_locale_basics(void)
{
/* Ensure a simple, predictable locale: C/POSIX */
setlocale(LC_ALL, "C");
/* The following identifiers are static globals in the program file.
Since this test is included in the same translation unit, we can set them. */
extern const char *decimal_point;
extern int decimal_point_length;
extern const char *thousands_sep;
extern int thousands_sep_length;
extern unsigned char line_delim;
/* Force simple decimal and no thousands separator. */
decimal_point = ".";
decimal_point_length = 1;
thousands_sep = "";
thousands_sep_length = 0;
line_delim = '\n';
}
static void reset_numfmt_state(void)
{
/* Reset a subset of globals to defaults that affect processing/printing. */
extern const char *delimiter;
extern int auto_padding;
extern intmax_t padding_width;
extern int zero_padding_width;
extern long int user_precision;
extern const char *suffix;
extern const char *unit_separator;
extern int grouping;
extern enum inval_type inval_style;
delimiter = NULL;
auto_padding = 0;
padding_width = 0;
zero_padding_width = 0;
user_precision = -1;
suffix = NULL;
unit_separator = NULL;
grouping = 0;
inval_style = inval_abort; /* default; tests that need ignore will set it */
}
/* Field range helpers: arrays to control which fields are included. */
static struct field_range_pair FRP_NONE[] = {
{ UINTMAX_MAX, 0 } /* terminator only: include no fields */
};
static struct field_range_pair FRP_ALL[] = {
{ 1, UINTMAX_MAX },
{ UINTMAX_MAX, 0 }
};
static struct field_range_pair FRP_FIRST_ONLY[] = {
{ 1, 1 },
{ UINTMAX_MAX, 0 }
};
void setUp(void) {
set_locale_basics();
reset_numfmt_state();
}
void tearDown(void) {
/* nothing to clean up */
}
/* Test: default whitespace delimiter, no fields included => output identical to input; newline appended. */
void test_process_line_whitespace_preserved_with_newline(void)
{
extern const char *delimiter;
delimiter = NULL; /* whitespace mode */
frp = FRP_NONE; /* include no fields => no numeric parsing/conversion */
const char *in = " 123 456\t789 ";
CapResult r = capture_process_line_call(in, true);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
/* Expected: identical input plus a newline. */
size_t exp_len = strlen(in) + 1;
char *exp = (char *)malloc(exp_len + 1);
TEST_ASSERT_NOT_NULL(exp);
strcpy(exp, in);
exp[exp_len - 1] = '\n';
exp[exp_len] = '\0';
TEST_ASSERT_EQUAL_STRING(exp, r.out);
TEST_ASSERT_TRUE(r.retval);
free(exp);
free(r.out);
}
/* Test: custom simple delimiter, no fields included => output identical; newline appended. */
void test_process_line_custom_delimiter_simple(void)
{
extern const char *delimiter;
delimiter = ",";
frp = FRP_NONE;
const char *in = "a,b,c";
CapResult r = capture_process_line_call(in, true);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
char expected[16];
snprintf(expected, sizeof expected, "%s\n", in);
TEST_ASSERT_EQUAL_STRING(expected, r.out);
TEST_ASSERT_TRUE(r.retval);
free(r.out);
}
/* Test: multibyte delimiter, no newline requested. */
void test_process_line_multibyte_delimiter_no_newline(void)
{
extern const char *delimiter;
delimiter = "\xE2\x98\x83"; /* UTF-8 snowman: ☃ */
frp = FRP_NONE;
const char *in = "x\xE2\x98\x83y\xE2\x98\x83z"; /* x☃y☃z */
CapResult r = capture_process_line_call(in, false);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
TEST_ASSERT_EQUAL_STRING(in, r.out);
TEST_ASSERT_TRUE(r.retval);
free(r.out);
}
/* Test: empty fields with delimiter are preserved exactly. */
void test_process_line_empty_fields_preserved_with_delimiter(void)
{
extern const char *delimiter;
delimiter = ",";
frp = FRP_NONE;
const char *in = "a,,b,,";
CapResult r = capture_process_line_call(in, true);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
char expected[32];
snprintf(expected, sizeof expected, "%s\n", in);
TEST_ASSERT_EQUAL_STRING(expected, r.out);
TEST_ASSERT_TRUE(r.retval);
free(r.out);
}
/* Test: invalid number in included field returns false, output remains unchanged. */
void test_process_line_invalid_number_included_field_returns_false(void)
{
/* Include first field only using FRP_FIRST_ONLY (or simply frp=NULL behavior),
ensure invalid input doesn't abort and returns false. */
extern enum inval_type inval_style;
inval_style = inval_ignore; /* avoid exiting on invalid input */
frp = FRP_FIRST_ONLY; /* include first field */
extern const char *delimiter;
delimiter = NULL; /* whitespace */
const char *in = "abc def"; /* 'abc' is not a number */
CapResult r = capture_process_line_call(in, true);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
char expected[32];
snprintf(expected, sizeof expected, "%s\n", in);
TEST_ASSERT_EQUAL_STRING(expected, r.out);
TEST_ASSERT_FALSE(r.retval);
free(r.out);
}
/* Test: include all fields with valid numbers, should return true and print numbers. */
void test_process_line_convert_all_fields_valid(void)
{
/* With default scale_to=none and no formatting overrides,
integer numbers should print unchanged. */
frp = FRP_ALL; /* include all fields */
extern const char *delimiter;
delimiter = NULL;
const char *in = "123 456";
CapResult r = capture_process_line_call(in, true);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");
char expected[32];
snprintf(expected, sizeof expected, "%s\n", in);
TEST_ASSERT_EQUAL_STRING(expected, r.out);
TEST_ASSERT_TRUE(r.retval);
free(r.out);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_process_line_whitespace_preserved_with_newline);
RUN_TEST(test_process_line_custom_delimiter_simple);
RUN_TEST(test_process_line_multibyte_delimiter_no_newline);
RUN_TEST(test_process_line_empty_fields_preserved_with_delimiter);
RUN_TEST(test_process_line_invalid_number_included_field_returns_false);
RUN_TEST(test_process_line_convert_all_fields_valid);
return UNITY_END();
}