diff --git a/doc/xml/release/2025/2.56.0.xml b/doc/xml/release/2025/2.56.0.xml index c4fbc777d..e1a49ead5 100644 --- a/doc/xml/release/2025/2.56.0.xml +++ b/doc/xml/release/2025/2.56.0.xml @@ -13,6 +13,19 @@

Fix defaults in command-line help.

+ + + + + + + + + + +

Improve the predictability of floating point numbers formatted as strings.

+
+
diff --git a/src/command/archive/get/get.c b/src/command/archive/get/get.c index 0bbebd182..612d50be8 100644 --- a/src/command/archive/get/get.c +++ b/src/command/archive/get/get.c @@ -793,7 +793,9 @@ cmdArchiveGet(void) ArchiveTimeoutError, "unable to get WAL file '%s' from the archive asynchronously after %s second(s)\n" "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))); } // Else report that the WAL segment could not be found diff --git a/src/command/backup/backup.c b/src/command/backup/backup.c index bb773e0e5..b5f43733e 100644 --- a/src/command/backup/backup.c +++ b/src/command/backup/backup.c @@ -1456,11 +1456,10 @@ backupJobResult( if (copySize != file.sizeOriginal) strCatFmt(logProgress, "%s->", strZ(strSizeFormat(file.sizeOriginal))); - // Store percentComplete as an integer - percentComplete = sizeTotal == 0 ? 10000 : (unsigned int)(((double)*sizeProgress / (double)sizeTotal) * 10000); + // Store percentComplete as an integer (used to update progress in the lock file) + percentComplete = cvtPctToUInt(*sizeProgress, sizeTotal); - strCatFmt( - logProgress, "%s, %u.%02u%%", strZ(strSizeFormat(copySize)), percentComplete / 100, percentComplete % 100); + strCatFmt(logProgress, "%s, %s", strZ(strSizeFormat(copySize)), strZ(strNewPct(*sizeProgress, sizeTotal))); // Format log checksum const String *const logChecksum = diff --git a/src/command/info/info.c b/src/command/info/info.c index 0a1e7279f..7e9f116d5 100644 --- a/src/command/info/info.c +++ b/src/command/info/info.c @@ -251,7 +251,7 @@ stanzaStatus(const int code, const InfoStanzaRepo *const stanzaData, const Varia { kvPut( backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR, - VARUINT((unsigned int)(((double)stanzaData->sizeComplete / (double)stanzaData->size) * 10000))); + VARUINT(cvtPctToUInt(stanzaData->sizeComplete, stanzaData->size))); } } diff --git a/src/command/restore/restore.c b/src/command/restore/restore.c index ef3789138..86b410f8b 100644 --- a/src/command/restore/restore.c +++ b/src/command/restore/restore.c @@ -2138,7 +2138,7 @@ restoreJobResult( // Add size and percent complete 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 (file.size != 0 && !zeroed) diff --git a/src/common/debug.h b/src/common/debug.h index 9b85e6ab4..ccc0a4ab7 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -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) \ 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 \ int #define FUNCTION_LOG_INT_FORMAT(value, buffer, bufferSize) \ diff --git a/src/common/type/convert.c b/src/common/type/convert.c index fbdb77339..13c9a1f33 100644 --- a/src/common/type/convert.c +++ b/src/common/type/convert.c @@ -118,48 +118,245 @@ cvtBoolToConstZ(bool value) return value ? TRUE_Z : FALSE_Z; } -/**********************************************************************************************************************************/ -FN_EXTERN size_t -cvtDoubleToZ(const double value, char *const buffer, const size_t bufferSize) +/*********************************************************************************************************************************** +Round an integer contained in a string +***********************************************************************************************************************************/ +static size_t +cvtRound(size_t result, char *const buffer, const size_t bufferSize) { FUNCTION_TEST_BEGIN(); - FUNCTION_TEST_PARAM(DOUBLE, value); + FUNCTION_TEST_PARAM(SIZE, result); FUNCTION_TEST_PARAM_P(CHARDATA, buffer); FUNCTION_TEST_PARAM(SIZE, bufferSize); FUNCTION_TEST_END(); ASSERT(buffer != NULL); + ASSERT(bufferSize > 0); - // Convert to a string - const size_t result = (size_t)snprintf(buffer, bufferSize, "%lf", value); + for (int roundIdx = (int)result - 2; roundIdx >= 0; roundIdx--) + { + // 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) THROW(AssertError, "buffer overflow"); - // Any formatted double should be at least 8 characters, i.e. 0.000000 - ASSERT(strlen(buffer) >= 8); - // Any formatted double should have a decimal point - ASSERT(strchr(buffer, '.') != NULL); + // Separate fractional part + result = cvtFraction(result, 2, false, buffer, bufferSize); - // Strip off any final 0s and the decimal point if there are no non-zero digits after it - char *end = buffer + strlen(buffer) - 1; + // Add percent sign + 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 - ASSERT(end > buffer); + case 0: + 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; } - // Zero terminate the string - end[1] = 0; + CHECK_FMT(AssertError, dividend <= UINT64_MAX / multiplier, "dividend %" PRIu64 " is too large", dividend); + + // 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 - FUNCTION_TEST_RETURN(SIZE, (size_t)(end - buffer + 1)); + FUNCTION_TEST_RETURN(SIZE, result); } /**********************************************************************************************************************************/ diff --git a/src/common/type/convert.h b/src/common/type/convert.h index 074878d43..140f7c947 100644 --- a/src/common/type/convert.h +++ b/src/common/type/convert.h @@ -19,7 +19,8 @@ Required buffer sizes ***********************************************************************************************************************************/ #define CVT_BOOL_BUFFER_SIZE 6 #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 /*********************************************************************************************************************************** @@ -35,8 +36,14 @@ cvtCharToZ(const char value, char *const buffer, const size_t bufferSize) return (size_t)snprintf(buffer, bufferSize, "%c", value); } -// Convert double to zero-terminated string -FN_EXTERN size_t cvtDoubleToZ(double value, char *buffer, size_t bufferSize); +// Convert percentage to an integer. This is useful for exporting a percentage in a way that does not incur rounding. +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 FN_EXTERN size_t cvtIntToZ(const int value, char *buffer, size_t bufferSize); diff --git a/src/common/type/string.c b/src/common/type/string.c index 70bab3b50..9f62651a1 100644 --- a/src/common/type/string.c +++ b/src/common/type/string.c @@ -162,17 +162,36 @@ strNewZ(const char *const string) /**********************************************************************************************************************************/ FN_EXTERN String * -strNewDbl(const double value) +strNewDiv(const uint64_t dividend, const uint64_t divisor, StrNewDivParam param) { 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(); - 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) 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 { - if ((uint64_t)((double)size / (1024 * 1024 * 102.4)) % 10 != 0) - result = strNewFmt("%.1lfGB", (double)size / (1024 * 1024 * 1024)); - else - result = strNewFmt("%" PRIu64 "GB", size / (1024 * 1024 * 1024)); + char working[CVT_DIV_BUFFER_SIZE]; + uint64_t divisor = 1024 * 1024 * 1024; + unsigned int precision = 1; + 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); diff --git a/src/common/type/string.h b/src/common/type/string.h index 1f3769c64..d87a1c00f 100644 --- a/src/common/type/string.h +++ b/src/common/type/string.h @@ -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. FN_EXTERN String *strNewBuf(const Buffer *buffer); -// Create a new fixed length string by converting the double value -FN_EXTERN String *strNewDbl(double value); +// Create a new fixed length string by performing division +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 typedef struct StrNewTimeParam diff --git a/src/config/config.c b/src/config/config.c index 25cf81e17..237cd34db 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -448,7 +448,7 @@ cfgOptionDisplayVar(const Variant *const value, const ConfigOptionType optionTyp } 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) { diff --git a/test/define.yaml b/test/define.yaml index ef20b109f..1431efddd 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -102,7 +102,7 @@ unit: # ---------------------------------------------------------------------------------------------------------------------------- - name: type-convert - total: 12 + total: 13 coverage: - common/type/convert diff --git a/test/src/command/test/coverage.c b/test/src/command/test/coverage.c index fc23b8b2c..5aece66fc 100644 --- a/test/src/command/test/coverage.c +++ b/test/src/command/test/coverage.c @@ -816,14 +816,11 @@ testCvgSummaryValue(const unsigned int hit, const unsigned int total) // Else render value else { - strCatFmt(result, "%u/%u (", hit, total); - - if (hit == total) - strCatZ(result, "100.0"); - else - strCatFmt(result, "%.2f", (float)hit * 100 / (float)total); - - strCatZ(result, "%)"); + MEM_CONTEXT_TEMP_BEGIN() + { + strCatFmt(result, "%u/%u (%s)", hit, total, strZ(strNewPct(hit, total))); + } + MEM_CONTEXT_TEMP_END(); } FUNCTION_LOG_RETURN(STRING, result); diff --git a/test/src/module/command/backupTest.c b/test/src/module/command/backupTest.c index b0e4285e1..245326b20 100644 --- a/test/src/module/command/backupTest.c +++ b/test/src/module/command/backupTest.c @@ -1944,7 +1944,7 @@ testRun(void) "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" " 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" " e3db315c260e79211b7b52587123b7aa060f30ab\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.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/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" "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" @@ -3569,7 +3569,7 @@ testRun(void) "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/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/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" @@ -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/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 (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" "P00 DETAIL: reference pg_data/PG_VERSION to 20191103-165320F\n" "P00 DETAIL: reference pg_data/block-incr-same to 20191103-165320F\n" diff --git a/test/src/module/command/infoTest.c b/test/src/module/command/infoTest.c index 36957953f..c94a5443b 100644 --- a/test/src/module/command/infoTest.c +++ b/test/src/module/command/infoTest.c @@ -607,13 +607,13 @@ testRun(void) " timestamp start/stop: 2018-11-16 15:47:56+00 / 2018-11-16 15:48:09+00\n" " wal start/stop: n/a\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" " full backup: 20201116-154900F\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" " 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"); // Notify child to release lock @@ -1530,7 +1530,7 @@ testRun(void) TEST_RESULT_STR_Z( infoRender(), "stanza: stanza1\n" - " status: ok (backup/expire running - 65.27% complete)\n" + " status: ok (backup/expire running - 65.28% complete)\n" " cipher: mixed\n" " repo1: none\n" " repo2: aes-256-cbc\n" diff --git a/test/src/module/command/manifestTest.c b/test/src/module/command/manifestTest.c index c3571c8e6..dd46701ce 100644 --- a/test/src/module/command/manifestTest.c +++ b/test/src/module/command/manifestTest.c @@ -179,7 +179,7 @@ testRun(void) "\n" "file list:\n" " - pg_data/base/1/2\n" - " size: 64KB, repo 64KB\n" + " size: 64KB, repo 64.1KB\n" " checksum: 1adc95bebe9eea8c112d40cd04ab7a8d75c4f961\n" " bundle: 1\n" " block: size 8KB, map size 58B, checksum size 6B\n" @@ -274,7 +274,7 @@ testRun(void) "\n" "file list:\n" " - pg_data/base/1/2\n" - " size: 96KB, repo 64KB\n" + " size: 96KB, repo 64.1KB\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " bundle: 1\n" " block: size 8KB, map size 99B, checksum size 6B\n" @@ -379,7 +379,7 @@ testRun(void) "\n" "file list:\n" " - pg_data/base/1/2\n" - " size: 96KB, repo 64KB\n" + " size: 96KB, repo 64.1KB\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " bundle: 1\n" " block: size 8KB, map size 99B, checksum size 6B\n" @@ -473,7 +473,7 @@ testRun(void) "\n" "file list:\n" " - pg_data/base/1/2\n" - " size: 96KB, repo 64KB\n" + " size: 96KB, repo 64.1KB\n" " checksum: d4976e362696a43fb09e7d4e780d7d9352a2ec2e\n" " bundle: 1\n" " block: size 8KB, map size 99B, checksum size 6B\n" diff --git a/test/src/module/command/restoreTest.c b/test/src/module/command/restoreTest.c index 3a6247bf4..7e03f2182 100644 --- a/test/src/module/command/restoreTest.c +++ b/test/src/module/command/restoreTest.c @@ -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 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(); hrnCfgArgRawZ(argList, cfgOptStanza, "test1"); diff --git a/test/src/module/command/verifyTest.c b/test/src/module/command/verifyTest.c index d6ae8cb08..bf7979b67 100644 --- a/test/src/module/command/verifyTest.c +++ b/test/src/module/command/verifyTest.c @@ -2337,7 +2337,7 @@ testRun(void) "P00 INFO: backup start archive = 0000000105D95D3000000000, lsn = 5d95d30/0\n" "P00 INFO: check archive for prior segment 0000000105D95D2F000000FF\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" "P00 DETAIL: reference pg_data/PG_VERSION to 20191002-070640F\n" "P00 DETAIL: reference pg_data/base/1/44 to 20191002-070640F\n" diff --git a/test/src/module/common/debugOnTest.c b/test/src/module/common/debugOnTest.c index e9fda3fc4..26eafae85 100644 --- a/test/src/module/common/debugOnTest.c +++ b/test/src/module/common/debugOnTest.c @@ -25,7 +25,7 @@ testFunction2(void) static int 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_PARAM(INT, paramInt); @@ -33,7 +33,6 @@ testFunction1( FUNCTION_LOG_PARAM_P(BOOL, paramBoolP); FUNCTION_LOG_PARAM_PP(BOOL, paramBoolPP); FUNCTION_LOG_PARAM_P(VOID, paramVoidP); - FUNCTION_LOG_PARAM(DOUBLE, paramDouble); FUNCTION_LOG_PARAM(MODE, paramMode); FUNCTION_LOG_END(); @@ -185,11 +184,11 @@ testRun(void) if (testBegin("FUNCTION_DEBUG() and FUNCTION_LOG_RETURN()")) { harnessLogLevelSet(logLevelTrace); - testFunction1(99, false, NULL, NULL, NULL, 1.17, 0755); + testFunction1(99, false, NULL, NULL, NULL, 0755); TEST_RESULT_LOG( "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 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); @@ -200,11 +199,11 @@ testRun(void) bool **testBoolPP = &testBoolP; void *testVoidP = NULL; - testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 1.17, 0755); + testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 0755); TEST_RESULT_LOG( "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 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); @@ -212,18 +211,18 @@ testRun(void) testBoolP = NULL; testVoidP = (void *)1; - testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 1.17, 0755); + testFunction1(99, false, testBoolP, testBoolPP, testVoidP, 0755); TEST_RESULT_LOG( "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 DEBUG: " TEST_PGB_PATH "/test/src/module/common/debugOnTest::testFunction1: => 1"); // ------------------------------------------------------------------------------------------------------------------------- harnessLogLevelReset(); - testFunction1(55, true, NULL, NULL, NULL, 0.99, 0755); + testFunction1(55, true, NULL, NULL, NULL, 0755); TEST_RESULT_LOG(""); } diff --git a/test/src/module/common/typeConvertTest.c b/test/src/module/common/typeConvertTest.c index ba16505d3..ef2a58e09 100644 --- a/test/src/module/common/typeConvertTest.c +++ b/test/src/module/common/typeConvertTest.c @@ -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_Z(buffer, "999.1234", " check buffer"); + TEST_RESULT_UINT(cvtDivToZ(100, 2, 0, false, buffer, sizeof(buffer)), 2, "no precision"); + 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_Z(buffer, "999999999.123456", " check buffer"); + TEST_RESULT_UINT(cvtDivToZ(100, 2, 3, false, buffer, sizeof(buffer)), 6, "all zero precision"); + 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_Z(buffer, "999", " check buffer"); + TEST_RESULT_UINT(cvtDivToZ(100, 2, 3, true, buffer, sizeof(buffer)), 2, "all zero precision trimmed"); + 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"); } // ***************************************************************************************************************************** diff --git a/test/src/module/common/typeStringTest.c b/test/src/module/common/typeStringTest.c index beb92262b..90583c39b 100644 --- a/test/src/module/common/typeStringTest.c +++ b/test/src/module/common/typeStringTest.c @@ -65,9 +65,16 @@ testRun(void) 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"); @@ -347,6 +354,7 @@ testRun(void) 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 + 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"); } diff --git a/test/src/module/test/coverageTest.c b/test/src/module/test/coverageTest.c index 9a7724825..4a07f371f 100644 --- a/test/src/module/test/coverageTest.c +++ b/test/src/module/test/coverageTest.c @@ -432,9 +432,9 @@ testRun(void) "\n" "\n" " common/error/sub\n" - " 1/1 (100.0%)\n" + " 1/1 (100.00%)\n" " ---\n" - " 1/1 (100.0%)\n" + " 1/1 (100.00%)\n" "\n" "\n" "\n"