1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-07 00:35:37 +02:00

Improve the predictability of floating point numbers formatted as strings.

Some C libraries (e.g.musl) render floating point numbers differently when using printf(). This does not cause any problems for the core code but the unit tests require more predictability to function smoothly without a lot of exceptions.

To accomplish this, wrap the various floating point operations in functions that mostly continue doing math using double but format the output string using integers. This leads to more predictable output at the cost of some complexity.

Rounding could also be accomplished using nearbyint() but this would require linking the math library, which does not seem worth it for a fairly simple operation.
This commit is contained in:
David Steele
2025-06-05 13:27:45 -04:00
committed by GitHub
parent a30442f168
commit ad7ba46bc6
22 changed files with 413 additions and 105 deletions

View File

@ -13,6 +13,19 @@
<p>Fix defaults in command-line help.</p> <p>Fix defaults in command-line help.</p>
</release-item> </release-item>
</release-improvement-list> </release-improvement-list>
<release-development-list>
<release-item>
<github-pull-request id="2636"/>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="david.christensen"/>
</release-item-contributor-list>
<p>Improve the predictability of floating point numbers formatted as strings.</p>
</release-item>
</release-development-list>
</release-core-list> </release-core-list>
<release-doc-list> <release-doc-list>

View File

@ -793,7 +793,9 @@ cmdArchiveGet(void)
ArchiveTimeoutError, ArchiveTimeoutError,
"unable to get WAL file '%s' from the archive asynchronously after %s second(s)\n" "unable to get WAL file '%s' from the archive asynchronously after %s second(s)\n"
"HINT: check '%s' for errors.", "HINT: check '%s' for errors.",
strZ(walSegment), strZ(strNewDbl((double)cfgOptionInt64(cfgOptArchiveTimeout) / MSEC_PER_SEC)), strZ(walSegment),
strZ(
strNewDivP((uint64_t)cfgOptionInt64(cfgOptArchiveTimeout), MSEC_PER_SEC, .precision = 3, .trim = true)),
strZ(cfgLoadLogFileName(cfgCmdRoleAsync))); strZ(cfgLoadLogFileName(cfgCmdRoleAsync)));
} }
// Else report that the WAL segment could not be found // Else report that the WAL segment could not be found

View File

@ -1456,11 +1456,10 @@ backupJobResult(
if (copySize != file.sizeOriginal) if (copySize != file.sizeOriginal)
strCatFmt(logProgress, "%s->", strZ(strSizeFormat(file.sizeOriginal))); strCatFmt(logProgress, "%s->", strZ(strSizeFormat(file.sizeOriginal)));
// Store percentComplete as an integer // Store percentComplete as an integer (used to update progress in the lock file)
percentComplete = sizeTotal == 0 ? 10000 : (unsigned int)(((double)*sizeProgress / (double)sizeTotal) * 10000); percentComplete = cvtPctToUInt(*sizeProgress, sizeTotal);
strCatFmt( strCatFmt(logProgress, "%s, %s", strZ(strSizeFormat(copySize)), strZ(strNewPct(*sizeProgress, sizeTotal)));
logProgress, "%s, %u.%02u%%", strZ(strSizeFormat(copySize)), percentComplete / 100, percentComplete % 100);
// Format log checksum // Format log checksum
const String *const logChecksum = const String *const logChecksum =

View File

@ -251,7 +251,7 @@ stanzaStatus(const int code, const InfoStanzaRepo *const stanzaData, const Varia
{ {
kvPut( kvPut(
backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR, backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR,
VARUINT((unsigned int)(((double)stanzaData->sizeComplete / (double)stanzaData->size) * 10000))); VARUINT(cvtPctToUInt(stanzaData->sizeComplete, stanzaData->size)));
} }
} }

View File

