You've already forked pgbackrest
							
							
				mirror of
				https://github.com/pgbackrest/pgbackrest.git
				synced 2025-10-30 23:37:45 +02:00 
			
		
		
		
	Move config parsing out of Perl tests.
cfgParseTest() is provided in the C library for parsing configs in unit tests.
This commit is contained in:
		| @@ -99,8 +99,11 @@ sub configLoad | ||||
|  | ||||
|     eval | ||||
|     { | ||||
|       $rhOption = (JSON::PP->new()->allow_nonref())->decode($strConfigJson); | ||||
|       return true; | ||||
|         # Hacky fix for backslashes that need to be escaped | ||||
|         $strConfigJson =~ s/\\/\\\\/g; | ||||
|  | ||||
|         $rhOption = (JSON::PP->new()->allow_nonref())->decode($strConfigJson); | ||||
|         return true; | ||||
|     } | ||||
|     or do | ||||
|     { | ||||
| @@ -171,28 +174,6 @@ sub configLoad | ||||
|         umask(0000); | ||||
|     } | ||||
|  | ||||
|     # Set pg-host-cmd and repo-host-cmd to defaults if they are not set.  The command depends on the currently running exe so can't | ||||
|     # be calculated correctly in the C Library -- perhaps in the future this value will be passed in or set some other way | ||||
|     # ??? THIS IS IMPLEMENTED IN C AND SHOULD BE REMOVED FROM HERE WITHOUT DUPLICATING TEST CODE | ||||
|     if (cfgOptionValid(CFGOPT_REPO_HOST_CMD) && cfgOptionTest(CFGOPT_REPO_HOST) && !cfgOptionTest(CFGOPT_REPO_HOST_CMD)) | ||||
|     { | ||||
|         cfgOptionSet(CFGOPT_REPO_HOST_CMD, backrestBin()); | ||||
|         $oOption{cfgOptionName(CFGOPT_REPO_HOST_CMD)}{source} = CFGDEF_SOURCE_DEFAULT; | ||||
|     } | ||||
|  | ||||
|     if (cfgOptionValid(CFGOPT_PG_HOST_CMD)) | ||||
|     { | ||||
|         for (my $iOptionIdx = 1; $iOptionIdx <= cfgOptionIndexTotal(CFGOPT_PG_HOST); $iOptionIdx++) | ||||
|         { | ||||
|             if (cfgOptionTest(cfgOptionIdFromIndex(CFGOPT_PG_HOST, $iOptionIdx)) && | ||||
|                 !cfgOptionTest(cfgOptionIdFromIndex(CFGOPT_PG_HOST_CMD, $iOptionIdx))) | ||||
|             { | ||||
|                 cfgOptionSet(cfgOptionIdFromIndex(CFGOPT_PG_HOST_CMD, $iOptionIdx), backrestBin()); | ||||
|                 $oOption{cfgOptionIdFromIndex(CFGOPT_PG_HOST_CMD, $iOptionIdx)}{source} = CFGDEF_SOURCE_DEFAULT; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     # Protocol timeout should be greater than db timeout | ||||
|     if (cfgOptionTest(CFGOPT_DB_TIMEOUT) && cfgOptionTest(CFGOPT_PROTOCOL_TIMEOUT) && | ||||
|         cfgOption(CFGOPT_PROTOCOL_TIMEOUT) <= cfgOption(CFGOPT_DB_TIMEOUT)) | ||||
|   | ||||
| @@ -45,6 +45,8 @@ These includes are from the src directory.  There is no Perl-specific code in th | ||||
| #include "common/error.h" | ||||
| #include "config/config.h" | ||||
| #include "config/define.h" | ||||
| #include "config/load.h" | ||||
| #include "perl/exec.h" | ||||
| #include "postgres/pageChecksum.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| @@ -97,5 +99,6 @@ INCLUDE: xs/cipher/block.xs | ||||
| INCLUDE: xs/cipher/random.xs | ||||
| INCLUDE: xs/common/encode.xs | ||||
| INCLUDE: xs/config/config.xs | ||||
| INCLUDE: xs/config/configTest.xs | ||||
| INCLUDE: xs/config/define.xs | ||||
| INCLUDE: xs/postgres/pageChecksum.xs | ||||
|   | ||||
| @@ -81,7 +81,11 @@ my @stryCFile = | ||||
|     'common/encode/base64.c', | ||||
|     'common/error.c', | ||||
|     'common/errorType.c', | ||||
|     'common/ini.c', | ||||
|     'common/log.c', | ||||
|     'common/memContext.c', | ||||
|     'common/regExp.c', | ||||
|     'common/time.c', | ||||
|     'common/type/buffer.c', | ||||
|     'common/type/keyValue.c', | ||||
|     'common/type/list.c', | ||||
| @@ -91,7 +95,12 @@ my @stryCFile = | ||||
|     'common/type/variantList.c', | ||||
|     'config/config.c', | ||||
|     'config/define.c', | ||||
|     'config/load.c', | ||||
|     'config/parse.c', | ||||
|     'perl/exec.c', | ||||
|     'postgres/pageChecksum.c', | ||||
|     'storage/helper.c', | ||||
|     'storage/storage.c', | ||||
| ); | ||||
|  | ||||
| # Add ../src for files that are outside libc | ||||
|   | ||||
| @@ -74,23 +74,8 @@ my $rhExport = | ||||
|     { | ||||
|         &BLD_EXPORTTYPE_SUB => [qw( | ||||
|             cfgCommandId | ||||
|             cfgDefOptionAllowList | ||||
|             cfgDefOptionAllowListValue | ||||
|             cfgDefOptionAllowListValueTotal | ||||
|             cfgDefOptionAllowListValueValid | ||||
|             cfgDefOptionAllowRange | ||||
|             cfgDefOptionAllowRangeMax | ||||
|             cfgDefOptionAllowRangeMin | ||||
|             cfgDefOptionDefault | ||||
|             cfgDefOptionDepend | ||||
|             cfgDefOptionDependOption | ||||
|             cfgDefOptionDependValue | ||||
|             cfgDefOptionDependValueTotal | ||||
|             cfgDefOptionDependValueValid | ||||
|             cfgDefOptionNegate | ||||
|             cfgDefOptionPrefix | ||||
|             cfgDefOptionRequired | ||||
|             cfgDefOptionSection | ||||
|             cfgDefOptionSecure | ||||
|             cfgDefOptionType | ||||
|             cfgDefOptionValid | ||||
| @@ -128,6 +113,13 @@ my $rhExport = | ||||
|             randomBytes | ||||
|         )], | ||||
|     }, | ||||
|  | ||||
|     'test' => | ||||
|     { | ||||
|         &BLD_EXPORTTYPE_SUB => [qw( | ||||
|             cfgParseTest | ||||
|         )], | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| #################################################################################################################################### | ||||
|   | ||||
| @@ -214,23 +214,8 @@ sub libcAutoExportTag | ||||
|             'CFGDEF_TYPE_LIST', | ||||
|             'CFGDEF_TYPE_STRING', | ||||
|             'cfgCommandId', | ||||
|             'cfgDefOptionAllowList', | ||||
|             'cfgDefOptionAllowListValue', | ||||
|             'cfgDefOptionAllowListValueTotal', | ||||
|             'cfgDefOptionAllowListValueValid', | ||||
|             'cfgDefOptionAllowRange', | ||||
|             'cfgDefOptionAllowRangeMax', | ||||
|             'cfgDefOptionAllowRangeMin', | ||||
|             'cfgDefOptionDefault', | ||||
|             'cfgDefOptionDepend', | ||||
|             'cfgDefOptionDependOption', | ||||
|             'cfgDefOptionDependValue', | ||||
|             'cfgDefOptionDependValueTotal', | ||||
|             'cfgDefOptionDependValueValid', | ||||
|             'cfgDefOptionNegate', | ||||
|             'cfgDefOptionPrefix', | ||||
|             'cfgDefOptionRequired', | ||||
|             'cfgDefOptionSection', | ||||
|             'cfgDefOptionSecure', | ||||
|             'cfgDefOptionType', | ||||
|             'cfgDefOptionValid', | ||||
| @@ -255,6 +240,11 @@ sub libcAutoExportTag | ||||
|         [ | ||||
|             'randomBytes', | ||||
|         ], | ||||
|  | ||||
|         test => | ||||
|         [ | ||||
|             'cfgParseTest', | ||||
|         ], | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										34
									
								
								libc/xs/config/configTest.xs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								libc/xs/config/configTest.xs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| # ---------------------------------------------------------------------------------------------------------------------------------- | ||||
| # Config Test Perl Exports | ||||
| # ---------------------------------------------------------------------------------------------------------------------------------- | ||||
|  | ||||
| MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # Parse command line and return a JSON object with results | ||||
| #################################################################################################################################### | ||||
| SV * | ||||
| cfgParseTest(backrestBin, parseParam) | ||||
|     const char *backrestBin | ||||
|     const char *parseParam | ||||
| CODE: | ||||
|     RETVAL = NULL; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         // This should run in a temp context but for some reason getopt_long gets upset when if gets called again after the previous | ||||
|         // arg list being freed.  So, this is a memory leak but it is only used for testing, not production. | ||||
|         StringList *paramList = strLstNewSplitZ(strCat(strNew("pgbackrest|"), parseParam), "|"); | ||||
|         cfgLoadParam(strLstSize(paramList), strLstPtr(paramList), strNew(backrestBin)); | ||||
|  | ||||
|         String *result = perlOptionJson(); | ||||
|  | ||||
|         RETVAL = newSV(strSize(result)); | ||||
|         SvPOK_only(RETVAL); | ||||
|  | ||||
|         strcpy(SvPV_nolen(RETVAL), strPtr(result)); | ||||
|         SvCUR_set(RETVAL, strSize(result)); | ||||
|     } | ||||
|     ERROR_XS_END() | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
| @@ -32,113 +32,6 @@ CODE: | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionAllowList(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowList(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char * | ||||
| cfgDefOptionAllowListValue(commandId, optionId, valueId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
|     U32 valueId | ||||
| CODE: | ||||
|     RETVAL = NULL; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowListValue(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId), valueId); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| I32 | ||||
| cfgDefOptionAllowListValueTotal(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = 0; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowListValueTotal(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionAllowListValueValid(commandId, optionId, value); | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
|     const char *value | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowListValueValid(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId), value); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionAllowRange(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowRange(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| double | ||||
| cfgDefOptionAllowRangeMax(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = 0; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowRangeMax(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| double | ||||
| cfgDefOptionAllowRangeMin(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = 0; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionAllowRangeMin(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char * | ||||
| cfgDefOptionDefault(commandId, optionId) | ||||
|     U32 commandId | ||||
| @@ -154,98 +47,6 @@ CODE: | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionDepend(commandId, optionId); | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionDepend(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| I32 | ||||
| cfgDefOptionDependOption(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = 0; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgOptionIdFromDefId( | ||||
|             cfgDefOptionDependOption( | ||||
|                 cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)), cfgOptionIndex(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char * | ||||
| cfgDefOptionDependValue(commandId, optionId, valueId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
|     U32 valueId | ||||
| CODE: | ||||
|     RETVAL = NULL; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionDependValue(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId), valueId); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| I32 | ||||
| cfgDefOptionDependValueTotal(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = 0; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionDependValueTotal(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionDependValueValid(commandId, optionId, value) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
|     const char *value | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionDependValueValid(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId), value); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool cfgDefOptionNegate(optionId) | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         RETVAL = cfgDefOptionNegate(cfgOptionDefIdFromId(optionId)); | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char * | ||||
| cfgDefOptionPrefix(optionId) | ||||
|     U32 optionId | ||||
| @@ -260,52 +61,6 @@ CODE: | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionRequired(commandId, optionId) | ||||
|     U32 commandId | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = false; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         // Only the first indexed option is ever required | ||||
|         if (cfgOptionIndex(optionId) == 0) | ||||
|         { | ||||
|             RETVAL = cfgDefOptionRequired(cfgCommandDefIdFromId(commandId), cfgOptionDefIdFromId(optionId)); | ||||
|         } | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char * | ||||
| cfgDefOptionSection(optionId) | ||||
|     U32 optionId | ||||
| CODE: | ||||
|     RETVAL = NULL; | ||||
|  | ||||
|     ERROR_XS_BEGIN() | ||||
|     { | ||||
|         switch (cfgDefOptionSection(cfgOptionDefIdFromId(optionId))) | ||||
|         { | ||||
|             case cfgDefSectionGlobal: | ||||
|                 RETVAL = "global"; | ||||
|                 break; | ||||
|  | ||||
|             case cfgDefSectionStanza: | ||||
|                 RETVAL = "stanza"; | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 RETVAL = NULL; | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|     ERROR_XS_END(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| bool | ||||
| cfgDefOptionSecure(optionId) | ||||
|     U32 optionId | ||||
|   | ||||
| @@ -6,6 +6,7 @@ Configuration Load | ||||
| #include "common/memContext.h" | ||||
| #include "common/log.h" | ||||
| #include "config/config.h" | ||||
| #include "config/load.h" | ||||
| #include "config/parse.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| @@ -13,6 +14,12 @@ Load the configuration | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| cfgLoad(int argListSize, const char *argList[]) | ||||
| { | ||||
|     cfgLoadParam(argListSize, argList, NULL); | ||||
| } | ||||
|  | ||||
| void | ||||
| cfgLoadParam(int argListSize, const char *argList[], String *exe) | ||||
| { | ||||
|     MEM_CONTEXT_TEMP_BEGIN() | ||||
|     { | ||||
| @@ -38,6 +45,10 @@ cfgLoad(int argListSize, const char *argList[]) | ||||
|             logInit(logLevelConsole, logLevelStdErr, logTimestamp); | ||||
|         } | ||||
|  | ||||
|         // If an exe was passed in the use that | ||||
|         if (exe != NULL) | ||||
|             cfgExeSet(exe); | ||||
|  | ||||
|         // Set default for repo-host-cmd | ||||
|         if (cfgOptionValid(cfgOptRepoHost) && cfgOption(cfgOptRepoHost) != NULL && | ||||
|             cfgOptionSource(cfgOptRepoHostCmd) == cfgSourceDefault) | ||||
|   | ||||
| @@ -8,5 +8,6 @@ Configuration Load | ||||
| Functions | ||||
| ***********************************************************************************************************************************/ | ||||
| void cfgLoad(int argListSize, const char *argList[]); | ||||
| void cfgLoadParam(int argListSize, const char *argList[], String *exe); | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										220
									
								
								src/perl/exec.c
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								src/perl/exec.c
									
									
									
									
									
								
							| @@ -24,6 +24,121 @@ Constants used to build perl options | ||||
| #define PGBACKREST_MODULE                                           PGBACKREST_NAME "::Main" | ||||
| #define PGBACKREST_MAIN                                             PGBACKREST_MODULE "::main" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Build JSON output from options | ||||
| ***********************************************************************************************************************************/ | ||||
| String * | ||||
| perlOptionJson() | ||||
| { | ||||
|     String *result = strNew("{"); | ||||
|  | ||||
|     MEM_CONTEXT_TEMP_BEGIN() | ||||
|     { | ||||
|         for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++) | ||||
|         { | ||||
|             // Skip the option if it is not valid | ||||
|             if (!cfgOptionValid(optionId)) | ||||
|                 continue; | ||||
|  | ||||
|             // Output option | ||||
|             if (strSize(result) != 1) | ||||
|                 strCat(result, ","); | ||||
|  | ||||
|             strCatFmt(result, "\"%s\":{", cfgOptionName(optionId)); | ||||
|  | ||||
|             // Output source unless it is default | ||||
|             if (cfgOptionSource(optionId) != cfgSourceDefault) | ||||
|             { | ||||
|                 strCat(result, "\"source\":\""); | ||||
|  | ||||
|                 if (cfgOptionSource(optionId) == cfgSourceParam) | ||||
|                     strCat(result, "param"); | ||||
|                 else | ||||
|                     strCat(result, "config"); | ||||
|  | ||||
|                 strCat(result, "\""); | ||||
|             } | ||||
|  | ||||
|             // If option was negated | ||||
|             if (cfgOptionNegate(optionId)) | ||||
|                 strCatFmt(result, ",\"negate\":%s", strPtr(varStrForce(varNewBool(true)))); | ||||
|             // Else not negated and has a value | ||||
|             else if (cfgOption(optionId) != NULL) | ||||
|             { | ||||
|                 if (cfgOptionSource(optionId) != cfgSourceDefault) | ||||
|                     strCat(result, ","); | ||||
|  | ||||
|                 strCat(result, "\"value\":"); | ||||
|  | ||||
|                 switch (cfgDefOptionType(cfgOptionDefIdFromId(optionId))) | ||||
|                 { | ||||
|                     case cfgDefOptTypeBoolean: | ||||
|                     case cfgDefOptTypeFloat: | ||||
|                     case cfgDefOptTypeInteger: | ||||
|                     { | ||||
|                         strCat(result, strPtr(varStrForce(cfgOption(optionId)))); | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     case cfgDefOptTypeString: | ||||
|                     { | ||||
|                         strCatFmt(result, "\"%s\"", strPtr(cfgOptionStr(optionId))); | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     case cfgDefOptTypeHash: | ||||
|                     { | ||||
|                         const KeyValue *valueKv = cfgOptionKv(optionId); | ||||
|                         const VariantList *keyList = kvKeyList(valueKv); | ||||
|  | ||||
|                         strCat(result, "{"); | ||||
|  | ||||
|                         for (unsigned int listIdx = 0; listIdx < varLstSize(keyList); listIdx++) | ||||
|                         { | ||||
|                             if (listIdx != 0) | ||||
|                                 strCat(result, ","); | ||||
|  | ||||
|                             strCatFmt( | ||||
|                                 result, "\"%s\":\"%s\"", strPtr(varStr(varLstGet(keyList, listIdx))), | ||||
|                                 strPtr(varStr(kvGet(valueKv, varLstGet(keyList, listIdx))))); | ||||
|                         } | ||||
|  | ||||
|                         strCat(result, "}"); | ||||
|  | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     case cfgDefOptTypeList: | ||||
|                     { | ||||
|                         StringList *valueList = strLstNewVarLst(cfgOptionLst(optionId)); | ||||
|  | ||||
|                         strCat(result, "{"); | ||||
|  | ||||
|                         for (unsigned int listIdx = 0; listIdx < strLstSize(valueList); listIdx++) | ||||
|                         { | ||||
|                             if (listIdx != 0) | ||||
|                                 strCat(result, ","); | ||||
|  | ||||
|                             strCatFmt(result, "\"%s\":true", strPtr(strLstGet(valueList, listIdx))); | ||||
|                         } | ||||
|  | ||||
|                         strCat(result, "}"); | ||||
|  | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             strCat(result, "}"); | ||||
|         } | ||||
|  | ||||
|         strCat(result, "}"); | ||||
|     } | ||||
|     MEM_CONTEXT_TEMP_END(); | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Build list of perl options to use for exec | ||||
| ***********************************************************************************************************************************/ | ||||
| @@ -43,109 +158,6 @@ perlCommand() | ||||
|         strLstAdd(perlArgList, strNew(PERL_EXE)); | ||||
|     } | ||||
|  | ||||
|     // Construct option list to pass to main | ||||
|     String *configJson = strNew("{"); | ||||
|  | ||||
|     for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++) | ||||
|     { | ||||
|         // Skip the option if it is not valid | ||||
|         if (!cfgOptionValid(optionId)) | ||||
|             continue; | ||||
|  | ||||
|         // Output option | ||||
|         if (strSize(configJson) != 1) | ||||
|             strCat(configJson, ","); | ||||
|  | ||||
|         strCatFmt(configJson, "\"%s\":{", cfgOptionName(optionId)); | ||||
|  | ||||
|         // Output source unless it is default | ||||
|         if (cfgOptionSource(optionId) != cfgSourceDefault) | ||||
|         { | ||||
|             strCat(configJson, "\"source\":\""); | ||||
|  | ||||
|             if (cfgOptionSource(optionId) == cfgSourceParam) | ||||
|                 strCat(configJson, "param"); | ||||
|             else | ||||
|                 strCat(configJson, "config"); | ||||
|  | ||||
|             strCat(configJson, "\""); | ||||
|         } | ||||
|  | ||||
|         // If option was negated | ||||
|         if (cfgOptionNegate(optionId)) | ||||
|             strCatFmt(configJson, ",\"negate\":%s", strPtr(varStrForce(varNewBool(true)))); | ||||
|         // Else not negated and has a value | ||||
|         else if (cfgOption(optionId) != NULL) | ||||
|         { | ||||
|             if (cfgOptionSource(optionId) != cfgSourceDefault) | ||||
|                 strCat(configJson, ","); | ||||
|  | ||||
|             strCat(configJson, "\"value\":"); | ||||
|  | ||||
|             switch (cfgDefOptionType(cfgOptionDefIdFromId(optionId))) | ||||
|             { | ||||
|                 case cfgDefOptTypeBoolean: | ||||
|                 case cfgDefOptTypeFloat: | ||||
|                 case cfgDefOptTypeInteger: | ||||
|                 { | ||||
|                     strCat(configJson, strPtr(varStrForce(cfgOption(optionId)))); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 case cfgDefOptTypeString: | ||||
|                 { | ||||
|                     strCatFmt(configJson, "\"%s\"", strPtr(cfgOptionStr(optionId))); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 case cfgDefOptTypeHash: | ||||
|                 { | ||||
|                     const KeyValue *valueKv = cfgOptionKv(optionId); | ||||
|                     const VariantList *keyList = kvKeyList(valueKv); | ||||
|  | ||||
|                     strCat(configJson, "{"); | ||||
|  | ||||
|                     for (unsigned int listIdx = 0; listIdx < varLstSize(keyList); listIdx++) | ||||
|                     { | ||||
|                         if (listIdx != 0) | ||||
|                             strCat(configJson, ","); | ||||
|  | ||||
|                         strCatFmt( | ||||
|                             configJson, "\"%s\":\"%s\"", strPtr(varStr(varLstGet(keyList, listIdx))), | ||||
|                             strPtr(varStr(kvGet(valueKv, varLstGet(keyList, listIdx))))); | ||||
|                     } | ||||
|  | ||||
|                     strCat(configJson, "}"); | ||||
|  | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 case cfgDefOptTypeList: | ||||
|                 { | ||||
|                     StringList *valueList = strLstNewVarLst(cfgOptionLst(optionId)); | ||||
|  | ||||
|                     strCat(configJson, "{"); | ||||
|  | ||||
|                     for (unsigned int listIdx = 0; listIdx < strLstSize(valueList); listIdx++) | ||||
|                     { | ||||
|                         if (listIdx != 0) | ||||
|                             strCat(configJson, ","); | ||||
|  | ||||
|                         strCatFmt(configJson, "\"%s\":true", strPtr(strLstGet(valueList, listIdx))); | ||||
|                     } | ||||
|  | ||||
|                     strCat(configJson, "}"); | ||||
|  | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         strCat(configJson, "}"); | ||||
|     } | ||||
|  | ||||
|     strCat(configJson, "}"); | ||||
|  | ||||
|     // Add command arguments to pass to main | ||||
|     String *commandParam = strNew(""); | ||||
|  | ||||
| @@ -161,7 +173,7 @@ perlCommand() | ||||
|  | ||||
|     // Construct Perl main call | ||||
|     String *mainCall = strNewFmt( | ||||
|         PGBACKREST_MAIN "('%s','%s','%s'%s)", strPtr(cfgExe()), cfgCommandName(cfgCommand()), strPtr(configJson), | ||||
|         PGBACKREST_MAIN "('%s','%s','%s'%s)", strPtr(cfgExe()), cfgCommandName(cfgCommand()), strPtr(perlOptionJson()), | ||||
|         strPtr(commandParam)); | ||||
|  | ||||
|     // End arg list for perl exec | ||||
|   | ||||
| @@ -9,6 +9,7 @@ Execute Perl for Legacy Functionality | ||||
| /*********************************************************************************************************************************** | ||||
| Functions | ||||
| ***********************************************************************************************************************************/ | ||||
| String *perlOptionJson(); | ||||
| StringList *perlCommand(); | ||||
| void perlExec(StringList *perlArgList); | ||||
|  | ||||
|   | ||||
| @@ -211,9 +211,8 @@ sub run | ||||
|             $strCommand = | ||||
|                 ($self->{oTest}->{&TEST_CONTAINER} ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') . | ||||
|                 testRunExe( | ||||
|                     vmCoverage($self->{oTest}->{&TEST_VM}), undef, abs_path($0), | ||||
|                     dirname($self->{strCoveragePath}), $self->{strBackRestBase}, $self->{oTest}->{&TEST_MODULE}, | ||||
|                     $self->{oTest}->{&TEST_NAME}, defined($self->{oTest}->{&TEST_RUN}) ? $self->{oTest}->{&TEST_RUN} : 'all') . | ||||
|                     vmCoverage($self->{oTest}->{&TEST_VM}), undef, abs_path($0), dirname($self->{strCoveragePath}), | ||||
|                     $self->{strBackRestBase}, $self->{oTest}->{&TEST_MODULE}, $self->{oTest}->{&TEST_NAME}) . | ||||
|                 " --test-path=${strVmTestPath}" . | ||||
|                 " --vm=$self->{oTest}->{&TEST_VM}" . | ||||
|                 " --vm-id=$self->{iVmIdx}" . | ||||
|   | ||||
| @@ -157,6 +157,13 @@ sub process | ||||
|     # Initialize test storage | ||||
|     $oStorage = new pgBackRest::Storage::Local($self->testPath(), new pgBackRest::Storage::Posix::Driver()); | ||||
|  | ||||
|     # Generate backrest exe | ||||
|     $self->{strBackRestExe} = testRunExe( | ||||
|         $self->coverage(), $self->{strBackRestExeC}, $self->{strBackRestExeHelper}, dirname($self->testPath()), $self->basePath(), | ||||
|         $self->module(), $self->moduleTest(), true); | ||||
|  | ||||
|     backrestBinSet($self->{strBackRestExe}); | ||||
|  | ||||
|     # Init, run, and end the test(s) | ||||
|     $self->initModule(); | ||||
|     $self->run(); | ||||
| @@ -236,13 +243,6 @@ sub begin | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     # Generate backrest exe | ||||
|     $self->{strBackRestExe} = testRunExe( | ||||
|         $self->coverage(), $self->{strBackRestExeC}, $self->{strBackRestExeHelper}, dirname($self->testPath()), $self->basePath(), | ||||
|         $self->module(), $self->moduleTest(), $self->runCurrent(), true); | ||||
|  | ||||
|     backrestBinSet($self->{strBackRestExe}); | ||||
|  | ||||
|     # Create an ExpectTest object | ||||
|     if ($self->doExpect()) | ||||
|     { | ||||
| @@ -532,7 +532,7 @@ sub testRunGet | ||||
| push @EXPORT, qw(testRunGet); | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # testExe | ||||
| # Generate test executable | ||||
| #################################################################################################################################### | ||||
| sub testRunExe | ||||
| { | ||||
| @@ -543,7 +543,6 @@ sub testRunExe | ||||
|     my $strBackRestBasePath = shift; | ||||
|     my $strModule = shift; | ||||
|     my $strTest = shift; | ||||
|     my $iRun = shift; | ||||
|     my $bLog = shift; | ||||
|  | ||||
|     my $strExe = defined($strExeC) ? $strExeC : undef; | ||||
|   | ||||
| @@ -17,6 +17,7 @@ use Getopt::Long qw(GetOptions); | ||||
| use pgBackRest::Common::Exception; | ||||
| use pgBackRest::Common::Log; | ||||
| use pgBackRest::Config::Config; | ||||
| use pgBackRest::LibC qw(:test); | ||||
| use pgBackRest::Version; | ||||
|  | ||||
| use constant CONFIGENVTEST                                          => 'ConfigEnvTest'; | ||||
| @@ -103,333 +104,6 @@ sub commandTestWrite | ||||
|     return @szyParam; | ||||
| } | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # testOptionValidate | ||||
| # | ||||
| # Make sure the command-line options are valid based on the command. | ||||
| #################################################################################################################################### | ||||
| sub testOptionValidate | ||||
| { | ||||
|     my $iCommandId = shift; | ||||
|  | ||||
|     # Build hash with all valid command-line options | ||||
|     my @stryOptionAllow; | ||||
|  | ||||
|     for (my $iOptionId = 0; $iOptionId < cfgOptionTotal(); $iOptionId++) | ||||
|     { | ||||
|         my $strOption = cfgOptionName($iOptionId); | ||||
|  | ||||
|         if (cfgDefOptionType($iOptionId) == CFGDEF_TYPE_HASH || cfgDefOptionType($iOptionId) == CFGDEF_TYPE_LIST) | ||||
|         { | ||||
|             $strOption .= '=s@'; | ||||
|         } | ||||
|         elsif (cfgDefOptionType($iOptionId) != CFGDEF_TYPE_BOOLEAN) | ||||
|         { | ||||
|             $strOption .= '=s'; | ||||
|         } | ||||
|  | ||||
|         push(@stryOptionAllow, $strOption); | ||||
|  | ||||
|         # Check if the option can be negated | ||||
|         if (cfgDefOptionNegate($iOptionId)) | ||||
|         { | ||||
|             push(@stryOptionAllow, 'no-' . cfgOptionName($iOptionId)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     # Get command-line options | ||||
|     my $oOptionTest = {}; | ||||
|  | ||||
|     # Parse command line options | ||||
|     if (!GetOptions($oOptionTest, @stryOptionAllow)) | ||||
|     { | ||||
|         confess &log(ASSERT, "error parsing command line"); | ||||
|     } | ||||
|  | ||||
|     # Store results of validation | ||||
|     my $rhOption = {}; | ||||
|  | ||||
|     # Keep track of unresolved dependencies | ||||
|     my $bDependUnresolved = true; | ||||
|     my %oOptionResolved; | ||||
|  | ||||
|     # Loop through all possible options | ||||
|     while ($bDependUnresolved) | ||||
|     { | ||||
|         # Assume that all dependencies will be resolved in this loop | ||||
|         $bDependUnresolved = false; | ||||
|  | ||||
|         for (my $iOptionId = 0; $iOptionId < cfgOptionTotal(); $iOptionId++) | ||||
|         { | ||||
|             my $strOption = cfgOptionName($iOptionId); | ||||
|  | ||||
|             # Skip the option if it has been resolved in a prior loop | ||||
|             if (defined($oOptionResolved{$strOption})) | ||||
|             { | ||||
|                 next; | ||||
|             } | ||||
|  | ||||
|             # Determine if an option is valid for a command | ||||
|             $rhOption->{$strOption}{valid} = cfgDefOptionValid($iCommandId, $iOptionId); | ||||
|  | ||||
|             if (!$rhOption->{$strOption}{valid}) | ||||
|             { | ||||
|                 $oOptionResolved{$strOption} = true; | ||||
|                 next; | ||||
|             } | ||||
|  | ||||
|             # Store the option value | ||||
|             my $strValue = $oOptionTest->{$strOption}; | ||||
|  | ||||
|             # Check to see if an option can be negated.  Make sure that it is not set and negated at the same time. | ||||
|             $rhOption->{$strOption}{negate} = false; | ||||
|  | ||||
|             if (cfgDefOptionNegate($iOptionId)) | ||||
|             { | ||||
|                 $rhOption->{$strOption}{negate} = defined($$oOptionTest{'no-' . $strOption}); | ||||
|  | ||||
|                 if ($rhOption->{$strOption}{negate} && defined($strValue)) | ||||
|                 { | ||||
|                     confess &log(ERROR, "option '${strOption}' cannot be both set and negated", ERROR_OPTION_NEGATE); | ||||
|                 } | ||||
|  | ||||
|                 if ($rhOption->{$strOption}{negate} && cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_BOOLEAN) | ||||
|                 { | ||||
|                     $strValue = false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             # Check dependency for the command then for the option | ||||
|             my $bDependResolved = true; | ||||
|             my $strDependOption; | ||||
|             my $strDependValue; | ||||
|             my $strDependType; | ||||
|  | ||||
|             if (cfgDefOptionDepend($iCommandId, $iOptionId)) | ||||
|             { | ||||
|                 # Check if the depend option has a value | ||||
|                 my $iDependOptionId = cfgDefOptionDependOption($iCommandId, $iOptionId); | ||||
|                 $strDependOption = cfgOptionName($iDependOptionId); | ||||
|                 $strDependValue = $rhOption->{$strDependOption}{value}; | ||||
|  | ||||
|                 # Make sure the depend option has been resolved, otherwise skip this option for now | ||||
|                 if (!defined($oOptionResolved{$strDependOption})) | ||||
|                 { | ||||
|                     $bDependUnresolved = true; | ||||
|                     next; | ||||
|                 } | ||||
|  | ||||
|                 if (!defined($strDependValue)) | ||||
|                 { | ||||
|                     $bDependResolved = false; | ||||
|                     $strDependType = 'source'; | ||||
|                 } | ||||
|  | ||||
|                 # If a depend value exists, make sure the option value matches | ||||
|                 if ($bDependResolved && cfgDefOptionDependValueTotal($iCommandId, $iOptionId) == 1 && | ||||
|                     cfgDefOptionDependValue($iCommandId, $iOptionId, 0) ne $strDependValue) | ||||
|                 { | ||||
|                     $bDependResolved = false; | ||||
|                     $strDependType = 'value'; | ||||
|                 } | ||||
|  | ||||
|                 # If a depend list exists, make sure the value is in the list | ||||
|                 if ($bDependResolved && cfgDefOptionDependValueTotal($iCommandId, $iOptionId) > 1 && | ||||
|                     !cfgDefOptionDependValueValid($iCommandId, $iOptionId, $strDependValue)) | ||||
|                 { | ||||
|                     $bDependResolved = false; | ||||
|                     $strDependType = 'list'; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (cfgDefOptionDepend($iCommandId, $iOptionId) && !$bDependResolved && defined($strValue)) | ||||
|             { | ||||
|                 my $strError = "option '${strOption}' not valid without option "; | ||||
|                 my $iDependOptionId = cfgOptionId($strDependOption); | ||||
|  | ||||
|                 if ($strDependType eq 'source') | ||||
|                 { | ||||
|                     confess &log(ERROR, "${strError}'${strDependOption}'", ERROR_OPTION_INVALID); | ||||
|                 } | ||||
|  | ||||
|                 # If a depend value exists, make sure the option value matches | ||||
|                 if ($strDependType eq 'value') | ||||
|                 { | ||||
|                     if (cfgDefOptionType($iDependOptionId) eq CFGDEF_TYPE_BOOLEAN) | ||||
|                     { | ||||
|                         $strError .= | ||||
|                             "'" . (cfgDefOptionDependValue($iCommandId, $iOptionId, 0) ? '' : 'no-') . "${strDependOption}'"; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         $strError .= "'${strDependOption}' = '" . cfgDefOptionDependValue($iCommandId, $iOptionId, 0) . "'"; | ||||
|                     } | ||||
|  | ||||
|                     confess &log(ERROR, $strError, ERROR_OPTION_INVALID); | ||||
|                 } | ||||
|  | ||||
|                 $strError .= "'${strDependOption}'"; | ||||
|  | ||||
|                 # If a depend list exists, make sure the value is in the list | ||||
|                 if ($strDependType eq 'list') | ||||
|                 { | ||||
|                     my @oyValue; | ||||
|  | ||||
|                     for (my $iValueId = 0; $iValueId < cfgDefOptionDependValueTotal($iCommandId, $iOptionId); $iValueId++) | ||||
|                     { | ||||
|                         push(@oyValue, "'" . cfgDefOptionDependValue($iCommandId, $iOptionId, $iValueId) . "'"); | ||||
|                     } | ||||
|  | ||||
|                     $strError .= @oyValue == 1 ? " = $oyValue[0]" : " in (" . join(", ", @oyValue) . ")"; | ||||
|                     confess &log(ERROR, $strError, ERROR_OPTION_INVALID); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             # Is the option defined? | ||||
|             if (defined($strValue)) | ||||
|             { | ||||
|                 # Check that floats and integers are valid | ||||
|                 if (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_INTEGER || | ||||
|                     cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_FLOAT) | ||||
|                 { | ||||
|                     # Test that the string is a valid float or integer by adding 1 to it.  It's pretty hokey but it works and it | ||||
|                     # beats requiring Scalar::Util::Numeric to do it properly. | ||||
|                     my $bError = false; | ||||
|  | ||||
|                     eval | ||||
|                     { | ||||
|                         my $strTest = $strValue + 1; | ||||
|                         return true; | ||||
|                     } | ||||
|                     or do | ||||
|                     { | ||||
|                         $bError = true; | ||||
|                     }; | ||||
|  | ||||
|                     # Check that integers are really integers | ||||
|                     if (!$bError && cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_INTEGER && | ||||
|                         (int($strValue) . 'S') ne ($strValue . 'S')) | ||||
|                     { | ||||
|                         $bError = true; | ||||
|                     } | ||||
|  | ||||
|                     # Error if the value did not pass tests | ||||
|                     !$bError | ||||
|                         or confess &log(ERROR, "'${strValue}' is not valid for '${strOption}' option", ERROR_OPTION_INVALID_VALUE); | ||||
|                 } | ||||
|  | ||||
|                 # Process an allow list for the command then for the option | ||||
|                 if (cfgDefOptionAllowList($iCommandId, $iOptionId) && | ||||
|                     !cfgDefOptionAllowListValueValid($iCommandId, $iOptionId, $strValue)) | ||||
|                 { | ||||
|                     confess &log(ERROR, "'${strValue}' is not valid for '${strOption}' option", ERROR_OPTION_INVALID_VALUE); | ||||
|                 } | ||||
|  | ||||
|                 # Process an allow range for the command then for the option | ||||
|                 if (cfgDefOptionAllowRange($iCommandId, $iOptionId) && | ||||
|                     ($strValue < cfgDefOptionAllowRangeMin($iCommandId, $iOptionId) || | ||||
|                      $strValue > cfgDefOptionAllowRangeMax($iCommandId, $iOptionId))) | ||||
|                 { | ||||
|                     confess &log(ERROR, "'${strValue}' is not valid for '${strOption}' option", ERROR_OPTION_INVALID_RANGE); | ||||
|                 } | ||||
|  | ||||
|                 # Set option value | ||||
|                 if (ref($strValue) eq 'ARRAY' && | ||||
|                     (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_HASH || cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_LIST)) | ||||
|                 { | ||||
|                     foreach my $strItem (@{$strValue}) | ||||
|                     { | ||||
|                         my $strKey; | ||||
|                         my $strValue; | ||||
|  | ||||
|                         # If the keys are expected to have values | ||||
|                         if (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_HASH) | ||||
|                         { | ||||
|                             # Check for = and make sure there is a least one character on each side | ||||
|                             my $iEqualPos = index($strItem, '='); | ||||
|  | ||||
|                             if ($iEqualPos < 1 || length($strItem) <= $iEqualPos + 1) | ||||
|                             { | ||||
|                                 confess &log(ERROR, "'${strItem}' not valid key/value for '${strOption}' option", | ||||
|                                                     ERROR_OPTION_INVALID_PAIR); | ||||
|                             } | ||||
|  | ||||
|                             $strKey = substr($strItem, 0, $iEqualPos); | ||||
|                             $strValue = substr($strItem, $iEqualPos + 1); | ||||
|                         } | ||||
|                         # Else no values are expected so set value to true | ||||
|                         else | ||||
|                         { | ||||
|                             $strKey = $strItem; | ||||
|                             $strValue = true; | ||||
|                         } | ||||
|  | ||||
|                         # Check that the key has not already been set | ||||
|                         if (defined($rhOption->{$strOption}{$strKey}{value})) | ||||
|                         { | ||||
|                             confess &log(ERROR, "'${$strItem}' already defined for '${strOption}' option", | ||||
|                                                 ERROR_OPTION_DUPLICATE_KEY); | ||||
|                         } | ||||
|  | ||||
|                         # Set key/value | ||||
|                         $rhOption->{$strOption}{value}{$strKey} = $strValue; | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     $rhOption->{$strOption}{value} = $strValue; | ||||
|                 } | ||||
|  | ||||
|                 # If not config sourced then it must be a param | ||||
|                 if (!defined($rhOption->{$strOption}{source})) | ||||
|                 { | ||||
|                     $rhOption->{$strOption}{source} = CFGDEF_SOURCE_PARAM; | ||||
|                 } | ||||
|             } | ||||
|             # Else try to set a default | ||||
|             elsif ($bDependResolved) | ||||
|             { | ||||
|                 # Source is default for this option | ||||
|                 $rhOption->{$strOption}{source} = CFGDEF_SOURCE_DEFAULT; | ||||
|  | ||||
|                 # Check for default in command then option | ||||
|                 my $strDefault = cfgDefOptionDefault($iCommandId, $iOptionId); | ||||
|  | ||||
|                 # If default is defined | ||||
|                 if (defined($strDefault)) | ||||
|                 { | ||||
|                     # Only set default if dependency is resolved | ||||
|                     $rhOption->{$strOption}{value} = $strDefault if !$rhOption->{$strOption}{negate}; | ||||
|                 } | ||||
|                 # Else check required | ||||
|                 elsif (cfgDefOptionRequired($iCommandId, $iOptionId)) | ||||
|                 { | ||||
|                     confess &log(ERROR, | ||||
|                         cfgCommandName($iCommandId) . " command requires option: ${strOption}" . | ||||
|                         ERROR_OPTION_REQUIRED); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $oOptionResolved{$strOption} = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     # Make sure all options specified on the command line are valid | ||||
|     foreach my $strOption (sort(keys(%{$oOptionTest}))) | ||||
|     { | ||||
|         # Strip "no-" off the option | ||||
|         $strOption = $strOption =~ /^no\-/ ? substr($strOption, 3) : $strOption; | ||||
|  | ||||
|         if (!$rhOption->{$strOption}{valid}) | ||||
|         { | ||||
|             confess &log( | ||||
|                 ERROR, "option '${strOption}' not valid for command '" . cfgCommandName($iCommandId) . "'", ERROR_OPTION_COMMAND); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return $rhOption; | ||||
| } | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # Load the configuration | ||||
| #################################################################################################################################### | ||||
| @@ -438,174 +112,11 @@ sub configTestLoad | ||||
|     my $self = shift; | ||||
|     my $iCommandId = shift; | ||||
|  | ||||
|     @ARGV = $self->commandTestWrite(cfgCommandName($iCommandId), $self->{&CONFIGENVTEST}); | ||||
|     my @stryArg = $self->commandTestWrite(cfgCommandName($iCommandId), $self->{&CONFIGENVTEST}); | ||||
|     my $strConfigJson = cfgParseTest(backrestBin(), join('|', @stryArg)); | ||||
|     $self->testResult( | ||||
|         sub {configLoad( | ||||
|             false, backrestBin(), cfgCommandName($iCommandId), | ||||
|             (JSON::PP->new()->allow_nonref())->encode(testOptionValidate($iCommandId)))}, | ||||
|         true, 'config load: ' . join(" ", @ARGV)); | ||||
| } | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # optionTestSetByName - used only by config unit tests, general option set should be done with optionTestSet | ||||
| #################################################################################################################################### | ||||
| sub optionTestSetByName | ||||
| { | ||||
|     my $self = shift; | ||||
|     my $strOption = shift; | ||||
|     my $strValue = shift; | ||||
|  | ||||
|     $self->{&CONFIGENVTEST}{option}{$strOption} = $strValue; | ||||
| } | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # configTestLoadExpect - used only by config unit tests, for general config load use configTestLoad() | ||||
| #################################################################################################################################### | ||||
| sub configTestLoadExpect | ||||
| { | ||||
|     my $self = shift; | ||||
|     my $strCommand = shift; | ||||
|     my $iExpectedError = shift; | ||||
|     my $strErrorParam1 = shift; | ||||
|     my $strErrorParam2 = shift; | ||||
|     my $strErrorParam3 = shift; | ||||
|  | ||||
|     @ARGV = $self->commandTestWrite($strCommand, $self->{&CONFIGENVTEST}); | ||||
|     $self->configTestClear(); | ||||
|     &log(INFO, "    command line: " . join(" ", @ARGV)); | ||||
|  | ||||
|     my $bErrorFound = false; | ||||
|  | ||||
|     eval | ||||
|     { | ||||
|         configLoad(false); | ||||
|         return true; | ||||
|     } | ||||
|     or do | ||||
|     { | ||||
|         my $oException = $EVAL_ERROR; | ||||
|  | ||||
|         if (!defined($iExpectedError)) | ||||
|         { | ||||
|             confess $oException; | ||||
|         } | ||||
|  | ||||
|         $bErrorFound = true; | ||||
|  | ||||
|         if (isException(\$oException)) | ||||
|         { | ||||
|             if ($oException->code() != $iExpectedError) | ||||
|             { | ||||
|                 confess "expected error ${iExpectedError} from configLoad but got [" . $oException->code() . | ||||
|                         "] '" . $oException->message() . "'"; | ||||
|             } | ||||
|  | ||||
|             my $strError; | ||||
|  | ||||
|             if ($iExpectedError == ERROR_OPTION_REQUIRED) | ||||
|             { | ||||
|                 $strError = "${strCommand} command requires option: ${strErrorParam1}" . | ||||
|                             (defined($strErrorParam2) ? "\nHINT: ${strErrorParam2}" : ''); | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_COMMAND_REQUIRED) | ||||
|             { | ||||
|                 $strError = "command must be specified"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_INVALID) | ||||
|             { | ||||
|                 $strError = "option '${strErrorParam1}' not valid without option '${strErrorParam2}'"; | ||||
|  | ||||
|                 if (defined($strErrorParam3)) | ||||
|                 { | ||||
|                     $strError .= @{$strErrorParam3} == 1 ? " = '$$strErrorParam3[0]'" : | ||||
|                                  " in ('" . join("', '",@{ $strErrorParam3}) . "')"; | ||||
|                 } | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_COMMAND) | ||||
|             { | ||||
|                 $strError = "option '${strErrorParam1}' not valid for command '${strErrorParam2}'"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_INVALID_VALUE) | ||||
|             { | ||||
|                 $strError = "'${strErrorParam1}' is not valid for '${strErrorParam2}' option" . | ||||
|                             (defined($strErrorParam3) ? "\nHINT: ${strErrorParam3}." : ''); | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_MULTIPLE_VALUE) | ||||
|             { | ||||
|                 $strError = "option '${strErrorParam1}' cannot be specified multiple times"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_INVALID_RANGE) | ||||
|             { | ||||
|                 $strError = "'${strErrorParam1}' is not valid for '${strErrorParam2}' option"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_INVALID_PAIR) | ||||
|             { | ||||
|                 $strError = "'${strErrorParam1}' not valid key/value for '${strErrorParam2}' option"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_OPTION_NEGATE) | ||||
|             { | ||||
|                 $strError = "option '${strErrorParam1}' cannot be both set and negated"; | ||||
|             } | ||||
|             elsif ($iExpectedError == ERROR_FILE_INVALID) | ||||
|             { | ||||
|                 $strError = "'${strErrorParam1}' is not a file"; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 confess | ||||
|                     "must construct message for error ${iExpectedError}, use this as an example: '" . $oException->message() . "'"; | ||||
|             } | ||||
|  | ||||
|             if ($oException->message() ne $strError) | ||||
|             { | ||||
|                 confess "expected error message \"${strError}\" from configLoad but got \"" . $oException->message() . "\""; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             confess "configLoad should throw pgBackRest::Common::Exception:\n$oException"; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     if (!$bErrorFound && defined($iExpectedError)) | ||||
|     { | ||||
|         confess "expected error ${iExpectedError} from configLoad but got success"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #################################################################################################################################### | ||||
| # configTestExpect - used only by config unit tests | ||||
| #################################################################################################################################### | ||||
| sub optionTestExpect | ||||
| { | ||||
|     my $self = shift; | ||||
|     my $iOptionId = shift; | ||||
|     my $strExpectedValue = shift; | ||||
|     my $strExpectedKey = shift; | ||||
|  | ||||
|     my $strOption = cfgOptionName($iOptionId); | ||||
|  | ||||
|     if (defined($strExpectedValue)) | ||||
|     { | ||||
|         my $strActualValue = cfgOption($iOptionId); | ||||
|  | ||||
|         if (defined($strExpectedKey)) | ||||
|         { | ||||
|             $strActualValue = $$strActualValue{$strExpectedKey}; | ||||
|         } | ||||
|  | ||||
|         if (!defined($strActualValue)) | ||||
|         { | ||||
|             confess "expected option ${strOption} to have value ${strExpectedValue} but [undef] found instead"; | ||||
|         } | ||||
|  | ||||
|         $strActualValue eq $strExpectedValue | ||||
|             or confess "expected option ${strOption} to have value ${strExpectedValue} but ${strActualValue} found instead"; | ||||
|     } | ||||
|     elsif (cfgOptionTest(cfgOptionId($strOption))) | ||||
|     { | ||||
|         confess "expected option ${strOption} to be [undef], but " . cfgOption(cfgOptionId($strOption)) . ' found instead'; | ||||
|     } | ||||
|         sub {configLoad(false, backrestBin(), cfgCommandName($iCommandId), $strConfigJson)}, | ||||
|         true, 'config load: ' . join(" ", @stryArg)); | ||||
| } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -35,6 +35,7 @@ testRun() | ||||
|  | ||||
|         TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load local config"); | ||||
|  | ||||
|         TEST_RESULT_STR(strPtr(cfgExe()), "pgbackrest", "check exe"); | ||||
|         TEST_RESULT_INT(logLevelStdOut, logLevelOff, "console logging is off"); | ||||
|         TEST_RESULT_INT(logLevelStdErr, logLevelWarn, "stderr logging is warn"); | ||||
|  | ||||
| @@ -46,8 +47,9 @@ testRun() | ||||
|         strLstAdd(argList, strNew("--repo1-path=/path/to/repo")); | ||||
|         strLstAdd(argList, strNew("stanza-create")); | ||||
|  | ||||
|         TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load local config"); | ||||
|         TEST_RESULT_VOID(cfgLoadParam(strLstSize(argList), strLstPtr(argList), strNew("pgbackrest2")), "load local config"); | ||||
|  | ||||
|         TEST_RESULT_STR(strPtr(cfgExe()), "pgbackrest2", "check exe"); | ||||
|         TEST_RESULT_INT(logLevelStdOut, logLevelWarn, "console logging is off"); | ||||
|         TEST_RESULT_INT(logLevelStdErr, logLevelOff, "stderr logging is off"); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user