├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── .gitignore ├── Makefile ├── array_length.c ├── complex_metadata.c ├── file_metadata.c ├── nested_metadata.c └── timestamp.c ├── mida.h └── test ├── .gitignore ├── Makefile ├── greatest.h └── test.c /.clang-format: -------------------------------------------------------------------------------- 1 | # LCSMULLER-FORMATTING 2 | # Requires clang-format 10 (at least) 3 | 4 | Language: Cpp 5 | # BasedOnStyle: Mozilla 6 | AccessModifierOffset: -2 7 | AlignAfterOpenBracket: Align 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlinesLeft: false 11 | AlignOperands: true 12 | AlignTrailingComments: false 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: true 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterReturnType: TopLevelDefinitions 20 | # AlwaysBreakAfterReturnType: None # enable for 'main' files 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: true 23 | BinPackArguments: true 24 | BinPackParameters: false 25 | BraceWrapping: 26 | AfterClass: true 27 | AfterControlStatement: MultiLine 28 | AfterEnum: false 29 | AfterFunction: true 30 | AfterNamespace: false 31 | AfterObjCDeclaration: false 32 | AfterStruct: false 33 | AfterUnion: false 34 | BeforeCatch: false 35 | BeforeElse: true 36 | IndentBraces: false 37 | BreakBeforeBinaryOperators: NonAssignment 38 | BreakBeforeBraces: Custom 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializersBeforeComma: true 41 | ColumnLimit: 79 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: false 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 51 | IncludeCategories: 52 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 53 | Priority: 2 54 | - Regex: '^(<|"(gtest|isl|json)/)' 55 | Priority: 3 56 | - Regex: '.*' 57 | Priority: 1 58 | IndentCaseLabels: false 59 | IndentWidth: 4 60 | IndentWrappedFunctionNames: false 61 | KeepEmptyLinesAtTheStartOfBlocks: true 62 | MacroBlockBegin: '' 63 | MacroBlockEnd: '' 64 | MaxEmptyLinesToKeep: 1 65 | NamespaceIndentation: None 66 | ObjCBlockIndentWidth: 4 67 | ObjCSpaceAfterProperty: true 68 | ObjCSpaceBeforeProtocolList: false 69 | PenaltyBreakBeforeFirstCallParameter: 19 70 | PenaltyBreakComment: 300 71 | PenaltyBreakFirstLessLess: 120 72 | PenaltyBreakString: 1000 73 | PenaltyExcessCharacter: 1000000 74 | PenaltyReturnTypeOnItsOwnLine: 200 75 | PointerAlignment: Right 76 | AlignConsecutiveMacros: true 77 | ReflowComments: true 78 | SortIncludes: false 79 | SpaceAfterCStyleCast: false 80 | SpaceBeforeAssignmentOperators: true 81 | SpaceInEmptyParentheses: false 82 | SpacesBeforeTrailingComments: 1 83 | SpacesInAngles: false 84 | SpacesInContainerLiterals: true 85 | SpacesInCStyleCastParentheses: false 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: Cpp11 89 | TabWidth: 8 90 | UseTab: Never 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | # But these 4 | !.gitignore 5 | !mida.h 6 | !Makefile 7 | !README.md 8 | !LICENSE 9 | !examples 10 | !test 11 | !.clang-format -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lucas Müller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIDA 2 | 3 | MIDA (**M**etadata **I**njection for **D**ata **A**ugmentation) is a lightweight C library that injects and manages metadata alongside C native structures. 4 | 5 | ## Table of Contents 6 | 7 | - [About](#about) 8 | - [Key Features](#key-features) 9 | - [Examples](#examples) 10 | - [Basic Metadata Injection](#basic-metadata-injection) 11 | - [Dynamic Storage](#dynamic-storage) 12 | - [Local Storage](#local-storage) 13 | - [Injecting Custom Metadata](#injecting-custom-metadata) 14 | - [Working with Strings](#working-with-strings) 15 | - [Using C99 Compound Literals](#using-c99-compound-literals-with-custom-metadata) 16 | - [Working with Nested Data Structures](#working-with-nested-data-structures) 17 | - [Memory Management](#memory-management) 18 | - [API Reference](#api-reference) 19 | - [Build](#build) 20 | - [Examples and Tests](#examples-and-tests) 21 | - [License](#license) 22 | 23 | ## About 24 | 25 | MIDA is a single-header library designed for seamless metadata injection into C data structures. This allows you to attach arbitrary metadata to your data, from simple tracking information to complex custom metadata structures. The library provides a clean API that makes metadata management transparent, letting you focus on your application logic while the metadata is handled automatically. 26 | 27 | The power of MIDA comes from its ability to inject and manage custom metadata for any data structure, giving you complete flexibility in defining what information you want to attach to your data. 28 | 29 | ## Key Features 30 | 31 | - **Flexible metadata injection** for any C data structure 32 | - **Custom metadata structure support** for defining your own fields 33 | - **Zero-copy transparent access** to both data and metadata 34 | - **Familiar API** through wrappers for standard memory functions 35 | - **Compound literal support** in C99 for creating data with metadata attached 36 | - **No dependencies** beyond standard C libraries 37 | - **Header-only** implementation for easy integration 38 | - **Zero overhead** for accessing the original data 39 | 40 | ## Examples 41 | 42 | ### Basic Metadata Injection 43 | 44 | #### Dynamic Storage 45 | 46 | ```c 47 | // Define a custom metadata structure 48 | typedef struct array_metadata { 49 | size_t length; 50 | } ArrayMD; 51 | 52 | // Allocate an array with metadata 53 | int *numbers = mida_malloc(ArrayMD, sizeof(int), 5); 54 | // Access and set the metadata via the MIDA macro 55 | MIDA(ArrayMD, numbers)->length = 5; 56 | // Initialize data 57 | for (size_t i = 0; i < MIDA(ArrayMD, numbers)->length; i++) { 58 | numbers[i] = i + 1; 59 | } 60 | 61 | printf("Array length: %zu\n", MIDA(ArrayMD, numbers)->length); // 5 62 | // Clean up 63 | mida_free(ArrayMD, numbers); 64 | ``` 65 | 66 | #### Local Storage 67 | 68 | ```c 69 | // Define a custom metadata structure 70 | typedef struct array_metadata { 71 | size_t length; 72 | } ArrayMD; 73 | 74 | // Allocate an array with metadata 75 | int data[5] = {0}; 76 | MIDA_BYTEMAP(ArrayMD, bytemap, sizeof(data)); 77 | int *numbers = mida_wrap(ArrayMD, data, bytemap); 78 | 79 | // Access and set the metadata via the MIDA macro 80 | MIDA(ArrayMD, numbers)->length = sizeof(data) / sizeof(data[0]); 81 | // Initialize data 82 | for (size_t i = 0; i < MIDA(ArrayMD, numbers)->length; i++) { 83 | numbers[i] = i + 1; 84 | } 85 | 86 | printf("Array length: %zu\n", MIDA(ArrayMD, numbers)->length); // 5 87 | ``` 88 | 89 | Or if you want to use C99 compound literals, you can skip the `MIDA_BYTEMAP` macro, and 90 | use `mida_bytemap` directly in the `mida_wrap` function: 91 | 92 | ```c 93 | ... // Previous code 94 | 95 | int data[5] = {0}; 96 | int *numbers = mida_wrap(ArrayMD, data, mida_bytemap(ArrayMD, sizeof(data))); 97 | 98 | ... // Continue with the rest of the code 99 | ``` 100 | 101 | ### Injecting Custom Metadata 102 | 103 | ```c 104 | // Define custom metadata with your own fields 105 | struct custom_metadata { 106 | int flags; 107 | char *owner; 108 | double timestamp; 109 | }; 110 | 111 | // Allocate data with custom metadata 112 | float *data = mida_malloc(struct custom_metadata, sizeof(float), 10); 113 | 114 | // Access the custom metadata 115 | MIDA(struct custom_metadata, data)->flags = 0x1; 116 | MIDA(struct custom_metadata, data)->owner = "example_user"; 117 | MIDA(struct custom_metadata, data)->timestamp = 1625000000.0; 118 | // Use the data normally 119 | for (size_t i = 0; i < 10; i++) { 120 | data[i] = (float)i * 1.5f; 121 | } 122 | 123 | // Clean up 124 | mida_free(struct custom_metadata, data); 125 | ``` 126 | 127 | ### Working with Strings 128 | 129 | MIDA provides convenient functions for handling strings with metadata: 130 | 131 | ```c 132 | // Define a metadata structure for strings 133 | typedef struct string_metadata { 134 | size_t length; 135 | } StrMD; 136 | 137 | // Create a string with metadata 138 | char *message = mida_string(StrMD, "Hello, World!"); 139 | // Set the length 140 | MIDA(StrMD, message)->length = strlen(message); 141 | // Access and use like a normal string 142 | printf("String length: %zu\n", MIDA(StrMD, message)->length); // 13 143 | printf("Message: %s\n", message); 144 | printf("First character: %c\n", message[0]); // 'H' 145 | 146 | // Create strings with custom metadata 147 | typedef struct extended_string_metadata { 148 | size_t length; 149 | char *language; 150 | time_t created; 151 | } ExtendedStrMD; 152 | 153 | char *greeting = mida_string(ExtendedStrMD, "Good morning!"); 154 | // Access the custom metadata 155 | MIDA(ExtendedStrMD, greeting)->length = strlen(greeting); 156 | MIDA(ExtendedStrMD, greeting)->language = "English"; 157 | MIDA(ExtendedStrMD, greeting)->created = time(NULL); 158 | // String functions still work normally 159 | printf("Greeting length (standard): %zu\n", strlen(greeting)); 160 | printf("Greeting length (MIDA): %zu\n", MIDA(ExtendedStrMD, greeting)->length); 161 | // Fully compatible with standard C string functions 162 | if (strcmp(greeting, "Good morning!") == 0) { 163 | printf("Strings match!\n"); 164 | } 165 | ``` 166 | 167 | ### Using C99 Compound Literals with Custom Metadata 168 | 169 | ```c 170 | // Define custom metadata structure 171 | typedef struct file_metadata { 172 | char *filename; 173 | unsigned long permissions; 174 | time_t modified; 175 | } FileMD; 176 | 177 | // Create a struct with custom metadata using compound literals 178 | struct point { 179 | double x, y, z; 180 | }; 181 | 182 | struct point *location = mida_struct( 183 | FileMD, 184 | struct point, { 185 | .x = 10.5, 186 | .y = 20.3, 187 | .z = 5.7 188 | } 189 | ); 190 | // Set custom metadata 191 | MIDA(FileMD, location)->filename = "point_data.bin"; 192 | MIDA(FileMD, location)->permissions = 0644; 193 | MIDA(FileMD, location)->modified = time(NULL); 194 | // Use the data as normal 195 | printf("Location: (%f, %f, %f)\n", location->x, location->y, location->z); 196 | ``` 197 | 198 | ### Working with Nested Data Structures 199 | 200 | ```c 201 | // Create a complex structure with metadata at multiple levels 202 | struct person { 203 | char *name; 204 | int *scores; 205 | struct person *manager; 206 | }; 207 | 208 | // Different metadata for different parts 209 | typedef struct name_meta { 210 | size_t length; 211 | const char *language; 212 | } NameMD; 213 | 214 | typedef struct scores_meta { 215 | size_t count; 216 | const char *subject; 217 | double average; 218 | } ScoresMD; 219 | 220 | typedef struct manager_meta { 221 | int department_id; 222 | } ManagerMD; 223 | 224 | // Create the structure with different metadata at each level 225 | struct person employee = { 226 | .name = mida_string(NameMD, "John Doe"), 227 | .scores = mida_array(ScoresMD, int, {85, 92, 78, 90}), 228 | .manager = mida_struct(ManagerMD, struct person, { 229 | .name = mida_string(NameMD, "Boss"), 230 | .scores = NULL, 231 | .manager = NULL 232 | }) 233 | }; 234 | 235 | // Access and set specific metadata for each component 236 | NameMD *name_info = MIDA(NameMD, employee.name); 237 | name_info->length = strlen(employee.name); 238 | name_info->language = "English"; 239 | 240 | ScoresMD *scores_info = MIDA(ScoresMD, employee.scores); 241 | scores_info->count = 4; 242 | scores_info->subject = "Computer Science"; 243 | scores_info->average = 86.25; 244 | 245 | ManagerMD *mgr_info = MIDA(ManagerMD, employee.manager); 246 | mgr_info->department_id = 42; 247 | 248 | // Use the data naturally 249 | printf("Employee: %s, Average: %.2f, Manager: %s, Dept: %d\n", 250 | employee.name, scores_info.average, employee.manager->name, mgr_info->department_id); 251 | ``` 252 | 253 | ## Memory Management 254 | 255 | MIDA uses a clever approach to store metadata alongside your data. The metadata is stored immediately before the data pointer that's returned to you, allowing for: 256 | 257 | 1. Direct access to your data with zero overhead 258 | 2. Easy retrieval of metadata when needed 259 | 3. Transparent memory management 260 | 261 | Here's a simplified view of the memory layout: 262 | 263 | ``` 264 | Memory Layout: 265 | +------------------+------------------+ 266 | | CUSTOM METADATA | ACTUAL DATA | 267 | | (your structure) | [your data here] | 268 | +------------------+------------------+ 269 | ^ 270 | | 271 | Pointer returned to user 272 | ``` 273 | 274 | ## API Reference 275 | 276 | ### Core Metadata Function 277 | 278 | | Function | Description | 279 | |----------|-------------| 280 | | `MIDA(container_type, ptr)` | Gets the container structure for a given data pointer | 281 | 282 | ### Memory Management Functions 283 | 284 | | Function | Description | 285 | |----------|-------------| 286 | | `mida_malloc(container_type, element_size, count)` | Allocates memory with metadata | 287 | | `mida_calloc(container_type, element_size, count)` | Allocates zeroed memory with metadata | 288 | | `mida_realloc(container_type, base, element_size, count)` | Resizes memory with metadata | 289 | | `mida_free(container_type, base)` | Frees memory with metadata | 290 | | `MIDA_BYTEMAP(container_type, bytemap, size)` | Defines a bytemap buffer for local storage metadata | 291 | | `mida_nwrap(container_type, data, bytemap, bytemap_size)` | Wraps data with metadata with bytemap size | 292 | | `mida_wrap(container_type, data, bytemap)` | Wraps data with metadata | 293 | 294 | ### C99 Macros (Compound Literals) 295 | 296 | | Macro | Description | 297 | |-------|-------------| 298 | | `mida_array(container_type, type, {...})` | Creates an unnamed array with metadata | 299 | | `mida_struct(container_type, type, {...})` | Creates an unnamed structure with metadata | 300 | | `mida_string(container_type, string)` | Creates a string-literal with metadata | 301 | | `mida_bytemap(container_type, size)` | Creates a unnamed bytemap for metadata | 302 | 303 | ## Build 304 | 305 | MIDA is a single-header-only library with flexible inclusion options: 306 | 307 | ```c 308 | /* In every .c file that uses MIDA include only declarations: */ 309 | #define MIDA_HEADER 310 | #include "mida.h" 311 | 312 | /* Additionally, create one mida.c file for MIDA implementation: */ 313 | #include "mida.h" 314 | ``` 315 | 316 | To make all MIDA functions static (to avoid symbol conflicts), use: 317 | 318 | ```c 319 | #define MIDA_STATIC 320 | #include "mida.h" 321 | ``` 322 | 323 | ## Examples and Tests 324 | 325 | For more examples and tests, please refer to the [examples](examples) and [tests](tests) directories in the repository. 326 | 327 | ## License 328 | 329 | [MIT License](LICENSE) - see LICENSE file for details 330 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | # But these 4 | !.gitignore 5 | !Makefile 6 | !array_length.c 7 | !complex_metadata.c 8 | !file_metadata.c 9 | !nested_metadata.c 10 | !timestamp.c 11 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | TOP = .. 2 | 3 | CC = gcc 4 | CFLAGS = -Wall -Wextra -g -I$(TOP) -O0 5 | 6 | EXES = array_length timestamp file_metadata complex_metadata nested_metadata 7 | 8 | all: $(EXES) 9 | 10 | clean: 11 | @ rm -f $(EXES) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /examples/array_length.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../mida.h" 3 | 4 | // Define a custom metadata structure for arrays 5 | typedef struct array_metadata { 6 | size_t length; 7 | } ArrayMD; 8 | 9 | int 10 | main() 11 | { 12 | // Allocate an array with length metadata 13 | int *numbers = mida_malloc(ArrayMD, sizeof(int), 5); 14 | 15 | // Set metadata 16 | ArrayMD *meta = MIDA(ArrayMD, numbers); 17 | meta->length = 5; 18 | 19 | // Initialize the array 20 | for (size_t i = 0; i < meta->length; i++) { 21 | numbers[i] = i * 10; 22 | } 23 | 24 | // Print the array with its length 25 | printf("Array length: %zu\n", meta->length); 26 | printf("Array contents: "); 27 | for (size_t i = 0; i < meta->length; i++) { 28 | printf("%d ", numbers[i]); 29 | } 30 | printf("\n"); 31 | 32 | // Free the array 33 | mida_free(ArrayMD, numbers); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /examples/complex_metadata.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../mida.h" 5 | 6 | // Define a custom metadata structure with various fields 7 | typedef struct record_metadata { 8 | size_t record_count; 9 | unsigned int version; 10 | char author[32]; 11 | time_t timestamp; 12 | int flags; 13 | } RecordMD; 14 | 15 | // Define our custom data structure 16 | typedef struct person { 17 | char name[64]; 18 | int age; 19 | float salary; 20 | } Person; 21 | 22 | // Helper to print record flags 23 | void 24 | print_flags(int flags) 25 | { 26 | printf("Flags: "); 27 | if (flags & 0x1) printf("ACTIVE "); 28 | if (flags & 0x2) printf("VERIFIED "); 29 | if (flags & 0x4) printf("ADMIN "); 30 | if (flags & 0x8) printf("PREMIUM "); 31 | printf("\n"); 32 | } 33 | 34 | int 35 | main() 36 | { 37 | // Create an array of Person structures with metadata 38 | Person *employees = mida_malloc(RecordMD, sizeof(Person), 3); 39 | 40 | // Set metadata 41 | RecordMD *meta = MIDA(RecordMD, employees); 42 | meta->record_count = 3; 43 | meta->version = 1; 44 | strcpy(meta->author, "John Doe"); 45 | meta->timestamp = time(NULL); 46 | meta->flags = 0x3; // ACTIVE | VERIFIED 47 | 48 | // Fill in the employee records 49 | strcpy(employees[0].name, "Alice Johnson"); 50 | employees[0].age = 32; 51 | employees[0].salary = 75000.0f; 52 | 53 | strcpy(employees[1].name, "Bob Smith"); 54 | employees[1].age = 45; 55 | employees[1].salary = 92000.0f; 56 | 57 | strcpy(employees[2].name, "Carol Davis"); 58 | employees[2].age = 28; 59 | employees[2].salary = 68000.0f; 60 | 61 | // Print metadata about the record set 62 | printf("Employee Records (Version %u)\n", meta->version); 63 | printf("Record Count: %zu\n", meta->record_count); 64 | printf("Author: %s\n", meta->author); 65 | 66 | char time_str[64]; 67 | struct tm *timeinfo = localtime(&meta->timestamp); 68 | strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo); 69 | printf("Timestamp: %s\n", time_str); 70 | 71 | print_flags(meta->flags); 72 | 73 | // Print the employee records 74 | printf("\nEmployee Records:\n"); 75 | printf("%-20s %-5s %-10s\n", "Name", "Age", "Salary"); 76 | printf("---------------------------------------\n"); 77 | 78 | for (size_t i = 0; i < meta->record_count; i++) { 79 | printf("%-20s %-5d $%-10.2f\n", employees[i].name, employees[i].age, 80 | employees[i].salary); 81 | } 82 | 83 | // Free the records 84 | mida_free(RecordMD, employees); 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /examples/file_metadata.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../mida.h" 5 | 6 | // Define file-like metadata 7 | typedef struct file_metadata { 8 | char filename[64]; 9 | char mime_type[32]; 10 | size_t size; 11 | time_t created; 12 | unsigned int permissions; 13 | } FileMD; 14 | 15 | int 16 | main() 17 | { 18 | // Create a buffer with file metadata 19 | char content[] = "This is some example file content.\n" 20 | "It has multiple lines of text.\n" 21 | "It could represent a document or any other file."; 22 | 23 | // Wrap the data with metadata 24 | char *file_data = 25 | mida_wrap(FileMD, content, mida_bytemap(FileMD, sizeof(content))); 26 | 27 | // Set the metadata 28 | FileMD *meta = MIDA(FileMD, file_data); 29 | strcpy(meta->filename, "example.txt"); 30 | strcpy(meta->mime_type, "text/plain"); 31 | meta->size = strlen(content); 32 | meta->created = time(NULL); 33 | meta->permissions = 0644; // rw-r--r-- 34 | 35 | // Print the file information 36 | printf("File: %s\n", meta->filename); 37 | printf("MIME Type: %s\n", meta->mime_type); 38 | printf("Size: %zu bytes\n", meta->size); 39 | 40 | char time_str[64]; 41 | struct tm *timeinfo = localtime(&meta->created); 42 | strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo); 43 | printf("Created: %s\n", time_str); 44 | 45 | printf("Permissions: %o\n", meta->permissions); 46 | 47 | printf("\nContent:\n%s\n", file_data); 48 | 49 | // No need to free, as we used a bytemap with automatic storage 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /examples/nested_metadata.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../mida.h" 5 | 6 | // Different metadata for different levels 7 | typedef struct string_metadata { 8 | size_t length; 9 | } StrMD; 10 | 11 | typedef struct array_metadata { 12 | size_t count; 13 | char description[64]; 14 | } ArrayMD; 15 | 16 | typedef struct object_metadata { 17 | char type[32]; 18 | int id; 19 | } ObjMD; 20 | 21 | // Define document structure 22 | typedef struct document { 23 | char *title; 24 | int *page_lengths; 25 | struct document *related_doc; 26 | } Document; 27 | 28 | int 29 | main() 30 | { 31 | // Create a document with different metadata for each component 32 | Document *doc = mida_malloc(ObjMD, sizeof(Document), 1); 33 | 34 | // Set metadata for document 35 | ObjMD *doc_meta = MIDA(ObjMD, doc); 36 | strcpy(doc_meta->type, "Report"); 37 | doc_meta->id = 101; 38 | 39 | // Create title with string metadata 40 | doc->title = mida_malloc(StrMD, sizeof(char), 22); 41 | strcpy(doc->title, "Annual Financial Report"); 42 | StrMD *title_meta = MIDA(StrMD, doc->title); 43 | title_meta->length = strlen(doc->title); 44 | 45 | // Create page lengths array with array metadata 46 | doc->page_lengths = mida_malloc(ArrayMD, sizeof(int), 5); 47 | doc->page_lengths[0] = 2; // Summary 48 | doc->page_lengths[1] = 10; // Introduction 49 | doc->page_lengths[2] = 25; // Financial Data 50 | doc->page_lengths[3] = 15; // Analysis 51 | doc->page_lengths[4] = 5; // Conclusion 52 | 53 | ArrayMD *pages_meta = MIDA(ArrayMD, doc->page_lengths); 54 | pages_meta->count = 5; 55 | strcpy(pages_meta->description, "Pages per section"); 56 | 57 | // Create related document with object metadata 58 | doc->related_doc = mida_malloc(ObjMD, sizeof(Document), 1); 59 | 60 | ObjMD *related_meta = MIDA(ObjMD, doc->related_doc); 61 | strcpy(related_meta->type, "Appendix"); 62 | related_meta->id = 102; 63 | 64 | // Create title for related document 65 | doc->related_doc->title = mida_malloc(StrMD, sizeof(char), 25); 66 | strcpy(doc->related_doc->title, "Financial Report Appendix"); 67 | StrMD *rel_title_meta = MIDA(StrMD, doc->related_doc->title); 68 | rel_title_meta->length = strlen(doc->related_doc->title); 69 | 70 | // No pages or related docs for the appendix 71 | doc->related_doc->page_lengths = NULL; 72 | doc->related_doc->related_doc = NULL; 73 | 74 | // Print document information using metadata 75 | printf("Document Type: %s (ID: %d)\n", doc_meta->type, doc_meta->id); 76 | printf("Title: %s (Length: %zu characters)\n", doc->title, 77 | title_meta->length); 78 | 79 | printf("\nSections (%s):\n", pages_meta->description); 80 | const char *sections[] = { "Summary", "Introduction", "Financial Data", 81 | "Analysis", "Conclusion" }; 82 | for (size_t i = 0; i < pages_meta->count; i++) { 83 | printf(" %s: %d pages\n", sections[i], doc->page_lengths[i]); 84 | } 85 | 86 | // Calculate total pages 87 | int total = 0; 88 | for (size_t i = 0; i < pages_meta->count; i++) { 89 | total += doc->page_lengths[i]; 90 | } 91 | printf("Total pages: %d\n", total); 92 | 93 | // Print related document information 94 | printf("\nRelated Document:\n"); 95 | printf("Type: %s (ID: %d)\n", related_meta->type, related_meta->id); 96 | printf("Title: %s (Length: %zu characters)\n", doc->related_doc->title, 97 | rel_title_meta->length); 98 | 99 | // Free all allocated memory 100 | mida_free(StrMD, doc->related_doc->title); 101 | mida_free(ObjMD, doc->related_doc); 102 | mida_free(ArrayMD, doc->page_lengths); 103 | mida_free(StrMD, doc->title); 104 | mida_free(ObjMD, doc); 105 | 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /examples/timestamp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../mida.h" 4 | 5 | // Define a custom metadata structure with timestamp 6 | typedef struct timestamp_metadata { 7 | time_t created; 8 | time_t modified; 9 | } TimestampMD; 10 | 11 | // Helper function to print timestamps 12 | void 13 | print_time(const char *label, time_t timestamp) 14 | { 15 | char time_str[64]; 16 | struct tm *timeinfo = localtime(×tamp); 17 | strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo); 18 | printf("%s: %s\n", label, time_str); 19 | } 20 | 21 | int 22 | main() 23 | { 24 | // Create a structure with timestamp metadata 25 | struct data { 26 | int value; 27 | char name[32]; 28 | }; 29 | 30 | // Allocate and initialize the structure 31 | struct data *record = mida_malloc(TimestampMD, sizeof(struct data), 1); 32 | record->value = 42; 33 | strcpy(record->name, "Example Data"); 34 | 35 | // Set metadata 36 | TimestampMD *meta = MIDA(TimestampMD, record); 37 | meta->created = time(NULL); 38 | meta->modified = meta->created; 39 | 40 | // Print initial information 41 | printf("Record: %s (value: %d)\n", record->name, record->value); 42 | print_time("Created", meta->created); 43 | print_time("Modified", meta->modified); 44 | 45 | // Modify the record and update the timestamp 46 | printf("\nModifying the record...\n"); 47 | record->value = 100; 48 | meta->modified = time(NULL); 49 | 50 | // Print updated information 51 | printf("Record: %s (value: %d)\n", record->name, record->value); 52 | print_time("Created", meta->created); 53 | print_time("Modified", meta->modified); 54 | 55 | // Free the record 56 | mida_free(TimestampMD, record); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /mida.h: -------------------------------------------------------------------------------- 1 | #ifndef MIDA_H 2 | #define MIDA_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif /* __cplusplus */ 7 | 8 | #include 9 | #include 10 | 11 | #if __STDC_VERSION__ && __STDC_VERSION__ >= 199901L 12 | #define MIDA_WITH_C99 13 | #endif /* __STDC_VERSION__ */ 14 | 15 | #ifdef MIDA_STATIC 16 | #define MIDA_API static 17 | #else 18 | #define MIDA_API extern 19 | #endif /* MIDA_STATIC */ 20 | 21 | /** 22 | * @typedef mida_byte 23 | * @brief Type used for raw byte operations 24 | * 25 | * Alias for char used for buffer operations to make it clearer that 26 | * we're dealing with raw bytes rather than characters. 27 | */ 28 | typedef char mida_byte; 29 | 30 | /** 31 | * @def MIDA_BYTEMAP(_container, _bytemap, _size) 32 | * @brief Defines an extended bytemap for storing metadata 33 | * 34 | * Creates a bytemap buffer with local storage, of the appropriate size to hold 35 | * both the metadata structure and the user data. 36 | * 37 | * @param _container Name of the container structure 38 | * @param _bytemap Name of the bytemap array 39 | * @param _sizeof Size of the data that will be stored with metadata 40 | */ 41 | #define MIDA_BYTEMAP(_container, _bytemap, _sizeof) \ 42 | mida_byte(_bytemap)[sizeof(_container) + (_sizeof)] 43 | 44 | MIDA_API void *__mida_malloc(const size_t container_size, 45 | const size_t element_size, 46 | const size_t count); 47 | 48 | /** 49 | * @def mida_malloc(_container, _element_size, _count) 50 | * @brief Allocates memory for an array with extended metadata 51 | * 52 | * This macro allocates memory for an array of elements with the specified 53 | * element size and count, and adds extended metadata using the provided 54 | * container type. 55 | * 56 | * @param _container Type of the container structure 57 | * @param _element_size Size of each element in bytes 58 | * @param _count Number of elements to allocate 59 | * @return Pointer to the allocated array (not the container) 60 | */ 61 | #define mida_malloc(_container, _element_size, _count) \ 62 | __mida_malloc(sizeof(_container), _element_size, _count) 63 | 64 | MIDA_API void *__mida_calloc(const size_t container_size, 65 | const size_t element_size, 66 | const size_t count); 67 | 68 | /** 69 | * @def mida_calloc(_container, _element_size, _count) 70 | * @brief Allocates and zeros memory for an array with extended metadata 71 | * 72 | * This function allocates and zeros memory for an array of elements with the 73 | * specified element size and count, and adds extended metadata using the 74 | * provided container type. 75 | * 76 | * @param _container Type of the container structure 77 | * @param _element_size Size of each element in bytes 78 | * @param _count Number of elements to allocate 79 | * @return Pointer to the allocated array (not the container) 80 | */ 81 | #define mida_calloc(_container, _element_size, _count) \ 82 | __mida_calloc(sizeof(_container), _element_size, _count) 83 | 84 | MIDA_API void *__mida_realloc(const size_t container_size, 85 | void *base, 86 | const size_t element_size, 87 | const size_t count); 88 | 89 | /** 90 | * @def mida_realloc(_container, _base, _element_size, _count) 91 | * @brief Reallocates memory for an array with extended metadata 92 | * 93 | * This macro reallocates memory for an array of elements with the specified 94 | * element size and count, preserving the extended metadata. 95 | * 96 | * @param _container Type of the container structure 97 | * @param _base Pointer to the original data (not the container) 98 | * @param _element_size Size of each element in bytes 99 | * @param _count New number of elements to allocate 100 | * @return Pointer to the reallocated array (not the container) 101 | */ 102 | #define mida_realloc(_container, _base, _element_size, _count) \ 103 | __mida_realloc(sizeof(_container), _base, _element_size, _count) 104 | 105 | /** 106 | * @def mida_free(_container, _base) 107 | * @brief Frees memory for an array with extended metadata 108 | * 109 | * This macro frees memory for an array with extended metadata. 110 | * 111 | * @param _container Type of the container structure 112 | * @param _base Pointer to the data (not the container) 113 | */ 114 | #define mida_free(_container, _base) free(MIDA(_container, _base)) 115 | 116 | MIDA_API void *__mida_nwrap(const size_t container_size, 117 | void *data, 118 | const size_t size, 119 | mida_byte *const container); 120 | 121 | /** 122 | * @def mida_nwrap(_container, _data, _bytemap, _bytemap_size) 123 | * @brief Wraps existing data with extended metadata 124 | * 125 | * This macro wraps existing data with extended metadata using a bytemap, 126 | * includes a bytemap_size parameter for fine-tuning the size of the 127 | * bytemap buffer. 128 | * 129 | * @param _container Type of the container structure 130 | * @param _data Pointer to the original data 131 | * @param _bytemap Pointer to the bytemap buffer created with MIDA_BYTEMAP 132 | * @param _bytemap_size Size of the bytemap buffer 133 | * @return Pointer to the wrapped data (not the container) 134 | */ 135 | #define mida_nwrap(_container, _data, _bytemap, _bytemap_size) \ 136 | __mida_nwrap(sizeof(_container), _data, \ 137 | _bytemap_size - sizeof(_container), _bytemap) 138 | 139 | /** 140 | * @def mida_wrap(_container, _data, _bytemap) 141 | * @brief Wraps existing data with extended metadata 142 | * 143 | * This macro wraps existing data with extended metadata using a bytemap. 144 | * 145 | * @param _container Type of the container structure 146 | * @param _data Pointer to the original data 147 | * @param _bytemap Pointer to the bytemap buffer created with MIDA_BYTEMAP 148 | * @return Pointer to the wrapped data (not the container) 149 | */ 150 | #define mida_wrap(_container, _data, _bytemap) \ 151 | mida_nwrap(_container, _data, _bytemap, sizeof(_bytemap)) 152 | 153 | #ifdef MIDA_WITH_C99 154 | 155 | /** 156 | * @def mida_bytemap(_container, _size) 157 | * @brief Creates a bytemap buffer with local storage, with the given size 158 | * 159 | * C99 only. Creates a compound literal for a bytemap of the specified size. 160 | * 161 | * @param _container The type of the container structure 162 | * @param _sizeof Size of the data that will be stored with metadata 163 | * @return A compound literal initialized to zero 164 | */ 165 | #define mida_bytemap(_container, _sizeof) \ 166 | (mida_byte[sizeof(_container) + _sizeof]) \ 167 | { \ 168 | 0 \ 169 | } 170 | 171 | /** 172 | * @def mida_array(_container, _type, ...) 173 | * @brief Creates an array with extended MIDA metadata 174 | * 175 | * C99 only. Creates an unnamed array of the specified type with values 176 | * provided as a compound literal, and wraps it with extended MIDA metadata. 177 | * 178 | * @param _container The type of the container structure 179 | * @param _type The type of array elements 180 | * @param ... Compound literal array initialization values 181 | * @return Pointer to the array with extended MIDA metadata 182 | */ 183 | #define mida_array(_container, _type, ...) \ 184 | (_type *)(__mida_nwrap( \ 185 | sizeof(_container), (_type[])__VA_ARGS__, \ 186 | sizeof((_type[])__VA_ARGS__), \ 187 | mida_bytemap(_container, sizeof((_type[])__VA_ARGS__)))) 188 | 189 | /** 190 | * @def mida_struct(_container, _type, ...) 191 | * @brief Creates an unnamed structure with extended MIDA metadata 192 | * 193 | * C99 only. Creates a structure of the specified type with values provided 194 | * as a compound literal, and wraps it with extended MIDA metadata. 195 | * 196 | * @param _container The type of the container structure 197 | * @param _type The structure type 198 | * @param ... Compound literal structure initialization values 199 | * @return Pointer to the structure with extended MIDA metadata 200 | */ 201 | #define mida_struct(_container, _type, ...) \ 202 | mida_array(_container, _type, { __VA_ARGS__ }) 203 | 204 | /** 205 | * @def mida_string(_container, _string) 206 | * @brief Creates a string literal with extended MIDA metadata 207 | * 208 | * C99 only. Creates a string with the provided string literal and wraps it 209 | * with extended MIDA metadata. 210 | * 211 | * @param _container The type of the container structure 212 | * @param _string The string literal to be wrapped 213 | * @return Pointer to the string with extended MIDA metadata 214 | * 215 | * @note The returned string can be used with standard string functions 216 | * while still maintaining access to its metadata. 217 | */ 218 | #define mida_string(_container, _string) \ 219 | (char *)(mida_wrap(_container, _string, \ 220 | mida_bytemap(_container, sizeof(_string)))) 221 | 222 | #endif /* MIDA_WITH_C99 */ 223 | 224 | #define MIDA(_container, _base) \ 225 | ((_container *)((mida_byte *)(_base) - sizeof(_container))) 226 | 227 | #ifndef MIDA_HEADER 228 | 229 | #include 230 | #include 231 | 232 | #define __mida_data_from_container(_container_ptr, _container_size) \ 233 | ((mida_byte *)_container_ptr + _container_size) 234 | #define __mida_container_from_data(_data_ptr, _container_size) \ 235 | (void *)((mida_byte *)_data_ptr - _container_size) 236 | 237 | MIDA_API void * 238 | __mida_malloc(const size_t container_size, 239 | const size_t element_size, 240 | const size_t count) 241 | { 242 | const size_t data_size = element_size * count, 243 | total_size = container_size + data_size; 244 | mida_byte *container = malloc(total_size); 245 | return !container ? NULL 246 | : __mida_data_from_container(container, container_size); 247 | } 248 | 249 | MIDA_API void * 250 | __mida_calloc(const size_t container_size, 251 | const size_t element_size, 252 | const size_t count) 253 | { 254 | const size_t data_size = element_size * count, 255 | total_size = container_size + data_size; 256 | mida_byte *container = calloc(1, total_size); 257 | return !container ? NULL 258 | : __mida_data_from_container(container, container_size); 259 | } 260 | 261 | MIDA_API void * 262 | __mida_realloc(const size_t container_size, 263 | void *base, 264 | const size_t element_size, 265 | const size_t count) 266 | { 267 | if (base) { 268 | const size_t data_size = element_size * count, 269 | total_size = container_size + data_size; 270 | mida_byte *original_container = 271 | __mida_container_from_data(base, container_size); 272 | mida_byte *container = realloc(original_container, total_size); 273 | return !container 274 | ? NULL 275 | : __mida_data_from_container(container, container_size); 276 | } 277 | return __mida_malloc(container_size, element_size, count); 278 | } 279 | 280 | MIDA_API void * 281 | __mida_nwrap(const size_t container_size, 282 | void *data, 283 | const size_t size, 284 | mida_byte *const container) 285 | { 286 | return memcpy(__mida_data_from_container(container, container_size), data, 287 | size); 288 | } 289 | 290 | #undef _mida_data_from_container 291 | #undef _mida_container_from_data 292 | 293 | #endif /* MIDA_HEADER */ 294 | 295 | #ifdef __cplusplus 296 | } 297 | #endif /* __cplusplus */ 298 | 299 | #endif /* MIDA_H */ 300 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | # But these 4 | !.gitignore 5 | !*.c 6 | !greatest.h 7 | !Makefile 8 | 9 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | TOP = .. 2 | CC = cc 3 | 4 | EXES = test 5 | 6 | CFLAGS += -Wall -Wextra -Wpedantic -g -I$(TOP) -std=c99 -O0 7 | 8 | all: $(EXES) 9 | 10 | clean: 11 | @ rm -f $(EXES) 12 | 13 | .PHONY : all clean 14 | -------------------------------------------------------------------------------- /test/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2021 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 21 | extern "C" { 22 | #endif 23 | 24 | /* 1.5.0 */ 25 | #define GREATEST_VERSION_MAJOR 1 26 | #define GREATEST_VERSION_MINOR 5 27 | #define GREATEST_VERSION_PATCH 0 28 | 29 | /* A unit testing system for C, contained in 1 file. 30 | * It doesn't use dynamic allocation or depend on anything 31 | * beyond ANSI C89. 32 | * 33 | * An up-to-date version can be found at: 34 | * https://github.com/silentbicycle/greatest/ 35 | */ 36 | 37 | 38 | /********************************************************************* 39 | * Minimal test runner template 40 | *********************************************************************/ 41 | #if 0 42 | 43 | #include "greatest.h" 44 | 45 | TEST foo_should_foo(void) { 46 | PASS(); 47 | } 48 | 49 | static void setup_cb(void *data) { 50 | printf("setup callback for each test case\n"); 51 | } 52 | 53 | static void teardown_cb(void *data) { 54 | printf("teardown callback for each test case\n"); 55 | } 56 | 57 | SUITE(suite) { 58 | /* Optional setup/teardown callbacks which will be run before/after 59 | * every test case. If using a test suite, they will be cleared when 60 | * the suite finishes. */ 61 | SET_SETUP(setup_cb, voidp_to_callback_data); 62 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 63 | 64 | RUN_TEST(foo_should_foo); 65 | } 66 | 67 | /* Add definitions that need to be in the test runner's main file. */ 68 | GREATEST_MAIN_DEFS(); 69 | 70 | /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ 71 | int run_tests(void) { 72 | GREATEST_INIT(); /* init. greatest internals */ 73 | /* List of suites to run (if any). */ 74 | RUN_SUITE(suite); 75 | 76 | /* Tests can also be run directly, without using test suites. */ 77 | RUN_TEST(foo_should_foo); 78 | 79 | GREATEST_PRINT_REPORT(); /* display results */ 80 | return greatest_all_passed(); 81 | } 82 | 83 | /* main(), for a standalone command-line test runner. 84 | * This replaces run_tests above, and adds command line option 85 | * handling and exiting with a pass/fail status. */ 86 | int main(int argc, char **argv) { 87 | GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ 88 | RUN_SUITE(suite); 89 | GREATEST_MAIN_END(); /* display results */ 90 | } 91 | 92 | #endif 93 | /*********************************************************************/ 94 | 95 | 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | /*********** 102 | * Options * 103 | ***********/ 104 | 105 | /* Default column width for non-verbose output. */ 106 | #ifndef GREATEST_DEFAULT_WIDTH 107 | #define GREATEST_DEFAULT_WIDTH 72 108 | #endif 109 | 110 | /* FILE *, for test logging. */ 111 | #ifndef GREATEST_STDOUT 112 | #define GREATEST_STDOUT stdout 113 | #endif 114 | 115 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 116 | #ifndef GREATEST_USE_ABBREVS 117 | #define GREATEST_USE_ABBREVS 1 118 | #endif 119 | 120 | /* Set to 0 to disable all use of setjmp/longjmp. */ 121 | #ifndef GREATEST_USE_LONGJMP 122 | #define GREATEST_USE_LONGJMP 0 123 | #endif 124 | 125 | /* Make it possible to replace fprintf with another 126 | * function with the same interface. */ 127 | #ifndef GREATEST_FPRINTF 128 | #define GREATEST_FPRINTF fprintf 129 | #endif 130 | 131 | #if GREATEST_USE_LONGJMP 132 | #include 133 | #endif 134 | 135 | /* Set to 0 to disable all use of time.h / clock(). */ 136 | #ifndef GREATEST_USE_TIME 137 | #define GREATEST_USE_TIME 1 138 | #endif 139 | 140 | #if GREATEST_USE_TIME 141 | #include 142 | #endif 143 | 144 | /* Floating point type, for ASSERT_IN_RANGE. */ 145 | #ifndef GREATEST_FLOAT 146 | #define GREATEST_FLOAT double 147 | #define GREATEST_FLOAT_FMT "%g" 148 | #endif 149 | 150 | /* Size of buffer for test name + optional '_' separator and suffix */ 151 | #ifndef GREATEST_TESTNAME_BUF_SIZE 152 | #define GREATEST_TESTNAME_BUF_SIZE 128 153 | #endif 154 | 155 | 156 | /********* 157 | * Types * 158 | *********/ 159 | 160 | /* Info for the current running suite. */ 161 | typedef struct greatest_suite_info { 162 | unsigned int tests_run; 163 | unsigned int passed; 164 | unsigned int failed; 165 | unsigned int skipped; 166 | 167 | #if GREATEST_USE_TIME 168 | /* timers, pre/post running suite and individual tests */ 169 | clock_t pre_suite; 170 | clock_t post_suite; 171 | clock_t pre_test; 172 | clock_t post_test; 173 | #endif 174 | } greatest_suite_info; 175 | 176 | /* Type for a suite function. */ 177 | typedef void greatest_suite_cb(void); 178 | 179 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 180 | * and passed the pointer to their additional data. */ 181 | typedef void greatest_setup_cb(void *udata); 182 | typedef void greatest_teardown_cb(void *udata); 183 | 184 | /* Type for an equality comparison between two pointers of the same type. 185 | * Should return non-0 if equal, otherwise 0. 186 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 187 | typedef int greatest_equal_cb(const void *expd, const void *got, void *udata); 188 | 189 | /* Type for a callback that prints a value pointed to by T. 190 | * Return value has the same meaning as printf's. 191 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 192 | typedef int greatest_printf_cb(const void *t, void *udata); 193 | 194 | /* Callbacks for an arbitrary type; needed for type-specific 195 | * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ 196 | typedef struct greatest_type_info { 197 | greatest_equal_cb *equal; 198 | greatest_printf_cb *print; 199 | } greatest_type_info; 200 | 201 | typedef struct greatest_memory_cmp_env { 202 | const unsigned char *exp; 203 | const unsigned char *got; 204 | size_t size; 205 | } greatest_memory_cmp_env; 206 | 207 | /* Callbacks for string and raw memory types. */ 208 | extern greatest_type_info greatest_type_info_string; 209 | extern greatest_type_info greatest_type_info_memory; 210 | 211 | typedef enum { 212 | GREATEST_FLAG_FIRST_FAIL = 0x01, 213 | GREATEST_FLAG_LIST_ONLY = 0x02, 214 | GREATEST_FLAG_ABORT_ON_FAIL = 0x04 215 | } greatest_flag_t; 216 | 217 | /* Internal state for a PRNG, used to shuffle test order. */ 218 | struct greatest_prng { 219 | unsigned char random_order; /* use random ordering? */ 220 | unsigned char initialized; /* is random ordering initialized? */ 221 | unsigned char pad_0[6]; 222 | unsigned long state; /* PRNG state */ 223 | unsigned long count; /* how many tests, this pass */ 224 | unsigned long count_ceil; /* total number of tests */ 225 | unsigned long count_run; /* total tests run */ 226 | unsigned long a; /* LCG multiplier */ 227 | unsigned long c; /* LCG increment */ 228 | unsigned long m; /* LCG modulus, based on count_ceil */ 229 | }; 230 | 231 | /* Struct containing all test runner state. */ 232 | typedef struct greatest_run_info { 233 | unsigned char flags; 234 | unsigned char verbosity; 235 | unsigned char running_test; /* guard for nested RUN_TEST calls */ 236 | unsigned char exact_name_match; 237 | 238 | unsigned int tests_run; /* total test count */ 239 | 240 | /* currently running test suite */ 241 | greatest_suite_info suite; 242 | 243 | /* overall pass/fail/skip counts */ 244 | unsigned int passed; 245 | unsigned int failed; 246 | unsigned int skipped; 247 | unsigned int assertions; 248 | 249 | /* info to print about the most recent failure */ 250 | unsigned int fail_line; 251 | unsigned int pad_1; 252 | const char *fail_file; 253 | const char *msg; 254 | 255 | /* current setup/teardown hooks and userdata */ 256 | greatest_setup_cb *setup; 257 | void *setup_udata; 258 | greatest_teardown_cb *teardown; 259 | void *teardown_udata; 260 | 261 | /* formatting info for ".....s...F"-style output */ 262 | unsigned int col; 263 | unsigned int width; 264 | 265 | /* only run a specific suite or test */ 266 | const char *suite_filter; 267 | const char *test_filter; 268 | const char *test_exclude; 269 | const char *name_suffix; /* print suffix with test name */ 270 | char name_buf[GREATEST_TESTNAME_BUF_SIZE]; 271 | 272 | struct greatest_prng prng[2]; /* 0: suites, 1: tests */ 273 | 274 | #if GREATEST_USE_TIME 275 | /* overall timers */ 276 | clock_t begin; 277 | clock_t end; 278 | #endif 279 | 280 | #if GREATEST_USE_LONGJMP 281 | int pad_jmp_buf; 282 | unsigned char pad_2[4]; 283 | jmp_buf jump_dest; 284 | #endif 285 | } greatest_run_info; 286 | 287 | struct greatest_report_t { 288 | /* overall pass/fail/skip counts */ 289 | unsigned int passed; 290 | unsigned int failed; 291 | unsigned int skipped; 292 | unsigned int assertions; 293 | }; 294 | 295 | /* Global var for the current testing context. 296 | * Initialized by GREATEST_MAIN_DEFS(). */ 297 | extern greatest_run_info greatest_info; 298 | 299 | /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ 300 | typedef const char *greatest_enum_str_fun(int value); 301 | 302 | 303 | /********************** 304 | * Exported functions * 305 | **********************/ 306 | 307 | /* These are used internally by greatest macros. */ 308 | int greatest_test_pre(const char *name); 309 | void greatest_test_post(int res); 310 | int greatest_do_assert_equal_t(const void *expd, const void *got, 311 | greatest_type_info *type_info, void *udata); 312 | void greatest_prng_init_first_pass(int id); 313 | int greatest_prng_init_second_pass(int id, unsigned long seed); 314 | void greatest_prng_step(int id); 315 | 316 | /* These are part of the public greatest API. */ 317 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 318 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 319 | void GREATEST_INIT(void); 320 | void GREATEST_PRINT_REPORT(void); 321 | int greatest_all_passed(void); 322 | void greatest_set_suite_filter(const char *filter); 323 | void greatest_set_test_filter(const char *filter); 324 | void greatest_set_test_exclude(const char *filter); 325 | void greatest_set_exact_name_match(void); 326 | void greatest_stop_at_first_fail(void); 327 | void greatest_abort_on_fail(void); 328 | void greatest_list_only(void); 329 | void greatest_get_report(struct greatest_report_t *report); 330 | unsigned int greatest_get_verbosity(void); 331 | void greatest_set_verbosity(unsigned int verbosity); 332 | void greatest_set_flag(greatest_flag_t flag); 333 | void greatest_set_test_suffix(const char *suffix); 334 | 335 | 336 | /******************** 337 | * Language Support * 338 | ********************/ 339 | 340 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 341 | * without needing to manually manage the argument struct. */ 342 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 19901L) || \ 343 | (defined(_MSC_VER) && _MSC_VER >= 1800) 344 | #define GREATEST_VA_ARGS 345 | #endif 346 | 347 | 348 | /********** 349 | * Macros * 350 | **********/ 351 | 352 | /* Define a suite. (The duplication is intentional -- it eliminates 353 | * a warning from -Wmissing-declarations.) */ 354 | #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) 355 | 356 | /* Declare a suite, provided by another compilation unit. */ 357 | #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) 358 | 359 | /* Start defining a test function. 360 | * The arguments are not included, to allow parametric testing. */ 361 | #define GREATEST_TEST static enum greatest_test_res 362 | 363 | /* PASS/FAIL/SKIP result from a test. Used internally. */ 364 | typedef enum greatest_test_res { 365 | GREATEST_TEST_RES_PASS = 0, 366 | GREATEST_TEST_RES_FAIL = -1, 367 | GREATEST_TEST_RES_SKIP = 1 368 | } greatest_test_res; 369 | 370 | /* Run a suite. */ 371 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 372 | 373 | /* Run a test in the current suite. */ 374 | #define GREATEST_RUN_TEST(TEST) \ 375 | do { \ 376 | if (greatest_test_pre(#TEST) == 1) { \ 377 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 378 | if (res == GREATEST_TEST_RES_PASS) { \ 379 | res = TEST(); \ 380 | } \ 381 | greatest_test_post(res); \ 382 | } \ 383 | } while (0) 384 | 385 | /* Ignore a test, don't warn about it being unused. */ 386 | #define GREATEST_IGNORE_TEST(TEST) (void)TEST 387 | 388 | /* Run a test in the current suite with one void * argument, 389 | * which can be a pointer to a struct with multiple arguments. */ 390 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 391 | do { \ 392 | if (greatest_test_pre(#TEST) == 1) { \ 393 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 394 | if (res == GREATEST_TEST_RES_PASS) { \ 395 | res = TEST(ENV); \ 396 | } \ 397 | greatest_test_post(res); \ 398 | } \ 399 | } while (0) 400 | 401 | #ifdef GREATEST_VA_ARGS 402 | #define GREATEST_RUN_TESTp(TEST, ...) \ 403 | do { \ 404 | if (greatest_test_pre(#TEST) == 1) { \ 405 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 406 | if (res == GREATEST_TEST_RES_PASS) { \ 407 | res = TEST(__VA_ARGS__); \ 408 | } \ 409 | greatest_test_post(res); \ 410 | } \ 411 | } while (0) 412 | #endif 413 | 414 | 415 | /* Check if the test runner is in verbose mode. */ 416 | #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) 417 | #define GREATEST_LIST_ONLY() \ 418 | (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 419 | #define GREATEST_FIRST_FAIL() \ 420 | (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 421 | #define GREATEST_ABORT_ON_FAIL() \ 422 | (greatest_info.flags & GREATEST_FLAG_ABORT_ON_FAIL) 423 | #define GREATEST_FAILURE_ABORT() \ 424 | (GREATEST_FIRST_FAIL() && \ 425 | (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) 426 | 427 | /* Message-less forms of tests defined below. */ 428 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 429 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 430 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 431 | #define GREATEST_ASSERT(COND) \ 432 | GREATEST_ASSERTm(#COND, COND) 433 | #define GREATEST_ASSERT_OR_LONGJMP(COND) \ 434 | GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) 435 | #define GREATEST_ASSERT_FALSE(COND) \ 436 | GREATEST_ASSERT_FALSEm(#COND, COND) 437 | #define GREATEST_ASSERT_EQ(EXP, GOT) \ 438 | GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 439 | #define GREATEST_ASSERT_NEQ(EXP, GOT) \ 440 | GREATEST_ASSERT_NEQm(#EXP " == " #GOT, EXP, GOT) 441 | #define GREATEST_ASSERT_GT(EXP, GOT) \ 442 | GREATEST_ASSERT_GTm(#EXP " <= " #GOT, EXP, GOT) 443 | #define GREATEST_ASSERT_GTE(EXP, GOT) \ 444 | GREATEST_ASSERT_GTEm(#EXP " < " #GOT, EXP, GOT) 445 | #define GREATEST_ASSERT_LT(EXP, GOT) \ 446 | GREATEST_ASSERT_LTm(#EXP " >= " #GOT, EXP, GOT) 447 | #define GREATEST_ASSERT_LTE(EXP, GOT) \ 448 | GREATEST_ASSERT_LTEm(#EXP " > " #GOT, EXP, GOT) 449 | #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ 450 | GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) 451 | #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ 452 | GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) 453 | #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ 454 | GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) 455 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ 456 | GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 457 | #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ 458 | GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 459 | #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ 460 | GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 461 | #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ 462 | GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) 463 | 464 | /* The following forms take an additional message argument first, 465 | * to be displayed by the test runner. */ 466 | 467 | /* Fail if a condition is not true, with message. */ 468 | #define GREATEST_ASSERTm(MSG, COND) \ 469 | do { \ 470 | greatest_info.assertions++; \ 471 | if (!(COND)) { GREATEST_FAILm(MSG); } \ 472 | } while (0) 473 | 474 | /* Fail if a condition is not true, longjmping out of test. */ 475 | #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ 476 | do { \ 477 | greatest_info.assertions++; \ 478 | if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ 479 | } while (0) 480 | 481 | /* Fail if a condition is not false, with message. */ 482 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 483 | do { \ 484 | greatest_info.assertions++; \ 485 | if ((COND)) { GREATEST_FAILm(MSG); } \ 486 | } while (0) 487 | 488 | /* Internal macro for relational assertions */ 489 | #define GREATEST__REL(REL, MSG, EXP, GOT) \ 490 | do { \ 491 | greatest_info.assertions++; \ 492 | if (!((EXP) REL (GOT))) { GREATEST_FAILm(MSG); } \ 493 | } while (0) 494 | 495 | /* Fail if EXP is not ==, !=, >, <, >=, or <= to GOT. */ 496 | #define GREATEST_ASSERT_EQm(MSG,E,G) GREATEST__REL(==, MSG,E,G) 497 | #define GREATEST_ASSERT_NEQm(MSG,E,G) GREATEST__REL(!=, MSG,E,G) 498 | #define GREATEST_ASSERT_GTm(MSG,E,G) GREATEST__REL(>, MSG,E,G) 499 | #define GREATEST_ASSERT_GTEm(MSG,E,G) GREATEST__REL(>=, MSG,E,G) 500 | #define GREATEST_ASSERT_LTm(MSG,E,G) GREATEST__REL(<, MSG,E,G) 501 | #define GREATEST_ASSERT_LTEm(MSG,E,G) GREATEST__REL(<=, MSG,E,G) 502 | 503 | /* Fail if EXP != GOT (equality comparison by ==). 504 | * Warning: FMT, EXP, and GOT will be evaluated more 505 | * than once on failure. */ 506 | #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ 507 | do { \ 508 | greatest_info.assertions++; \ 509 | if ((EXP) != (GOT)) { \ 510 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 511 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ 512 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 513 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ 514 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 515 | GREATEST_FAILm(MSG); \ 516 | } \ 517 | } while (0) 518 | 519 | /* Fail if EXP is not equal to GOT, printing enum IDs. */ 520 | #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ 521 | do { \ 522 | int greatest_EXP = (int)(EXP); \ 523 | int greatest_GOT = (int)(GOT); \ 524 | greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ 525 | if (greatest_EXP != greatest_GOT) { \ 526 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ 527 | greatest_ENUM_STR(greatest_EXP)); \ 528 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ 529 | greatest_ENUM_STR(greatest_GOT)); \ 530 | GREATEST_FAILm(MSG); \ 531 | } \ 532 | } while (0) \ 533 | 534 | /* Fail if GOT not in range of EXP +|- TOL. */ 535 | #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ 536 | do { \ 537 | GREATEST_FLOAT greatest_EXP = (EXP); \ 538 | GREATEST_FLOAT greatest_GOT = (GOT); \ 539 | GREATEST_FLOAT greatest_TOL = (TOL); \ 540 | greatest_info.assertions++; \ 541 | if ((greatest_EXP > greatest_GOT && \ 542 | greatest_EXP - greatest_GOT > greatest_TOL) || \ 543 | (greatest_EXP < greatest_GOT && \ 544 | greatest_GOT - greatest_EXP > greatest_TOL)) { \ 545 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 546 | "\nExpected: " GREATEST_FLOAT_FMT \ 547 | " +/- " GREATEST_FLOAT_FMT \ 548 | "\n Got: " GREATEST_FLOAT_FMT \ 549 | "\n", \ 550 | greatest_EXP, greatest_TOL, greatest_GOT); \ 551 | GREATEST_FAILm(MSG); \ 552 | } \ 553 | } while (0) 554 | 555 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 556 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 557 | do { \ 558 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 559 | &greatest_type_info_string, NULL); \ 560 | } while (0) \ 561 | 562 | /* Fail if EXP is not equal to GOT, according to strncmp. */ 563 | #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ 564 | do { \ 565 | size_t size = SIZE; \ 566 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 567 | &greatest_type_info_string, &size); \ 568 | } while (0) \ 569 | 570 | /* Fail if EXP is not equal to GOT, according to memcmp. */ 571 | #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ 572 | do { \ 573 | greatest_memory_cmp_env env; \ 574 | env.exp = (const unsigned char *)EXP; \ 575 | env.got = (const unsigned char *)GOT; \ 576 | env.size = SIZE; \ 577 | GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ 578 | &greatest_type_info_memory, &env); \ 579 | } while (0) \ 580 | 581 | /* Fail if EXP is not equal to GOT, according to a comparison 582 | * callback in TYPE_INFO. If they are not equal, optionally use a 583 | * print callback in TYPE_INFO to print them. */ 584 | #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ 585 | do { \ 586 | greatest_type_info *type_info = (TYPE_INFO); \ 587 | greatest_info.assertions++; \ 588 | if (!greatest_do_assert_equal_t(EXP, GOT, \ 589 | type_info, UDATA)) { \ 590 | if (type_info == NULL || type_info->equal == NULL) { \ 591 | GREATEST_FAILm("type_info->equal callback missing!"); \ 592 | } else { \ 593 | GREATEST_FAILm(MSG); \ 594 | } \ 595 | } \ 596 | } while (0) \ 597 | 598 | /* Pass. */ 599 | #define GREATEST_PASSm(MSG) \ 600 | do { \ 601 | greatest_info.msg = MSG; \ 602 | return GREATEST_TEST_RES_PASS; \ 603 | } while (0) 604 | 605 | /* Fail. */ 606 | #define GREATEST_FAILm(MSG) \ 607 | do { \ 608 | greatest_info.fail_file = __FILE__; \ 609 | greatest_info.fail_line = __LINE__; \ 610 | greatest_info.msg = MSG; \ 611 | if (GREATEST_ABORT_ON_FAIL()) { abort(); } \ 612 | return GREATEST_TEST_RES_FAIL; \ 613 | } while (0) 614 | 615 | /* Optional GREATEST_FAILm variant that longjmps. */ 616 | #if GREATEST_USE_LONGJMP 617 | #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) 618 | #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ 619 | do { \ 620 | greatest_info.fail_file = __FILE__; \ 621 | greatest_info.fail_line = __LINE__; \ 622 | greatest_info.msg = MSG; \ 623 | longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ 624 | } while (0) 625 | #endif 626 | 627 | /* Skip the current test. */ 628 | #define GREATEST_SKIPm(MSG) \ 629 | do { \ 630 | greatest_info.msg = MSG; \ 631 | return GREATEST_TEST_RES_SKIP; \ 632 | } while (0) 633 | 634 | /* Check the result of a subfunction using ASSERT, etc. */ 635 | #define GREATEST_CHECK_CALL(RES) \ 636 | do { \ 637 | enum greatest_test_res greatest_RES = RES; \ 638 | if (greatest_RES != GREATEST_TEST_RES_PASS) { \ 639 | return greatest_RES; \ 640 | } \ 641 | } while (0) \ 642 | 643 | #if GREATEST_USE_TIME 644 | #define GREATEST_SET_TIME(NAME) \ 645 | NAME = clock(); \ 646 | if (NAME == (clock_t) -1) { \ 647 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 648 | "clock error: %s\n", #NAME); \ 649 | exit(EXIT_FAILURE); \ 650 | } 651 | 652 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 653 | GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 654 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 655 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) 656 | #else 657 | #define GREATEST_SET_TIME(UNUSED) 658 | #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) 659 | #endif 660 | 661 | #if GREATEST_USE_LONGJMP 662 | #define GREATEST_SAVE_CONTEXT() \ 663 | /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ 664 | * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ 665 | ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) 666 | #else 667 | #define GREATEST_SAVE_CONTEXT() \ 668 | /*a no-op, since setjmp/longjmp aren't being used */ \ 669 | GREATEST_TEST_RES_PASS 670 | #endif 671 | 672 | /* Run every suite / test function run within BODY in pseudo-random 673 | * order, seeded by SEED. (The top 3 bits of the seed are ignored.) 674 | * 675 | * This should be called like: 676 | * GREATEST_SHUFFLE_TESTS(seed, { 677 | * GREATEST_RUN_TEST(some_test); 678 | * GREATEST_RUN_TEST(some_other_test); 679 | * GREATEST_RUN_TEST(yet_another_test); 680 | * }); 681 | * 682 | * Note that the body of the second argument will be evaluated 683 | * multiple times. */ 684 | #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) 685 | #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) 686 | #define GREATEST_SHUFFLE(ID, SD, BODY) \ 687 | do { \ 688 | struct greatest_prng *prng = &greatest_info.prng[ID]; \ 689 | greatest_prng_init_first_pass(ID); \ 690 | do { \ 691 | prng->count = 0; \ 692 | if (prng->initialized) { greatest_prng_step(ID); } \ 693 | BODY; \ 694 | if (!prng->initialized) { \ 695 | if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ 696 | } else if (prng->count_run == prng->count_ceil) { \ 697 | break; \ 698 | } \ 699 | } while (!GREATEST_FAILURE_ABORT()); \ 700 | prng->count_run = prng->random_order = prng->initialized = 0; \ 701 | } while(0) 702 | 703 | /* Include several function definitions in the main test file. */ 704 | #define GREATEST_MAIN_DEFS() \ 705 | \ 706 | /* Is FILTER a subset of NAME? */ \ 707 | static int greatest_name_match(const char *name, const char *filter, \ 708 | int res_if_none) { \ 709 | size_t offset = 0; \ 710 | size_t filter_len = filter ? strlen(filter) : 0; \ 711 | if (filter_len == 0) { return res_if_none; } /* no filter */ \ 712 | if (greatest_info.exact_name_match && strlen(name) != filter_len) { \ 713 | return 0; /* ignore substring matches */ \ 714 | } \ 715 | while (name[offset] != '\0') { \ 716 | if (name[offset] == filter[0]) { \ 717 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 718 | return 1; \ 719 | } \ 720 | } \ 721 | offset++; \ 722 | } \ 723 | \ 724 | return 0; \ 725 | } \ 726 | \ 727 | static void greatest_buffer_test_name(const char *name) { \ 728 | struct greatest_run_info *g = &greatest_info; \ 729 | size_t len = strlen(name), size = sizeof(g->name_buf); \ 730 | memset(g->name_buf, 0x00, size); \ 731 | (void)strncat(g->name_buf, name, size - 1); \ 732 | if (g->name_suffix && (len + 1 < size)) { \ 733 | g->name_buf[len] = '_'; \ 734 | strncat(&g->name_buf[len+1], g->name_suffix, size-(len+2)); \ 735 | } \ 736 | } \ 737 | \ 738 | /* Before running a test, check the name filtering and \ 739 | * test shuffling state, if applicable, and then call setup hooks. */ \ 740 | int greatest_test_pre(const char *name) { \ 741 | struct greatest_run_info *g = &greatest_info; \ 742 | int match; \ 743 | greatest_buffer_test_name(name); \ 744 | match = greatest_name_match(g->name_buf, g->test_filter, 1) && \ 745 | !greatest_name_match(g->name_buf, g->test_exclude, 0); \ 746 | if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ 747 | if (match) { \ 748 | GREATEST_FPRINTF(GREATEST_STDOUT, " %s\n", g->name_buf); \ 749 | } \ 750 | goto clear; \ 751 | } \ 752 | if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ 753 | struct greatest_prng *p = &g->prng[1]; \ 754 | if (p->random_order) { \ 755 | p->count++; \ 756 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 757 | goto clear; /* don't run this test yet */ \ 758 | } \ 759 | } \ 760 | if (g->running_test) { \ 761 | fprintf(stderr, "Error: Test run inside another test.\n"); \ 762 | return 0; \ 763 | } \ 764 | GREATEST_SET_TIME(g->suite.pre_test); \ 765 | if (g->setup) { g->setup(g->setup_udata); } \ 766 | p->count_run++; \ 767 | g->running_test = 1; \ 768 | return 1; /* test should be run */ \ 769 | } else { \ 770 | goto clear; /* skipped */ \ 771 | } \ 772 | clear: \ 773 | g->name_suffix = NULL; \ 774 | return 0; \ 775 | } \ 776 | \ 777 | static void greatest_do_pass(void) { \ 778 | struct greatest_run_info *g = &greatest_info; \ 779 | if (GREATEST_IS_VERBOSE()) { \ 780 | GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ 781 | g->name_buf, g->msg ? g->msg : ""); \ 782 | } else { \ 783 | GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ 784 | } \ 785 | g->suite.passed++; \ 786 | } \ 787 | \ 788 | static void greatest_do_fail(void) { \ 789 | struct greatest_run_info *g = &greatest_info; \ 790 | if (GREATEST_IS_VERBOSE()) { \ 791 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 792 | "FAIL %s: %s (%s:%u)", g->name_buf, \ 793 | g->msg ? g->msg : "", g->fail_file, g->fail_line); \ 794 | } else { \ 795 | GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ 796 | g->col++; /* add linebreak if in line of '.'s */ \ 797 | if (g->col != 0) { \ 798 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 799 | g->col = 0; \ 800 | } \ 801 | GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 802 | g->name_buf, g->msg ? g->msg : "", \ 803 | g->fail_file, g->fail_line); \ 804 | } \ 805 | g->suite.failed++; \ 806 | } \ 807 | \ 808 | static void greatest_do_skip(void) { \ 809 | struct greatest_run_info *g = &greatest_info; \ 810 | if (GREATEST_IS_VERBOSE()) { \ 811 | GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ 812 | g->name_buf, g->msg ? g->msg : ""); \ 813 | } else { \ 814 | GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ 815 | } \ 816 | g->suite.skipped++; \ 817 | } \ 818 | \ 819 | void greatest_test_post(int res) { \ 820 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 821 | if (greatest_info.teardown) { \ 822 | void *udata = greatest_info.teardown_udata; \ 823 | greatest_info.teardown(udata); \ 824 | } \ 825 | \ 826 | greatest_info.running_test = 0; \ 827 | if (res <= GREATEST_TEST_RES_FAIL) { \ 828 | greatest_do_fail(); \ 829 | } else if (res >= GREATEST_TEST_RES_SKIP) { \ 830 | greatest_do_skip(); \ 831 | } else if (res == GREATEST_TEST_RES_PASS) { \ 832 | greatest_do_pass(); \ 833 | } \ 834 | greatest_info.name_suffix = NULL; \ 835 | greatest_info.suite.tests_run++; \ 836 | greatest_info.col++; \ 837 | if (GREATEST_IS_VERBOSE()) { \ 838 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 839 | greatest_info.suite.post_test); \ 840 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 841 | } else if (greatest_info.col % greatest_info.width == 0) { \ 842 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 843 | greatest_info.col = 0; \ 844 | } \ 845 | fflush(GREATEST_STDOUT); \ 846 | } \ 847 | \ 848 | static void report_suite(void) { \ 849 | if (greatest_info.suite.tests_run > 0) { \ 850 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 851 | "\n%u test%s - %u passed, %u failed, %u skipped", \ 852 | greatest_info.suite.tests_run, \ 853 | greatest_info.suite.tests_run == 1 ? "" : "s", \ 854 | greatest_info.suite.passed, \ 855 | greatest_info.suite.failed, \ 856 | greatest_info.suite.skipped); \ 857 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 858 | greatest_info.suite.post_suite); \ 859 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 860 | } \ 861 | } \ 862 | \ 863 | static void update_counts_and_reset_suite(void) { \ 864 | greatest_info.setup = NULL; \ 865 | greatest_info.setup_udata = NULL; \ 866 | greatest_info.teardown = NULL; \ 867 | greatest_info.teardown_udata = NULL; \ 868 | greatest_info.passed += greatest_info.suite.passed; \ 869 | greatest_info.failed += greatest_info.suite.failed; \ 870 | greatest_info.skipped += greatest_info.suite.skipped; \ 871 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 872 | memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ 873 | greatest_info.col = 0; \ 874 | } \ 875 | \ 876 | static int greatest_suite_pre(const char *suite_name) { \ 877 | struct greatest_prng *p = &greatest_info.prng[0]; \ 878 | if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ 879 | || (GREATEST_FAILURE_ABORT())) { return 0; } \ 880 | if (p->random_order) { \ 881 | p->count++; \ 882 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 883 | return 0; /* don't run this suite yet */ \ 884 | } \ 885 | } \ 886 | p->count_run++; \ 887 | update_counts_and_reset_suite(); \ 888 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 889 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 890 | return 1; \ 891 | } \ 892 | \ 893 | static void greatest_suite_post(void) { \ 894 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 895 | report_suite(); \ 896 | } \ 897 | \ 898 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 899 | const char *suite_name) { \ 900 | if (greatest_suite_pre(suite_name)) { \ 901 | suite_cb(); \ 902 | greatest_suite_post(); \ 903 | } \ 904 | } \ 905 | \ 906 | int greatest_do_assert_equal_t(const void *expd, const void *got, \ 907 | greatest_type_info *type_info, void *udata) { \ 908 | int eq = 0; \ 909 | if (type_info == NULL || type_info->equal == NULL) { return 0; } \ 910 | eq = type_info->equal(expd, got, udata); \ 911 | if (!eq) { \ 912 | if (type_info->print != NULL) { \ 913 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 914 | (void)type_info->print(expd, udata); \ 915 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 916 | (void)type_info->print(got, udata); \ 917 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 918 | } \ 919 | } \ 920 | return eq; \ 921 | } \ 922 | \ 923 | static void greatest_usage(const char *name) { \ 924 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 925 | "Usage: %s [-hlfavex] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ 926 | " -h, --help print this Help\n" \ 927 | " -l List suites and tests, then exit (dry run)\n" \ 928 | " -f Stop runner after first failure\n" \ 929 | " -a Abort on first failure (implies -f)\n" \ 930 | " -v Verbose output\n" \ 931 | " -s SUITE only run suites containing substring SUITE\n" \ 932 | " -t TEST only run tests containing substring TEST\n" \ 933 | " -e only run exact name match for -s or -t\n" \ 934 | " -x EXCLUDE exclude tests containing substring EXCLUDE\n", \ 935 | name); \ 936 | } \ 937 | \ 938 | static void greatest_parse_options(int argc, char **argv) { \ 939 | int i = 0; \ 940 | for (i = 1; i < argc; i++) { \ 941 | if (argv[i][0] == '-') { \ 942 | char f = argv[i][1]; \ 943 | if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ 944 | greatest_usage(argv[0]); exit(EXIT_FAILURE); \ 945 | } \ 946 | switch (f) { \ 947 | case 's': /* suite name filter */ \ 948 | greatest_set_suite_filter(argv[i + 1]); i++; break; \ 949 | case 't': /* test name filter */ \ 950 | greatest_set_test_filter(argv[i + 1]); i++; break; \ 951 | case 'x': /* test name exclusion */ \ 952 | greatest_set_test_exclude(argv[i + 1]); i++; break; \ 953 | case 'e': /* exact name match */ \ 954 | greatest_set_exact_name_match(); break; \ 955 | case 'f': /* first fail flag */ \ 956 | greatest_stop_at_first_fail(); break; \ 957 | case 'a': /* abort() on fail flag */ \ 958 | greatest_abort_on_fail(); break; \ 959 | case 'l': /* list only (dry run) */ \ 960 | greatest_list_only(); break; \ 961 | case 'v': /* first fail flag */ \ 962 | greatest_info.verbosity++; break; \ 963 | case 'h': /* help */ \ 964 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 965 | default: \ 966 | case '-': \ 967 | if (0 == strncmp("--help", argv[i], 6)) { \ 968 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 969 | } else if (0 == strcmp("--", argv[i])) { \ 970 | return; /* ignore following arguments */ \ 971 | } \ 972 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 973 | "Unknown argument '%s'\n", argv[i]); \ 974 | greatest_usage(argv[0]); \ 975 | exit(EXIT_FAILURE); \ 976 | } \ 977 | } \ 978 | } \ 979 | } \ 980 | \ 981 | int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ 982 | \ 983 | void greatest_set_test_filter(const char *filter) { \ 984 | greatest_info.test_filter = filter; \ 985 | } \ 986 | \ 987 | void greatest_set_test_exclude(const char *filter) { \ 988 | greatest_info.test_exclude = filter; \ 989 | } \ 990 | \ 991 | void greatest_set_suite_filter(const char *filter) { \ 992 | greatest_info.suite_filter = filter; \ 993 | } \ 994 | \ 995 | void greatest_set_exact_name_match(void) { \ 996 | greatest_info.exact_name_match = 1; \ 997 | } \ 998 | \ 999 | void greatest_stop_at_first_fail(void) { \ 1000 | greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ 1001 | } \ 1002 | \ 1003 | void greatest_abort_on_fail(void) { \ 1004 | greatest_set_flag(GREATEST_FLAG_ABORT_ON_FAIL); \ 1005 | } \ 1006 | \ 1007 | void greatest_list_only(void) { \ 1008 | greatest_set_flag(GREATEST_FLAG_LIST_ONLY); \ 1009 | } \ 1010 | \ 1011 | void greatest_get_report(struct greatest_report_t *report) { \ 1012 | if (report) { \ 1013 | report->passed = greatest_info.passed; \ 1014 | report->failed = greatest_info.failed; \ 1015 | report->skipped = greatest_info.skipped; \ 1016 | report->assertions = greatest_info.assertions; \ 1017 | } \ 1018 | } \ 1019 | \ 1020 | unsigned int greatest_get_verbosity(void) { \ 1021 | return greatest_info.verbosity; \ 1022 | } \ 1023 | \ 1024 | void greatest_set_verbosity(unsigned int verbosity) { \ 1025 | greatest_info.verbosity = (unsigned char)verbosity; \ 1026 | } \ 1027 | \ 1028 | void greatest_set_flag(greatest_flag_t flag) { \ 1029 | greatest_info.flags = (unsigned char)(greatest_info.flags | flag); \ 1030 | } \ 1031 | \ 1032 | void greatest_set_test_suffix(const char *suffix) { \ 1033 | greatest_info.name_suffix = suffix; \ 1034 | } \ 1035 | \ 1036 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 1037 | greatest_info.setup = cb; \ 1038 | greatest_info.setup_udata = udata; \ 1039 | } \ 1040 | \ 1041 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata) { \ 1042 | greatest_info.teardown = cb; \ 1043 | greatest_info.teardown_udata = udata; \ 1044 | } \ 1045 | \ 1046 | static int greatest_string_equal_cb(const void *expd, const void *got, \ 1047 | void *udata) { \ 1048 | size_t *size = (size_t *)udata; \ 1049 | return (size != NULL \ 1050 | ? (0 == strncmp((const char *)expd, (const char *)got, *size)) \ 1051 | : (0 == strcmp((const char *)expd, (const char *)got))); \ 1052 | } \ 1053 | \ 1054 | static int greatest_string_printf_cb(const void *t, void *udata) { \ 1055 | (void)udata; /* note: does not check \0 termination. */ \ 1056 | return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ 1057 | } \ 1058 | \ 1059 | greatest_type_info greatest_type_info_string = { \ 1060 | greatest_string_equal_cb, greatest_string_printf_cb, \ 1061 | }; \ 1062 | \ 1063 | static int greatest_memory_equal_cb(const void *expd, const void *got, \ 1064 | void *udata) { \ 1065 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1066 | return (0 == memcmp(expd, got, env->size)); \ 1067 | } \ 1068 | \ 1069 | /* Hexdump raw memory, with differences highlighted */ \ 1070 | static int greatest_memory_printf_cb(const void *t, void *udata) { \ 1071 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1072 | const unsigned char *buf = (const unsigned char *)t; \ 1073 | unsigned char diff_mark = ' '; \ 1074 | FILE *out = GREATEST_STDOUT; \ 1075 | size_t i, line_i, line_len = 0; \ 1076 | int len = 0; /* format hexdump with differences highlighted */ \ 1077 | for (i = 0; i < env->size; i+= line_len) { \ 1078 | diff_mark = ' '; \ 1079 | line_len = env->size - i; \ 1080 | if (line_len > 16) { line_len = 16; } \ 1081 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1082 | if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ 1083 | } \ 1084 | len += GREATEST_FPRINTF(out, "\n%04x %c ", \ 1085 | (unsigned int)i, diff_mark); \ 1086 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1087 | int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ 1088 | len += GREATEST_FPRINTF(out, "%02x%c", \ 1089 | buf[line_i], m ? ' ' : '<'); \ 1090 | } \ 1091 | for (line_i = 0; line_i < 16 - line_len; line_i++) { \ 1092 | len += GREATEST_FPRINTF(out, " "); \ 1093 | } \ 1094 | GREATEST_FPRINTF(out, " "); \ 1095 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1096 | unsigned char c = buf[line_i]; \ 1097 | len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ 1098 | } \ 1099 | } \ 1100 | len += GREATEST_FPRINTF(out, "\n"); \ 1101 | return len; \ 1102 | } \ 1103 | \ 1104 | void greatest_prng_init_first_pass(int id) { \ 1105 | greatest_info.prng[id].random_order = 1; \ 1106 | greatest_info.prng[id].count_run = 0; \ 1107 | } \ 1108 | \ 1109 | int greatest_prng_init_second_pass(int id, unsigned long seed) { \ 1110 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1111 | if (p->count == 0) { return 0; } \ 1112 | p->count_ceil = p->count; \ 1113 | for (p->m = 1; p->m < p->count; p->m <<= 1) {} \ 1114 | p->state = seed & 0x1fffffff; /* only use lower 29 bits */ \ 1115 | p->a = 4LU * p->state; /* to avoid overflow when */ \ 1116 | p->a = (p->a ? p->a : 4) | 1; /* multiplied by 4 */ \ 1117 | p->c = 2147483647; /* and so p->c ((2 ** 31) - 1) is */ \ 1118 | p->initialized = 1; /* always relatively prime to p->a. */ \ 1119 | fprintf(stderr, "init_second_pass: a %lu, c %lu, state %lu\n", \ 1120 | p->a, p->c, p->state); \ 1121 | return 1; \ 1122 | } \ 1123 | \ 1124 | /* Step the pseudorandom number generator until its state reaches \ 1125 | * another test ID between 0 and the test count. \ 1126 | * This use a linear congruential pseudorandom number generator, \ 1127 | * with the power-of-two ceiling of the test count as the modulus, the \ 1128 | * masked seed as the multiplier, and a prime as the increment. For \ 1129 | * each generated value < the test count, run the corresponding test. \ 1130 | * This will visit all IDs 0 <= X < mod once before repeating, \ 1131 | * with a starting position chosen based on the initial seed. \ 1132 | * For details, see: Knuth, The Art of Computer Programming \ 1133 | * Volume. 2, section 3.2.1. */ \ 1134 | void greatest_prng_step(int id) { \ 1135 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1136 | do { \ 1137 | p->state = ((p->a * p->state) + p->c) & (p->m - 1); \ 1138 | } while (p->state >= p->count_ceil); \ 1139 | } \ 1140 | \ 1141 | void GREATEST_INIT(void) { \ 1142 | /* Suppress unused function warning if features aren't used */ \ 1143 | (void)greatest_run_suite; \ 1144 | (void)greatest_parse_options; \ 1145 | (void)greatest_prng_step; \ 1146 | (void)greatest_prng_init_first_pass; \ 1147 | (void)greatest_prng_init_second_pass; \ 1148 | (void)greatest_set_test_suffix; \ 1149 | \ 1150 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 1151 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 1152 | GREATEST_SET_TIME(greatest_info.begin); \ 1153 | } \ 1154 | \ 1155 | /* Report passes, failures, skipped tests, the number of \ 1156 | * assertions, and the overall run time. */ \ 1157 | void GREATEST_PRINT_REPORT(void) { \ 1158 | if (!GREATEST_LIST_ONLY()) { \ 1159 | update_counts_and_reset_suite(); \ 1160 | GREATEST_SET_TIME(greatest_info.end); \ 1161 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1162 | "\nTotal: %u test%s", \ 1163 | greatest_info.tests_run, \ 1164 | greatest_info.tests_run == 1 ? "" : "s"); \ 1165 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 1166 | greatest_info.end); \ 1167 | GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ 1168 | greatest_info.assertions, \ 1169 | greatest_info.assertions == 1 ? "" : "s"); \ 1170 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1171 | "Pass: %u, fail: %u, skip: %u.\n", \ 1172 | greatest_info.passed, \ 1173 | greatest_info.failed, greatest_info.skipped); \ 1174 | } \ 1175 | } \ 1176 | \ 1177 | greatest_type_info greatest_type_info_memory = { \ 1178 | greatest_memory_equal_cb, greatest_memory_printf_cb, \ 1179 | }; \ 1180 | \ 1181 | greatest_run_info greatest_info 1182 | 1183 | /* Handle command-line arguments, etc. */ 1184 | #define GREATEST_MAIN_BEGIN() \ 1185 | do { \ 1186 | GREATEST_INIT(); \ 1187 | greatest_parse_options(argc, argv); \ 1188 | } while (0) 1189 | 1190 | /* Report results, exit with exit status based on results. */ 1191 | #define GREATEST_MAIN_END() \ 1192 | do { \ 1193 | GREATEST_PRINT_REPORT(); \ 1194 | return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ 1195 | } while (0) 1196 | 1197 | /* Make abbreviations without the GREATEST_ prefix for the 1198 | * most commonly used symbols. */ 1199 | #if GREATEST_USE_ABBREVS 1200 | #define TEST GREATEST_TEST 1201 | #define SUITE GREATEST_SUITE 1202 | #define SUITE_EXTERN GREATEST_SUITE_EXTERN 1203 | #define RUN_TEST GREATEST_RUN_TEST 1204 | #define RUN_TEST1 GREATEST_RUN_TEST1 1205 | #define RUN_SUITE GREATEST_RUN_SUITE 1206 | #define IGNORE_TEST GREATEST_IGNORE_TEST 1207 | #define ASSERT GREATEST_ASSERT 1208 | #define ASSERTm GREATEST_ASSERTm 1209 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 1210 | #define ASSERT_EQ GREATEST_ASSERT_EQ 1211 | #define ASSERT_NEQ GREATEST_ASSERT_NEQ 1212 | #define ASSERT_GT GREATEST_ASSERT_GT 1213 | #define ASSERT_GTE GREATEST_ASSERT_GTE 1214 | #define ASSERT_LT GREATEST_ASSERT_LT 1215 | #define ASSERT_LTE GREATEST_ASSERT_LTE 1216 | #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT 1217 | #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE 1218 | #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T 1219 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 1220 | #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ 1221 | #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ 1222 | #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ 1223 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 1224 | #define ASSERT_EQm GREATEST_ASSERT_EQm 1225 | #define ASSERT_NEQm GREATEST_ASSERT_NEQm 1226 | #define ASSERT_GTm GREATEST_ASSERT_GTm 1227 | #define ASSERT_GTEm GREATEST_ASSERT_GTEm 1228 | #define ASSERT_LTm GREATEST_ASSERT_LTm 1229 | #define ASSERT_LTEm GREATEST_ASSERT_LTEm 1230 | #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm 1231 | #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm 1232 | #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm 1233 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 1234 | #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm 1235 | #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm 1236 | #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm 1237 | #define PASS GREATEST_PASS 1238 | #define FAIL GREATEST_FAIL 1239 | #define SKIP GREATEST_SKIP 1240 | #define PASSm GREATEST_PASSm 1241 | #define FAILm GREATEST_FAILm 1242 | #define SKIPm GREATEST_SKIPm 1243 | #define SET_SETUP GREATEST_SET_SETUP_CB 1244 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 1245 | #define CHECK_CALL GREATEST_CHECK_CALL 1246 | #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS 1247 | #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES 1248 | 1249 | #ifdef GREATEST_VA_ARGS 1250 | #define RUN_TESTp GREATEST_RUN_TESTp 1251 | #endif 1252 | 1253 | #if GREATEST_USE_LONGJMP 1254 | #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP 1255 | #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm 1256 | #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP 1257 | #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm 1258 | #endif 1259 | 1260 | #endif /* USE_ABBREVS */ 1261 | 1262 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 1263 | } 1264 | #endif 1265 | 1266 | #endif 1267 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include "greatest.h" 2 | #include "mida.h" 3 | 4 | typedef struct test_metadata { 5 | size_t size; 6 | size_t length; 7 | } MD; 8 | 9 | void * 10 | _test_array(void *data, size_t size, size_t length) 11 | { 12 | MIDA(MD, data)->size = size; 13 | MIDA(MD, data)->length = length; 14 | return data; 15 | } 16 | 17 | void * 18 | _test_struct(void *data, size_t size, size_t length) 19 | { 20 | MIDA(MD, data)->size = size; 21 | MIDA(MD, data)->length = length; 22 | return data; 23 | } 24 | 25 | char * 26 | _test_string(char *data, size_t size) 27 | { 28 | MIDA(MD, data)->size = size; 29 | MIDA(MD, data)->length = size - 1; // Exclude null terminator 30 | return data; 31 | } 32 | 33 | void * 34 | _test_wrap(void *data, size_t size, size_t length) 35 | { 36 | MIDA(MD, data)->size = size; 37 | MIDA(MD, data)->length = length; 38 | return data; 39 | } 40 | 41 | void * 42 | test_malloc(size_t size, size_t length) 43 | { 44 | void *data = mida_malloc(MD, size, length); 45 | MIDA(MD, data)->size = size * length; 46 | MIDA(MD, data)->length = length; 47 | return data; 48 | } 49 | 50 | void * 51 | test_calloc(size_t size, size_t length) 52 | { 53 | void *data = mida_calloc(MD, size, length); 54 | MIDA(MD, data)->size = size * length; 55 | MIDA(MD, data)->length = length; 56 | return data; 57 | } 58 | 59 | void * 60 | test_realloc(void *base, size_t size, size_t length) 61 | { 62 | void *data = mida_realloc(MD, base, size, length); 63 | MIDA(MD, data)->size = size * length; 64 | MIDA(MD, data)->length = length; 65 | return data; 66 | } 67 | 68 | #define test_array(_type, ...) \ 69 | (_type *)_test_array(mida_array(MD, _type, __VA_ARGS__), \ 70 | sizeof((_type[])__VA_ARGS__), \ 71 | sizeof((_type[])__VA_ARGS__) / sizeof(_type)) 72 | 73 | #define test_struct(_type, ...) \ 74 | (_type *)_test_struct(mida_struct(MD, _type, __VA_ARGS__), sizeof(_type), \ 75 | 1) 76 | 77 | #define test_string(_string) \ 78 | _test_string(mida_string(MD, _string), sizeof(_string)) 79 | 80 | #define test_wrap(_data, _bytemap) \ 81 | _test_wrap(mida_wrap(MD, _data, _bytemap), sizeof(_bytemap) - sizeof(MD), \ 82 | (sizeof(_bytemap) - sizeof(MD)) / sizeof *_data) 83 | 84 | TEST 85 | test_init_compound_literals(void) 86 | { 87 | struct test { 88 | int *x; 89 | float *y; 90 | struct test *z; 91 | }; 92 | 93 | struct test foo = { 94 | .x = test_array(int, { 1, 2, 3 }), 95 | .y = test_array(float, { 1.0f, 2.0f, 3.0f, 4.0f }), 96 | .z = test_struct(struct test, { .x = test_array(int, { 1, 2, 3 }), 97 | .y = test_array(float, { 1.0f, 2.0f }), 98 | .z = NULL }), 99 | }; 100 | 101 | ASSERT_EQ(3, MIDA(MD, foo.x)->length); 102 | ASSERT_EQ(4, MIDA(MD, foo.y)->length); 103 | ASSERT_EQ(1, MIDA(MD, foo.z)->length); 104 | ASSERT_EQ(3, MIDA(MD, foo.z->x)->length); 105 | ASSERT_EQ(2, MIDA(MD, foo.z->y)->length); 106 | ASSERT_EQ(NULL, foo.z->z); 107 | ASSERT_EQ(sizeof(int[3]), MIDA(MD, foo.x)->size); 108 | ASSERT_EQ(sizeof(float[4]), MIDA(MD, foo.y)->size); 109 | ASSERT_EQ(sizeof(struct test), MIDA(MD, foo.z)->size); 110 | ASSERT_EQ(sizeof(int[3]), MIDA(MD, foo.z->x)->size); 111 | ASSERT_EQ(sizeof(float[2]), MIDA(MD, foo.z->y)->size); 112 | 113 | PASS(); 114 | } 115 | 116 | TEST 117 | test_init_bytemap(void) 118 | { 119 | struct test { 120 | int *x; 121 | float *y; 122 | struct test *z; 123 | }; 124 | 125 | int zx[] = { 1, 2, 3 }; 126 | float zy[] = { 1.0f, 2.0f }; 127 | struct test z = { 128 | test_wrap(zx, mida_bytemap(MD, sizeof(zx))), 129 | test_wrap(zy, mida_bytemap(MD, sizeof(zy))), 130 | NULL, 131 | }; 132 | 133 | int x[] = { 1, 2, 3 }; 134 | float y[] = { 1.0f, 2.0f, 3.0f, 4.0f }; 135 | struct test foo = { 136 | test_wrap(x, mida_bytemap(MD, sizeof(x))), 137 | test_wrap(x, mida_bytemap(MD, sizeof(y))), 138 | test_wrap(&z, mida_bytemap(MD, sizeof(z))), 139 | }; 140 | 141 | ASSERT_EQ(3, MIDA(MD, foo.x)->length); 142 | ASSERT_EQ(4, MIDA(MD, foo.y)->length); 143 | ASSERT_EQ(1, MIDA(MD, foo.z)->length); 144 | ASSERT_EQ(3, MIDA(MD, foo.z->x)->length); 145 | ASSERT_EQ(2, MIDA(MD, foo.z->y)->length); 146 | ASSERT_EQ(NULL, foo.z->z); 147 | ASSERT_EQ(sizeof(int[3]), MIDA(MD, foo.x)->size); 148 | ASSERT_EQ(sizeof(float[4]), MIDA(MD, foo.y)->size); 149 | ASSERT_EQ(sizeof(struct test), MIDA(MD, foo.z)->size); 150 | ASSERT_EQ(sizeof(int[3]), MIDA(MD, foo.z->x)->size); 151 | ASSERT_EQ(sizeof(float[2]), MIDA(MD, foo.z->y)->size); 152 | 153 | PASS(); 154 | } 155 | 156 | TEST 157 | test_different_types(void) 158 | { 159 | char *str_array = test_string("abcd"); 160 | double *double_array = test_array(double, { 1.1, 2.2, 3.3 }); 161 | 162 | ASSERT_EQ(4, MIDA(MD, str_array)->length); 163 | ASSERT_EQ(3, MIDA(MD, double_array)->length); 164 | ASSERT_EQ(sizeof("abcd"), MIDA(MD, str_array)->size); 165 | ASSERT_EQ(sizeof(double[3]), MIDA(MD, double_array)->size); 166 | 167 | PASS(); 168 | } 169 | 170 | TEST 171 | test_large_array(void) 172 | { 173 | int *large_array = 174 | test_array(int, { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 175 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }); 176 | 177 | ASSERT_EQ(20, MIDA(MD, large_array)->length); 178 | ASSERT_EQ(sizeof(int[20]), MIDA(MD, large_array)->size); 179 | ASSERT_EQ(5, large_array[4]); 180 | ASSERT_EQ(20, large_array[19]); 181 | 182 | PASS(); 183 | } 184 | 185 | TEST 186 | test_mida_malloc(void) 187 | { 188 | int *array = test_malloc(sizeof(int), 5); 189 | 190 | ASSERT_EQ(5, MIDA(MD, array)->length); 191 | ASSERT_EQ(sizeof(int) * 5, MIDA(MD, array)->size); 192 | 193 | for (size_t i = 0; i < MIDA(MD, array)->length; i++) { 194 | array[i] = (int)i * 10; 195 | } 196 | 197 | ASSERT_EQ(0, array[0]); 198 | ASSERT_EQ(10, array[1]); 199 | ASSERT_EQ(40, array[4]); 200 | 201 | mida_free(MD, array); 202 | PASS(); 203 | } 204 | 205 | TEST 206 | test_mida_calloc(void) 207 | { 208 | int *array = test_calloc(sizeof(int), 5); 209 | 210 | ASSERT_EQ(5, MIDA(MD, array)->length); 211 | ASSERT_EQ(sizeof(int) * 5, MIDA(MD, array)->size); 212 | 213 | for (size_t i = 0; i < MIDA(MD, array)->length; i++) { 214 | ASSERT_EQ(0, array[i]); 215 | } 216 | 217 | for (size_t i = 0; i < MIDA(MD, array)->length; i++) { 218 | array[i] = (int)i + 5; 219 | } 220 | 221 | ASSERT_EQ(5, array[0]); 222 | ASSERT_EQ(9, array[4]); 223 | 224 | mida_free(MD, array); 225 | PASS(); 226 | } 227 | 228 | TEST 229 | test_mida_realloc(void) 230 | { 231 | typedef struct metadata { 232 | size_t size; 233 | size_t length; 234 | } MD; 235 | 236 | float *array = test_malloc(sizeof(float), 3); 237 | 238 | ASSERT_EQ(3, MIDA(MD, array)->length); 239 | ASSERT_EQ(sizeof(float) * 3, MIDA(MD, array)->size); 240 | 241 | array[0] = 1.1f; 242 | array[1] = 2.2f; 243 | array[2] = 3.3f; 244 | 245 | array = test_realloc(array, sizeof(float), 5); 246 | 247 | // Update metadata after realloc 248 | MIDA(MD, array)->size = sizeof(float) * 5; 249 | MIDA(MD, array)->length = 5; 250 | 251 | ASSERT_EQ(5, MIDA(MD, array)->length); 252 | ASSERT_EQ(sizeof(float) * 5, MIDA(MD, array)->size); 253 | 254 | ASSERT_EQ_FMT(1.1f, array[0], "%.1f"); 255 | ASSERT_EQ_FMT(2.2f, array[1], "%.1f"); 256 | ASSERT_EQ_FMT(3.3f, array[2], "%.1f"); 257 | 258 | array[3] = 4.4f; 259 | array[4] = 5.5f; 260 | 261 | array = test_realloc(array, sizeof(float), 2); 262 | 263 | // Update metadata after realloc 264 | MIDA(MD, array)->size = sizeof(float) * 2; 265 | MIDA(MD, array)->length = 2; 266 | 267 | ASSERT_EQ(2, MIDA(MD, array)->length); 268 | ASSERT_EQ(sizeof(float) * 2, MIDA(MD, array)->size); 269 | 270 | ASSERT_EQ_FMT(1.1f, array[0], "%.1f"); 271 | ASSERT_EQ_FMT(2.2f, array[1], "%.1f"); 272 | 273 | mida_free(MD, array); 274 | PASS(); 275 | } 276 | 277 | TEST 278 | test_mida_realloc_null(void) 279 | { 280 | char *array = test_realloc(NULL, sizeof(char), 5); 281 | 282 | ASSERT_EQ(5, MIDA(MD, array)->length); 283 | ASSERT_EQ(sizeof(char) * 5, MIDA(MD, array)->size); 284 | array[0] = 'T'; 285 | array[1] = 'E'; 286 | array[2] = 'S'; 287 | array[3] = 'T'; 288 | array[4] = '\0'; 289 | 290 | ASSERT_STR_EQ("TEST", array); 291 | 292 | mida_free(MD, array); 293 | PASS(); 294 | } 295 | 296 | TEST 297 | test_deep_nested_arrays(void) 298 | { 299 | // Create a structure with nested arrays - strings within arrays within 300 | // arrays 301 | struct { 302 | char ***nested_arrays; 303 | } container = { 304 | .nested_arrays = test_array( 305 | char **, 306 | { test_array(char *, { test_array(char, { 'f', 'o', 'o', '\0' }), 307 | test_string("bar") }), 308 | test_array(char *, { test_string("foo") }) }) 309 | }; 310 | 311 | // Test the outermost array (has 2 elements) 312 | ASSERT_EQ(2, MIDA(MD, container.nested_arrays)->length); 313 | 314 | // Test the first inner array (has 2 elements: "foo" and "bar") 315 | ASSERT_EQ(2, MIDA(MD, container.nested_arrays[0])->length); 316 | 317 | // Test the second inner array (has 1 element: "foo") 318 | ASSERT_EQ(1, MIDA(MD, container.nested_arrays[1])->length); 319 | 320 | // Test string contents of the innermost arrays 321 | ASSERT_EQ(4, MIDA(MD, container.nested_arrays[0][0])->length); 322 | // When using strings, the length excludes the null terminator 323 | ASSERT_EQ(3, MIDA(MD, container.nested_arrays[0][1])->length); 324 | ASSERT_EQ(3, MIDA(MD, container.nested_arrays[1][0])->length); 325 | 326 | ASSERT_STR_EQ("foo", container.nested_arrays[0][0]); 327 | ASSERT_STR_EQ("bar", container.nested_arrays[0][1]); 328 | ASSERT_STR_EQ("foo", container.nested_arrays[1][0]); 329 | 330 | PASS(); 331 | } 332 | 333 | TEST 334 | test_shallow_mida_deep_nesting(void) 335 | { 336 | // Define a regular C array of strings (char arrays) 337 | const char *regular_strings[] = { "hello", "world" }; 338 | 339 | // Define a regular 2D int array 340 | int regular_2d_array[][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; 341 | 342 | // Create an array where only the outer container is mida-tracked 343 | // but the inner elements are regular C arrays 344 | struct { 345 | void **mixed_array; 346 | } container = { 347 | .mixed_array = test_array( 348 | void *, 349 | { 350 | regular_strings, // Regular array of strings 351 | regular_2d_array, // Regular 2D array 352 | (const char *[]){ "foo", "bar", "baz" } // Unnamed array 353 | }) 354 | }; 355 | 356 | // Test that the outermost array has metadata 357 | ASSERT_EQ(3, MIDA(MD, container.mixed_array)->length); 358 | ASSERT_EQ(sizeof(void *[3]), MIDA(MD, container.mixed_array)->size); 359 | 360 | // Access the inner arrays as regular C arrays (no mida metadata) 361 | const char **strings = (const char **)container.mixed_array[0]; 362 | ASSERT_STR_EQ("hello", strings[0]); 363 | ASSERT_STR_EQ("world", strings[1]); 364 | 365 | int(*matrix)[3] = (int(*)[3])container.mixed_array[1]; 366 | ASSERT_EQ(1, matrix[0][0]); 367 | ASSERT_EQ(5, matrix[1][1]); 368 | ASSERT_EQ(9, matrix[2][2]); 369 | 370 | const char **more_strings = (const char **)container.mixed_array[2]; 371 | ASSERT_STR_EQ("foo", more_strings[0]); 372 | ASSERT_STR_EQ("bar", more_strings[1]); 373 | ASSERT_STR_EQ("baz", more_strings[2]); 374 | 375 | // We can't use MIDA to access metadata on the inner elements 376 | // because they're not mida-managed arrays 377 | 378 | PASS(); 379 | } 380 | 381 | TEST 382 | test_custom_metadata(void) 383 | { 384 | // Define a custom metadata structure with additional fields 385 | struct custom_metadata { 386 | int flags; 387 | char tag[16]; 388 | }; 389 | 390 | // Allocate memory with extended metadata 391 | int *array = mida_malloc(struct custom_metadata, sizeof(int), 5); 392 | 393 | // Set metadata manually 394 | struct custom_metadata *meta = MIDA(struct custom_metadata, array); 395 | meta->flags = 42; 396 | strcpy(meta->tag, "test-tag"); 397 | 398 | // Verify data and metadata 399 | ASSERT_EQ(42, meta->flags); 400 | ASSERT_STR_EQ("test-tag", meta->tag); 401 | 402 | mida_free(struct custom_metadata, array); 403 | PASS(); 404 | } 405 | 406 | TEST 407 | test_custom_calloc(void) 408 | { 409 | // Define a custom metadata structure with additional fields 410 | struct custom_metadata { 411 | int flags; 412 | float version; 413 | }; 414 | 415 | // Allocate zeroed memory with custom metadata 416 | int *array = mida_calloc(struct custom_metadata, sizeof(int), 5); 417 | 418 | // Set metadata manually 419 | struct custom_metadata *meta = MIDA(struct custom_metadata, array); 420 | meta->flags = 0x1234; 421 | meta->version = 1.5f; 422 | 423 | // Verify metadata 424 | ASSERT_EQ(0x1234, meta->flags); 425 | ASSERT_EQ_FMT(1.5f, meta->version, "%.1f"); 426 | 427 | mida_free(struct custom_metadata, array); 428 | PASS(); 429 | } 430 | 431 | SUITE(suite_compound_literals) 432 | { 433 | RUN_TEST(test_init_compound_literals); 434 | RUN_TEST(test_init_bytemap); 435 | RUN_TEST(test_different_types); 436 | RUN_TEST(test_large_array); 437 | RUN_TEST(test_deep_nested_arrays); 438 | RUN_TEST(test_shallow_mida_deep_nesting); 439 | } 440 | 441 | SUITE(suite_stdlib) 442 | { 443 | RUN_TEST(test_mida_malloc); 444 | RUN_TEST(test_mida_calloc); 445 | RUN_TEST(test_mida_realloc); 446 | RUN_TEST(test_mida_realloc_null); 447 | } 448 | 449 | SUITE(suite_custom_metadata) 450 | { 451 | RUN_TEST(test_custom_metadata); 452 | RUN_TEST(test_custom_calloc); 453 | } 454 | 455 | GREATEST_MAIN_DEFS(); 456 | 457 | int 458 | main(int argc, char *argv[]) 459 | { 460 | GREATEST_MAIN_BEGIN(); 461 | RUN_SUITE(suite_compound_literals); 462 | RUN_SUITE(suite_stdlib); 463 | RUN_SUITE(suite_custom_metadata); 464 | GREATEST_MAIN_END(); 465 | } 466 | --------------------------------------------------------------------------------