File size: 8,712 Bytes
78d2150 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
#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();
} |