You've already forked pgbackrest
							
							
				mirror of
				https://github.com/pgbackrest/pgbackrest.git
				synced 2025-10-30 23:37:45 +02:00 
			
		
		
		
	Add progress-only detail level for info command output.
The info command fetches a lot of information from the repository about backups and archives, so this operation can be slow. Because progress data is stored in local lock files, accessing the repository is unnecessary when only progress information is required. This patch introduces the `--detail-level=[progress|full]` option, with `full` as the default. The `progress` level limits the info command output to progress details without querying the repository. The only remaining operations are scanning the folder structure to list available stanzas and reading lock files. Note: When `progress` is selected, the info command performs no checks beyond verifying stanza availability.
This commit is contained in:
		| @@ -15,6 +15,20 @@ | ||||
|             </release-item> | ||||
|         </release-bug-list> | ||||
|  | ||||
|         <release-feature-list> | ||||
|             <release-item> | ||||
|                 <github-pull-request id="2550"/> | ||||
|  | ||||
|                 <release-item-contributor-list> | ||||
|                     <release-item-contributor id="denis.garsh"/> | ||||
|                     <release-item-reviewer id="david.steele"/> | ||||
|                     <release-item-reviewer id="stefan.fercot"/> | ||||
|                 </release-item-contributor-list> | ||||
|  | ||||
|                 <p>Add progress-only detail level for <cmd>info</cmd> command output.</p> | ||||
|             </release-item> | ||||
|         </release-feature-list> | ||||
|  | ||||
|         <release-improvement-list> | ||||
|             <release-item> | ||||
|                 <github-pull-request id="2642"/> | ||||
|   | ||||
| @@ -324,6 +324,11 @@ | ||||
|     <contributor-id type="github">youattd</contributor-id> | ||||
| </contributor> | ||||
|  | ||||
| <contributor id="denis.garsh"> | ||||
|     <contributor-name-display>Denis Garsh</contributor-name-display> | ||||
|     <contributor-id type="github">hilltracer</contributor-id> | ||||
| </contributor> | ||||
|  | ||||
| <contributor id="devrim.gunduz"> | ||||
|     <contributor-name-display>Devrim G&uuml;nd&uuml;z</contributor-name-display> | ||||
|     <contributor-id type="github">devrimgunduz</contributor-id> | ||||
|   | ||||
| @@ -284,6 +284,17 @@ option: | ||||
|     default-type: literal | ||||
|     default: CFGOPTDEF_CONFIG_PATH | ||||
|  | ||||
|   detail-level: | ||||
|     type: string-id | ||||
|     default: full | ||||
|     command: | ||||
|       info: {} | ||||
|     allow-list: | ||||
|       - progress | ||||
|       - full | ||||
|     command-role: | ||||
|       main: {} | ||||
|  | ||||
|   dry-run: | ||||
|     type: boolean | ||||
|     default: false | ||||
|   | ||||
| @@ -2550,6 +2550,8 @@ | ||||
|  | ||||
|                     <p>For machine-readable output use <br-option>--output=json</br-option>. The JSON output contains far more information than the text output and is kept stable unless a bug is found.</p> | ||||
|  | ||||
|                     <p>To speed up execution, limit the output to only progress information by specifying <br-option>--detail-level=progress</br-option>. Note that this skips all checks except for availability of the stanza.</p> | ||||
|  | ||||
|                     <p>Each stanza has a separate section and it is possible to limit output to a single stanza with the <br-option>--stanza</br-option> option. The stanza '<id>status</id>' gives a brief indication of the stanza's health. If this is '<id>ok</id>' then <backrest/> is functioning normally. If there are multiple repositories, then a status of '<id>mixed</id>' indicates that the stanza is not in a healthy state on one or more of the repositories; in this case the state of the stanza will be detailed per repository. For cases in which an error on a repository occurred that is not one of the known error codes, then an error code of '<id>other</id>' will be used and the full error details will be provided. The '<id>wal archive min/max</id>' shows the minimum and maximum WAL currently stored in the archive and, in the case of multiple repositories, will be reported across all repositories unless the <br-option>{[dash]}-repo</br-option> option is set. Note that there may be gaps due to archive retention policies or other reasons.</p> | ||||
|  | ||||
|                     <p>The '<id>backup/expire running</id>' message will appear beside the '<id>status</id>' information if one of those commands is currently running on the host.</p> | ||||
| @@ -2570,6 +2572,21 @@ | ||||
|                 </text> | ||||
|  | ||||
|                 <option-list> | ||||
|                     <option id="detail-level" name="Detail level"> | ||||
|                         <summary>Output detail level.</summary> | ||||
|  | ||||
|                         <text> | ||||
|                             <p>The following levels are supported:</p> | ||||
|  | ||||
|                             <list> | ||||
|                                 <list-item><id>progress</id> - Output only the current backup/expire progress. This level cannot be used with the <br-option>--set</br-option> option.</list-item> | ||||
|                                 <list-item><id>full</id> - Output full info.</list-item> | ||||
|                             </list> | ||||
|                         </text> | ||||
|  | ||||
|                         <example>progress</example> | ||||
|                     </option> | ||||
|  | ||||
|                     <option id="output" name="Output"> | ||||
|                         <summary>Output format.</summary> | ||||
|  | ||||
|   | ||||
| @@ -731,6 +731,9 @@ stanzaInfoList( | ||||
|  | ||||
|     ASSERT(stanzaRepoList != NULL); | ||||
|  | ||||
|     // Is full output requested? | ||||
|     const bool outputFull = cfgOptionStrId(cfgOptDetailLevel) == CFGOPTVAL_DETAIL_LEVEL_FULL; | ||||
|  | ||||
|     VariantList *const result = varLstNew(); | ||||
|  | ||||
|     // Sort the list of stanzas | ||||
| @@ -759,6 +762,20 @@ stanzaInfoList( | ||||
|         { | ||||
|             InfoRepoData *const repoData = &stanzaData->repoList[repoIdx]; | ||||
|  | ||||
|             // When full output is not requested (progress mode), skip collecting detailed information and only update status code | ||||
|             if (!outputFull) | ||||
|             { | ||||
|                 if (repoIdx == repoIdxMin) | ||||
|                     stanzaStatusCode = repoData->stanzaStatus; | ||||
|                 else | ||||
|                 { | ||||
|                     stanzaStatusCode = | ||||
|                         stanzaStatusCode != repoData->stanzaStatus ? INFO_STANZA_STATUS_CODE_MIXED : repoData->stanzaStatus; | ||||
|                 } | ||||
|  | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             Variant *const repoInfo = varNewKv(kvNew()); | ||||
|             kvPut(varKv(repoInfo), REPO_KEY_KEY_VAR, VARUINT(repoData->key)); | ||||
|             kvPut(varKv(repoInfo), KEY_CIPHER_VAR, VARSTR(strIdToStr(repoData->cipher))); | ||||
| @@ -845,19 +862,23 @@ stanzaInfoList( | ||||
|             kvPut(varKv(stanzaInfo), STANZA_KEY_REPO_VAR, varNewVarLst(repoSection)); | ||||
|         } | ||||
|  | ||||
|         // Get a sorted list of the data for all existing backups for this stanza over all repos | ||||
|         backupList(backupSection, stanzaData, backupLabel, repoIdxMin, repoIdxMax); | ||||
|         kvPut(varKv(stanzaInfo), STANZA_KEY_BACKUP_VAR, varNewVarLst(backupSection)); | ||||
|         // Collect backup and cipher data if full output is requested | ||||
|         if (outputFull) | ||||
|         { | ||||
|             // Get a sorted list of the data for all existing backups for this stanza over all repos | ||||
|             backupList(backupSection, stanzaData, backupLabel, repoIdxMin, repoIdxMax); | ||||
|             kvPut(varKv(stanzaInfo), STANZA_KEY_BACKUP_VAR, varNewVarLst(backupSection)); | ||||
|  | ||||
|         // Set the overall stanza status | ||||
|             // Set the overall cipher type | ||||
|             if (stanzaCipherType != INFO_STANZA_STATUS_CODE_MIXED) | ||||
|                 kvPut(varKv(stanzaInfo), KEY_CIPHER_VAR, VARSTR(strIdToStr(stanzaCipherType))); | ||||
|             else | ||||
|                 kvPut(varKv(stanzaInfo), KEY_CIPHER_VAR, VARSTRDEF(INFO_STANZA_MIXED)); | ||||
|         } | ||||
|  | ||||
|         // Set the overall stanza status and gather progress information | ||||
|         stanzaStatus(stanzaStatusCode, stanzaData, stanzaInfo); | ||||
|  | ||||
|         // Set the overall cipher type | ||||
|         if (stanzaCipherType != INFO_STANZA_STATUS_CODE_MIXED) | ||||
|             kvPut(varKv(stanzaInfo), KEY_CIPHER_VAR, VARSTR(strIdToStr(stanzaCipherType))); | ||||
|         else | ||||
|             kvPut(varKv(stanzaInfo), KEY_CIPHER_VAR, VARSTRDEF(INFO_STANZA_MIXED)); | ||||
|  | ||||
|         varLstAdd(result, stanzaInfo); | ||||
|     } | ||||
|  | ||||
| @@ -1309,47 +1330,58 @@ infoUpdateStanza( | ||||
|  | ||||
|         TRY_BEGIN() | ||||
|         { | ||||
|             // Catch certain errors | ||||
|             TRY_BEGIN() | ||||
|             { | ||||
|                 // Attempt to load the backup info file | ||||
|                 stanzaRepo->repoList[repoIdx].backupInfo = infoBackupLoadFile( | ||||
|                     storage, strNewFmt(STORAGE_PATH_BACKUP "/%s/%s", strZ(stanzaRepo->name), INFO_BACKUP_FILE), | ||||
|                     stanzaRepo->repoList[repoIdx].cipher, stanzaRepo->repoList[repoIdx].cipherPass); | ||||
|             } | ||||
|             CATCH(FileMissingError) | ||||
|             { | ||||
|                 // If there is no backup.info then set the status to indicate missing | ||||
|                 stanzaStatus = INFO_STANZA_STATUS_CODE_MISSING_STANZA_DATA; | ||||
|             } | ||||
|             CATCH(CryptoError) | ||||
|             { | ||||
|                 // If a reason for the error is due to a an encryption error, add a hint | ||||
|                 THROW_FMT( | ||||
|                     CryptoError, | ||||
|                     "%s\n" | ||||
|                     "HINT: use option --stanza if encryption settings are different for the stanza than the global settings.", | ||||
|                     errorMessage()); | ||||
|             } | ||||
|             TRY_END(); | ||||
|             // If full output is requested read info and manifest files | ||||
|             const bool outputFull = cfgOptionStrId(cfgOptDetailLevel) == CFGOPTVAL_DETAIL_LEVEL_FULL; | ||||
|  | ||||
|             // If backup.info was found, then get the archive.info file, which must exist if the backup.info exists, else the failed | ||||
|             // load will throw an error which will be trapped and recorded | ||||
|             if (stanzaRepo->repoList[repoIdx].backupInfo != NULL) | ||||
|             if (outputFull) | ||||
|             { | ||||
|                 stanzaRepo->repoList[repoIdx].archiveInfo = infoArchiveLoadFile( | ||||
|                     storage, strNewFmt(STORAGE_PATH_ARCHIVE "/%s/%s", strZ(stanzaRepo->name), INFO_ARCHIVE_FILE), | ||||
|                     stanzaRepo->repoList[repoIdx].cipher, stanzaRepo->repoList[repoIdx].cipherPass); | ||||
|  | ||||
|                 // If a specific backup exists on this repo then attempt to load the manifest | ||||
|                 if (backupLabel != NULL) | ||||
|                 // Catch certain errors | ||||
|                 TRY_BEGIN() | ||||
|                 { | ||||
|                     stanzaRepo->repoList[repoIdx].manifest = manifestLoadFile( | ||||
|                         storage, strNewFmt(STORAGE_REPO_BACKUP "/%s/" BACKUP_MANIFEST_FILE, strZ(backupLabel)), | ||||
|                         stanzaRepo->repoList[repoIdx].cipher, | ||||
|                         infoPgCipherPass(infoBackupPg(stanzaRepo->repoList[repoIdx].backupInfo))); | ||||
|                     // Attempt to load the backup info file | ||||
|                     stanzaRepo->repoList[repoIdx].backupInfo = infoBackupLoadFile( | ||||
|                         storage, strNewFmt(STORAGE_PATH_BACKUP "/%s/%s", strZ(stanzaRepo->name), INFO_BACKUP_FILE), | ||||
|                         stanzaRepo->repoList[repoIdx].cipher, stanzaRepo->repoList[repoIdx].cipherPass); | ||||
|                 } | ||||
|                 CATCH(FileMissingError) | ||||
|                 { | ||||
|                     // If there is no backup.info then set the status to indicate missing | ||||
|                     stanzaStatus = INFO_STANZA_STATUS_CODE_MISSING_STANZA_DATA; | ||||
|                 } | ||||
|                 CATCH(CryptoError) | ||||
|                 { | ||||
|                     // If a reason for the error is due to a an encryption error, add a hint | ||||
|                     THROW_FMT( | ||||
|                         CryptoError, | ||||
|                         "%s\n" | ||||
|                         "HINT: use option --stanza if encryption settings are different for the stanza than the global settings.", | ||||
|                         errorMessage()); | ||||
|                 } | ||||
|                 TRY_END(); | ||||
|  | ||||
|                 // If backup.info was found, then get the archive.info file, which must exist if backup.info exists, else the failed | ||||
|                 // load will throw an error which will be trapped and recorded | ||||
|                 if (stanzaRepo->repoList[repoIdx].backupInfo != NULL) | ||||
|                 { | ||||
|                     stanzaRepo->repoList[repoIdx].archiveInfo = infoArchiveLoadFile( | ||||
|                         storage, strNewFmt(STORAGE_PATH_ARCHIVE "/%s/%s", strZ(stanzaRepo->name), INFO_ARCHIVE_FILE), | ||||
|                         stanzaRepo->repoList[repoIdx].cipher, stanzaRepo->repoList[repoIdx].cipherPass); | ||||
|  | ||||
|                     // If a specific backup exists on this repo then attempt to load the manifest | ||||
|                     if (backupLabel != NULL) | ||||
|                     { | ||||
|                         stanzaRepo->repoList[repoIdx].manifest = manifestLoadFile( | ||||
|                             storage, strNewFmt(STORAGE_REPO_BACKUP "/%s/" BACKUP_MANIFEST_FILE, strZ(backupLabel)), | ||||
|                             stanzaRepo->repoList[repoIdx].cipher, | ||||
|                             infoPgCipherPass(infoBackupPg(stanzaRepo->repoList[repoIdx].backupInfo))); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Read the lock file if backup.info is present. Exception: when only progress is requested, backup.info is skipped for | ||||
|             // performance, so the lock file is read unconditionally -- though it may be outdated in this case. | ||||
|             if (stanzaRepo->repoList[repoIdx].backupInfo != NULL || !outputFull) | ||||
|             { | ||||
|                 // If there is a valid backup lock for this stanza then backup/expire must be running | ||||
|                 const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name, repoIdx); | ||||
|  | ||||
| @@ -1414,6 +1446,13 @@ infoRender(void) | ||||
|         const String *const backupLabel = cfgOptionStrNull(cfgOptSet); | ||||
|         bool backupFound = false; | ||||
|  | ||||
|         // If only progress info is requested then details about a specific backup may not be requested | ||||
|         if (backupLabel != NULL && cfgOptionStrId(cfgOptDetailLevel) == CFGOPTVAL_DETAIL_LEVEL_PROGRESS) | ||||
|         { | ||||
|             THROW_FMT(OptionInvalidError, "option '%s' cannot be used with option '%s' = '%s'", | ||||
|                       cfgOptionName(cfgOptSet), cfgOptionName(cfgOptDetailLevel), CFGOPTVAL_DETAIL_LEVEL_PROGRESS_Z); | ||||
|         } | ||||
|  | ||||
|         // Initialize the repo index | ||||
|         unsigned int repoIdxMin = 0; | ||||
|         const unsigned int repoTotal = cfgOptionGroupIdxTotal(cfgOptGrpRepo); | ||||
| @@ -1597,6 +1636,9 @@ infoRender(void) | ||||
|             // Process any stanza directories | ||||
|             if (!varLstEmpty(infoList)) | ||||
|             { | ||||
|                 // Is full output requested? | ||||
|                 const bool outputFull = cfgOptionStrId(cfgOptDetailLevel) == CFGOPTVAL_DETAIL_LEVEL_FULL; | ||||
|  | ||||
|                 for (unsigned int stanzaIdx = 0; stanzaIdx < varLstSize(infoList); stanzaIdx++) | ||||
|                 { | ||||
|                     const KeyValue *const stanzaInfo = varKv(varLstGet(infoList, stanzaIdx)); | ||||
| @@ -1626,8 +1668,9 @@ infoRender(void) | ||||
|                     if (statusCode != INFO_STANZA_STATUS_CODE_OK) | ||||
|                     { | ||||
|                         // Update the overall stanza status and change displayed status if backup lock is found | ||||
|                         if (statusCode == INFO_STANZA_STATUS_CODE_MIXED || statusCode == INFO_STANZA_STATUS_CODE_PG_MISMATCH || | ||||
|                             statusCode == INFO_STANZA_STATUS_CODE_OTHER) | ||||
|                         if (outputFull && | ||||
|                             (statusCode == INFO_STANZA_STATUS_CODE_MIXED || statusCode == INFO_STANZA_STATUS_CODE_PG_MISMATCH || | ||||
|                              statusCode == INFO_STANZA_STATUS_CODE_OTHER)) | ||||
|                         { | ||||
|                             // Stanza status | ||||
|                             strCatFmt( | ||||
| @@ -1700,7 +1743,7 @@ infoRender(void) | ||||
|                     } | ||||
|  | ||||
|                     // Add cipher type if the stanza is found on at least one repo | ||||
|                     if (statusCode != INFO_STANZA_STATUS_CODE_MISSING_STANZA_PATH) | ||||
|                     if (outputFull && statusCode != INFO_STANZA_STATUS_CODE_MISSING_STANZA_PATH) | ||||
|                     { | ||||
|                         strCatFmt(resultStr, "    cipher: %s\n", strZ(varStr(kvGet(stanzaInfo, KEY_CIPHER_VAR)))); | ||||
|  | ||||
| @@ -1721,7 +1764,7 @@ infoRender(void) | ||||
|                     } | ||||
|  | ||||
|                     // Get the current database for this stanza | ||||
|                     if (!varLstEmpty(kvGetList(stanzaInfo, STANZA_KEY_DB_VAR))) | ||||
|                     if (outputFull && !varLstEmpty(kvGetList(stanzaInfo, STANZA_KEY_DB_VAR))) | ||||
|                     { | ||||
|                         const InfoStanzaRepo *const stanzaRepo = lstFind(stanzaRepoList, &stanzaName); | ||||
|  | ||||
|   | ||||
| @@ -72,6 +72,7 @@ Option constants | ||||
| #define CFGOPT_DB_INCLUDE                                           "db-include" | ||||
| #define CFGOPT_DB_TIMEOUT                                           "db-timeout" | ||||
| #define CFGOPT_DELTA                                                "delta" | ||||
| #define CFGOPT_DETAIL_LEVEL                                         "detail-level" | ||||
| #define CFGOPT_DRY_RUN                                              "dry-run" | ||||
| #define CFGOPT_EXCLUDE                                              "exclude" | ||||
| #define CFGOPT_EXEC_ID                                              "exec-id" | ||||
| @@ -139,7 +140,7 @@ Option constants | ||||
| #define CFGOPT_VERBOSE                                              "verbose" | ||||
| #define CFGOPT_VERSION                                              "version" | ||||
|  | ||||
| #define CFG_OPTION_TOTAL                                            186 | ||||
| #define CFG_OPTION_TOTAL                                            187 | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Option value constants | ||||
| @@ -167,6 +168,11 @@ Option value constants | ||||
| #define CFGOPTVAL_COMPRESS_TYPE_ZST                                 STRID5("zst", 0x527a0) | ||||
| #define CFGOPTVAL_COMPRESS_TYPE_ZST_Z                               "zst" | ||||
|  | ||||
| #define CFGOPTVAL_DETAIL_LEVEL_FULL                                 STRID5("full", 0x632a60) | ||||
| #define CFGOPTVAL_DETAIL_LEVEL_FULL_Z                               "full" | ||||
| #define CFGOPTVAL_DETAIL_LEVEL_PROGRESS                             STRID5("progress", 0x9ccb23be500) | ||||
| #define CFGOPTVAL_DETAIL_LEVEL_PROGRESS_Z                           "progress" | ||||
|  | ||||
| #define CFGOPTVAL_LOG_LEVEL_CONSOLE_DEBUG                           STRID5("debug", 0x7a88a40) | ||||
| #define CFGOPTVAL_LOG_LEVEL_CONSOLE_DEBUG_Z                         "debug" | ||||
| #define CFGOPTVAL_LOG_LEVEL_CONSOLE_DETAIL                          STRID5("detail", 0x1890d0a40) | ||||
| @@ -423,6 +429,7 @@ typedef enum | ||||
|     cfgOptDbInclude, | ||||
|     cfgOptDbTimeout, | ||||
|     cfgOptDelta, | ||||
|     cfgOptDetailLevel, | ||||
|     cfgOptDryRun, | ||||
|     cfgOptExclude, | ||||
|     cfgOptExecId, | ||||
|   | ||||
| @@ -111,6 +111,7 @@ static const StringPubConst parseRuleValueStr[] = | ||||
|     PARSE_RULE_STRPUB("postgres"),                                                                                        // val/str
 | ||||
|     PARSE_RULE_STRPUB("prefer"),                                                                                          // val/str
 | ||||
|     PARSE_RULE_STRPUB("preserve"),                                                                                        // val/str
 | ||||
|     PARSE_RULE_STRPUB("progress"),                                                                                        // val/str
 | ||||
|     PARSE_RULE_STRPUB("promote"),                                                                                         // val/str
 | ||||
|     PARSE_RULE_STRPUB("repo"),                                                                                            // val/str
 | ||||
|     PARSE_RULE_STRPUB("s3"),                                                                                              // val/str
 | ||||
| @@ -242,6 +243,7 @@ typedef enum | ||||
|     parseRuleValStrQT_postgres_QT,                                                                                   // val/str/enum
 | ||||
|     parseRuleValStrQT_prefer_QT,                                                                                     // val/str/enum
 | ||||
|     parseRuleValStrQT_preserve_QT,                                                                                   // val/str/enum
 | ||||
|     parseRuleValStrQT_progress_QT,                                                                                   // val/str/enum
 | ||||
|     parseRuleValStrQT_promote_QT,                                                                                    // val/str/enum
 | ||||
|     parseRuleValStrQT_repo_QT,                                                                                       // val/str/enum
 | ||||
|     parseRuleValStrQT_s3_QT,                                                                                         // val/str/enum
 | ||||
| @@ -315,6 +317,7 @@ static const StringId parseRuleValueStrId[] = | ||||
|     STRID5("posix", 0x184cdf00),                                                                                        // val/strid
 | ||||
|     STRID5("prefer", 0x245316500),                                                                                      // val/strid
 | ||||
|     STRID5("preserve", 0x2da45996500),                                                                                  // val/strid
 | ||||
|     STRID5("progress", 0x9ccb23be500),                                                                                  // val/strid
 | ||||
|     STRID5("promote", 0x168f6be500),                                                                                    // val/strid
 | ||||
|     STRID5("repo", 0x7c0b20),                                                                                           // val/strid
 | ||||
|     STRID6("s3", 0x7d31),                                                                                               // val/strid
 | ||||
| @@ -379,6 +382,7 @@ static const uint8_t parseRuleValueStrIdStrMap[] = | ||||
|     parseRuleValStrQT_posix_QT,                                                                                  // val/strid/strmap
 | ||||
|     parseRuleValStrQT_prefer_QT,                                                                                 // val/strid/strmap
 | ||||
|     parseRuleValStrQT_preserve_QT,                                                                               // val/strid/strmap
 | ||||
|     parseRuleValStrQT_progress_QT,                                                                               // val/strid/strmap
 | ||||
|     parseRuleValStrQT_promote_QT,                                                                                // val/strid/strmap
 | ||||
|     parseRuleValStrQT_repo_QT,                                                                                   // val/strid/strmap
 | ||||
|     parseRuleValStrQT_s3_QT,                                                                                     // val/strid/strmap
 | ||||
| @@ -443,6 +447,7 @@ typedef enum | ||||
|     parseRuleValStrIdPosix,                                                                                        // val/strid/enum
 | ||||
|     parseRuleValStrIdPrefer,                                                                                       // val/strid/enum
 | ||||
|     parseRuleValStrIdPreserve,                                                                                     // val/strid/enum
 | ||||
|     parseRuleValStrIdProgress,                                                                                     // val/strid/enum
 | ||||
|     parseRuleValStrIdPromote,                                                                                      // val/strid/enum
 | ||||
|     parseRuleValStrIdRepo,                                                                                         // val/strid/enum
 | ||||
|     parseRuleValStrIdS3,                                                                                           // val/strid/enum
 | ||||
| @@ -2377,6 +2382,36 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] = | ||||
|         ),                                                                                                              // opt/delta
 | ||||
|     ),                                                                                                                  // opt/delta
 | ||||
|     // -----------------------------------------------------------------------------------------------------------------------------
 | ||||
|     PARSE_RULE_OPTION                                                                                            // opt/detail-level
 | ||||
|     (                                                                                                            // opt/detail-level
 | ||||
|         PARSE_RULE_OPTION_NAME("detail-level"),                                                                  // opt/detail-level
 | ||||
|         PARSE_RULE_OPTION_TYPE(StringId),                                                                        // opt/detail-level
 | ||||
|         PARSE_RULE_OPTION_REQUIRED(true),                                                                        // opt/detail-level
 | ||||
|         PARSE_RULE_OPTION_SECTION(CommandLine),                                                                  // opt/detail-level
 | ||||
|                                                                                                                  // opt/detail-level
 | ||||
|         PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST                                                           // opt/detail-level
 | ||||
|         (                                                                                                        // opt/detail-level
 | ||||
|             PARSE_RULE_OPTION_COMMAND(Info)                                                                      // opt/detail-level
 | ||||
|         ),                                                                                                       // opt/detail-level
 | ||||
|                                                                                                                  // opt/detail-level
 | ||||
|         PARSE_RULE_OPTIONAL                                                                                      // opt/detail-level
 | ||||
|         (                                                                                                        // opt/detail-level
 | ||||
|             PARSE_RULE_OPTIONAL_GROUP                                                                            // opt/detail-level
 | ||||
|             (                                                                                                    // opt/detail-level
 | ||||
|                 PARSE_RULE_OPTIONAL_ALLOW_LIST                                                                   // opt/detail-level
 | ||||
|                 (                                                                                                // opt/detail-level
 | ||||
|                     PARSE_RULE_VAL_STRID(Progress),                                                              // opt/detail-level
 | ||||
|                     PARSE_RULE_VAL_STRID(Full),                                                                  // opt/detail-level
 | ||||
|                 ),                                                                                               // opt/detail-level
 | ||||
|                                                                                                                  // opt/detail-level
 | ||||
|                 PARSE_RULE_OPTIONAL_DEFAULT                                                                      // opt/detail-level
 | ||||
|                 (                                                                                                // opt/detail-level
 | ||||
|                     PARSE_RULE_VAL_STRID(Full),                                                                  // opt/detail-level
 | ||||
|                 ),                                                                                               // opt/detail-level
 | ||||
|             ),                                                                                                   // opt/detail-level
 | ||||
|         ),                                                                                                       // opt/detail-level
 | ||||
|     ),                                                                                                           // opt/detail-level
 | ||||
|     // -----------------------------------------------------------------------------------------------------------------------------
 | ||||
|     PARSE_RULE_OPTION                                                                                                 // opt/dry-run
 | ||||
|     (                                                                                                                 // opt/dry-run
 | ||||
|         PARSE_RULE_OPTION_NAME("dry-run"),                                                                            // opt/dry-run
 | ||||
| @@ -11501,6 +11536,7 @@ static const uint8_t optionResolveOrder[] = | ||||
|     cfgOptDbInclude,                                                                                            // opt-resolve-order
 | ||||
|     cfgOptDbTimeout,                                                                                            // opt-resolve-order
 | ||||
|     cfgOptDelta,                                                                                                // opt-resolve-order
 | ||||
|     cfgOptDetailLevel,                                                                                          // opt-resolve-order
 | ||||
|     cfgOptDryRun,                                                                                               // opt-resolve-order
 | ||||
|     cfgOptExclude,                                                                                              // opt-resolve-order
 | ||||
|     cfgOptExecId,                                                                                               // opt-resolve-order
 | ||||
|   | ||||
| @@ -36,14 +36,26 @@ testRun(void) | ||||
|         hrnCfgArgRawZ(argList, cfgOptOutput, "json"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList); | ||||
|  | ||||
|         StringList *argListProgressOnly = strLstDup(argList); | ||||
|         hrnCfgArgRawZ(argListProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|  | ||||
|         StringList *argListTextProgressOnly = strLstDup(argListText); | ||||
|         hrnCfgArgRawZ(argListTextProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("no stanzas have been created"); | ||||
|  | ||||
|         TEST_RESULT_STR_Z(infoRender(), "[]", "json - repo but no stanzas"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|         TEST_RESULT_STR_Z(infoRender(), "[]", "json (progress only) - repo but no stanzas"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListText); | ||||
|         TEST_RESULT_STR_Z(infoRender(), "No stanzas exist in the repository.\n", "text - no stanzas"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListTextProgressOnly); | ||||
|         TEST_RESULT_STR_Z(infoRender(), "No stanzas exist in the repository.\n", "text (progress only) - no stanzas"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("repo is still empty but stanza option is specified"); | ||||
|  | ||||
| @@ -80,6 +92,27 @@ testRun(void) | ||||
|             // {uncrustify_on} | ||||
|             "json - empty repo, stanza option specified"); | ||||
|  | ||||
|         StringList *argListProgressOnlyStanzaOpt = strLstDup(argListProgressOnly); | ||||
|         hrnCfgArgRawZ(argListProgressOnlyStanzaOpt, cfgOptStanza, "stanza1"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnlyStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             // {uncrustify_off - indentation} | ||||
|             "[" | ||||
|                 "{" | ||||
|                     "\"name\":\"stanza1\"," | ||||
|                     "\"status\":{" | ||||
|                         "\"code\":1," | ||||
|                         "\"lock\":{" | ||||
|                             "\"backup\":{\"held\":false}" | ||||
|                         "}," | ||||
|                         "\"message\":\"missing stanza path\"" | ||||
|                     "}" | ||||
|                 "}" | ||||
|             "]", | ||||
|             // {uncrustify_on} | ||||
|             "json (progress only) - empty repo, stanza option specified"); | ||||
|  | ||||
|         StringList *argListTextStanzaOpt = strLstDup(argListText); | ||||
|         hrnCfgArgRawZ(argListTextStanzaOpt, cfgOptStanza, "stanza1"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListTextStanzaOpt); | ||||
| @@ -89,12 +122,22 @@ testRun(void) | ||||
|             "    status: error (missing stanza path)\n", | ||||
|             "text - empty repo, stanza option specified"); | ||||
|  | ||||
|         StringList *argListTextProgressOnlyStanzaOpt = strLstDup(argListTextProgressOnly); | ||||
|         hrnCfgArgRawZ(argListTextProgressOnlyStanzaOpt, cfgOptStanza, "stanza1"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListTextProgressOnlyStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: error (missing stanza path)\n", | ||||
|             "text (progress only) - empty repo, stanza option specified"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("stanza path exists but is empty"); | ||||
|  | ||||
|         HRN_STORAGE_PATH_CREATE(storageRepoWrite(), STORAGE_REPO_ARCHIVE, .comment = "create repo stanza archive path"); | ||||
|         HRN_STORAGE_PATH_CREATE(storageRepoWrite(), STORAGE_REPO_BACKUP, .comment = "create repo stanza backup path"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListTextStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
| @@ -102,7 +145,15 @@ testRun(void) | ||||
|             "    cipher: none\n", | ||||
|             "text - missing stanza data"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList); | ||||
|         // If only progress is requested, the info command skips additional checks. | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListTextProgressOnlyStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: ok\n", | ||||
|             "text (progress only) - missing stanza data"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             // {uncrustify_off - indentation} | ||||
| @@ -133,6 +184,26 @@ testRun(void) | ||||
|             // {uncrustify_on} | ||||
|             "json - missing stanza data"); | ||||
|  | ||||
|         // If only progress is requested, the info command skips additional checks. | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnlyStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             // {uncrustify_off - indentation} | ||||
|             "[" | ||||
|                 "{" | ||||
|                     "\"name\":\"stanza1\"," | ||||
|                     "\"status\":{" | ||||
|                         "\"code\":0," | ||||
|                         "\"lock\":{" | ||||
|                             "\"backup\":{\"held\":false}" | ||||
|                         "}," | ||||
|                         "\"message\":\"ok\"" | ||||
|                     "}" | ||||
|                 "}" | ||||
|             "]", | ||||
|             // {uncrustify_on} | ||||
|             "json (progress only) - missing stanza data"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("backup.info file exists, but archive.info does not"); | ||||
|  | ||||
| @@ -152,6 +223,7 @@ testRun(void) | ||||
|             "2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665679" | ||||
|             ",\"db-version\":\"9.6\"}\n"); | ||||
|  | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListStanzaOpt); | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             // {uncrustify_off - indentation} | ||||
| @@ -343,6 +415,16 @@ testRun(void) | ||||
|             "    status: error (missing stanza path)\n", | ||||
|             "text - multi-repo, requested stanza missing on selected repo"); | ||||
|  | ||||
|         StringList *argList2ProgressOnly = strLstDup(argList2); | ||||
|         hrnCfgArgRawZ(argList2ProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList2ProgressOnly); | ||||
|  | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: error (missing stanza path)\n", | ||||
|             "text (progress only) - multi-repo, requested stanza missing on selected repo"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("multi-repo - WAL segment on repo1"); | ||||
|  | ||||
| @@ -367,6 +449,16 @@ testRun(void) | ||||
|             "        wal archive min/max (9.6): 000000030000000000000001/000000030000000000000001\n", | ||||
|             "text - multi-repo, single stanza, one wal segment"); | ||||
|  | ||||
|         argList2ProgressOnly = strLstDup(argList2); | ||||
|         hrnCfgArgRawZ(argList2ProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList2ProgressOnly); | ||||
|         // If only progress is requested, the info command skips additional checks. | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: ok\n", | ||||
|             "text (progress only) - multi-repo, single stanza, one wal segment"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("coverage for stanzaStatus branches && percent complete null"); | ||||
|  | ||||
| @@ -590,6 +682,25 @@ testRun(void) | ||||
|                     // {uncrustify_on} | ||||
|                     "json - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected"); | ||||
|  | ||||
|                 HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|                 TEST_RESULT_STR_Z( | ||||
|                     infoRender(), | ||||
|                     // {uncrustify_off - indentation} | ||||
|                     "[" | ||||
|                         "{" | ||||
|                             "\"name\":\"stanza1\"," | ||||
|                             "\"status\":{" | ||||
|                                 "\"code\":0," | ||||
|                                 "\"lock\":{" | ||||
|                                     "\"backup\":{\"held\":true}" | ||||
|                                 "}," | ||||
|                                 "\"message\":\"ok\"" | ||||
|                             "}" | ||||
|                         "}" | ||||
|                     "]", | ||||
|                     // {uncrustify_on} | ||||
|                     "json (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected"); | ||||
|  | ||||
|                 HRN_CFG_LOAD(cfgCmdInfo, argListText); | ||||
|                 TEST_RESULT_STR_Z( | ||||
|                     infoRender(), | ||||
| @@ -616,6 +727,13 @@ testRun(void) | ||||
|                     "            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"); | ||||
|  | ||||
|                 HRN_CFG_LOAD(cfgCmdInfo, argListTextProgressOnly); | ||||
|                 TEST_RESULT_STR_Z( | ||||
|                     infoRender(), | ||||
|                     "stanza: stanza1\n" | ||||
|                     "    status: ok (backup/expire running)\n", | ||||
|                     "text (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected"); | ||||
|  | ||||
|                 // Notify child to release lock | ||||
|                 HRN_FORK_PARENT_NOTIFY_PUT(0); | ||||
|             } | ||||
| @@ -1037,6 +1155,12 @@ testRun(void) | ||||
|         StringList *argListMultiRepoJson = strLstDup(argListMultiRepo); | ||||
|         hrnCfgArgRawZ(argListMultiRepoJson, cfgOptOutput, "json"); | ||||
|  | ||||
|         StringList *argListMultiRepoProgressOnly = strLstDup(argListMultiRepo); | ||||
|         hrnCfgArgRawZ(argListMultiRepoProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|  | ||||
|         StringList *argListMultiRepoJsonProgressOnly = strLstDup(argListMultiRepoJson); | ||||
|         hrnCfgArgRawZ(argListMultiRepoJsonProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|  | ||||
|         HRN_FORK_BEGIN() | ||||
|         { | ||||
|             HRN_FORK_CHILD_BEGIN() | ||||
| @@ -1474,6 +1598,45 @@ testRun(void) | ||||
|                     // {uncrustify_on} | ||||
|                     "json - multiple stanzas, some with valid backups, archives in latest DB, backup lock held on one stanza"); | ||||
|  | ||||
|                 HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepoJsonProgressOnly); | ||||
|                 TEST_RESULT_STR_Z( | ||||
|                     infoRender(), | ||||
|                     // {uncrustify_off - indentation} | ||||
|                     "[" | ||||
|                         "{" | ||||
|                             "\"name\":\"stanza1\"," | ||||
|                             "\"status\":{" | ||||
|                                 "\"code\":0," | ||||
|                                 "\"lock\":{" | ||||
|                                     "\"backup\":{\"held\":false}" | ||||
|                                 "}," | ||||
|                                 "\"message\":\"ok\"" | ||||
|                             "}" | ||||
|                         "}," | ||||
|                         "{" | ||||
|                             "\"name\":\"stanza2\"," | ||||
|                             "\"status\":{" | ||||
|                                 "\"code\":0," | ||||
|                                 "\"lock\":{" | ||||
|                                     "\"backup\":{\"held\":true,\"size\":3159000,\"size-cplt\":1435765}" | ||||
|                                 "}," | ||||
|                                 "\"message\":\"ok\"" | ||||
|                             "}" | ||||
|                         "}," | ||||
|                         "{" | ||||
|                             "\"name\":\"stanza3\"," | ||||
|                             "\"status\":{" | ||||
|                                 "\"code\":0," | ||||
|                                 "\"lock\":{" | ||||
|                                     "\"backup\":{\"held\":false}" | ||||
|                                 "}," | ||||
|                                 "\"message\":\"ok\"" | ||||
|                             "}" | ||||
|                         "}" | ||||
|                     "]", | ||||
|                     // {uncrustify_on} | ||||
|                     "json (progress only) - multiple stanzas, some with valid backups, archives in latest DB, backup lock held on one stanza"); | ||||
|  | ||||
|                 // Notify child to release lock | ||||
|                 HRN_FORK_PARENT_NOTIFY_PUT(0); | ||||
|             } | ||||
| @@ -1610,6 +1773,19 @@ testRun(void) | ||||
|                     "            repo2: backup set size: 3MB, backup size: 3KB\n", | ||||
|                     "text - multiple stanzas, multi-repo with valid backups, backup lock held on one stanza"); | ||||
|  | ||||
|                 HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepoProgressOnly); | ||||
|                 TEST_RESULT_STR_Z( | ||||
|                     infoRender(), | ||||
|                     "stanza: stanza1\n" | ||||
|                     "    status: ok (backup/expire running - 65.28% complete)\n" | ||||
|                     "\n" | ||||
|                     "stanza: stanza2\n" | ||||
|                     "    status: ok (backup/expire running - 55.55% complete)\n" | ||||
|                     "\n" | ||||
|                     "stanza: stanza3\n" | ||||
|                     "    status: ok\n", | ||||
|                     "text (progress only) - multiple stanzas, multi-repo with valid backups, backup lock held on one stanza"); | ||||
|  | ||||
|                 // Notify child to release lock | ||||
|                 HRN_FORK_PARENT_NOTIFY_PUT(0); | ||||
|             } | ||||
| @@ -3394,6 +3570,15 @@ testRun(void) | ||||
|  | ||||
|         TEST_ERROR(hrnCfgLoadP(cfgCmdInfo, argList), OptionInvalidError, "option 'set' not valid without option 'stanza'"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("set option invalid if only progress info is requested"); | ||||
|  | ||||
|         hrnCfgArgRawZ(argList, cfgOptStanza, "bogus"); | ||||
|         hrnCfgArgRawZ(argList, cfgOptDetailLevel, "progress"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList); | ||||
|  | ||||
|         TEST_ERROR(infoRender(), OptionInvalidError, "option 'set' cannot be used with option 'detail-level' = 'progress'"); | ||||
|  | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
|         TEST_TITLE("repo-level error"); | ||||
|  | ||||
| @@ -3411,6 +3596,16 @@ testRun(void) | ||||
|             "    cipher: none\n", | ||||
|             "text - invalid stanza"); | ||||
|  | ||||
|         StringList *argListProgressOnly = strLstDup(argList); | ||||
|         hrnCfgArgRawZ(argListProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|  | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: [invalid]\n" | ||||
|             "    status: error (other)\n", | ||||
|             "text (progress only) - invalid stanza"); | ||||
|  | ||||
|         hrnCfgArgRawZ(argList, cfgOptOutput, "json"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList); | ||||
|  | ||||
| @@ -3445,6 +3640,27 @@ testRun(void) | ||||
|             // {uncrustify_on} | ||||
|             "json - invalid stanza"); | ||||
|  | ||||
|         hrnCfgArgRawZ(argListProgressOnly, cfgOptOutput, "json"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|  | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             // {uncrustify_off - indentation} | ||||
|             "[" | ||||
|                 "{" | ||||
|                     "\"name\":\"[invalid]\"," | ||||
|                     "\"status\":{" | ||||
|                         "\"code\":99," | ||||
|                         "\"lock\":{" | ||||
|                             "\"backup\":{\"held\":false}" | ||||
|                         "}," | ||||
|                         "\"message\":\"other\"" | ||||
|                     "}" | ||||
|                 "}" | ||||
|             "]", | ||||
|             // {uncrustify_on} | ||||
|             "json (progress only) - invalid stanza"); | ||||
|  | ||||
|         argList = strLstNew(); | ||||
|         hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, TEST_PATH "/repo2"); | ||||
|         hrnCfgArgRawZ(argList, cfgOptStanza, "stanza1"); | ||||
| @@ -3458,6 +3674,16 @@ testRun(void) | ||||
|             "    cipher: none\n", | ||||
|             "text - stanza requested"); | ||||
|  | ||||
|         argListProgressOnly = strLstDup(argList); | ||||
|         hrnCfgArgRawZ(argListProgressOnly, cfgOptDetailLevel, "progress"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|  | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: error (other)\n", | ||||
|             "text (progress only) - stanza requested"); | ||||
|  | ||||
|         hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argList); | ||||
|  | ||||
| @@ -3471,6 +3697,15 @@ testRun(void) | ||||
|             "        repo2: error (missing stanza path)\n" | ||||
|             "    cipher: none\n", | ||||
|             "text - stanza repo structure exists"); | ||||
|  | ||||
|         hrnCfgArgKeyRawZ(argListProgressOnly, cfgOptRepoPath, 2, TEST_PATH "/repo"); | ||||
|         HRN_CFG_LOAD(cfgCmdInfo, argListProgressOnly); | ||||
|  | ||||
|         TEST_RESULT_STR_Z( | ||||
|             infoRender(), | ||||
|             "stanza: stanza1\n" | ||||
|             "    status: error (different across repos)\n", | ||||
|             "text (progress only) - stanza repo structure exists"); | ||||
|     } | ||||
|  | ||||
|     FUNCTION_HARNESS_RETURN_VOID(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user