Branch data Line data Source code
1 : : #include "test_librational_all.h"
2 : : #include <errno.h>
3 : : #include <float.h>
4 : : #include <inttypes.h>
5 : : #include <math.h>
6 : :
7 : : /**
8 : : * @brief Copy a formatted number without thousands separators
9 : : *
10 : : * @param src Source string that may contain comma separators
11 : : * @param dst Destination buffer
12 : : */
13 : 11 : static void strip_commas(
14 : : const char *src,
15 : : char *dst)
16 : : {
17 : 11 : size_t write = 0U;
18 : :
19 [ + + ]: 166 : for(size_t read = 0U; src[read] != '\0'; read++)
20 : : {
21 [ + + ]: 155 : if(src[read] == ',')
22 : : {
23 : 28 : continue;
24 : : }
25 : :
26 : 127 : dst[write++] = src[read];
27 : : }
28 : :
29 : 11 : dst[write] = '\0';
30 : 11 : }
31 : :
32 : : /**
33 : : * @brief Check comma grouping in an integer string
34 : : *
35 : : * @param val String to validate
36 : : * @return `true` when @p val uses valid groups of three digits
37 : : */
38 : 5 : static bool valid_comma_grouping(const char *val)
39 : : {
40 : 5 : size_t start = 0U;
41 : :
42 [ + + ]: 5 : if(val[0] == '-')
43 : : {
44 : 2 : start = 1U;
45 : : }
46 : :
47 : 5 : size_t len = strlen(val);
48 : :
49 : 5 : size_t digits_since_comma = 0U;
50 : :
51 [ + + ]: 99 : for(size_t i = len; i > start; i--)
52 : : {
53 : 94 : const char ch = val[i - 1U];
54 : :
55 [ + + + - ]: 94 : if(ch >= '0' && ch <= '9')
56 : : {
57 : 72 : digits_since_comma++;
58 : 72 : continue;
59 : : }
60 : :
61 [ + - ]: 22 : if(ch == ',')
62 : : {
63 [ - + ]: 22 : if(digits_since_comma != 3U)
64 : : {
65 : 0 : return false;
66 : : }
67 : :
68 : 22 : digits_since_comma = 0U;
69 : 22 : continue;
70 : : }
71 : :
72 : 0 : return false;
73 : : }
74 : :
75 [ + - + - ]: 5 : return digits_since_comma >= 1U && digits_since_comma <= 3U;
76 : : }
77 : :
78 : : /**
79 : : * @brief Compare a signed formatted integer with the standard ungrouped value
80 : : *
81 : : * @param val Integer value that was formatted
82 : : * @param formatted Candidate formatted string with comma grouping
83 : : * @return `true` when @p formatted has valid grouping and preserves the value
84 : : */
85 : 4 : static bool form_intmax_matches_standard(
86 : : intmax_t val,
87 : : const char *formatted)
88 : : {
89 : : char expected[MAX_CHARACTERS];
90 : : char stripped[MAX_CHARACTERS];
91 : :
92 : 4 : (void)snprintf(expected,sizeof(expected),"%" PRIdMAX,val);
93 : :
94 [ - + ]: 4 : if(valid_comma_grouping(formatted) == false)
95 : : {
96 : 0 : return false;
97 : : }
98 : :
99 : 4 : strip_commas(formatted,stripped);
100 : :
101 : 4 : return 0 == strcmp(stripped,expected);
102 : : }
103 : :
104 : : /**
105 : : * @brief Compare an unsigned formatted integer with the standard ungrouped value
106 : : *
107 : : * @param val Integer value that was formatted
108 : : * @param formatted Candidate formatted string with comma grouping
109 : : * @return `true` when @p formatted has valid grouping and preserves the value
110 : : */
111 : 1 : static bool form_uintmax_matches_standard(
112 : : uintmax_t val,
113 : : const char *formatted)
114 : : {
115 : : char expected[MAX_CHARACTERS];
116 : : char stripped[MAX_CHARACTERS];
117 : :
118 : 1 : (void)snprintf(expected,sizeof(expected),"%" PRIuMAX,val);
119 : :
120 [ - + ]: 1 : if(valid_comma_grouping(formatted) == false)
121 : : {
122 : 0 : return false;
123 : : }
124 : :
125 : 1 : strip_commas(formatted,stripped);
126 : :
127 : 1 : return 0 == strcmp(stripped,expected);
128 : : }
129 : :
130 : : /**
131 : : * @brief Validate the grouped integer part of a formatted real number
132 : : *
133 : : * @param val Full formatted real string
134 : : * @param start First character index of the integer part
135 : : * @param end One-past-last character index of the integer part
136 : : * @return `true` when the integer part uses valid comma grouping
137 : : */
138 : 6 : static bool valid_grouped_integer_part(
139 : : const char *val,
140 : : size_t start,
141 : : size_t end)
142 : : {
143 : 6 : size_t digits_since_comma = 0U;
144 : :
145 [ + + ]: 42 : for(size_t i = end; i > start; i--)
146 : : {
147 : 36 : const char ch = val[i - 1U];
148 : :
149 [ + + + - ]: 36 : if(ch >= '0' && ch <= '9')
150 : : {
151 : 30 : digits_since_comma++;
152 : 30 : continue;
153 : : }
154 : :
155 [ + - ]: 6 : if(ch == ',')
156 : : {
157 [ - + ]: 6 : if(digits_since_comma != 3U)
158 : : {
159 : 0 : return false;
160 : : }
161 : :
162 : 6 : digits_since_comma = 0U;
163 : 6 : continue;
164 : : }
165 : :
166 : 0 : return false;
167 : : }
168 : :
169 [ + - + - ]: 6 : return digits_since_comma >= 1U && digits_since_comma <= 3U;
170 : : }
171 : :
172 : : /**
173 : : * @brief Validate portable formatting rules for a grouped real number string
174 : : *
175 : : * @param val Formatted real string
176 : : * @return `true` when @p val has valid grouping and fractional text
177 : : */
178 : 6 : static bool valid_grouped_real(const char *val)
179 : : {
180 : 6 : size_t start = 0U;
181 : :
182 [ + + ]: 6 : if(val[0] == '-')
183 : : {
184 : 1 : start = 1U;
185 : : }
186 : :
187 : 6 : const char *dot = strchr(val + start,'.');
188 : 6 : const size_t total_len = strlen(val);
189 [ + + ]: 6 : const size_t integer_end = dot == NULL ? total_len : (size_t)(dot - val);
190 : :
191 [ - + ]: 6 : if(valid_grouped_integer_part(val,start,integer_end) == false)
192 : : {
193 : 0 : return false;
194 : : }
195 : :
196 [ + + ]: 6 : if(dot == NULL)
197 : : {
198 : 3 : return true;
199 : : }
200 : :
201 : 3 : const char *fraction = dot + 1U;
202 : :
203 [ - + ]: 3 : if(*fraction == '\0')
204 : : {
205 : 0 : return false;
206 : : }
207 : :
208 [ + + ]: 22 : for(size_t i = 0U; fraction[i] != '\0'; i++)
209 : : {
210 [ + - - + ]: 19 : if(fraction[i] < '0' || fraction[i] > '9')
211 : : {
212 : 0 : return false;
213 : : }
214 : : }
215 : :
216 : 3 : return fraction[strlen(fraction) - 1U] != '0';
217 : : }
218 : :
219 : : /**
220 : : * @brief Check a formatted real number without depending on exact long double text
221 : : *
222 : : * @param expected Numeric value that was formatted
223 : : * @param formatted Candidate formatted string
224 : : * @return `true` when @p formatted parses back close enough to @p expected
225 : : */
226 : 6 : static bool form_real_matches_portable(
227 : : long double expected,
228 : : const char *formatted)
229 : : {
230 [ - + ]: 6 : if(valid_grouped_real(formatted) == false)
231 : : {
232 : 0 : return false;
233 : : }
234 : :
235 : : char stripped[MAX_CHARACTERS];
236 : 6 : strip_commas(formatted,stripped);
237 : :
238 : 6 : errno = 0;
239 : 6 : const long double parsed = strtold(stripped,NULL);
240 : :
241 [ - + ]: 6 : if(errno != 0)
242 : : {
243 : 0 : return false;
244 : : }
245 : :
246 : 6 : long double tolerance = fabsl(expected) * LDBL_EPSILON * 4.0L;
247 : :
248 [ + + ]: 6 : if(tolerance < 0.000000001L)
249 : : {
250 : 5 : tolerance = 0.000000001L;
251 : : }
252 : 6 : return fabsl(parsed - expected) <= tolerance;
253 : : }
254 : :
255 : : /**
256 : : * @brief Check real-number formatting through the generic form() macro
257 : : *
258 : : * @details Exercises stable text expectations (zero handling, near-zero
259 : : * normalization where magnitudes below 1e-10 collapse to "0", rounding
260 : : * to at most 9 fractional digits, grouped integer parts using ',' as
261 : : * thousands separator and '.' as decimal point regardless of the
262 : : * current locale, sign placement, and stripping of trailing fractional
263 : : * zeros) and portable equivalence for long double values whose exact
264 : : * decimal text is platform dependent — those are parsed back through
265 : : * strtold() and compared against the original value with an LDBL_EPSILON
266 : : * tolerance. Also verifies that the generic dispatch correctly routes
267 : : * plain double and float arguments to the real formatter, and that a
268 : : * successful call returns the caller-provided buffer pointer (not an
269 : : * internal static or a literal "")
270 : : *
271 : : * @return Return describing success or failure
272 : : */
273 : 1 : static Return test_librational_0001_1(void)
274 : : {
275 : 1 : INITTEST;
276 : :
277 : : char formatted[MAX_CHARACTERS];
278 : :
279 : : /* The thousands separator ',' and decimal point '.' are fixed by the library and not affected by the current locale */
280 : :
281 : : /* Exact checks cover zero handling, near-zero normalization, rounding, and grouped integer parts */
282 : 1 : ASSERT(0 == strcmp(form(0.0L,formatted,sizeof(formatted)),"0"));
283 : 1 : ASSERT(0 == strcmp(form(-0.0000000004L,formatted,sizeof(formatted)),"0"));
284 : 1 : ASSERT(0 == strcmp(form(0.0000000006L,formatted,sizeof(formatted)),"0.000000001"));
285 : 1 : ASSERT(0 == strcmp(form(-0.0000000006L,formatted,sizeof(formatted)),"-0.000000001"));
286 : 1 : ASSERT(0 == strcmp(form(1234567.0L,formatted,sizeof(formatted)),"1,234,567"));
287 : 1 : ASSERT(0 == strcmp(form(1234567890123456.0L,formatted,sizeof(formatted)),"1,234,567,890,123,456"));
288 : 1 : ASSERT(0 == strcmp(form(1234567.125L,formatted,sizeof(formatted)),"1,234,567.125"));
289 : 1 : ASSERT(0 == strcmp(form(-9876.5L,formatted,sizeof(formatted)),"-9,876.5"));
290 : :
291 : : /* Portable checks avoid tying the test to one exact long double decimal spelling */
292 : 1 : ASSERT(form_real_matches_portable(123456789.123456780L,form(123456789.123456780L,formatted,sizeof(formatted))));
293 : 1 : ASSERT(form_real_matches_portable(987654321098.123456789L,form(987654321098.123456789L,formatted,sizeof(formatted))));
294 : 1 : ASSERT(form_real_matches_portable(1.23L,form(1.23L,formatted,sizeof(formatted))));
295 : 1 : ASSERT(form_real_matches_portable(9.9999999995L,form(9.9999999995L,formatted,sizeof(formatted))));
296 : 1 : ASSERT(form_real_matches_portable(-9.9999999995L,form(-9.9999999995L,formatted,sizeof(formatted))));
297 : 1 : ASSERT(form_real_matches_portable(999.9999999995L,form(999.9999999995L,formatted,sizeof(formatted))));
298 : :
299 : : /* The generic form() macro must also route plain double and float arguments to the real formatter.
300 : : * The chosen values are exactly representable in float so the textual result remains stable */
301 : 1 : ASSERT(0 == strcmp(form((double)0.0,formatted,sizeof(formatted)),"0"));
302 : 1 : ASSERT(0 == strcmp(form((double)1234.5,formatted,sizeof(formatted)),"1,234.5"));
303 : 1 : ASSERT(0 == strcmp(form((float)0.0F,formatted,sizeof(formatted)),"0"));
304 : 1 : ASSERT(0 == strcmp(form((float)1234.5F,formatted,sizeof(formatted)),"1,234.5"));
305 : :
306 : : /* A successful call must return the caller-provided buffer pointer, not an internal static or literal */
307 : 1 : ASSERT(form(1.5L,formatted,sizeof(formatted)) == formatted);
308 : :
309 : 1 : RETURN_STATUS;
310 : : }
311 : :
312 : : /**
313 : : * @brief Check integer formatting and caller-provided buffer isolation
314 : : *
315 : : * @details Confirms _Generic routing for boolean and signed integer
316 : : * types, validates platform-dependent extremes (INTMAX_MIN, INTMAX_MAX,
317 : : * UINTMAX_MAX) by stripping commas and comparing against the standard
318 : : * %PRIdMAX/%PRIuMAX text, verifies that consecutive calls into two
319 : : * separate destination buffers leave the earlier buffer untouched, and
320 : : * verifies that the integer backends return the caller-provided buffer
321 : : * pointer for successful calls
322 : : *
323 : : * @return Return describing success or failure
324 : : */
325 : 1 : static Return test_librational_0001_2(void)
326 : : {
327 : 1 : INITTEST;
328 : :
329 : : char formatted[MAX_CHARACTERS];
330 : : char first[FORM_OUTPUT_BUFFER_SIZE];
331 : : char second[FORM_OUTPUT_BUFFER_SIZE];
332 : :
333 : : /* The generic form() macro must dispatch boolean and signed integer values to integer formatting */
334 : 1 : ASSERT(0 == strcmp(form((_Bool)0,formatted,sizeof(formatted)),"0"));
335 : 1 : ASSERT(0 == strcmp(form((_Bool)1,formatted,sizeof(formatted)),"1"));
336 : 1 : ASSERT(0 == strcmp(form((int)-12345,formatted,sizeof(formatted)),"-12,345"));
337 : 1 : ASSERT(0 == strcmp(form((short)-12345,formatted,sizeof(formatted)),"-12,345"));
338 : :
339 : : /* Extremes are checked by stripping commas and comparing with the standard conversion */
340 : 1 : const char *intmin_formatted = form_intmax_r(INTMAX_MIN,formatted,sizeof(formatted));
341 : 1 : ASSERT(form_intmax_matches_standard(INTMAX_MIN,intmin_formatted));
342 : 1 : const char *intmax_formatted = form_intmax_r(INTMAX_MAX,formatted,sizeof(formatted));
343 : 1 : ASSERT(form_intmax_matches_standard(INTMAX_MAX,intmax_formatted));
344 : 1 : const char *intneg_formatted = form_intmax_r(-1234567,formatted,sizeof(formatted));
345 : 1 : ASSERT(form_intmax_matches_standard(-1234567,intneg_formatted));
346 : 1 : const char *intpos_formatted = form_intmax_r(1234567,formatted,sizeof(formatted));
347 : 1 : ASSERT(form_intmax_matches_standard(1234567,intpos_formatted));
348 : 1 : const char *uintmax_formatted = form_uintmax_r(UINTMAX_MAX,formatted,sizeof(formatted));
349 : 1 : ASSERT(form_uintmax_matches_standard(UINTMAX_MAX,uintmax_formatted));
350 : :
351 : : /* Two destination buffers must remain independent between consecutive calls */
352 : 1 : ASSERT(0 == strcmp(form((size_t)1234,first,sizeof(first)),"1,234"));
353 : 1 : ASSERT(0 == strcmp(form((size_t)5678,second,sizeof(second)),"5,678"));
354 : 1 : ASSERT(0 == strcmp(first,"1,234"));
355 : :
356 : : /* A successful call into either integer backend must return the caller-provided buffer pointer */
357 : 1 : ASSERT(form_intmax_r((intmax_t)42,formatted,sizeof(formatted)) == formatted);
358 : 1 : ASSERT(form_uintmax_r((uintmax_t)42U,formatted,sizeof(formatted)) == formatted);
359 : :
360 : 1 : RETURN_STATUS;
361 : : }
362 : :
363 : : /**
364 : : * @brief Check numeric formatter behavior with tiny destination buffers
365 : : *
366 : : * @details Covers shrink-to-fit behavior of form_real_r() (silent
367 : : * reduction of fractional precision until the result fits, or fallback
368 : : * to an empty terminated string when even the integer part cannot be
369 : : * written), the non-finite branch (NaN, +-Inf and values larger than
370 : : * UINTMAX_MAX become empty strings), the all-or-nothing behavior of
371 : : * the integer formatters (either the full grouped value fits or the
372 : : * result is empty — no partial digits are ever returned), the extra
373 : : * byte that the leading '-' demands for signed integers, the rule that
374 : : * a successful shrink-to-fit result still returns the caller-provided
375 : : * buffer pointer instead of a literal, and explicit NULL/size=0 input
376 : : * validation for form_real_r(), form_intmax_r() and form_uintmax_r()
377 : : *
378 : : * @return Return describing success or failure
379 : : */
380 : 1 : static Return test_librational_0001_3(void)
381 : : {
382 : 1 : INITTEST;
383 : :
384 : : char formatted[MAX_CHARACTERS];
385 : 1 : char tiny_real_1[1] = {'X'};
386 : : /* Two two-byte buffers are kept separate so the zero-value case and the
387 : : nonzero-value case do not share initial contents between assertions */
388 : 1 : char tiny_real_2[2] = {'X','Y'};
389 : 1 : char tiny_real_2_nonzero[2] = {'X','Y'};
390 : 1 : char tiny_real_zero_1[1] = {'X'};
391 : 1 : char tiny_real_group_fail[5] = {'X','X','X','X','X'};
392 : 1 : char tiny_real_group_ok[6] = {'X','X','X','X','X','X'};
393 : 1 : char tiny_real_frac_fail[7] = {'X','X','X','X','X','X','X'};
394 : 1 : char tiny_real_frac_ok[8] = {'X','X','X','X','X','X','X','X'};
395 : 1 : char tiny_uint_1[1] = {'X'};
396 : 1 : char tiny_uint_2[2] = {'X','Y'};
397 : 1 : char tiny_uint_3[3] = {'X','Y','Z'};
398 : 1 : char tiny_uint_comma_fail[5] = {'X','Y','Z','Q','W'};
399 : 1 : char tiny_uint_7[7] = {'X','Y','Z','Q','W','E','R'};
400 : 1 : char tiny_int_1[1] = {'X'};
401 : 1 : char tiny_int_2[2] = {'X','Y'};
402 : 1 : char tiny_int_3[3] = {'X','Y','Z'};
403 : 1 : char tiny_int_6[6] = {'X','Y','Z','Q','W','E'};
404 : 1 : char tiny_int_7[7] = {'X','Y','Z','Q','W','E','R'};
405 : :
406 : : /* NULL output buffer and zero output size must produce a literal empty string without writing through the pointer */
407 : 1 : ASSERT(0 == strcmp(form_real_r(1.0L,NULL,sizeof(formatted)),""));
408 : 1 : ASSERT(0 == strcmp(form_real_r(1.0L,formatted,0U),""));
409 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)1,NULL,sizeof(formatted)),""));
410 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)1,formatted,0U),""));
411 : 1 : ASSERT(0 == strcmp(form_uintmax_r((uintmax_t)1U,NULL,sizeof(formatted)),""));
412 : 1 : ASSERT(0 == strcmp(form_uintmax_r((uintmax_t)1U,formatted,0U),""));
413 : :
414 : : /* Real formatting must either fit, reduce precision, or write an empty terminated string.
415 : : A 1-byte buffer cannot even hold a terminator behind a digit, so both the zero
416 : : fast path and the regular path write only '\0'. A 2-byte buffer exercises two
417 : : different code paths in form_real_r() that both happen to produce a single-digit
418 : : result:
419 : : - 1.25 takes the regular path that calls shrink-to-fit until the fractional part
420 : : is fully dropped, leaving "1"
421 : : - 0.0 takes the dedicated form_write_zero() fast path, which requires
422 : : result_size > 1 to write "0" */
423 : 1 : (void)form_real_r(1234.5L,tiny_real_1,sizeof(tiny_real_1));
424 : 1 : ASSERT(tiny_real_1[0] == '\0');
425 : 1 : (void)form_real_r(0.0L,tiny_real_zero_1,sizeof(tiny_real_zero_1));
426 : 1 : ASSERT(tiny_real_zero_1[0] == '\0');
427 : 1 : ASSERT(0 == strcmp(form_real_r(1.25L,tiny_real_2_nonzero,sizeof(tiny_real_2_nonzero)),"1"));
428 : 1 : ASSERT(0 == strcmp(form_real_r(0.0L,tiny_real_2,sizeof(tiny_real_2)),"0"));
429 : :
430 : : /* The next four buffer sizes walk the boundary for "1,234" and "1,234.5":
431 : : - 5 bytes cannot fit "1,234" plus its terminator (6 bytes total), so the result must be empty
432 : : - 6 bytes fit "1,234" plus its terminator exactly
433 : : - 7 bytes still cannot fit "1,234.5" plus its terminator (8 bytes total), so the fractional part is dropped with rounding ("1,235")
434 : : - 8 bytes fit "1,234.5" plus its terminator exactly.
435 : : For every shrink-to-fit branch the function must still return the caller-provided
436 : : buffer pointer, not a literal "" — this guards against a silent fallthrough into
437 : : form_write_empty() when shrink-to-fit succeeds */
438 : 1 : (void)form_real_r(1234.0L,tiny_real_group_fail,sizeof(tiny_real_group_fail));
439 : 1 : ASSERT(tiny_real_group_fail[0] == '\0');
440 : :
441 : 1 : const char *group_ok_result = form_real_r(1234.0L,tiny_real_group_ok,sizeof(tiny_real_group_ok));
442 : 1 : ASSERT(group_ok_result == tiny_real_group_ok);
443 : 1 : ASSERT(0 == strcmp(group_ok_result,"1,234"));
444 : :
445 : 1 : const char *frac_fail_result = form_real_r(1234.5L,tiny_real_frac_fail,sizeof(tiny_real_frac_fail));
446 : 1 : ASSERT(frac_fail_result == tiny_real_frac_fail);
447 : 1 : ASSERT(0 == strcmp(frac_fail_result,"1,235"));
448 : :
449 : 1 : const char *frac_ok_result = form_real_r(1234.5L,tiny_real_frac_ok,sizeof(tiny_real_frac_ok));
450 : 1 : ASSERT(frac_ok_result == tiny_real_frac_ok);
451 : 1 : ASSERT(0 == strcmp(frac_ok_result,"1,234.5"));
452 : :
453 : : /* Non-finite real values are intentionally represented as empty strings */
454 : 1 : ASSERT(0 == strcmp(form_real_r(NAN,formatted,sizeof(formatted)),""));
455 : 1 : ASSERT(0 == strcmp(form_real_r(INFINITY,formatted,sizeof(formatted)),""));
456 : 1 : ASSERT(0 == strcmp(form_real_r(-INFINITY,formatted,sizeof(formatted)),""));
457 : 1 : ASSERT(0 == strcmp(form_real_r((long double)UINTMAX_MAX * 2.0L,formatted,sizeof(formatted)),""));
458 : :
459 : : /* Unsigned integer formatting has no truncation mode: either the full value with its
460 : : thousands separators fits, or the buffer is left as an empty NUL-terminated string.
461 : : The "X"/"Y"/"Z" sentinels in the destination buffers must not be left visible:
462 : : - 1 byte: holds only the terminator, even for UINTMAX_MAX
463 : : - 2 and 3 bytes: too small for "12,345" (which needs 7 bytes including the terminator), result must be empty
464 : : - 5 bytes: enough for the digits of "1234" but not for the comma in "1,234" (6 bytes), so empty
465 : : - 7 bytes: exactly fits "12,345" plus its terminator */
466 : 1 : (void)form_uintmax_r(UINTMAX_MAX,tiny_uint_1,sizeof(tiny_uint_1));
467 : 1 : ASSERT(tiny_uint_1[0] == '\0');
468 : 1 : ASSERT(0 == strcmp(form_uintmax_r(12345U,tiny_uint_2,sizeof(tiny_uint_2)),""));
469 : 1 : ASSERT(0 == strcmp(form_uintmax_r(12345U,tiny_uint_3,sizeof(tiny_uint_3)),""));
470 : 1 : ASSERT(0 == strcmp(form_uintmax_r(1234U,tiny_uint_comma_fail,sizeof(tiny_uint_comma_fail)),""));
471 : 1 : ASSERT(0 == strcmp(form_uintmax_r(12345U,tiny_uint_7,sizeof(tiny_uint_7)),"12,345"));
472 : :
473 : : /* Signed integer formatting follows the same all-or-nothing rule, but the leading
474 : : '-' takes one extra byte and shifts every boundary by one:
475 : : - 1 byte: holds only the terminator
476 : : - 2 bytes: enough digits for "-1" by digit count, but no room for the sign byte, so empty
477 : : - 3 bytes: exactly fits "-1" with its terminator
478 : : - 6 bytes: enough for "1,234" but not for "-1,234" (7 bytes with the terminator), so empty
479 : : - 7 bytes: exactly fits "-1,234" with its terminator */
480 : 1 : (void)form_intmax_r((intmax_t)-1,tiny_int_1,sizeof(tiny_int_1));
481 : 1 : ASSERT(tiny_int_1[0] == '\0');
482 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)-1,tiny_int_2,sizeof(tiny_int_2)),""));
483 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)-1,tiny_int_3,sizeof(tiny_int_3)),"-1"));
484 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)-1234,tiny_int_6,sizeof(tiny_int_6)),""));
485 : 1 : ASSERT(0 == strcmp(form_intmax_r((intmax_t)-1234,tiny_int_7,sizeof(tiny_int_7)),"-1,234"));
486 : :
487 : 1 : RETURN_STATUS;
488 : : }
489 : :
490 : : /**
491 : : * @brief Check test-visible private form helper guards
492 : : *
493 : : * @details Directly calls helpers that are normally static. These branches are
494 : : * defensive guards behind the public form API and cannot all be reached through
495 : : * the public entry points alone
496 : : *
497 : : * @return Return describing success or failure
498 : : */
499 : 1 : static Return test_librational_0001_6(void)
500 : : {
501 : 1 : INITTEST;
502 : :
503 : 1 : char buffer[2] = {'X','Y'};
504 : :
505 : : /* form_write_empty() returns a literal for invalid storage and clears valid storage */
506 : 1 : ASSERT(0 == strcmp(form_write_empty(NULL,sizeof(buffer)),""));
507 : 1 : ASSERT(0 == strcmp(form_write_empty(buffer,0U),""));
508 : 1 : ASSERT(form_write_empty(buffer,sizeof(buffer)) == buffer);
509 : 1 : ASSERT(buffer[0] == '\0');
510 : 1 : ASSERT(buffer[1] == 'Y');
511 : :
512 : : /* form_write_zero() delegates invalid and too-small storage to form_write_empty() */
513 : 1 : buffer[0] = 'X';
514 : 1 : buffer[1] = 'Y';
515 : 1 : ASSERT(0 == strcmp(form_write_zero(NULL,sizeof(buffer)),""));
516 : 1 : ASSERT(0 == strcmp(form_write_zero(buffer,0U),""));
517 : 1 : ASSERT(buffer[0] == 'X');
518 : 1 : ASSERT(buffer[1] == 'Y');
519 : :
520 : 1 : ASSERT(form_write_zero(buffer,1U) == buffer);
521 : 1 : ASSERT(buffer[0] == '\0');
522 : 1 : ASSERT(buffer[1] == 'Y');
523 : :
524 : 1 : buffer[0] = 'X';
525 : 1 : buffer[1] = 'Y';
526 : 1 : ASSERT(form_write_zero(buffer,sizeof(buffer)) == buffer);
527 : 1 : ASSERT(0 == strcmp(buffer,"0"));
528 : :
529 : 1 : RETURN_STATUS;
530 : : }
531 : :
532 : : /**
533 : : * @brief Check byte-size formatting in static and caller-provided buffers
534 : : *
535 : : * @details Validates both the static-buffer variant bkbmbgbtbpbeb()
536 : : * (FULL_VIEW for the full decomposition, MAJOR_VIEW for the largest
537 : : * unit only with floor semantics — a 1.2 GiB input must produce "1GiB",
538 : : * never "2GiB") and the reentrant variant bkbmbgbtbpbeb_r() (NULL/size=0
539 : : * rejection, caller-buffer isolation between consecutive calls, return
540 : : * of the caller-provided buffer pointer for successful calls, and
541 : : * well-terminated truncation when the destination buffer is too small
542 : : * to hold the full decomposition). Builds with GNU ld --wrap support also
543 : : * exercise the simulated snprintf() failure branch through the testmocking
544 : : * helpers
545 : : *
546 : : * @return Return describing success or failure
547 : : */
548 : 1 : static Return test_librational_0001_4(void)
549 : : {
550 : 1 : INITTEST;
551 : :
552 : : char bkb_r_a[MAX_CHARACTERS];
553 : : char bkb_r_b[MAX_CHARACTERS];
554 : 1 : char bkb_r_tiny_1[1] = {'X'};
555 : 1 : char bkb_r_tiny_2[2] = {'X','Y'};
556 : 1 : char bkb_r_tiny_3[3] = {'X','Y','Z'};
557 : 1 : char bkb_r_tiny_6[6] = {'X','X','X','X','X','X'};
558 : 1 : char bkb_r_tiny_9[9] = {'X','X','X','X','X','X','X','X','X'};
559 : 1 : char bkb_r_tiny_10[10] = {'X','X','X','X','X','X','X','X','X','X'};
560 : : #ifndef EVIL_EMPIRE_OS
561 : 1 : char bkb_r_snprintf_failure[MAX_CHARACTERS] = "not empty";
562 : : #endif
563 : 1 : const size_t kibibyte = 1024ULL;
564 : 1 : const size_t mebibyte = kibibyte * 1024ULL;
565 : 1 : const size_t gibibyte = mebibyte * 1024ULL;
566 : 1 : const size_t tebibyte = gibibyte * 1024ULL;
567 : 1 : const size_t pebibyte = tebibyte * 1024ULL;
568 : 1 : const size_t exbibyte = pebibyte * 1024ULL;
569 : 1 : const size_t mixed_units = 4ULL * exbibyte
570 : 1 : + 5ULL * pebibyte
571 : 1 : + 6ULL * tebibyte
572 : 1 : + 7ULL * gibibyte
573 : 1 : + 8ULL * mebibyte
574 : 1 : + 9ULL * kibibyte
575 : : + 10ULL;
576 : :
577 : : /* Static-buffer formatting must support full output and largest-unit-only output.
578 : : The last assertion uses 1,291,845,632 bytes = 1 GiB + 208 MiB; in MAJOR_VIEW the
579 : : formatter must show only the largest unit ("1GiB") and never round upward despite
580 : : the sizable remainder, otherwise a 1.5 GiB value would mislead callers as "2GiB" */
581 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(0,FULL_VIEW),"0B"));
582 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(1024,FULL_VIEW),"1KiB"));
583 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(1536,FULL_VIEW),"1KiB 512B"));
584 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(0,MAJOR_VIEW),"0B"));
585 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(1536,MAJOR_VIEW),"1KiB"));
586 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb(1291845632ULL,MAJOR_VIEW),"1GiB"));
587 : :
588 : : /* Reentrant formatting must reject invalid output buffers before writing */
589 : 1 : ASSERT(NULL == bkbmbgbtbpbeb_r(1U,FULL_VIEW,NULL,sizeof(bkb_r_a)));
590 : 1 : ASSERT(NULL == bkbmbgbtbpbeb_r(1U,FULL_VIEW,bkb_r_a,0U));
591 : :
592 : : /* A successful reentrant call must return the caller-provided buffer pointer, not an internal static */
593 : 1 : ASSERT(bkbmbgbtbpbeb_r(1024U,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)) == bkb_r_a);
594 : :
595 : : /* Valid reentrant calls should mirror the static-buffer formatter */
596 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(0U,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"0B"));
597 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1024U,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1KiB"));
598 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1536U,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1KiB 512B"));
599 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1536U,MAJOR_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1KiB"));
600 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1291845632ULL,MAJOR_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1GiB"));
601 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(kibibyte - 1ULL,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1023B"));
602 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(mebibyte - 1ULL,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1023KiB 1023B"));
603 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(mebibyte,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1MiB"));
604 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(gibibyte,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1GiB"));
605 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(tebibyte,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1TiB"));
606 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(pebibyte,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1PiB"));
607 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(exbibyte,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1EiB"));
608 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(tebibyte,MAJOR_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1TiB"));
609 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(exbibyte,MAJOR_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1EiB"));
610 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(exbibyte - 1ULL,MAJOR_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1023PiB"));
611 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(mixed_units,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"4EiB 5PiB 6TiB 7GiB 8MiB 9KiB 10B"));
612 : :
613 : : /* Writing into a second caller buffer must not modify the first one */
614 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1024U,FULL_VIEW,bkb_r_a,sizeof(bkb_r_a)),"1KiB"));
615 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(2048U,FULL_VIEW,bkb_r_b,sizeof(bkb_r_b)),"2KiB"));
616 : 1 : ASSERT(0 == strcmp(bkb_r_a,"1KiB"));
617 : :
618 : : /* Tiny buffers are allowed to hold truncated but still terminated output */
619 : 1 : (void)bkbmbgbtbpbeb_r(1536U,FULL_VIEW,bkb_r_tiny_1,sizeof(bkb_r_tiny_1));
620 : 1 : ASSERT('\0' == bkb_r_tiny_1[0]);
621 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(0U,FULL_VIEW,bkb_r_tiny_2,sizeof(bkb_r_tiny_2)),"0"));
622 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(0U,FULL_VIEW,bkb_r_tiny_3,sizeof(bkb_r_tiny_3)),"0B"));
623 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1536U,FULL_VIEW,bkb_r_tiny_6,sizeof(bkb_r_tiny_6)),"1KiB"));
624 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1536U,FULL_VIEW,bkb_r_tiny_9,sizeof(bkb_r_tiny_9)),"1KiB 512"));
625 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(1536U,FULL_VIEW,bkb_r_tiny_10,sizeof(bkb_r_tiny_10)),"1KiB 512B"));
626 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(mixed_units,FULL_VIEW,bkb_r_tiny_10,sizeof(bkb_r_tiny_10)),"4EiB 5PiB"));
627 : :
628 : : #ifndef EVIL_EMPIRE_OS
629 : : /* Simulated snprintf() failure must leave the caller with an empty, terminated result */
630 : 1 : testmocking_snprintf_fail_next(1);
631 : 1 : ASSERT(0 == strcmp(bkbmbgbtbpbeb_r(kibibyte,FULL_VIEW,bkb_r_snprintf_failure,sizeof(bkb_r_snprintf_failure)),""));
632 : 1 : testmocking_snprintf_disable();
633 : : #endif
634 : :
635 : 1 : RETURN_STATUS;
636 : : }
637 : :
638 : : /**
639 : : * @brief Check duration formatting in static and caller-provided buffers
640 : : *
641 : : * @details Exercises form_date() and form_date_r() over nanosecond inputs.
642 : : * Confirms zero handling, mixed-unit FULL_VIEW output, single-unit
643 : : * MAJOR_VIEW output across every supported unit from nanoseconds through
644 : : * years, successful calls returning their caller-provided buffer pointer,
645 : : * buffer isolation, and terminated truncation in tiny buffers down to a
646 : : * single byte. Link-time snprintf mock coverage for form_date_r() lives
647 : : * separately in the test_librational_0003 suite to keep this test runnable
648 : : * on platforms that lack GNU ld --wrap
649 : : *
650 : : * @return Return describing success or failure
651 : : */
652 : 1 : static Return test_librational_0001_5(void)
653 : : {
654 : 1 : INITTEST;
655 : :
656 : : char date_r_a[MAX_CHARACTERS];
657 : : char date_r_b[MAX_CHARACTERS];
658 : 1 : char date_r_tiny_1[1] = {'X'};
659 : 1 : char date_r_tiny_2[2] = {'X','Y'};
660 : 1 : char date_r_tiny_3[3] = {'X','Y','Z'};
661 : 1 : char date_r_tiny_truncated_unit[4] = {'X','X','X','X'};
662 : 1 : const long long int ns_in_microsecond = 1000LL;
663 : 1 : const long long int ns_in_millisecond = 1000000LL;
664 : 1 : const long long int ns_in_second = 1000000000LL;
665 : 1 : const long long int ns_in_minute = 60000000000LL;
666 : 1 : const long long int ns_in_hour = 3600000000000LL;
667 : 1 : const long long int ns_in_day = 86400000000000LL;
668 : 1 : const long long int ns_in_week = 604800000000000LL;
669 : 1 : const long long int ns_in_month = 2628000000000000LL;
670 : 1 : const long long int ns_in_year = 31536000000000000LL;
671 : :
672 : : /* Static-buffer formatting must support full output and largest-unit-only output */
673 : 1 : ASSERT(0 == strcmp(form_date(0LL,FULL_VIEW),"0ns"));
674 : 1 : ASSERT(0 == strcmp(form_date(0LL,MAJOR_VIEW),"0ns"));
675 : 1 : ASSERT(0 == strcmp(form_date(273000528LL,FULL_VIEW),"273ms 528ns"));
676 : 1 : ASSERT(0 == strcmp(form_date(273000528LL,MAJOR_VIEW),"273ms"));
677 : 1 : ASSERT(0 == strcmp(form_date(3600000000001LL,FULL_VIEW),"1h 1ns"));
678 : 1 : ASSERT(0 == strcmp(form_date(3600000000001LL,MAJOR_VIEW),"1h"));
679 : :
680 : : /* A successful reentrant call must return the caller-provided buffer pointer, not an internal static */
681 : 1 : ASSERT(form_date_r(1LL,FULL_VIEW,date_r_a,sizeof(date_r_a)) == date_r_a);
682 : :
683 : : /* Valid and compact reentrant buffers should receive terminated output */
684 : 1 : ASSERT(0 == strcmp(form_date_r(273000528LL,FULL_VIEW,date_r_a,sizeof(date_r_a)),"273ms 528ns"));
685 : 1 : ASSERT(0 == strcmp(form_date_r(273000528LL,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"273ms"));
686 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_microsecond - 1LL,FULL_VIEW,date_r_a,sizeof(date_r_a)),"999ns"));
687 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_microsecond,FULL_VIEW,date_r_a,sizeof(date_r_a)),"1μs"));
688 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_millisecond - 1LL,FULL_VIEW,date_r_a,sizeof(date_r_a)),"999μs 999ns"));
689 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_millisecond,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1ms"));
690 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_second - 1LL,FULL_VIEW,date_r_a,sizeof(date_r_a)),"999ms 999μs 999ns"));
691 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_second,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1s"));
692 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_minute,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1min"));
693 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_hour,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1h"));
694 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_day,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1d"));
695 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_week,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1w"));
696 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_month,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1mon"));
697 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_year,MAJOR_VIEW,date_r_a,sizeof(date_r_a)),"1y"));
698 : :
699 : : /* Writing into a second caller buffer must not modify the first one.
700 : : Two values are picked from different unit branches so an accidental shared
701 : : static buffer would visibly leak the latter value into the former */
702 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_second,FULL_VIEW,date_r_a,sizeof(date_r_a)),"1s"));
703 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_minute,FULL_VIEW,date_r_b,sizeof(date_r_b)),"1min"));
704 : 1 : ASSERT(0 == strcmp(date_r_a,"1s"));
705 : :
706 : : /* The next group walks tiny destination buffers for form_date_r():
707 : : - 1 byte holds only the terminator, so any nonzero duration yields an empty string
708 : : - 2 bytes fit the literal "0" for the zero-time fast path, but not the trailing "ns" unit
709 : : - 3 bytes fit "0n" — zero plus a truncated unit suffix, still NUL terminated
710 : : - 4 bytes fit "1y" plus its terminator for the largest unit, and any further units
711 : : produced by FULL_VIEW past that point are dropped because catdate_r() refuses
712 : : to write into a buffer that is already full */
713 : 1 : (void)form_date_r(273000528LL,FULL_VIEW,date_r_tiny_1,sizeof(date_r_tiny_1));
714 : 1 : ASSERT('\0' == date_r_tiny_1[0]);
715 : 1 : ASSERT(0 == strcmp(form_date_r(0LL,FULL_VIEW,date_r_tiny_2,sizeof(date_r_tiny_2)),"0"));
716 : 1 : ASSERT(0 == strcmp(form_date_r(0LL,FULL_VIEW,date_r_tiny_3,sizeof(date_r_tiny_3)),"0n"));
717 : 1 : ASSERT(0 == strcmp(form_date_r(ns_in_year + 1LL,FULL_VIEW,date_r_tiny_truncated_unit,sizeof(date_r_tiny_truncated_unit)),"1y"));
718 : :
719 : 1 : RETURN_STATUS;
720 : : }
721 : :
722 : : /**
723 : : * @brief Run librational formatting helper tests
724 : : *
725 : : * The suite covers numeric formatting, byte-size formatting, and duration
726 : : * formatting. It checks exact stable text where practical, portable numeric
727 : : * equivalence for long double cases, invalid argument handling, tiny buffer
728 : : * behavior, and caller-provided buffer isolation
729 : : *
730 : : * Covered API surface:
731 : : * - generic dispatch macro form() and its concrete backends
732 : : * form_real_r(), form_intmax_r() and form_uintmax_r()
733 : : * - byte-size formatters bkbmbgbtbpbeb() and bkbmbgbtbpbeb_r()
734 : : * - duration formatters form_date() and form_date_r()
735 : : *
736 : : * @return Return describing success or failure
737 : : */
738 : 1 : Return test_librational_0001(void)
739 : : {
740 : 1 : INITTEST;
741 : :
742 : 1 : TEST(test_librational_0001_1,"form() formats real values with grouping, rounding, _Generic dispatch and caller-buffer return");
743 : 1 : TEST(test_librational_0001_2,"integer formatters add grouping, cover platform extremes and preserve caller buffers");
744 : 1 : TEST(test_librational_0001_3,"numeric formatters validate NULL/size=0, non-finite values and tiny destination buffers");
745 : 1 : TEST(test_librational_0001_6,"private form helpers guard invalid storage and tiny zero buffers");
746 : 1 : TEST(test_librational_0001_4,"byte-size formatters cover full and major views, tiny buffers and snprintf failure");
747 : 1 : TEST(test_librational_0001_5,"duration formatters cover full and major views, two-buffer isolation and tiny buffers");
748 : :
749 : 1 : RETURN_STATUS;
750 : : }
|