You've already forked pgbackrest
							
							
				mirror of
				https://github.com/pgbackrest/pgbackrest.git
				synced 2025-10-30 23:37:45 +02:00 
			
		
		
		
	Add C error handler.
Basic try ... catch ... finally pattern to simplify error handling in C.
This commit is contained in:
		| @@ -28,6 +28,10 @@ | ||||
|                     <release-item> | ||||
|                         <p>Add <file>LibC.template.pm</file> to simplify LibC module generation.</p> | ||||
|                     </release-item> | ||||
|  | ||||
|                     <release-item> | ||||
|                         <p>Add C error handler.</p> | ||||
|                     </release-item> | ||||
|                 </release-refactor-list> | ||||
|             </release-core-list> | ||||
|  | ||||
|   | ||||
| @@ -156,6 +156,8 @@ use constant ERROR_PATH_EXISTS                                      => ERROR_MIN | ||||
|     push @EXPORT, qw(ERROR_PATH_EXISTS); | ||||
| use constant ERROR_FILE_EXISTS                                      => ERROR_MINIMUM + 68; | ||||
|     push @EXPORT, qw(ERROR_FILE_EXISTS); | ||||
| use constant ERROR_MEMORY                                           => ERROR_MINIMUM + 69; # Thrown by C library | ||||
|     push @EXPORT, qw(ERROR_CRYPT); | ||||
|  | ||||
| use constant ERROR_INVALID_VALUE                                    => ERROR_MAXIMUM - 2; | ||||
|     push @EXPORT, qw(ERROR_INVALID_VALUE); | ||||
|   | ||||
| @@ -25,6 +25,7 @@ C includes | ||||
|  | ||||
| These includes are from the src directory.  There is no Perl-specific code in them. | ||||
| ***********************************************************************************************************************************/ | ||||
| #include "common/error.h" | ||||
| #include "common/type.h" | ||||
| #include "config/config.h" | ||||
| #include "config/configRule.h" | ||||
|   | ||||
							
								
								
									
										269
									
								
								src/common/error.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/common/error.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,269 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Error Handler | ||||