@ -2138,7 +2138,7 @@ restoreJobResult(
// Add size and percent complete // Add size and percent complete
sizeRestored += file.size; sizeRestored += file.size;
strCatFmt(log, "%s, %.2lf%%)", strZ(strSizeFormat(file.size)), (double)sizeRestored * 100.00 / (double)sizeTotal); strCatFmt(log, "%s, %s)", strZ(strSizeFormat(file.size)), strZ(strNewPct(sizeRestored, sizeTotal)));
// If not zero-length add the checksum // If not zero-length add the checksum
if (file.size != 0 && !zeroed) if (file.size != 0 && !zeroed)

View File

@ -200,11 +200,6 @@ FN_EXTERN size_t typeToLog(const char *typeName, char *buffer, size_t bufferSize
#define FUNCTION_LOG_CHARPY_FORMAT(value, buffer, bufferSize) \ #define FUNCTION_LOG_CHARPY_FORMAT(value, buffer, bufferSize) \
ptrToLog(value, "char *[]", buffer, bufferSize) ptrToLog(value, "char *[]", buffer, bufferSize)
#define FUNCTION_LOG_DOUBLE_TYPE \
double
#define FUNCTION_LOG_DOUBLE_FORMAT(value, buffer, bufferSize) \
cvtDoubleToZ(value, buffer, bufferSize)
#define FUNCTION_LOG_INT_TYPE \ #define FUNCTION_LOG_INT_TYPE \
int int
#define FUNCTION_LOG_INT_FORMAT(value, buffer, bufferSize) \ #define FUNCTION_LOG_INT_FORMAT(value, buffer, bufferSize) \

View File

@ -118,48 +118,245 @@ cvtBoolToConstZ(bool value)
return value ? TRUE_Z : FALSE_Z; return value ? TRUE_Z : FALSE_Z;
} }
/**********************************************************************************************************************************/ /***********************************************************************************************************************************
FN_EXTERN size_t Round an integer contained in a string
cvtDoubleToZ(const double value, char *const buffer, const size_t bufferSize) ***********************************************************************************************************************************/
static size_t
cvtRound(size_t result, char *const buffer, const size_t bufferSize)
{ {
FUNCTION_TEST_BEGIN(); FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(DOUBLE, value); FUNCTION_TEST_PARAM(SIZE, result);
FUNCTION_TEST_PARAM_P(CHARDATA, buffer); FUNCTION_TEST_PARAM_P(CHARDATA, buffer);
FUNCTION_TEST_PARAM(SIZE, bufferSize); FUNCTION_TEST_PARAM(SIZE, bufferSize);
FUNCTION_TEST_END(); FUNCTION_TEST_END();
ASSERT(buffer != NULL); ASSERT(buffer != NULL);
ASSERT(bufferSize > 0);
// Convert to a string for (int roundIdx = (int)result - 2; roundIdx >= 0; roundIdx--)
const size_t result = (size_t)snprintf(buffer, bufferSize, "%lf", value); {
// Round when the rounding digit is >= 5 (current digit needs to be incremented) or the current digit > 9 (prior carry
// overflowed and needs to be carried again)
if (((roundIdx == (int)result - 2) && buffer[roundIdx + 1] >= '5') || buffer[roundIdx] > '9')
{
// Carry to the prior digit
if (buffer[roundIdx] >= '9')
{
// If this is the first digit then add a new prior digit to carry to. Since it will start as zero we can just
// set it to one.
if (roundIdx == 0)
{
if (result + 1 >= bufferSize)
THROW(AssertError, "buffer overflow");
memmove(buffer + 1, buffer, ++result);
buffer[1] = '0';
buffer[0] = '1';
}
// Else set current digit to zero and carry to prior digit. An overflow is handled on the next iteration.
else
{
buffer[roundIdx] = '0';
buffer[roundIdx - 1]++;
}
}
// Else increment current digit
else
buffer[roundIdx]++;
}
// Else stop rounding
else
break;
}
// Remove rightmost digit used to start rounding
result--;
buffer[result] = '\0';
// Return string length
FUNCTION_TEST_RETURN(SIZE, result);
}
/***********************************************************************************************************************************
Separate the fractional part of an integer container in a string
***********************************************************************************************************************************/
static size_t
cvtFraction(size_t result, const unsigned int precision, const bool trim, char *const buffer, const size_t bufferSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(SIZE, result);
FUNCTION_TEST_PARAM(UINT, precision);
FUNCTION_TEST_PARAM(BOOL, trim);
FUNCTION_TEST_PARAM_P(CHARDATA, buffer);
FUNCTION_TEST_PARAM(SIZE, bufferSize);
FUNCTION_TEST_END();
ASSERT(buffer != NULL);
ASSERT(bufferSize > 0);
// Add decimal point
if (result + 1 >= bufferSize)
THROW(AssertError, "buffer overflow");
memmove(buffer + result - precision + 1, buffer + result - precision, precision + 1);
buffer[result - precision] = '.';
buffer[++result] = '\0';
// Strip off any final 0s and the decimal point if there are no non-zero digits after it
if (trim)
{
char *end = buffer + result - 1;
while (*end == '0' || *end == '.')
{
// It should not be possible to go past the beginning because a decimal point is always written
ASSERT(end > buffer);
end--;
if (*(end + 1) == '.')
break;
}
// Zero terminate the string
end[1] = 0;
// Calculate length
result = (size_t)(end - buffer + 1);
}
// Return string length
FUNCTION_TEST_RETURN(SIZE, result);
}
/**********************************************************************************************************************************/
FN_EXTERN unsigned int
cvtPctToUInt(const uint64_t dividend, const uint64_t divisor)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT64, dividend);
FUNCTION_TEST_PARAM(UINT64, divisor);
FUNCTION_TEST_END();
ASSERT(dividend <= divisor);
// If 100% then return a fixed value to avoid any rounding throwing off the result
if (dividend == divisor)
FUNCTION_TEST_RETURN(UINT, 10000);
// Calculate percentage
char buffer[CVT_PCT_BUFFER_SIZE];
size_t size = (size_t)snprintf(buffer, sizeof(buffer), "%04" PRIu64, (uint64_t)((double)dividend / (double)divisor * 100000));
// Round
cvtRound(size, buffer, sizeof(buffer));
FUNCTION_TEST_RETURN(UINT, cvtZToUInt(buffer));
}
/**********************************************************************************************************************************/
FN_EXTERN size_t
cvtPctToZ(const uint64_t dividend, const uint64_t divisor, char *const buffer, const size_t bufferSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT64, dividend);
FUNCTION_TEST_PARAM(UINT64, divisor);
FUNCTION_TEST_PARAM_P(CHARDATA, buffer);
FUNCTION_TEST_PARAM(SIZE, bufferSize);
FUNCTION_TEST_END();
ASSERT(buffer != NULL);
ASSERT(bufferSize > 0);
ASSERT(dividend <= divisor);
// Calculate percentage as an integer
size_t result = (size_t)snprintf(buffer, bufferSize, "%03u", cvtPctToUInt(dividend, divisor));
if (result >= bufferSize) if (result >= bufferSize)
THROW(AssertError, "buffer overflow"); THROW(AssertError, "buffer overflow");
// Any formatted double should be at least 8 characters, i.e. 0.000000 // Separate fractional part
ASSERT(strlen(buffer) >= 8); result = cvtFraction(result, 2, false, buffer, bufferSize);
// Any formatted double should have a decimal point
ASSERT(strchr(buffer, '.') != NULL);
// Strip off any final 0s and the decimal point if there are no non-zero digits after it // Add percent sign
char *end = buffer + strlen(buffer) - 1; if (result + 1 >= bufferSize)
THROW(AssertError, "buffer overflow");
while (*end == '0' || *end == '.') buffer[result++] = '%';
buffer[result] = '\0';
// Return string length
FUNCTION_TEST_RETURN(SIZE, result);
}
/**********************************************************************************************************************************/
FN_EXTERN size_t
cvtDivToZ(
const uint64_t dividend, const uint64_t divisor, const unsigned int precision, const bool trim, char *const buffer,
const size_t bufferSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT64, dividend);
FUNCTION_TEST_PARAM(UINT64, divisor);
FUNCTION_TEST_PARAM(UINT, precision);
FUNCTION_TEST_PARAM(BOOL, trim);
FUNCTION_TEST_PARAM_P(CHARDATA, buffer);
FUNCTION_TEST_PARAM(SIZE, bufferSize);
FUNCTION_TEST_END();
ASSERT(buffer != NULL);
ASSERT(bufferSize > 0);
// Determine multiplier for precision digits
unsigned int multiplier = 1;
switch (precision)
{ {
// It should not be possible to go past the beginning because format "%lf" will always write a decimal point case 0:
ASSERT(end > buffer); break;
end--; case 1:
multiplier = 10;
break;
if (*(end + 1) == '.') case 2:
multiplier = 100;
break;
default:
CHECK_FMT(AssertError, precision <= 3, "precision %u is invalid", precision);
multiplier = 1000;
break; break;
} }
// Zero terminate the string CHECK_FMT(AssertError, dividend <= UINT64_MAX / multiplier, "dividend %" PRIu64 " is too large", dividend);
end[1] = 0;
// If possible add a digit for rounding
bool round = false;
if (dividend <= UINT64_MAX / (multiplier * 10))
{
round = true;
multiplier *= 10;
}
// Convert to string
size_t result = (size_t)snprintf(buffer, bufferSize, "%0*" PRIu64, (int)precision, dividend * multiplier / divisor);
if (result >= bufferSize)
THROW(AssertError, "buffer overflow");
// Round
if (round)
result = cvtRound(result, buffer, bufferSize);
// Separate fractional part
if (precision > 0)
result = cvtFraction(result, precision, trim, buffer, bufferSize);
// Return string length // Return string length
FUNCTION_TEST_RETURN(SIZE, (size_t)(end - buffer + 1)); FUNCTION_TEST_RETURN(SIZE, result);
} }
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/

