LCOV - code coverage report
Current view: top level - libs/rational/tests/src - test_librational_0001.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.6 % 323 312
Test Date: 2026-07-12 01:01:34 Functions: 100.0 % 14 14
Branches: 70.0 % 60 42

             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                 :             : }
        

Generated by: LCOV version 2.0-1