| ***********************************************************************************************************************************/ | ||||
| #include <stdarg.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #include "error.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Maximum allowed number of nested try blocks | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_TRY_MAX                                             32 | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| States for each try | ||||
| ***********************************************************************************************************************************/ | ||||
| typedef enum {errorStateBegin, errorStateTry, errorStateCatch, errorStateFinal, errorStateEnd} ErrorState; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Track error handling | ||||
| ***********************************************************************************************************************************/ | ||||
| struct | ||||
| { | ||||
|     // Array of jump buffers | ||||
|     jmp_buf jumpList[ERROR_TRY_MAX]; | ||||
|  | ||||
|     // State of each try | ||||
|     int tryTotal; | ||||
|  | ||||
|     struct | ||||
|     { | ||||
|         ErrorState state; | ||||
|         bool uncaught; | ||||
|     } tryList[ERROR_TRY_MAX + 1]; | ||||
|  | ||||
|     // Last error | ||||
|     struct | ||||
|     { | ||||
|         const ErrorType *errorType;                                     // Error type | ||||
|         const char *fileName;                                           // Source file where the error occurred | ||||
|         int fileLine;                                                   // Source file line where the error occurred | ||||
|         const char *message;                                            // Description of the error | ||||
|     } error; | ||||
| } errorContext; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Message buffer and buffer size | ||||
|  | ||||
| The message buffer is statically allocated so there is some space to store error messages.  Not being able to allocate such a small | ||||
| amount of memory seems pretty unlikely so just keep the code simple and let the loader deal with massively constrained memory | ||||
| situations. | ||||
|  | ||||
| The temp buffer is required because the error message being passed might be the error already stored in the message buffer. | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_MESSAGE_BUFFER_SIZE                                   8192 | ||||
|  | ||||
| static char messageBuffer[ERROR_MESSAGE_BUFFER_SIZE]; | ||||
| static char messageBufferTemp[ERROR_MESSAGE_BUFFER_SIZE]; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error type | ||||
| ***********************************************************************************************************************************/ | ||||
| const ErrorType * | ||||
| errorType() | ||||
| { | ||||
|     return errorContext.error.errorType; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error code (pulled from error type) | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| errorCode() | ||||
| { | ||||
|     return errorTypeCode(errorType()); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error filename | ||||
| ***********************************************************************************************************************************/ | ||||
| const char * | ||||
| errorFileName() | ||||
| { | ||||
|     return errorContext.error.fileName; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error file line number | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| errorFileLine() | ||||
| { | ||||
|     return errorContext.error.fileLine; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error message | ||||
| ***********************************************************************************************************************************/ | ||||
| const char * | ||||
| errorMessage() | ||||
| { | ||||
|     return errorContext.error.message; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error name (pulled from error type) | ||||
| ***********************************************************************************************************************************/ | ||||
| const char * | ||||
| errorName() | ||||
| { | ||||
|     return errorTypeName(errorType()); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Is this error an instance of the error type? | ||||
| ***********************************************************************************************************************************/ | ||||
| bool | ||||
| errorInstanceOf(const ErrorType *errorTypeTest) | ||||
| { | ||||
|     return errorType() == errorTypeTest || errorTypeExtends(errorType(), errorTypeTest); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Return current error context state | ||||
| ***********************************************************************************************************************************/ | ||||
| static ErrorState | ||||
| errorInternalState() | ||||
| { | ||||
|     return errorContext.tryList[errorContext.tryTotal].state; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| True when in try state | ||||
| ***********************************************************************************************************************************/ | ||||
| bool | ||||
| errorInternalStateTry() | ||||
| { | ||||
|     return errorInternalState() == errorStateTry; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| True when in catch state and the expected error matches | ||||
| ***********************************************************************************************************************************/ | ||||
| bool | ||||
| errorInternalStateCatch(const ErrorType *errorTypeCatch) | ||||
| { | ||||
|     return errorInternalState() == errorStateCatch && errorInstanceOf(errorTypeCatch) && errorInternalProcess(true); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| True when in final state | ||||
| ***********************************************************************************************************************************/ | ||||
| bool | ||||
| errorInternalStateFinal() | ||||
| { | ||||
|     return errorInternalState() == errorStateFinal; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Return jump buffer for current try | ||||
| ***********************************************************************************************************************************/ | ||||
| jmp_buf * | ||||
| errorInternalJump() | ||||
| { | ||||
|     return &errorContext.jumpList[errorContext.tryTotal - 1]; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Begin the try block | ||||
| ***********************************************************************************************************************************/ | ||||
| bool errorInternalTry(const char *fileName, int fileLine) | ||||
| { | ||||
|     // If try total has been exceeded then throw an error | ||||
|     if (errorContext.tryTotal >= ERROR_TRY_MAX) | ||||
|         errorInternalThrow(&AssertError, fileName, fileLine, "too many nested try blocks"); | ||||
|  | ||||
|     // Increment try total | ||||
|     errorContext.tryTotal++; | ||||
|  | ||||
|     // Setup try | ||||
|     errorContext.tryList[errorContext.tryTotal].state = errorStateBegin; | ||||
|     errorContext.tryList[errorContext.tryTotal].uncaught = false; | ||||
|  | ||||
|     // Try setup was successful | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Propogate the error up so it can be caught | ||||
| ***********************************************************************************************************************************/ | ||||
| void errorInternalPropagate() | ||||
| { | ||||
|     // Mark the error as uncaught | ||||
|     errorContext.tryList[errorContext.tryTotal].uncaught = true; | ||||
|  | ||||
|     // If there is a parent try then jump to it | ||||
|     if (errorContext.tryTotal > 0) | ||||
|         longjmp(errorContext.jumpList[errorContext.tryTotal - 1], 1); | ||||
|  | ||||
|     // If there was no try to catch this error then output to stderr | ||||
|     if (fprintf(                                                    // {uncovered - output to stderr is a problem for test harness} | ||||
|             stderr, "\nUncaught %s: %s\n    thrown at %s:%d\n\n", | ||||
|             errorName(), errorMessage(), errorFileName(), errorFileLine()) > 0) | ||||
|         fflush(stderr);                                                                                             // {+uncovered} | ||||
|  | ||||
|     // Exit with failure | ||||
|     exit(EXIT_FAILURE);                                             // {uncovered - exit failure is a problem for test harness} | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Process the error through each try and state | ||||
| ***********************************************************************************************************************************/ | ||||
| bool errorInternalProcess(bool catch) | ||||
| { | ||||
|     // If a catch statement then return | ||||
|     if (catch) | ||||
|     { | ||||
|         errorContext.tryList[errorContext.tryTotal].uncaught = false; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // Increment the state | ||||
|     errorContext.tryList[errorContext.tryTotal].state++; | ||||
|  | ||||
|     // If the error has been caught then increment the state | ||||
|     if (errorContext.tryList[errorContext.tryTotal].state == errorStateCatch && | ||||
|         !errorContext.tryList[errorContext.tryTotal].uncaught) | ||||
|     { | ||||
|         errorContext.tryList[errorContext.tryTotal].state++; | ||||
|     } | ||||
|  | ||||
|     // Return if not done | ||||
|     if (errorContext.tryList[errorContext.tryTotal].state < errorStateEnd) | ||||
|         return true; | ||||
|  | ||||
|     // Remove the try | ||||
|     errorContext.tryTotal--; | ||||
|  | ||||
|     // If not caught in the last try then propogate | ||||
|     if (errorContext.tryList[errorContext.tryTotal + 1].uncaught) | ||||
|         errorInternalPropagate(); | ||||
|  | ||||
|     // Nothing left to process | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Throw an error | ||||
| ***********************************************************************************************************************************/ | ||||
| void errorInternalThrow(const ErrorType *errorType, const char *fileName, int fileLine, const char *format, ...) | ||||
| { | ||||
|     // Setup error data | ||||
|     errorContext.error.errorType = errorType; | ||||
|     errorContext.error.fileName = fileName; | ||||
|     errorContext.error.fileLine = fileLine; | ||||
|  | ||||
|     // Create message | ||||
|     va_list argument; | ||||
|     va_start(argument, format); | ||||
|     vsnprintf(messageBufferTemp, ERROR_MESSAGE_BUFFER_SIZE - 1, format, argument); | ||||
|  | ||||
|     // Assign message to the error | ||||
|     strcpy(messageBuffer, messageBufferTemp); | ||||
|     errorContext.error.message = (const char *)messageBuffer; | ||||
|  | ||||
|     // Propogate the error | ||||
|     errorInternalPropagate(); | ||||
| }                                                                   // {uncoverable - errorPropagate() does not return} | ||||
							
								
								
									
										106
									
								
								src/common/error.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/common/error.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Error Handler | ||||
|  | ||||
| Implement a try ... catch ... finally error handler. | ||||
|  | ||||
| ERROR_TRY | ||||
| { | ||||
|     <Do something that might throw an error> | ||||
| } | ||||
| ERROR_CATCH(Error1) | ||||
| { | ||||
|     <Handle Error1> | ||||
| } | ||||
| ERROR_CATCH(Error2) | ||||
| { | ||||
|     <Handle Error2> | ||||
| } | ||||
| ERROR_CATCH_ANY() | ||||
| { | ||||
|     <Handle errors that are not Error1 or Error2> | ||||
| } | ||||
| FINALLY | ||||
| { | ||||
|     <Cleanup code that runs in all cases> | ||||
| } | ||||
|  | ||||
| The ERROR_CATCH() and ERROR_FINALLY() blocks are optional but at least one must be specified.  There is no need for an ERROR_TRY | ||||
| block by itself because errors will automatically be propagated to the nearest try block in the call stack. | ||||
|  | ||||
| Never call return from within any of the ERROR*() blocks. | ||||
| ***********************************************************************************************************************************/ | ||||
| #ifndef ERROR_H | ||||
| #define ERROR_H | ||||
|  | ||||
| #include <setjmp.h> | ||||
|  | ||||
| #include "common/errorType.h" | ||||
| #include "common/type.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error accessor functions | ||||
| ***********************************************************************************************************************************/ | ||||
| const ErrorType *errorType(); | ||||
| int errorCode(); | ||||
| const char *errorFileName(); | ||||
| int errorFileLine(); | ||||
| const char *errorMessage(); | ||||
| const char *errorName(); | ||||
| bool errorInstanceOf(const ErrorType *errorTypeTest); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Begin a block where errors can be thrown | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_TRY()                                                                                                                \ | ||||
|     if (errorInternalTry(__FILE__, __LINE__) && setjmp(*errorInternalJump()) >= 0)                                                 \ | ||||
|         while (errorInternalProcess(false))                                                                                        \ | ||||
|             if (errorInternalStateTry()) | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Catch a specific error thrown in the try block | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_CATCH(errorTypeCatch)                                                                                                \ | ||||
|     else if (errorInternalStateCatch(&errorTypeCatch)) | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Catch any error thrown in the try block | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_CATCH_ANY()                                                                                                          \ | ||||
|     else if (errorInternalStateCatch(&RuntimeError)) | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Code to run whether the try block was successful or not | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_FINALLY()                                                                                                            \ | ||||
|     else if (errorInternalStateFinal()) | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Throw an error | ||||
|  | ||||
| Errors can be thrown any time, but if there is no ERROR_TRY block somewhere in the call stack then the program will exit and print | ||||
| the error information to stderr. | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_THROW(errorType, ...)                                                                                                \ | ||||
|     errorInternalThrow(&errorType, __FILE__, __LINE__, __VA_ARGS__) | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Rethrow the current error | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_RETHROW()                                                                                                            \ | ||||
|     errorInternalPropagate() | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Internal functions | ||||
|  | ||||
| These functions are used by the macros to implement the error handler and should never be called independently. | ||||
| ***********************************************************************************************************************************/ | ||||
| bool errorInternalTry(const char *fileName, int fileLine); | ||||
| jmp_buf *errorInternalJump(); | ||||
| bool errorInternalStateTry(); | ||||
| bool errorInternalStateCatch(const ErrorType *errorTypeCatch); | ||||
| bool errorInternalStateFinal(); | ||||
| bool errorInternalProcess(bool catch); | ||||
| void errorInternalPropagate(); | ||||
| void errorInternalThrow(const ErrorType *errorType, const char *fileName, int fileLine, const char *format, ...); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										78
									
								
								src/common/errorType.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/common/errorType.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Application-Defined Errors | ||||
| ***********************************************************************************************************************************/ | ||||
| #include "errorType.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error code range -- chosen to not overlap with defined return values | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_CODE_MIN                                              25 | ||||
| #define ERROR_CODE_MAX                                              125 | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Represents an error type | ||||
| ***********************************************************************************************************************************/ | ||||
| struct ErrorType | ||||
| { | ||||
|     const int code; | ||||
|     const char *name; | ||||
|     const struct ErrorType *parentType; | ||||
| }; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Macro to create error structs | ||||
| ***********************************************************************************************************************************/ | ||||
| #define ERROR_DEFINE(code, name, parentType)                                                                                       \ | ||||
|     const ErrorType name = {code, #name, &parentType} | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Define errors | ||||
| ***********************************************************************************************************************************/ | ||||
| ERROR_DEFINE(ERROR_CODE_MIN, AssertError, RuntimeError); | ||||
|  | ||||
| ERROR_DEFINE(ERROR_CODE_MIN + 69, MemoryError, RuntimeError); | ||||
|  | ||||
| ERROR_DEFINE(ERROR_CODE_MAX, RuntimeError, RuntimeError); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error type code | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| errorTypeCode(const ErrorType *errorType) | ||||
| { | ||||
|     return errorType->code; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error type name | ||||
| ***********************************************************************************************************************************/ | ||||
| const char * | ||||
| errorTypeName(const ErrorType *errorType) | ||||
| { | ||||
|     return errorType->name; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Error type parent | ||||
| ***********************************************************************************************************************************/ | ||||
| const ErrorType * | ||||
| errorTypeParent(const ErrorType *errorType) | ||||
| { | ||||
|     return errorType->parentType; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Does the child error type extend the parent error type? | ||||
| ***********************************************************************************************************************************/ | ||||
| bool errorTypeExtends(const ErrorType *child, const ErrorType *parent) | ||||
| { | ||||
|     // Search for the parent | ||||
|     for (; child && errorTypeParent(child) != child; child = (ErrorType *)errorTypeParent(child)) | ||||
|     { | ||||
|         if (errorTypeParent(child) == parent) | ||||
|             return true; | ||||
|     } | ||||
|  | ||||
|     // Parent was not found | ||||
|     return false; | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/common/errorType.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/common/errorType.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Application-Defined Errors | ||||
| ***********************************************************************************************************************************/ | ||||
| #ifndef ERROR_TYPE_H | ||||
| #define ERROR_TYPE_H | ||||
|  | ||||
| #include "common/type.h" | ||||
|  | ||||
| // Represents an error type | ||||
| typedef struct ErrorType ErrorType; | ||||
|  | ||||
| // Macros for declaring and defining new error types | ||||
| #define ERROR_DECLARE(name)                                                                                                        \ | ||||
|     extern const ErrorType name | ||||
|  | ||||
| // Error types | ||||
| ERROR_DECLARE(AssertError); | ||||
|  | ||||
| ERROR_DECLARE(MemoryError); | ||||
|  | ||||
| ERROR_DECLARE(RuntimeError); | ||||
|  | ||||
| // Functions | ||||
| int errorTypeCode(const ErrorType *errorType); | ||||
| const char *errorTypeName(const ErrorType *errorType); | ||||
| const ErrorType * errorTypeParent(const ErrorType *errorType); | ||||
| bool errorTypeExtends(const ErrorType *child, const ErrorType *parent); | ||||
|  | ||||
| #endif | ||||
| @@ -107,6 +107,17 @@ my $oTestDef = | ||||
|                         'common/type' => TESTDEF_COVERAGE_NOCODE, | ||||
|                     }, | ||||
|                 }, | ||||
|                 { | ||||
|                     &TESTDEF_NAME => 'error', | ||||
|                     &TESTDEF_TOTAL => 4, | ||||
|                     &TESTDEF_C => true, | ||||
|  | ||||
|                     &TESTDEF_COVERAGE => | ||||
|                     { | ||||
|                         'common/error' => TESTDEF_COVERAGE_FULL, | ||||
|                         'common/errorType' => TESTDEF_COVERAGE_FULL, | ||||
|                     }, | ||||
|                 }, | ||||
|                 { | ||||
|                     &TESTDEF_NAME => 'http-client', | ||||
|                     &TESTDEF_TOTAL => 2, | ||||
|   | ||||
| @@ -3,6 +3,9 @@ C Test Harness | ||||
| ***********************************************************************************************************************************/ | ||||
| #include "common/type.h" | ||||
|  | ||||
| // Bogus values | ||||
| #define BOGUS_STR                                                   "BOGUS" | ||||
|  | ||||
| // Functions | ||||
| void testAdd(int run, bool selected); | ||||
| bool testBegin(const char *name); | ||||
|   | ||||
							
								
								
									
										169
									
								
								test/src/module/common/errorTest.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								test/src/module/common/errorTest.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Test Error Handling | ||||
| ***********************************************************************************************************************************/ | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| testTryRecurse - test to blow up try stack | ||||
| ***********************************************************************************************************************************/ | ||||
| int testTryRecurseTotal = 0; | ||||
| bool testTryRecurseCatch = false; | ||||
| bool testTryRecurseFinally = false; | ||||
|  | ||||
| void testTryRecurse() | ||||
| { | ||||
|     ERROR_TRY() | ||||
|     { | ||||
|         testTryRecurseTotal++; | ||||
|         assert(errorContext.tryTotal == testTryRecurseTotal + 1); | ||||
|  | ||||
|         testTryRecurse(); | ||||
|     } | ||||
|     ERROR_CATCH(MemoryError) | ||||
|     { | ||||
|         testTryRecurseCatch = true;                                 // {uncoverable - catch should never be executed} | ||||
|     } | ||||
|     ERROR_FINALLY() | ||||
|     { | ||||
|         testTryRecurseFinally = true; | ||||
|     } | ||||
| }                                                                   // {uncoverable - function throws error, never returns} | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Test Run | ||||
| ***********************************************************************************************************************************/ | ||||
| void testRun() | ||||
| { | ||||
|     // ----------------------------------------------------------------------------------------------------------------------------- | ||||
|     if (testBegin("check that try stack is initialized correctly")) | ||||
|     { | ||||
|         assert(errorContext.tryTotal == 0); | ||||
|     } | ||||
|  | ||||
|     // ----------------------------------------------------------------------------------------------------------------------------- | ||||
|     if (testBegin("ERROR_TRY() with no errors")) | ||||
|     { | ||||
|         bool tryDone = false; | ||||
|         bool catchDone = false; | ||||
|         bool finallyDone = false; | ||||
|  | ||||
|         ERROR_TRY() | ||||
|         { | ||||
|             assert(errorContext.tryTotal == 1); | ||||
|             tryDone = true; | ||||
|         } | ||||
|         ERROR_CATCH_ANY() | ||||
|         { | ||||
|             catchDone = true;                                       // {uncoverable - catch should never be executed} | ||||
|         } | ||||
|         ERROR_FINALLY() | ||||
|         { | ||||
|             assert(errorContext.tryList[1].state == errorStateFinal); | ||||
|             finallyDone = true; | ||||
|         } | ||||
|  | ||||
|         assert(tryDone); | ||||
|         assert(!catchDone); | ||||
|         assert(finallyDone); | ||||
|         assert(errorContext.tryTotal == 0); | ||||
|     } | ||||
|  | ||||
|     // ----------------------------------------------------------------------------------------------------------------------------- | ||||
|     if (testBegin("ERROR_TRY() with multiple catches")) | ||||
|     { | ||||
|         bool tryDone = false; | ||||
|         bool catchDone = false; | ||||
|         bool finallyDone = false; | ||||
|  | ||||
|         ERROR_TRY() | ||||
|         { | ||||
|             assert(errorContext.tryTotal == 1); | ||||
|  | ||||
|             ERROR_TRY() | ||||
|             { | ||||
|                 assert(errorContext.tryTotal == 2); | ||||
|  | ||||
|                 ERROR_TRY() | ||||
|                 { | ||||
|                     assert(errorContext.tryTotal == 3); | ||||
|  | ||||
|                     ERROR_TRY() | ||||
|                     { | ||||
|                         assert(errorContext.tryTotal == 4); | ||||
|                         tryDone = true; | ||||
|  | ||||
|                         ERROR_THROW(AssertError, BOGUS_STR); | ||||
|                     } | ||||
|                 } | ||||
|                 ERROR_CATCH(AssertError) | ||||
|                 { | ||||
|                     // Finally below should run even though this error has been rethrown | ||||
|                     ERROR_RETHROW(); | ||||
|                 } | ||||
|                 ERROR_FINALLY() | ||||
|                 { | ||||
|                     finallyDone = true; | ||||
|                 } | ||||
|             } | ||||
|             ERROR_CATCH_ANY() | ||||
|             { | ||||
|                 ERROR_RETHROW(); | ||||
|             } | ||||
|         } | ||||
|         ERROR_CATCH(MemoryError) | ||||
|         { | ||||
|             assert(false);                                              // {uncoverable - catch should never be executed} | ||||
|         } | ||||
|         ERROR_CATCH(RuntimeError) | ||||
|         { | ||||
|             assert(errorContext.tryTotal == 1); | ||||
|             assert(errorContext.tryList[1].state == errorStateCatch); | ||||
|  | ||||
|             catchDone = true; | ||||
|         } | ||||
|  | ||||
|         assert(tryDone); | ||||
|         assert(catchDone); | ||||
|         assert(finallyDone); | ||||
|         assert(errorContext.tryTotal == 0); | ||||
|     } | ||||
|  | ||||
|     // ----------------------------------------------------------------------------------------------------------------------------- | ||||
|     if (testBegin("too deep recursive TRY_ERROR()")) | ||||
|     { | ||||
|         bool tryDone = false; | ||||
|         bool catchDone = false; | ||||
|         bool finallyDone = false; | ||||
|  | ||||
|         ERROR_TRY() | ||||
|         { | ||||
|             tryDone = true; | ||||
|             testTryRecurse(); | ||||
|         } | ||||
|         ERROR_CATCH(AssertError) | ||||
|         { | ||||
|             assert(errorCode() == AssertError.code); | ||||
|             assert(errorFileName() != NULL); | ||||
|             assert(errorFileLine() >= 1); | ||||
|             assert(strcmp(errorMessage(), "too many nested try blocks") == 0); | ||||
|             assert(strcmp(errorName(), AssertError.name) == 0); | ||||
|             assert(errorType() == &AssertError); | ||||
|             assert(errorTypeCode(errorType()) == AssertError.code); | ||||
|             assert(strcmp(errorTypeName(errorType()), AssertError.name) == 0); | ||||
|             catchDone = true; | ||||
|         } | ||||
|         ERROR_FINALLY() | ||||
|         { | ||||
|             finallyDone = true; | ||||
|         } | ||||
|  | ||||
|         assert(tryDone); | ||||
|         assert(catchDone); | ||||
|         assert(finallyDone); | ||||
|         assert(errorContext.tryTotal == 0); | ||||
|  | ||||
|         // This is only ERROR_TRY_MAX - 1 because one try was used up by the wrapper above the recursive function | ||||
|         assert(testTryRecurseTotal == ERROR_TRY_MAX - 1); | ||||
|         assert(!testTryRecurseCatch); | ||||
|         assert(testTryRecurseFinally); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user