View File

@ -19,7 +19,8 @@ Required buffer sizes
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
#define CVT_BOOL_BUFFER_SIZE 6 #define CVT_BOOL_BUFFER_SIZE 6
#define CVT_BASE10_BUFFER_SIZE 64 #define CVT_BASE10_BUFFER_SIZE 64
#define CVT_DOUBLE_BUFFER_SIZE 318 #define CVT_DIV_BUFFER_SIZE 68
#define CVT_PCT_BUFFER_SIZE 8
#define CVT_VARINT128_BUFFER_SIZE 10 #define CVT_VARINT128_BUFFER_SIZE 10
/*********************************************************************************************************************************** /***********************************************************************************************************************************
@ -35,8 +36,14 @@ cvtCharToZ(const char value, char *const buffer, const size_t bufferSize)
return (size_t)snprintf(buffer, bufferSize, "%c", value); return (size_t)snprintf(buffer, bufferSize, "%c", value);
} }
// Convert double to zero-terminated string // Convert percentage to an integer. This is useful for exporting a percentage in a way that does not incur rounding.
FN_EXTERN size_t cvtDoubleToZ(double value, char *buffer, size_t bufferSize); FN_EXTERN unsigned int cvtPctToUInt(uint64_t dividend, uint64_t divisor);
// Convert percentage to zero-terminated string. This is more reproducible than formatting the results of floating point division.
FN_EXTERN size_t cvtPctToZ(uint64_t dividend, uint64_t divisor, char *buffer, size_t bufferSize);
// Convert division to zero-terminated string. This is more reproducible than formatting the results of floating point division.
FN_EXTERN size_t cvtDivToZ(uint64_t dividend, uint64_t divisor, unsigned int precision, bool trim, char *buffer, size_t bufferSize);
// Convert int to zero-terminated string and vice versa // Convert int to zero-terminated string and vice versa
FN_EXTERN size_t cvtIntToZ(const int value, char *buffer, size_t bufferSize); FN_EXTERN size_t cvtIntToZ(const int value, char *buffer, size_t bufferSize);

View File

@ -162,17 +162,36 @@ strNewZ(const char *const string)
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN String * FN_EXTERN String *
strNewDbl(const double value) strNewDiv(const uint64_t dividend, const uint64_t divisor, StrNewDivParam param)
{ {
FUNCTION_TEST_BEGIN(); FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(DOUBLE, value); FUNCTION_TEST_PARAM(UINT64, dividend);
FUNCTION_TEST_PARAM(UINT64, divisor);
FUNCTION_TEST_PARAM(UINT, param.precision);
FUNCTION_TEST_PARAM(BOOL, param.trim);
FUNCTION_TEST_END(); FUNCTION_TEST_END();
char working[CVT_DOUBLE_BUFFER_SIZE]; char working[CVT_DIV_BUFFER_SIZE];
cvtDoubleToZ(value, working, sizeof(working)); size_t resultSize = cvtDivToZ(dividend, divisor, param.precision, param.trim, working, sizeof(working));
FUNCTION_TEST_RETURN(STRING, strNewZ(working)); FUNCTION_TEST_RETURN(STRING, strNewZN(working, resultSize));
}
/**********************************************************************************************************************************/
FN_EXTERN String *
strNewPct(const uint64_t dividend, const uint64_t divisor)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT64, dividend);
FUNCTION_TEST_PARAM(UINT64, divisor);
FUNCTION_TEST_END();
char working[CVT_PCT_BUFFER_SIZE];
size_t resultSize = cvtPctToZ(dividend, divisor, working, sizeof(working));
FUNCTION_TEST_RETURN(STRING, strNewZN(working, resultSize));
} }
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
@ -1052,26 +1071,31 @@ strSizeFormat(const uint64_t size)
if (size < 1024) if (size < 1024)
result = strNewFmt("%" PRIu64 "B", size); result = strNewFmt("%" PRIu64 "B", size);
else if (size < (1024 * 1024))
{
if ((uint64_t)((double)size / 102.4) % 10 != 0)
result = strNewFmt("%.1lfKB", (double)size / 1024);
else
result = strNewFmt("%" PRIu64 "KB", size / 1024);
}
else if (size < (1024 * 1024 * 1024))
{
if ((uint64_t)((double)size / (1024 * 102.4)) % 10 != 0)
result = strNewFmt("%.1lfMB", (double)size / (1024 * 1024));
else
result = strNewFmt("%" PRIu64 "MB", size / (1024 * 1024));
}
else else
{ {
if ((uint64_t)((double)size / (1024 * 1024 * 102.4)) % 10 != 0) char working[CVT_DIV_BUFFER_SIZE];
result = strNewFmt("%.1lfGB", (double)size / (1024 * 1024 * 1024)); uint64_t divisor = 1024 * 1024 * 1024;
else unsigned int precision = 1;
result = strNewFmt("%" PRIu64 "GB", size / (1024 * 1024 * 1024)); const char *suffix = "GB";
if (size < (1024 * 1024))
{
divisor = 1024;
suffix = "KB";
}
else if (size < (1024 * 1024 * 1024))
{
divisor = 1024 * 1024;
suffix = "MB";
}
// Skip precision when it would cause overflow
if (size > UINT64_MAX / 10)
precision = 0;
// Format size
cvtDivToZ(size, divisor, precision, true, working, sizeof(working));
result = strNewFmt("%s%s", working, suffix);
} }
FUNCTION_TEST_RETURN(STRING, result); FUNCTION_TEST_RETURN(STRING, result);

View File

@ -66,8 +66,21 @@ FN_EXTERN String *strNewZN(const char *string, size_t size);
// will be copied but only the data before the NULL character will be used as a string. // will be copied but only the data before the NULL character will be used as a string.
FN_EXTERN String *strNewBuf(const Buffer *buffer); FN_EXTERN String *strNewBuf(const Buffer *buffer);
// Create a new fixed length string by converting the double value // Create a new fixed length string by performing division
FN_EXTERN String *strNewDbl(double value); typedef struct StrNewDivParam
{
VAR_PARAM_HEADER;
unsigned int precision; // Digits of precision after the decimal
bool trim; // Trim trailing zeros and decimal point?
} StrNewDivParam;
#define strNewDivP(dividend, divisor, ...) \
strNewDiv(dividend, divisor, (StrNewDivParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN String *strNewDiv(uint64_t dividend, uint64_t divisor, StrNewDivParam param);
// Create a new fixed length string containing a percentage
FN_EXTERN String *strNewPct(uint64_t dividend, uint64_t divisor);
// Create a new fixed length string by converting a timestamp // Create a new fixed length string by converting a timestamp
typedef struct StrNewTimeParam typedef struct StrNewTimeParam

View File

@ -448,7 +448,7 @@ cfgOptionDisplayVar(const Variant *const value, const ConfigOptionType optionTyp
} }
else if (optionType == cfgOptTypeTime) else if (optionType == cfgOptTypeTime)
{ {
FUNCTION_TEST_RETURN_CONST(STRING, strNewDbl((double)varInt64(value) / MSEC_PER_SEC)); FUNCTION_TEST_RETURN_CONST(STRING, strNewDivP(varUInt64Force(value), MSEC_PER_SEC, .precision = 3, .trim = true));
} }
else if (optionType == cfgOptTypeStringId) else if (optionType == cfgOptTypeStringId)
{ {

View File

@ -102,7 +102,7 @@ unit:
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: type-convert - name: type-convert
total: 12 total: 13
coverage: coverage:
- common/type/convert - common/type/convert

View File

@ -816,14 +816,11 @@ testCvgSummaryValue(const unsigned int hit, const unsigned int total)
// Else render value // Else render value
else else
{ {
strCatFmt(result, "%u/%u (", hit, total); MEM_CONTEXT_TEMP_BEGIN()
{
if (hit == total) strCatFmt(result, "%u/%u (%s)", hit, total, strZ(strNewPct(hit, total)));
strCatZ(result, "100.0"); }
else MEM_CONTEXT_TEMP_END();
strCatFmt(result, "%.2f", (float)hit * 100 / (float)total);
strCatZ(result, "%)");
} }
FUNCTION_LOG_RETURN(STRING, result); FUNCTION_LOG_RETURN(STRING, result);

View File

@ -1944,7 +1944,7 @@ testRun(void)
"P00 WARN: no prior backup exists, incr backup has been changed to full\n" "P00 WARN: no prior backup exists, incr backup has been changed to full\n"
"P00 WARN: --no-online passed and " PG_FILE_POSTMTRPID " exists but --force was passed so backup will continue though" "P00 WARN: --no-online passed and " PG_FILE_POSTMTRPID " exists but --force was passed so backup will continue though"
" it looks like " PG_NAME " is running and the backup will probably not be consistent\n" " it looks like " PG_NAME " is running and the backup will probably not be consistent\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (8KB, 99.86%%) checksum %s\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (8KB, 99.87%%) checksum %s\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.conf (11B, 100.00%%) checksum" "P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.conf (11B, 100.00%%) checksum"
" e3db315c260e79211b7b52587123b7aa060f30ab\n" " e3db315c260e79211b7b52587123b7aa060f30ab\n"
"P00 INFO: new backup label = [FULL-1]\n" "P00 INFO: new backup label = [FULL-1]\n"
@ -3268,7 +3268,7 @@ testRun(void)
"P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.auto.conf (bundle 1/32, 12B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.auto.conf (bundle 1/32, 12B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.conf (bundle 1/64, 11B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.conf (bundle 1/64, 11B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/PG_VERSION (bundle 1/95, 2B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/PG_VERSION (bundle 1/95, 2B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/bigish.dat (bundle 2/0, 8.0KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/bigish.dat (bundle 2/0, 8KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 3/0, 8KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 3/0, 8KB, [PCT]) checksum [SHA1]\n"
"P00 INFO: execute non-exclusive backup stop and wait for all WAL segments to archive\n" "P00 INFO: execute non-exclusive backup stop and wait for all WAL segments to archive\n"
"P00 INFO: backup stop archive = 0000000105DB8EB000000001, lsn = 5db8eb0/180000\n" "P00 INFO: backup stop archive = 0000000105DB8EB000000001, lsn = 5db8eb0/180000\n"
@ -3569,7 +3569,7 @@ testRun(void)
"P01 DETAIL: checksum resumed file " TEST_PATH "/pg1/block-incr-grow (24KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: checksum resumed file " TEST_PATH "/pg1/block-incr-grow (24KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/PG_VERSION (bundle 1/0, 2B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/PG_VERSION (bundle 1/0, 2B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/normal-same (bundle 1/2, 4B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/normal-same (bundle 1/2, 4B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/grow-to-block-incr (bundle 1/6, 16.0KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/grow-to-block-incr (bundle 1/6, 16KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/16389, 8KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/16389, 8KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-block (bundle 1/24581, 16KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-block (bundle 1/24581, 16KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-below (bundle 1/40987, 16KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-below (bundle 1/40987, 16KB, [PCT]) checksum [SHA1]\n"
@ -3704,7 +3704,7 @@ testRun(void)
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/16411, 8KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/16411, 8KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-block (bundle 1/24603, 8KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-block (bundle 1/24603, 8KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-below (bundle 1/24620, 8B, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink-below (bundle 1/24620, 8B, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink (bundle 1/24628, 16.0KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/block-incr-shrink (bundle 1/24628, 16KB, [PCT]) checksum [SHA1]\n"
"P01 DETAIL: match file from prior backup " TEST_PATH "/pg1/block-incr-same (16KB, [PCT]) checksum [SHA1]\n" "P01 DETAIL: match file from prior backup " TEST_PATH "/pg1/block-incr-same (16KB, [PCT]) checksum [SHA1]\n"
"P00 DETAIL: reference pg_data/PG_VERSION to 20191103-165320F\n" "P00 DETAIL: reference pg_data/PG_VERSION to 20191103-165320F\n"
"P00 DETAIL: reference pg_data/block-incr-same to 20191103-165320F\n" "P00 DETAIL: reference pg_data/block-incr-same to 20191103-165320F\n"

View File

@ -607,13 +607,13 @@ testRun(void)
" timestamp start/stop: 2018-11-16 15:47:56+00 / 2018-11-16 15:48:09+00\n" " timestamp start/stop: 2018-11-16 15:47:56+00 / 2018-11-16 15:48:09+00\n"
" wal start/stop: n/a\n" " wal start/stop: n/a\n"
" database size: 25.7MB, database backup size: 25.7MB\n" " database size: 25.7MB, database backup size: 25.7MB\n"
" repo1: backup set size: 3MB, backup size: 3KB\n" " repo1: backup set size: 3MB, backup size: 3.1KB\n"
"\n" "\n"
" full backup: 20201116-154900F\n" " full backup: 20201116-154900F\n"
" timestamp start/stop: 2020-11-16 15:47:56+00 / 2020-11-16 15:48:00+00\n" " timestamp start/stop: 2020-11-16 15:47:56+00 / 2020-11-16 15:48:00+00\n"
" wal start/stop: 000000030000000000000001 / 000000030000000000000001\n" " wal start/stop: 000000030000000000000001 / 000000030000000000000001\n"
" database size: 25.7MB, database backup size: 25.7MB\n" " database size: 25.7MB, database backup size: 25.7MB\n"
" repo1: backup set size: 3MB, backup size: 3KB\n", " repo1: backup set size: 3MB, backup size: 3.1KB\n",
"text - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected"); "text - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected");
// Notify child to release lock // Notify child to release lock
@ -1530,7 +1530,7 @@ testRun(void)
TEST_RESULT_STR_Z( TEST_RESULT_STR_Z(
infoRender(), infoRender(),
"stanza: stanza1\n" "stanza: stanza1\n"
" status: ok (backup/expire running - 65.27% complete)\n" " status: ok (backup/expire running - 65.28% complete)\n"
" cipher: mixed\n" " cipher: mixed\n"
" repo1: none\n" " repo1: none\n"
" repo2: aes-256-cbc\n" " repo2: aes-256-cbc\n"

View File

@ -179,7 +179,7 @@ testRun(void)
"\n" "\n"
"file list:\n" "file list:\n"
" - pg_data/base/1/2\n" " - pg_data/base/1/2\n"
" size: 64KB, repo 64KB\n" " size: 64KB, repo 64.1KB\n"
" checksum: 1adc95bebe9eea8c112d40cd04ab7a8d75c4f961\n" " checksum: 1adc95bebe9eea8c112d40cd04ab7a8d75c4f961\n"
" bundle: 1\n" " bundle: 1\n"
" block: size 8KB, map size 58B, checksum size 6B\n" " block: size 8KB, map size 58B, checksum size 6B\n"
@ -274,7 +274,7 @@ testRun(void)
"\n" "\n"
"file list:\n" "file list:\n"
" - pg_data/base/1/2\n" " - pg_data/base/1/2\n"
" size: 96KB, repo 64KB\n" " size: 96KB, repo 64.1KB\n"
" checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n"
" bundle: 1\n" " bundle: 1\n"
" block: size 8KB, map size 99B, checksum size 6B\n" " block: size 8KB, map size 99B, checksum size 6B\n"
@ -379,7 +379,7 @@ testRun(void)
"\n" "\n"
"file list:\n" "file list:\n"
" - pg_data/base/1/2\n" " - pg_data/base/1/2\n"
" size: 96KB, repo 64KB\n" " size: 96KB, repo 64.1KB\n"
" checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n"
" bundle: 1\n" " bundle: 1\n"
" block: size 8KB, map size 99B, checksum size 6B\n" " block: size 8KB, map size 99B, checksum size 6B\n"
@ -473,7 +473,7 @@ testRun(void)
"\n" "\n"
"file list:\n" "file list:\n"
" - pg_data/base/1/2\n" " - pg_data/base/1/2\n"
" size: 96KB, repo 64KB\n" " size: 96KB, repo 64.1KB\n"
" checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n"
" bundle: 1\n" " bundle: 1\n"
" block: size 8KB, map size 99B, checksum size 6B\n" " block: size 8KB, map size 99B, checksum size 6B\n"

View File

@ -2503,7 +2503,7 @@ testRun(void)
// Replace percent complete and restore size since they can cause a lot of churn when files are added/removed // Replace percent complete and restore size since they can cause a lot of churn when files are added/removed
hrnLogReplaceAdd(", [0-9]{1,3}\\.[0-9]{2}%\\)", "[0-9]+\\.[0-9]+%", "PCT", false); hrnLogReplaceAdd(", [0-9]{1,3}\\.[0-9]{2}%\\)", "[0-9]+\\.[0-9]+%", "PCT", false);
hrnLogReplaceAdd(" restore size = [0-9]+[A-Z]+", "[^ ]+$", "SIZE", false); hrnLogReplaceAdd(" restore size = [0-9]+(\\.[0-9]+){0,1}[A-Z]+", "[^ ]+$", "SIZE", false);
argList = strLstNew(); argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test1"); hrnCfgArgRawZ(argList, cfgOptStanza, "test1");

View File

@ -2337,7 +2337,7 @@ testRun(void)
"P00 INFO: backup start archive = 0000000105D95D3000000000, lsn = 5d95d30/0\n" "P00 INFO: backup start archive = 0000000105D95D3000000000, lsn = 5d95d30/0\n"
"P00 INFO: check archive for prior segment 0000000105D95D2F000000FF\n" "P00 INFO: check archive for prior segment 0000000105D95D2F000000FF\n"
"P00 DETAIL: store zero-length file " TEST_PATH "/pg1/postgresql.auto.conf\n" "P00 DETAIL: store zero-length file " TEST_PATH "/pg1/postgresql.auto.conf\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/base/1/2 (bundle 1/0, 256KB, 96.96%) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/base/1/2 (bundle 1/0, 256KB, 96.97%) checksum [SHA1]\n"
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/237, 8KB, 100.00%) checksum [SHA1]\n" "P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (bundle 1/237, 8KB, 100.00%) checksum [SHA1]\n"
"P00 DETAIL: reference pg_data/PG_VERSION to 20191002-070640F\n" "P00 DETAIL: reference pg_data/PG_VERSION to 20191002-070640F\n"
"P00 DETAIL: reference pg_data/base/1/44 to 20191002-070640F\n" "P00 DETAIL: reference pg_data/base/1/44 to 20191002-070640F\n"

View File

@ -25,7 +25,7 @@ testFunction2(void)
static int static int
testFunction1( testFunction1(
int paramInt, bool paramBool, bool *paramBoolP, bool **paramBoolPP, void *paramVoidP, double paramDouble, mode_t paramMode) int paramInt, bool paramBool, bool *paramBoolP, bool **paramBoolPP, void *paramVoidP, mode_t paramMode)
{ {
FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(INT, paramInt); FUNCTION_LOG_PARAM(INT, paramInt);
@ -33,7 +33,6 @@ testFunction1(
FUNCTION_LOG_PARAM_P(BOOL, paramBoolP); FUNCTION_LOG_PARAM_P(BOOL, paramBoolP);
FUNCTION_LOG_PARAM_PP(BOOL, paramBoolPP); FUNCTION_LOG_PARAM_PP(BOOL, paramBoolPP);
FUNCTION_LOG_PARAM_P(VOID, paramVoidP); FUNCTION_LOG_PARAM_P(VOID, paramVoidP);
FUNCTION_LOG_PARAM(DOUBLE, paramDouble);
FUNCTION_LOG_PARAM(MODE, paramMode); FUNCTION_LOG_PARAM(MODE, paramMode);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
@ -185,11 +184,11 @@ testRun(void)
if (testBegin("FUNCTION_DEBUG() and FUNCTION_LOG_RETURN()")) if (testBegin("FUNCTION_DEBUG() and FUNCTION_LOG_RETURN()"))
{ {
harnessLogLevelSet(logLevelTrace); harnessLogLevelSet(logLevelTrace);
testFunction1(99, false, NULL, NULL, NULL, 1.17, 0755); testFunction1(99, false, NULL, NULL, NULL, 0755);
TEST_RESULT_LOG( TEST_RESULT_LOG(
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false," "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false,"
" paramBoolP: null, paramBoolPP: null, paramVoidP: null, paramDouble: 1.17, paramMode: 0755)\n" " paramBoolP: null, paramBoolPP: null, paramVoidP: null, paramMode: 0755)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n"
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1");
@ -200,11 +199,11 @@ testRun(void)
bool **testBoolPP = &testBoolP; bool **testBoolPP = &testBoolP;
void *testVoidP = NULL; void *testVoidP = NULL;
testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 1.17, 0755); testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 0755);
TEST_RESULT_LOG( TEST_RESULT_LOG(
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false," "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false,"
" paramBoolP: *true, paramBoolPP: **true, paramVoidP: null, paramDouble: 1.17, paramMode: 0755)\n" " paramBoolP: *true, paramBoolPP: **true, paramVoidP: null, paramMode: 0755)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n"
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1");
@ -212,18 +211,18 @@ testRun(void)
testBoolP = NULL; testBoolP = NULL;
testVoidP = (void *)1; testVoidP = (void *)1;
testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 1.17, 0755); testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 0755);
TEST_RESULT_LOG( TEST_RESULT_LOG(
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false," "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: (paramInt: 99, paramBool: false,"
" paramBoolP: null, paramBoolPP: *null, paramVoidP: *void, paramDouble: 1.17, paramMode: 0755)\n" " paramBoolP: null, paramBoolPP: *null, paramVoidP: *void, paramMode: 0755)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: (void)\n"
"P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n" "P00 TRACE: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction2: => void\n"
"P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); "P00 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
harnessLogLevelReset(); harnessLogLevelReset();
testFunction1(55, true, NULL, NULL, NULL, 0.99, 0755); testFunction1(55, true, NULL, NULL, NULL, 0755);
TEST_RESULT_LOG(""); TEST_RESULT_LOG("");
} }

View File

@ -34,20 +34,74 @@ testRun(void)
} }
// ***************************************************************************************************************************** // *****************************************************************************************************************************
if (testBegin("cvtDoubleToZ()")) if (testBegin("cvtDivToZ()"))
{ {
char buffer[STACK_TRACE_PARAM_MAX]; char buffer[CVT_DIV_BUFFER_SIZE];
TEST_ERROR(cvtDoubleToZ(999.1234, buffer, 4), AssertError, "buffer overflow"); TEST_ERROR(cvtDivToZ(100, 2, 0, false, buffer, 2), AssertError, "buffer overflow");
TEST_ERROR(cvtDivToZ(999999, 100, 1, false, buffer, 7), AssertError, "buffer overflow");
TEST_ERROR(cvtDivToZ(UINT64_MAX / 100, 100, 2, false, buffer, 19), AssertError, "buffer overflow");
TEST_RESULT_UINT(cvtDoubleToZ(999.1234, buffer, STACK_TRACE_PARAM_MAX), 8, "convert double to string"); TEST_RESULT_UINT(cvtDivToZ(100, 2, 0, false, buffer, sizeof(buffer)), 2, "no precision");
TEST_RESULT_Z(buffer, "999.1234", " check buffer"); TEST_RESULT_Z(buffer, "50", " check buffer");
TEST_RESULT_UINT(cvtDoubleToZ(999999999.123456, buffer, STACK_TRACE_PARAM_MAX), 16, "convert double to string"); TEST_RESULT_UINT(cvtDivToZ(100, 2, 3, false, buffer, sizeof(buffer)), 6, "all zero precision");
TEST_RESULT_Z(buffer, "999999999.123456", " check buffer"); TEST_RESULT_Z(buffer, "50.000", " check buffer");
TEST_RESULT_UINT(cvtDoubleToZ(999.0, buffer, STACK_TRACE_PARAM_MAX), 3, "convert double to string"); TEST_RESULT_UINT(cvtDivToZ(100, 2, 3, true, buffer, sizeof(buffer)), 2, "all zero precision trimmed");
TEST_RESULT_Z(buffer, "999", " check buffer"); TEST_RESULT_Z(buffer, "50", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(110, 100, 2, true, buffer, sizeof(buffer)), 3, "precision trimmed");
TEST_RESULT_Z(buffer, "1.1", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(5, 4, 1, true, buffer, sizeof(buffer)), 3, "precision rounded");
TEST_RESULT_Z(buffer, "1.3", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(5, 4, 3, true, buffer, sizeof(buffer)), 4, "precision exact");
TEST_RESULT_Z(buffer, "1.25", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(100, 3, 3, false, buffer, sizeof(buffer)), 6, "precision rounded");
TEST_RESULT_Z(buffer, "33.333", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(UINT64_MAX, 3, 0, false, buffer, sizeof(buffer)), 19, "no rounding for max allowed");
TEST_RESULT_Z(buffer, "6148914691236517205", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(1999, 10, 0, false, buffer, sizeof(buffer)), 3, "round up");
TEST_RESULT_Z(buffer, "200", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(999999, 100, 1, false, buffer, sizeof(buffer)), 7, "round up");
TEST_RESULT_Z(buffer, "10000.0", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(9199, 10, 0, false, buffer, sizeof(buffer)), 3, "partial round up");
TEST_RESULT_Z(buffer, "920", " check buffer");
TEST_RESULT_UINT(cvtDivToZ(5555, 100, 1, false, buffer, sizeof(buffer)), 4, "partial round up");
TEST_RESULT_Z(buffer, "55.6", " check buffer");
}
// *****************************************************************************************************************************
if (testBegin("cvtPctToZ()"))
{
char buffer[CVT_PCT_BUFFER_SIZE];
TEST_ERROR(cvtPctToZ(2, 100, buffer, 2), AssertError, "buffer overflow");
TEST_ERROR(cvtPctToZ(100, 100, buffer, 2), AssertError, "buffer overflow");
TEST_ERROR(cvtPctToZ(2, 100, buffer, 5), AssertError, "buffer overflow");
TEST_RESULT_UINT(cvtPctToZ(467, 467, buffer, sizeof(buffer)), 7, "100%");
TEST_RESULT_Z(buffer, "100.00%", " check buffer");
TEST_RESULT_UINT(cvtPctToZ(111, 10000, buffer, sizeof(buffer)), 5, "100%");
TEST_RESULT_Z(buffer, "1.11%", " check buffer");
TEST_RESULT_UINT(cvtPctToZ(2, 3, buffer, sizeof(buffer)), 6, "66.67%");
TEST_RESULT_Z(buffer, "66.67%", " check buffer");
TEST_RESULT_UINT(cvtPctToZ(1, 10000, buffer, sizeof(buffer)), 5, "0.01%");
TEST_RESULT_Z(buffer, "0.01%", " check buffer");
TEST_RESULT_UINT(cvtPctToZ(0, 1, buffer, sizeof(buffer)), 5, "0.00%");
TEST_RESULT_Z(buffer, "0.00%", " check buffer");
} }
// ***************************************************************************************************************************** // *****************************************************************************************************************************

View File

@ -65,9 +65,16 @@ testRun(void)
TEST_RESULT_STR_Z(strNewBuf(buffer), "12345678", "new string from buffer"); TEST_RESULT_STR_Z(strNewBuf(buffer), "12345678", "new string from buffer");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("new from double"); TEST_TITLE("new from division");
TEST_RESULT_STR_Z(strNewDbl(999.1), "999.1", "new"); TEST_RESULT_STR_Z(strNewDivP(9991, 10, .precision = 2), "999.10", "new div");
TEST_RESULT_STR_Z(strNewDivP(9991, 10, .precision = 2, .trim = true), "999.1", "new div");
TEST_RESULT_STR_Z(strNewDivP(9991, 10, .precision = 0), "999", "new div");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("new from percentage");
TEST_RESULT_STR_Z(strNewPct(2, 3), "66.67%", "new pct");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("new from time"); TEST_TITLE("new from time");
@ -347,6 +354,7 @@ testRun(void)
TEST_RESULT_STR_Z(strSizeFormat(20162900), "19.2MB", "19.2 MB"); TEST_RESULT_STR_Z(strSizeFormat(20162900), "19.2MB", "19.2 MB");
TEST_RESULT_STR_Z(strSizeFormat(1073741824), "1GB", "1 GB"); TEST_RESULT_STR_Z(strSizeFormat(1073741824), "1GB", "1 GB");
TEST_RESULT_STR_Z(strSizeFormat(1073741824 + 107374183), "1.1GB", "1.1 GB"); TEST_RESULT_STR_Z(strSizeFormat(1073741824 + 107374183), "1.1GB", "1.1 GB");
TEST_RESULT_STR_Z(strSizeFormat(UINT64_MAX / 10), "1717986918.3GB", "uint64 max / 10");
TEST_RESULT_STR_Z(strSizeFormat(UINT64_MAX), "17179869183GB", "uint64 max"); TEST_RESULT_STR_Z(strSizeFormat(UINT64_MAX), "17179869183GB", "uint64 max");
} }

View File

@ -432,9 +432,9 @@ testRun(void)
"\n" "\n"
"<table-row>\n" "<table-row>\n"
" <table-cell>common/error/sub</table-cell>\n" " <table-cell>common/error/sub</table-cell>\n"
" <table-cell>1/1 (100.0%)</table-cell>\n" " <table-cell>1/1 (100.00%)</table-cell>\n"
" <table-cell>---</table-cell>\n" " <table-cell>---</table-cell>\n"
" <table-cell>1/1 (100.0%)</table-cell>\n" " <table-cell>1/1 (100.00%)</table-cell>\n"
"</table-row>\n" "</table-row>\n"
"\n" "\n"
"<table-row>\n" "<table-row>\n"