├── .github └── workflows │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── adopt.c ├── adopt.h ├── examples ├── loop.c └── parse.c ├── rename.pl └── tests ├── adopt.c ├── adopt_clar.c ├── clar.c ├── clar.h ├── clar ├── fixtures.h ├── fs.h ├── print.h ├── sandbox.h └── summary.h └── generate.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | repository_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Build 16 | run: | 17 | cmake -B ${{github.workspace}}/build 18 | cmake --build ${{github.workspace}}/build 19 | - name: Test 20 | run: ./adopt_tests 21 | working-directory: ${{github.workspace}}/build 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/clar.suite 2 | tests/.clarcache 3 | build 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake build script for adopt tests 2 | # 3 | # Building (out of source build): 4 | # > mkdir build && cd build 5 | # > cmake .. [-DSETTINGS=VALUE] 6 | # > cmake --build . 7 | # 8 | # Testing: 9 | # > ctest -V 10 | 11 | PROJECT(adopt C) 12 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 13 | 14 | # Platform specific compilation flags 15 | IF (MSVC) 16 | STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 17 | 18 | # /GF - String pooling 19 | # /MP - Parallel build 20 | SET(CMAKE_C_FLAGS "/GF /MP /nologo ${CMAKE_C_FLAGS}") 21 | 22 | # /Zi - Create debugging information 23 | # /Od - Disable optimization 24 | # /D_DEBUG - #define _DEBUG 25 | # /MTd - Statically link the multithreaded debug version of the CRT 26 | # /MDd - Dynamically link the multithreaded debug version of the CRT 27 | # /RTC1 - Run time checks 28 | SET(CMAKE_C_FLAGS_DEBUG "/Zi /Od /D_DEBUG /RTC1 ${CRT_FLAG_DEBUG}") 29 | 30 | # /DNDEBUG - Disables asserts 31 | # /MT - Statically link the multithreaded release version of the CRT 32 | # /MD - Dynamically link the multithreaded release version of the CRT 33 | # /O2 - Optimize for speed 34 | # /Oy - Enable frame pointer omission (FPO) (otherwise CMake will automatically turn it off) 35 | # /GL - Link time code generation (whole program optimization) 36 | # /Gy - Function-level linking 37 | SET(CMAKE_C_FLAGS_RELEASE "/DNDEBUG /O2 /Oy /GL /Gy ${CRT_FLAG_RELEASE}") 38 | 39 | # /Oy- - Disable frame pointer omission (FPO) 40 | SET(CMAKE_C_FLAGS_RELWITHDEBINFO "/DNDEBUG /Zi /O2 /Oy- /GL /Gy ${CRT_FLAG_RELEASE}") 41 | 42 | # /O1 - Optimize for size 43 | SET(CMAKE_C_FLAGS_MINSIZEREL "/DNDEBUG /O1 /Oy /GL /Gy ${CRT_FLAG_RELEASE}") 44 | 45 | # /DYNAMICBASE - Address space load randomization (ASLR) 46 | # /NXCOMPAT - Data execution prevention (DEP) 47 | # /LARGEADDRESSAWARE - >2GB user address space on x86 48 | # /VERSION - Embed version information in PE header 49 | SET(CMAKE_EXE_LINKER_FLAGS "/DYNAMICBASE /NXCOMPAT /LARGEADDRESSAWARE") 50 | 51 | # /DEBUG - Create a PDB 52 | # /LTCG - Link time code generation (whole program optimization) 53 | # /OPT:REF /OPT:ICF - Fold out duplicate code at link step 54 | # /INCREMENTAL:NO - Required to use /LTCG 55 | # /DEBUGTYPE:cv,fixup - Additional data embedded in the PDB (requires /INCREMENTAL:NO, so not on for Debug) 56 | SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG") 57 | SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO") 58 | SET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO /DEBUGTYPE:cv,fixup") 59 | SET(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO") 60 | 61 | ELSE () 62 | 63 | SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes ${CMAKE_C_FLAGS}") 64 | 65 | IF (WIN32 AND NOT CYGWIN) 66 | SET(CMAKE_C_FLAGS_DEBUG "-D_DEBUG") 67 | ENDIF () 68 | 69 | IF (MINGW) 70 | # MinGW >= 3.14 uses the C99-style stdio functions 71 | # automatically, but forks like mingw-w64 still want 72 | # us to define this in order to use them 73 | ADD_DEFINITIONS(-D__USE_MINGW_ANSI_STDIO=1) 74 | ENDIF () 75 | 76 | IF (PROFILE) 77 | SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}") 78 | SET(CMAKE_EXE_LINKER_FLAGS "-pg ${CMAKE_EXE_LINKER_FLAGS}") 79 | ENDIF () 80 | ENDIF() 81 | 82 | 83 | IF(NOT CMAKE_CONFIGURATION_TYPES) 84 | # Build Debug by default 85 | IF (NOT CMAKE_BUILD_TYPE) 86 | SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 87 | ENDIF () 88 | ELSE() 89 | # Using a multi-configuration generator eg MSVC or Xcode 90 | # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE 91 | ENDIF() 92 | 93 | 94 | FILE(GLOB ALL_SRC adopt.c adopt.h) 95 | INCLUDE_DIRECTORIES(.) 96 | 97 | FIND_PACKAGE(PythonInterp REQUIRED) 98 | 99 | SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests") 100 | 101 | FILE(GLOB_RECURSE SRC_TEST ${ALL_SRC} 102 | tests/*.c tests/*.h tests/clar/*.c tests/clar/*.h) 103 | 104 | ADD_CUSTOM_COMMAND( 105 | OUTPUT ${CLAR_PATH}/clar.suite 106 | COMMAND ${PYTHON_EXECUTABLE} generate.py -f . 107 | DEPENDS ${SRC_TEST} 108 | WORKING_DIRECTORY ${CLAR_PATH} 109 | ) 110 | 111 | SET_SOURCE_FILES_PROPERTIES( 112 | ${CLAR_PATH}/clar.c 113 | PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) 114 | 115 | ADD_EXECUTABLE(adopt_tests ${SRC_TEST}) 116 | ADD_EXECUTABLE(example_parse ${ALL_SRC} examples/parse.c) 117 | ADD_EXECUTABLE(example_loop ${ALL_SRC} examples/loop.c) 118 | 119 | IF (WIN32) 120 | TARGET_LINK_LIBRARIES(adopt_tests ws2_32) 121 | ENDIF () 122 | 123 | SET_TARGET_PROPERTIES(adopt_tests PROPERTIES COMPILE_DEFINITIONS "CLAR") 124 | 125 | ENABLE_TESTING() 126 | ADD_TEST(adopt_tests adopt_tests) 127 | 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Edward Thomson 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Adopt 2 | ===== 3 | 4 | A portable command-line argument parser for C that handles short 5 | (`-a`, `-b foo`, etc) and long (`--arg-a`, `--arg-b=foo`) style 6 | options. It is meant to be compatible with GNU getopt in 7 | command-line usage (though not as an API) and available under an 8 | MIT license. 9 | 10 | Adopt can also produces help syntax and usage messages based on 11 | the arguments you accept, so that you don't need to remember to 12 | update your usage messages when you add a new option. 13 | 14 | Types of options 15 | ---------------- 16 | 17 | * Boolean values are options that do not take a value, and are 18 | either set or unset, for example `-v` or `--verbose`. If a 19 | boolean has a long name (eg `--verbose`) then it implicitly has 20 | a negation prefixed by `no-` (in this case, `--no-verbose`). 21 | The given value will be set to `1` when the boolean is given on 22 | the command line and `0` when its negation is given. 23 | * Accumulators are options that can be provided multiple times to 24 | increase its value. For example, `-v` will set verbosity to `1`, 25 | but `-vvv` will set verbosity to `3`. 26 | * Switches are options that do not take a value on the command 27 | line, for example `--long` or `--short`. When a switch is present 28 | on the command line, a variable will be set to a predetermined value. 29 | This is useful for (say) having a `length` variable that both 30 | `--long` and `--short` will update. 31 | * Values are options that take a value, for example `-m 2` or 32 | `--level=2`. 33 | * Literal separators are bare double-dashes, `--` as a lone 34 | word, indicating that further words will be treated as 35 | arguments (even if they begin with a dash). 36 | * Arguments are bare words, not prefixed with a single or double dash, 37 | for example `filename.txt`. 38 | * Argument lists are the remainder of arguments, not prefixed 39 | with dashes. For example, an array: `file1.txt`, `file2.txt`, 40 | ... 41 | 42 | Specifying command-line options 43 | ------------------------------- 44 | 45 | Options should be specified as an array of `adopt_spec` elements, 46 | elements, terminated with an `adopt_spec` initialized to zeroes. 47 | 48 | ```c 49 | int debug = 0; 50 | int verbose = 0; 51 | int volume = 1; 52 | char *channel = "default"; 53 | char *filename1 = NULL; 54 | char *filename2 = NULL; 55 | 56 | adopt_spec opt_specs[] = { 57 | /* `verbose`, above, will increment each time `-v` is specified. */ 58 | { ADOPT_ACCUMULATOR, "verbose", 'v', &verbose }, 59 | 60 | /* `debug`, above, will be set to `1` when `--debug` is specified, 61 | * or to `0` if `--no-debug` is specified. 62 | */ 63 | { ADOPT_BOOL, "debug", 'd', &debug }, 64 | 65 | /* `volume` will be set to `0` when `--quiet` is specified, and 66 | * set to `2` when `--loud` is specified. if neither is specified, 67 | * it will retain its default value of `1`, defined above. 68 | */ 69 | { ADOPT_SWITCH, "quiet", 'q', &volume, 0 }, 70 | { ADOPT_SWITCH, "loud", 'l', &volume, 2 }, 71 | 72 | /* `channel` will be set to the given argument if an argument is 73 | * provided to `--channel`, or will be set to `NULL` if no argument 74 | * was specified. 75 | */ 76 | { ADOPT_VALUE, "channel", 'c', &channel, NULL }, 77 | 78 | /* A double dash (`--`) may be specified to indicate that the parser 79 | * should stop treated arguments as possible options and treat 80 | * remaining arguments as literal arguments; which allows a user 81 | * to specify `--loud` as an actual literal filename (below). 82 | */ 83 | { ADOPT_LITERAL }, 84 | 85 | /* `filename` will be set to the first bare argument */ 86 | { ADOPT_ARG, NULL, 0, &filename1 }, 87 | 88 | /* `filename2` will be set to the first bare argument */ 89 | { ADOPT_ARG, NULL, 0, &filename2 }, 90 | 91 | /* End of the specification list. */ 92 | { 0 }, 93 | }; 94 | ``` 95 | 96 | Parsing arguments 97 | ----------------- 98 | 99 | The simplest way to parse arguments is by calling `adopt_parse`. 100 | This will parse the arguments given to it, will set the given 101 | `spec->value`s to the appropriate arguments, and will stop and 102 | return an error on any invalid input. If there are errors, you 103 | can display that information with `adopt_status_fprint`. 104 | 105 | ```c 106 | int main(int argc, const char **argv) 107 | { 108 | adopt_parser parser; 109 | adopt_opt result; 110 | const char *value; 111 | const char *file; 112 | 113 | if (adopt_parse(&result, opt_specs, argv + 1, argc - 1, ADOPT_PARSE_DEFAULT) < 0) { 114 | adopt_status_fprint(stderr, &opt); 115 | adopt_usage_fprint(stderr, argv[0], opt_specs); 116 | return 129; 117 | } 118 | } 119 | ``` 120 | 121 | Parsing arguments individually 122 | ------------------------------- 123 | 124 | For more control over your parsing, you may iterate over the 125 | parser in a loop. After initializing the parser by calling 126 | `adopt_parser_init` with the `adopt_spec`s and the command-line 127 | arguments given to your program, you can call `adopt_parser_next` 128 | in a loop to handle each option. 129 | 130 | ```c 131 | int main(int argc, const char **argv) 132 | { 133 | adopt_parser parser; 134 | adopt_opt opt; 135 | const char *value; 136 | const char *file; 137 | 138 | adopt_parser_init(&parser, opt_specs, argv + 1, argc - 1); 139 | 140 | while (adopt_parser_next(&opt, &parser)) { 141 | /* 142 | * Although regular practice would be to simply let the parser 143 | * set the variables for you, there is information about the 144 | * parsed argument available in the `opt` struct. For example, 145 | * `opt.spec` will point to the `adopt_spec` that was parsed, 146 | * or `NULL` if it did not match a specification. 147 | */ 148 | if (!opt.spec) { 149 | fprintf(stderr, "Unknown option: %s\n", opt.arg); 150 | return 129; 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | Required arguments 157 | ------------------ 158 | 159 | Some value options may require arguments, and it would be an error for 160 | callers to omit those. 161 | 162 | The simplest way to detect unspecified values is to inspect for `NULL`. 163 | First, set a variable to a default value, like with `channel` above. If 164 | the `--channel` option is specified without a value, then `channel` will 165 | be set to `NULL` when it is read. This can be detected either during the 166 | processing loop, or after. For example: 167 | 168 | ```c 169 | if (!channel) { 170 | fprintf(stderr, "Option: %s requires an argument\n", opt.arg); 171 | return 129; 172 | } 173 | ``` 174 | 175 | ### Inspecting the `opt` struct 176 | 177 | If you cannot use a sentinal value, perhaps because `NULL` is a useful 178 | value, you can also inspect the `opt` struct. For example: 179 | 180 | ```c 181 | if (opt.spec->value == &channel && !channel) { 182 | fprintf(stderr, "Option: %s requires an argument\n", opt.arg); 183 | return 129; 184 | } 185 | ``` 186 | 187 | Displaying usage 188 | ---------------- 189 | 190 | If you provide additional usage information in your `adopt_spec`s, you 191 | can have adopt print this usage information for you based on the options 192 | you accept. This allows you to define a single structure for both 193 | argument parsing _and_ help information, which keeps your help in sync 194 | with the code automatically. 195 | 196 | Simply set the three additional structure members: `value_name` is the 197 | one-word description of the argument (for options of type `ADOPT_VALUE`), 198 | `help` is a short description of the option, and `usage` is a set of 199 | flags that determine whether all or part of the option is required. 200 | 201 | (Specifying an option or a value does not affect parsing, it only affects 202 | how help is displayed. If an option or value is not required, its help 203 | will display it within square brackets.) 204 | 205 | Adding these to the above example: 206 | 207 | ```c 208 | adopt_spec opt_specs[] = { 209 | /* For bools and switches, you probably only want to set the help. 210 | * There is no value to describe, and these are unlikely to be 211 | * required. 212 | */ 213 | { ADOPT_BOOL, "verbose", 'v', &verbose, 214 | NULL, "Turn on verbose mode", 0 }, 215 | 216 | /* Specifying that an option is an `ADOPT_USAGE_CHOICE` indicates 217 | * that it is orthogonal to the previous entry. These two options 218 | * will be rendered together in help output as `[-q|-l]`. 219 | */ 220 | { ADOPT_SWITCH, "quiet", 'q', &volume, 0, 221 | NULL, "Emit no output", 0 }, 222 | { ADOPT_SWITCH, "loud", 'l', &volume, 2 }, 223 | NULL, "Emit louder than usual output", ADOPT_USAGE_CHOICE }, 224 | 225 | /* Set the `value_name` and specify that the value is required. 226 | * This will be rendered in help output as `-c `; 227 | * if it was not required, it would be rendered as `-c []`. 228 | */ 229 | { ADOPT_VALUE, "channel", 'c', &channel, NULL, 230 | "channel", "Set the channel number", ADOPT_USAGE_VALUE_REQUIRED }, 231 | 232 | { ADOPT_LITERAL }, 233 | 234 | /* `filename` is required. It will be rendered in help output as 235 | * ``. 236 | */ 237 | { ADOPT_ARG, NULL, 0, &filename1, NULL, 238 | "file1", "The first filename", ADOPT_USAGE_REQUIRED }, 239 | 240 | /* `filename` is not required. It will be rendered in help output 241 | * as `[]`. 242 | */ 243 | { ADOPT_ARG, NULL, 0, &filename2, NULL, 244 | "file2", "The second (optional) filename", 0 }, 245 | 246 | /* End of the specification list. */ 247 | { 0 }, 248 | }; 249 | ``` 250 | 251 | If you call `adopt_usage_fprint` with the above specifications, it will emit: 252 | 253 | ``` 254 | usage: ./example [-v] [-q] [-l] [-c ] -- [] 255 | ``` 256 | 257 | Inclusion in your product 258 | ------------------------- 259 | 260 | If you loathe additional namespaces, and want to use adopt inside your 261 | product without using the `adopt_` prefix, you can use the included 262 | `rename.pl` script to give the functions and structs your own prefix. 263 | 264 | For example: 265 | 266 | ```bash 267 | % ./rename.pl ed_opt 268 | ``` 269 | 270 | Will product `ed_opt.c` and `ed_opt.h` that can be included in an 271 | application, with the variables renamed to `ed_opt` (instead of `adopt`) 272 | and enum names renamed to `ED_OPT` (instead of `ADOPT`). 273 | 274 | Or simply: 275 | 276 | ```bash 277 | % ./rename.pl opt 278 | ``` 279 | 280 | To produce `opt.c` and `opt.h`, with variables prefixed with `opt`. 281 | 282 | About Adopt 283 | ----------- 284 | Adopt was written by Edward Thomson 285 | and is available under the MIT License. Please see the 286 | included file `LICENSE` for more information. 287 | 288 | Adopt takes its name from the only entry in `/usr/dict/words` 289 | that ends in the letters **opt**. But if you happen to like adopt, 290 | consider a donation to your local pet shelter or human society. 291 | 292 | -------------------------------------------------------------------------------- /adopt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c), Edward Thomson 3 | * All rights reserved. 4 | * 5 | * This file is part of adopt, distributed under the MIT license. 6 | * For full terms and conditions, see the included LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #if defined(__sun) || defined(__illumos__) 16 | # include 17 | #endif 18 | 19 | #include "adopt.h" 20 | 21 | #ifdef _WIN32 22 | # include 23 | #else 24 | # include 25 | # include 26 | #endif 27 | 28 | #ifdef _MSC_VER 29 | # define INLINE(type) static __inline type 30 | #else 31 | # define INLINE(type) static inline type 32 | #endif 33 | 34 | #ifdef _MSC_VER 35 | # define alloca _alloca 36 | #endif 37 | 38 | #define spec_is_option_type(x) \ 39 | ((x)->type == ADOPT_TYPE_BOOL || \ 40 | (x)->type == ADOPT_TYPE_SWITCH || \ 41 | (x)->type == ADOPT_TYPE_VALUE) 42 | 43 | INLINE(const adopt_spec *) spec_for_long( 44 | int *is_negated, 45 | int *has_value, 46 | const char **value, 47 | const adopt_parser *parser, 48 | const char *arg) 49 | { 50 | const adopt_spec *spec; 51 | char *eql; 52 | size_t eql_pos; 53 | 54 | eql = strchr(arg, '='); 55 | eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg); 56 | 57 | for (spec = parser->specs; spec->type; ++spec) { 58 | /* Handle -- (everything after this is literal) */ 59 | if (spec->type == ADOPT_TYPE_LITERAL && arg[0] == '\0') 60 | return spec; 61 | 62 | /* Handle --no-option arguments for bool types */ 63 | if (spec->type == ADOPT_TYPE_BOOL && 64 | strncmp(arg, "no-", 3) == 0 && 65 | strcmp(arg + 3, spec->name) == 0) { 66 | *is_negated = 1; 67 | return spec; 68 | } 69 | 70 | /* Handle the typical --option arguments */ 71 | if (spec_is_option_type(spec) && 72 | spec->name && 73 | strcmp(arg, spec->name) == 0) 74 | return spec; 75 | 76 | /* Handle --option=value arguments */ 77 | if (spec->type == ADOPT_TYPE_VALUE && 78 | spec->name && eql && 79 | strncmp(arg, spec->name, eql_pos) == 0 && 80 | spec->name[eql_pos] == '\0') { 81 | *has_value = 1; 82 | *value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL; 83 | return spec; 84 | } 85 | } 86 | 87 | return NULL; 88 | } 89 | 90 | INLINE(const adopt_spec *) spec_for_short( 91 | const char **value, 92 | const adopt_parser *parser, 93 | const char *arg) 94 | { 95 | const adopt_spec *spec; 96 | 97 | for (spec = parser->specs; spec->type; ++spec) { 98 | /* Handle -svalue short options with a value */ 99 | if (spec->type == ADOPT_TYPE_VALUE && 100 | arg[0] == spec->alias && 101 | arg[1] != '\0') { 102 | *value = &arg[1]; 103 | return spec; 104 | } 105 | 106 | /* Handle typical -s short options */ 107 | if (arg[0] == spec->alias) { 108 | *value = NULL; 109 | return spec; 110 | } 111 | } 112 | 113 | return NULL; 114 | } 115 | 116 | INLINE(const adopt_spec *) spec_for_arg(adopt_parser *parser) 117 | { 118 | const adopt_spec *spec; 119 | size_t args = 0; 120 | 121 | for (spec = parser->specs; spec->type; ++spec) { 122 | if (spec->type == ADOPT_TYPE_ARG) { 123 | if (args == parser->arg_idx) { 124 | parser->arg_idx++; 125 | return spec; 126 | } 127 | 128 | args++; 129 | } 130 | 131 | if (spec->type == ADOPT_TYPE_ARGS && args == parser->arg_idx) 132 | return spec; 133 | } 134 | 135 | return NULL; 136 | } 137 | 138 | INLINE(int) spec_is_choice(const adopt_spec *spec) 139 | { 140 | return ((spec + 1)->type && 141 | ((spec + 1)->usage & ADOPT_USAGE_CHOICE)); 142 | } 143 | 144 | /* 145 | * If we have a choice with switches and bare arguments, and we see 146 | * the switch, then we no longer expect the bare argument. 147 | */ 148 | INLINE(void) consume_choices(const adopt_spec *spec, adopt_parser *parser) 149 | { 150 | /* back up to the beginning of the choices */ 151 | while (spec->type && (spec->usage & ADOPT_USAGE_CHOICE)) 152 | --spec; 153 | 154 | if (!spec_is_choice(spec)) 155 | return; 156 | 157 | do { 158 | if (spec->type == ADOPT_TYPE_ARG) 159 | parser->arg_idx++; 160 | ++spec; 161 | } while(spec->type && (spec->usage & ADOPT_USAGE_CHOICE)); 162 | } 163 | 164 | static adopt_status_t parse_long(adopt_opt *opt, adopt_parser *parser) 165 | { 166 | const adopt_spec *spec; 167 | char *arg = parser->args[parser->idx++]; 168 | const char *value = NULL; 169 | int is_negated = 0, has_value = 0; 170 | 171 | opt->arg = arg; 172 | 173 | if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) { 174 | opt->spec = NULL; 175 | opt->status = ADOPT_STATUS_UNKNOWN_OPTION; 176 | goto done; 177 | } 178 | 179 | opt->spec = spec; 180 | 181 | /* Future options parsed as literal */ 182 | if (spec->type == ADOPT_TYPE_LITERAL) 183 | parser->in_literal = 1; 184 | 185 | /* --bool or --no-bool */ 186 | else if (spec->type == ADOPT_TYPE_BOOL && spec->value) 187 | *((int *)spec->value) = !is_negated; 188 | 189 | /* --accumulate */ 190 | else if (spec->type == ADOPT_TYPE_ACCUMULATOR && spec->value) 191 | *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; 192 | 193 | /* --switch */ 194 | else if (spec->type == ADOPT_TYPE_SWITCH && spec->value) 195 | *((int *)spec->value) = spec->switch_value; 196 | 197 | /* Parse values as "--foo=bar" or "--foo bar" */ 198 | else if (spec->type == ADOPT_TYPE_VALUE) { 199 | if (has_value) 200 | opt->value = (char *)value; 201 | else if ((parser->idx + 1) <= parser->args_len) 202 | opt->value = parser->args[parser->idx++]; 203 | 204 | if (spec->value) 205 | *((char **)spec->value) = opt->value; 206 | } 207 | 208 | /* Required argument was not provided */ 209 | if (spec->type == ADOPT_TYPE_VALUE && 210 | !opt->value && 211 | !(spec->usage & ADOPT_USAGE_VALUE_OPTIONAL)) 212 | opt->status = ADOPT_STATUS_MISSING_VALUE; 213 | else 214 | opt->status = ADOPT_STATUS_OK; 215 | 216 | consume_choices(opt->spec, parser); 217 | 218 | done: 219 | return opt->status; 220 | } 221 | 222 | static adopt_status_t parse_short(adopt_opt *opt, adopt_parser *parser) 223 | { 224 | const adopt_spec *spec; 225 | char *arg = parser->args[parser->idx++]; 226 | const char *value; 227 | 228 | opt->arg = arg; 229 | 230 | if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) { 231 | opt->spec = NULL; 232 | opt->status = ADOPT_STATUS_UNKNOWN_OPTION; 233 | goto done; 234 | } 235 | 236 | opt->spec = spec; 237 | 238 | if (spec->type == ADOPT_TYPE_BOOL && spec->value) 239 | *((int *)spec->value) = 1; 240 | 241 | else if (spec->type == ADOPT_TYPE_ACCUMULATOR && spec->value) 242 | *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; 243 | 244 | else if (spec->type == ADOPT_TYPE_SWITCH && spec->value) 245 | *((int *)spec->value) = spec->switch_value; 246 | 247 | /* Parse values as "-ifoo" or "-i foo" */ 248 | else if (spec->type == ADOPT_TYPE_VALUE) { 249 | if (value) 250 | opt->value = (char *)value; 251 | else if ((parser->idx + 1) <= parser->args_len) 252 | opt->value = parser->args[parser->idx++]; 253 | 254 | if (spec->value) 255 | *((char **)spec->value) = opt->value; 256 | } 257 | 258 | /* 259 | * Handle compressed short arguments, like "-fbcd"; see if there's 260 | * another character after the one we processed. If not, advance 261 | * the parser index. 262 | */ 263 | if (spec->type != ADOPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') { 264 | parser->in_short++; 265 | parser->idx--; 266 | } else { 267 | parser->in_short = 0; 268 | } 269 | 270 | /* Required argument was not provided */ 271 | if (spec->type == ADOPT_TYPE_VALUE && !opt->value) 272 | opt->status = ADOPT_STATUS_MISSING_VALUE; 273 | else 274 | opt->status = ADOPT_STATUS_OK; 275 | 276 | consume_choices(opt->spec, parser); 277 | 278 | done: 279 | return opt->status; 280 | } 281 | 282 | static adopt_status_t parse_arg(adopt_opt *opt, adopt_parser *parser) 283 | { 284 | const adopt_spec *spec = spec_for_arg(parser); 285 | 286 | opt->spec = spec; 287 | opt->arg = parser->args[parser->idx]; 288 | 289 | if (!spec) { 290 | parser->idx++; 291 | opt->status = ADOPT_STATUS_UNKNOWN_OPTION; 292 | } else if (spec->type == ADOPT_TYPE_ARGS) { 293 | if (spec->value) 294 | *((char ***)spec->value) = &parser->args[parser->idx]; 295 | 296 | /* 297 | * We have started a list of arguments; the remainder of 298 | * given arguments need not be examined. 299 | */ 300 | parser->in_args = (parser->args_len - parser->idx); 301 | parser->idx = parser->args_len; 302 | opt->args_len = parser->in_args; 303 | opt->status = ADOPT_STATUS_OK; 304 | } else { 305 | if (spec->value) 306 | *((char **)spec->value) = parser->args[parser->idx]; 307 | 308 | parser->idx++; 309 | opt->status = ADOPT_STATUS_OK; 310 | } 311 | 312 | return opt->status; 313 | } 314 | 315 | static int support_gnu_style(unsigned int flags) 316 | { 317 | if ((flags & ADOPT_PARSE_FORCE_GNU) != 0) 318 | return 1; 319 | 320 | if ((flags & ADOPT_PARSE_GNU) == 0) 321 | return 0; 322 | 323 | /* TODO: Windows */ 324 | #if defined(_WIN32) && defined(UNICODE) 325 | if (_wgetenv(L"POSIXLY_CORRECT") != NULL) 326 | return 0; 327 | #else 328 | if (getenv("POSIXLY_CORRECT") != NULL) 329 | return 0; 330 | #endif 331 | 332 | return 1; 333 | } 334 | 335 | void adopt_parser_init( 336 | adopt_parser *parser, 337 | const adopt_spec specs[], 338 | char **args, 339 | size_t args_len, 340 | unsigned int flags) 341 | { 342 | assert(parser); 343 | 344 | memset(parser, 0x0, sizeof(adopt_parser)); 345 | 346 | parser->specs = specs; 347 | parser->args = args; 348 | parser->args_len = args_len; 349 | parser->flags = flags; 350 | 351 | parser->needs_sort = support_gnu_style(flags); 352 | } 353 | 354 | INLINE(const adopt_spec *) spec_for_sort( 355 | int *needs_value, 356 | const adopt_parser *parser, 357 | const char *arg) 358 | { 359 | int is_negated, has_value = 0; 360 | const char *value; 361 | const adopt_spec *spec = NULL; 362 | size_t idx = 0; 363 | 364 | *needs_value = 0; 365 | 366 | if (strncmp(arg, "--", 2) == 0) { 367 | spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]); 368 | *needs_value = !has_value; 369 | } 370 | 371 | else if (strncmp(arg, "-", 1) == 0) { 372 | spec = spec_for_short(&value, parser, &arg[1]); 373 | 374 | /* 375 | * Advance through compressed short arguments to see if 376 | * the last one has a value, eg "-xvffilename". 377 | */ 378 | while (spec && !value && arg[1 + ++idx] != '\0') 379 | spec = spec_for_short(&value, parser, &arg[1 + idx]); 380 | 381 | *needs_value = (value == NULL); 382 | } 383 | 384 | return spec; 385 | } 386 | 387 | /* 388 | * Some parsers allow for handling arguments like "file1 --help file2"; 389 | * this is done by re-sorting the arguments in-place; emulate that. 390 | */ 391 | static int sort_gnu_style(adopt_parser *parser) 392 | { 393 | size_t i, j, insert_idx = parser->idx, offset; 394 | const adopt_spec *spec; 395 | char *option, *value; 396 | int needs_value, changed = 0; 397 | 398 | parser->needs_sort = 0; 399 | 400 | for (i = parser->idx; i < parser->args_len; i++) { 401 | spec = spec_for_sort(&needs_value, parser, parser->args[i]); 402 | 403 | /* Not a "-" or "--" prefixed option. No change. */ 404 | if (!spec) 405 | continue; 406 | 407 | /* A "--" alone means remaining args are literal. */ 408 | if (spec->type == ADOPT_TYPE_LITERAL) 409 | break; 410 | 411 | option = parser->args[i]; 412 | 413 | /* 414 | * If the argument is a value type and doesn't already 415 | * have a value (eg "--foo=bar" or "-fbar") then we need 416 | * to copy the next argument as its value. 417 | */ 418 | if (spec->type == ADOPT_TYPE_VALUE && needs_value) { 419 | /* 420 | * A required value is not provided; set parser 421 | * index to this value so that we fail on it. 422 | */ 423 | if (i + 1 >= parser->args_len) { 424 | parser->idx = i; 425 | return 1; 426 | } 427 | 428 | value = parser->args[i + 1]; 429 | offset = 1; 430 | } else { 431 | value = NULL; 432 | offset = 0; 433 | } 434 | 435 | /* Caller error if args[0] is an option. */ 436 | if (i == 0) 437 | return 0; 438 | 439 | /* Shift args up one (or two) and insert the option */ 440 | for (j = i; j > insert_idx; j--) 441 | parser->args[j + offset] = parser->args[j - 1]; 442 | 443 | parser->args[insert_idx] = option; 444 | 445 | if (value) 446 | parser->args[insert_idx + 1] = value; 447 | 448 | insert_idx += (1 + offset); 449 | i += offset; 450 | 451 | changed = 1; 452 | } 453 | 454 | return changed; 455 | } 456 | 457 | adopt_status_t adopt_parser_next(adopt_opt *opt, adopt_parser *parser) 458 | { 459 | assert(opt && parser); 460 | 461 | memset(opt, 0x0, sizeof(adopt_opt)); 462 | 463 | if (parser->idx >= parser->args_len) { 464 | opt->args_len = parser->in_args; 465 | return ADOPT_STATUS_DONE; 466 | } 467 | 468 | /* Handle options in long form, those beginning with "--" */ 469 | if (strncmp(parser->args[parser->idx], "--", 2) == 0 && 470 | !parser->in_short && 471 | !parser->in_literal) 472 | return parse_long(opt, parser); 473 | 474 | /* Handle options in short form, those beginning with "-" */ 475 | else if (parser->in_short || 476 | (strncmp(parser->args[parser->idx], "-", 1) == 0 && 477 | !parser->in_literal)) 478 | return parse_short(opt, parser); 479 | 480 | /* 481 | * We've reached the first "bare" argument. In POSIX mode, all 482 | * remaining items on the command line are arguments. In GNU 483 | * mode, there may be long or short options after this. Sort any 484 | * options up to this position then re-parse the current position. 485 | */ 486 | if (parser->needs_sort && sort_gnu_style(parser)) 487 | return adopt_parser_next(opt, parser); 488 | 489 | return parse_arg(opt, parser); 490 | } 491 | 492 | INLINE(int) spec_included(const adopt_spec **specs, const adopt_spec *spec) 493 | { 494 | const adopt_spec **i; 495 | 496 | for (i = specs; *i; ++i) { 497 | if (spec == *i) 498 | return 1; 499 | } 500 | 501 | return 0; 502 | } 503 | 504 | static adopt_status_t validate_required( 505 | adopt_opt *opt, 506 | const adopt_spec specs[], 507 | const adopt_spec **given_specs) 508 | { 509 | const adopt_spec *spec, *required; 510 | int given; 511 | 512 | /* 513 | * Iterate over the possible specs to identify requirements and 514 | * ensure that those have been given on the command-line. 515 | * Note that we can have required *choices*, where one in a 516 | * list of choices must be specified. 517 | */ 518 | for (spec = specs, required = NULL, given = 0; spec->type; ++spec) { 519 | if (!required && (spec->usage & ADOPT_USAGE_REQUIRED)) { 520 | required = spec; 521 | given = 0; 522 | } else if (!required) { 523 | continue; 524 | } 525 | 526 | if (!given) 527 | given = spec_included(given_specs, spec); 528 | 529 | /* 530 | * Validate the requirement unless we're in a required 531 | * choice. In that case, keep the required state and 532 | * validate at the end of the choice list. 533 | */ 534 | if (!spec_is_choice(spec)) { 535 | if (!given) { 536 | opt->spec = required; 537 | opt->status = ADOPT_STATUS_MISSING_ARGUMENT; 538 | break; 539 | } 540 | 541 | required = NULL; 542 | given = 0; 543 | } 544 | } 545 | 546 | return opt->status; 547 | } 548 | 549 | adopt_status_t adopt_parse( 550 | adopt_opt *opt, 551 | const adopt_spec specs[], 552 | char **args, 553 | size_t args_len, 554 | unsigned int flags) 555 | { 556 | adopt_parser parser; 557 | const adopt_spec **given_specs; 558 | size_t given_idx = 0; 559 | 560 | adopt_parser_init(&parser, specs, args, args_len, flags); 561 | 562 | given_specs = alloca(sizeof(const adopt_spec *) * (args_len + 1)); 563 | 564 | while (adopt_parser_next(opt, &parser)) { 565 | if (opt->status != ADOPT_STATUS_OK && 566 | opt->status != ADOPT_STATUS_DONE) 567 | return opt->status; 568 | 569 | if ((opt->spec->usage & ADOPT_USAGE_STOP_PARSING)) 570 | return (opt->status = ADOPT_STATUS_DONE); 571 | 572 | given_specs[given_idx++] = opt->spec; 573 | } 574 | 575 | given_specs[given_idx] = NULL; 576 | 577 | return validate_required(opt, specs, given_specs); 578 | } 579 | 580 | int adopt_foreach( 581 | const adopt_spec specs[], 582 | char **args, 583 | size_t args_len, 584 | unsigned int flags, 585 | int (*callback)(adopt_opt *, void *), 586 | void *callback_data) 587 | { 588 | adopt_parser parser; 589 | adopt_opt opt; 590 | int ret; 591 | 592 | adopt_parser_init(&parser, specs, args, args_len, flags); 593 | 594 | while (adopt_parser_next(&opt, &parser)) { 595 | if ((ret = callback(&opt, callback_data)) != 0) 596 | return ret; 597 | } 598 | 599 | return 0; 600 | } 601 | 602 | static int spec_name_fprint(FILE *file, const adopt_spec *spec) 603 | { 604 | int error; 605 | 606 | if (spec->type == ADOPT_TYPE_ARG) 607 | error = fprintf(file, "%s", spec->value_name); 608 | else if (spec->type == ADOPT_TYPE_ARGS) 609 | error = fprintf(file, "%s", spec->value_name); 610 | else if (spec->alias && !(spec->usage & ADOPT_USAGE_SHOW_LONG)) 611 | error = fprintf(file, "-%c", spec->alias); 612 | else 613 | error = fprintf(file, "--%s", spec->name); 614 | 615 | return error; 616 | } 617 | 618 | int adopt_status_fprint( 619 | FILE *file, 620 | const char *command, 621 | const adopt_opt *opt) 622 | { 623 | const adopt_spec *choice; 624 | int error; 625 | 626 | if (command && (error = fprintf(file, "%s: ", command)) < 0) 627 | return error; 628 | 629 | switch (opt->status) { 630 | case ADOPT_STATUS_DONE: 631 | error = fprintf(file, "finished processing arguments (no error)\n"); 632 | break; 633 | case ADOPT_STATUS_OK: 634 | error = fprintf(file, "no error\n"); 635 | break; 636 | case ADOPT_STATUS_UNKNOWN_OPTION: 637 | error = fprintf(file, "unknown option: %s\n", opt->arg); 638 | break; 639 | case ADOPT_STATUS_MISSING_VALUE: 640 | if ((error = fprintf(file, "argument '")) < 0 || 641 | (error = spec_name_fprint(file, opt->spec)) < 0 || 642 | (error = fprintf(file, "' requires a value.\n")) < 0) 643 | break; 644 | break; 645 | case ADOPT_STATUS_MISSING_ARGUMENT: 646 | if (spec_is_choice(opt->spec)) { 647 | int is_choice = 1; 648 | 649 | if (spec_is_choice((opt->spec)+1)) 650 | error = fprintf(file, "one of"); 651 | else 652 | error = fprintf(file, "either"); 653 | 654 | if (error < 0) 655 | break; 656 | 657 | for (choice = opt->spec; is_choice; ++choice) { 658 | is_choice = spec_is_choice(choice); 659 | 660 | if (!is_choice) 661 | error = fprintf(file, " or"); 662 | else if (choice != opt->spec) 663 | error = fprintf(file, ","); 664 | 665 | if ((error < 0) || 666 | (error = fprintf(file, " '")) < 0 || 667 | (error = spec_name_fprint(file, choice)) < 0 || 668 | (error = fprintf(file, "'")) < 0) 669 | break; 670 | 671 | if (!spec_is_choice(choice)) 672 | break; 673 | } 674 | 675 | if ((error < 0) || 676 | (error = fprintf(file, " is required.\n")) < 0) 677 | break; 678 | } else { 679 | if ((error = fprintf(file, "argument '")) < 0 || 680 | (error = spec_name_fprint(file, opt->spec)) < 0 || 681 | (error = fprintf(file, "' is required.\n")) < 0) 682 | break; 683 | } 684 | 685 | break; 686 | default: 687 | error = fprintf(file, "Unknown status: %d\n", opt->status); 688 | break; 689 | } 690 | 691 | return error; 692 | } 693 | 694 | int adopt_usage_fprint( 695 | FILE *file, 696 | const char *command, 697 | const adopt_spec specs[]) 698 | { 699 | const adopt_spec *spec; 700 | int choice = 0, next_choice = 0, optional = 0; 701 | int error; 702 | 703 | if ((error = fprintf(file, "usage: %s", command)) < 0) 704 | goto done; 705 | 706 | for (spec = specs; spec->type; ++spec) { 707 | if (!choice) 708 | optional = !(spec->usage & ADOPT_USAGE_REQUIRED); 709 | 710 | next_choice = !!((spec + 1)->usage & ADOPT_USAGE_CHOICE); 711 | 712 | if (spec->usage & ADOPT_USAGE_HIDDEN) 713 | continue; 714 | 715 | if (choice) 716 | error = fprintf(file, "|"); 717 | else 718 | error = fprintf(file, " "); 719 | 720 | if (error < 0) 721 | goto done; 722 | 723 | if (optional && !choice && (error = fprintf(file, "[")) < 0) 724 | error = fprintf(file, "["); 725 | if (!optional && !choice && next_choice) 726 | error = fprintf(file, "("); 727 | 728 | if (error < 0) 729 | goto done; 730 | 731 | if (spec->type == ADOPT_TYPE_VALUE && spec->alias && 732 | !(spec->usage & ADOPT_USAGE_VALUE_OPTIONAL) && 733 | !(spec->usage & ADOPT_USAGE_SHOW_LONG)) 734 | error = fprintf(file, "-%c <%s>", spec->alias, spec->value_name); 735 | else if (spec->type == ADOPT_TYPE_VALUE && spec->alias && 736 | !(spec->usage & ADOPT_USAGE_SHOW_LONG)) 737 | error = fprintf(file, "-%c [<%s>]", spec->alias, spec->value_name); 738 | else if (spec->type == ADOPT_TYPE_VALUE && 739 | !(spec->usage & ADOPT_USAGE_VALUE_OPTIONAL)) 740 | error = fprintf(file, "--%s[=<%s>]", spec->name, spec->value_name); 741 | else if (spec->type == ADOPT_TYPE_VALUE) 742 | error = fprintf(file, "--%s=<%s>", spec->name, spec->value_name); 743 | else if (spec->type == ADOPT_TYPE_ARG) 744 | error = fprintf(file, "<%s>", spec->value_name); 745 | else if (spec->type == ADOPT_TYPE_ARGS) 746 | error = fprintf(file, "<%s>...", spec->value_name); 747 | else if (spec->type == ADOPT_TYPE_LITERAL) 748 | error = fprintf(file, "--"); 749 | else if (spec->alias && !(spec->usage & ADOPT_USAGE_SHOW_LONG)) 750 | error = fprintf(file, "-%c", spec->alias); 751 | else 752 | error = fprintf(file, "--%s", spec->name); 753 | 754 | if (error < 0) 755 | goto done; 756 | 757 | if (!optional && choice && !next_choice) 758 | error = fprintf(file, ")"); 759 | else if (optional && !next_choice) 760 | error = fprintf(file, "]"); 761 | 762 | if (error < 0) 763 | goto done; 764 | 765 | choice = next_choice; 766 | } 767 | 768 | error = fprintf(file, "\n"); 769 | 770 | done: 771 | error = (error < 0) ? -1 : 0; 772 | return error; 773 | } 774 | 775 | -------------------------------------------------------------------------------- /adopt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c), Edward Thomson 3 | * All rights reserved. 4 | * 5 | * This file is part of adopt, distributed under the MIT license. 6 | * For full terms and conditions, see the included LICENSE file. 7 | */ 8 | 9 | #ifndef ADOPT_H 10 | #define ADOPT_H 11 | 12 | #include 13 | #include 14 | 15 | /** 16 | * The type of argument to be parsed. 17 | */ 18 | typedef enum { 19 | ADOPT_TYPE_NONE = 0, 20 | 21 | /** 22 | * An option that, when specified, sets a given value to true. 23 | * This is useful for options like "--debug". A negation 24 | * option (beginning with "no-") is implicitly specified; for 25 | * example "--no-debug". The `value` pointer in the returned 26 | * option will be set to `1` when this is specified, and set to 27 | * `0` when the negation "no-" option is specified. 28 | */ 29 | ADOPT_TYPE_BOOL, 30 | 31 | /** 32 | * An option that, when specified, sets the given `value` pointer 33 | * to the specified `switch_value`. This is useful for booleans 34 | * where you do not want the implicit negation that comes with an 35 | * `ADOPT_TYPE_BOOL`, or for switches that multiplex a value, like 36 | * setting a mode. For example, `--read` may set the `value` to 37 | * `MODE_READ` and `--write` may set the `value` to `MODE_WRITE`. 38 | */ 39 | ADOPT_TYPE_SWITCH, 40 | 41 | /** 42 | * An option that, when specified, increments the given 43 | * `value` by the given `switch_value`. This can be specified 44 | * multiple times to continue to increment the `value`. 45 | * (For example, "-vvv" to set verbosity to 3.) 46 | */ 47 | ADOPT_TYPE_ACCUMULATOR, 48 | 49 | /** 50 | * An option that takes a value, for example `-n value`, 51 | * `-nvalue`, `--name value` or `--name=value`. 52 | */ 53 | ADOPT_TYPE_VALUE, 54 | 55 | /** 56 | * A bare "--" that indicates that arguments following this are 57 | * literal. This allows callers to specify things that might 58 | * otherwise look like options, for example to operate on a file 59 | * named "-rf" then you can invoke "program -- -rf" to treat 60 | * "-rf" as an argument not an option. 61 | */ 62 | ADOPT_TYPE_LITERAL, 63 | 64 | /** 65 | * A single argument, not an option. When options are exhausted, 66 | * arguments will be matches in the order that they're specified 67 | * in the spec list. For example, if two `ADOPT_TYPE_ARGS` are 68 | * specified, `input_file` and `output_file`, then the first bare 69 | * argument on the command line will be `input_file` and the 70 | * second will be `output_file`. 71 | */ 72 | ADOPT_TYPE_ARG, 73 | 74 | /** 75 | * A collection of arguments. This is useful when you want to take 76 | * a list of arguments, for example, multiple paths. When specified, 77 | * the value will be set to the first argument in the list. 78 | */ 79 | ADOPT_TYPE_ARGS, 80 | } adopt_type_t; 81 | 82 | /** 83 | * Additional information about an option, including parsing 84 | * restrictions and usage information to be displayed to the end-user. 85 | */ 86 | typedef enum { 87 | /** Defaults for the argument. */ 88 | ADOPT_USAGE_DEFAULT = 0, 89 | 90 | /** This argument is required. */ 91 | ADOPT_USAGE_REQUIRED = (1u << 0), 92 | 93 | /** 94 | * This is a multiple choice argument, combined with the previous 95 | * argument. For example, when the previous argument is `-f` and 96 | * this optional is applied to an argument of type `-b` then one 97 | * of `-f` or `-b` may be specified. 98 | */ 99 | ADOPT_USAGE_CHOICE = (1u << 1), 100 | 101 | /** 102 | * This argument short-circuits the remainder of parsing. 103 | * Useful for arguments like `--help`. 104 | */ 105 | ADOPT_USAGE_STOP_PARSING = (1u << 2), 106 | 107 | /** The argument's value is optional ("-n" or "-n foo") */ 108 | ADOPT_USAGE_VALUE_OPTIONAL = (1u << 3), 109 | 110 | /** This argument should not be displayed in usage. */ 111 | ADOPT_USAGE_HIDDEN = (1u << 4), 112 | 113 | /** In usage, show the long format instead of the abbreviated format. */ 114 | ADOPT_USAGE_SHOW_LONG = (1u << 5), 115 | } adopt_usage_t; 116 | 117 | typedef enum { 118 | /** Default parsing behavior. */ 119 | ADOPT_PARSE_DEFAULT = 0, 120 | 121 | /** 122 | * Parse with GNU `getopt_long` style behavior, where options can 123 | * be intermixed with arguments at any position (for example, 124 | * "file1 --help file2".) Like `getopt_long`, this can mutate the 125 | * arguments given. 126 | */ 127 | ADOPT_PARSE_GNU = (1u << 0), 128 | 129 | /** 130 | * Force GNU `getopt_long` style behavior; the `POSIXLY_CORRECT` 131 | * environment variable is ignored. 132 | */ 133 | ADOPT_PARSE_FORCE_GNU = (1u << 1), 134 | } adopt_flag_t; 135 | 136 | /** Specification for an available option. */ 137 | typedef struct adopt_spec { 138 | /** Type of option expected. */ 139 | adopt_type_t type; 140 | 141 | /** Name of the long option. */ 142 | const char *name; 143 | 144 | /** The alias is the short (one-character) option alias. */ 145 | const char alias; 146 | 147 | /** 148 | * If this spec is of type `ADOPT_TYPE_BOOL`, this is a pointer 149 | * to an `int` that will be set to `1` if the option is specified. 150 | * 151 | * If this spec is of type `ADOPT_TYPE_SWITCH`, this is a pointer 152 | * to an `int` that will be set to the opt's `switch_value` (below) 153 | * when this option is specified. 154 | * 155 | * If this spec is of type `ADOPT_TYPE_ACCUMULATOR`, this is a 156 | * pointer to an `int` that will be incremented by the opt's 157 | * `switch_value` (below). If no `switch_value` is provided then 158 | * the value will be incremented by 1. 159 | * 160 | * If this spec is of type `ADOPT_TYPE_VALUE`, 161 | * `ADOPT_TYPE_VALUE_OPTIONAL`, or `ADOPT_TYPE_ARG`, this is 162 | * a pointer to a `char *` that will be set to the value 163 | * specified on the command line. 164 | * 165 | * If this spec is of type `ADOPT_TYPE_ARGS`, this is a pointer 166 | * to a `char **` that will be set to the remaining values 167 | * specified on the command line. 168 | */ 169 | void *value; 170 | 171 | /** 172 | * If this spec is of type `ADOPT_TYPE_SWITCH`, this is the value 173 | * to set in the option's `value` pointer when it is specified. If 174 | * this spec is of type `ADOPT_TYPE_ACCUMULATOR`, this is the value 175 | * to increment in the option's `value` pointer when it is 176 | * specified. This is ignored for other opt types. 177 | */ 178 | int switch_value; 179 | 180 | /** 181 | * Optional usage flags that change parsing behavior and how 182 | * usage information is shown to the end-user. 183 | */ 184 | uint32_t usage; 185 | 186 | /** 187 | * The name of the value, provided when creating usage information. 188 | * This is required only for the functions that display usage 189 | * information and only when a spec is of type `ADOPT_TYPE_VALUE, 190 | * `ADOPT_TYPE_ARG` or `ADOPT_TYPE_ARGS``. 191 | */ 192 | const char *value_name; 193 | 194 | /** 195 | * Optional short description of the option to display to the 196 | * end-user. This is only used when creating usage information. 197 | */ 198 | const char *help; 199 | } adopt_spec; 200 | 201 | /** Return value for `adopt_parser_next`. */ 202 | typedef enum { 203 | /** Parsing is complete; there are no more arguments. */ 204 | ADOPT_STATUS_DONE = 0, 205 | 206 | /** 207 | * This argument was parsed correctly; the `opt` structure is 208 | * populated and the value pointer has been set. 209 | */ 210 | ADOPT_STATUS_OK = 1, 211 | 212 | /** 213 | * The argument could not be parsed correctly, it does not match 214 | * any of the specifications provided. 215 | */ 216 | ADOPT_STATUS_UNKNOWN_OPTION = 2, 217 | 218 | /** 219 | * The argument matched a spec of type `ADOPT_VALUE`, but no value 220 | * was provided. 221 | */ 222 | ADOPT_STATUS_MISSING_VALUE = 3, 223 | 224 | /** A required argument was not provided. */ 225 | ADOPT_STATUS_MISSING_ARGUMENT = 4, 226 | } adopt_status_t; 227 | 228 | /** An option provided on the command-line. */ 229 | typedef struct adopt_opt { 230 | /** The status of parsing the most recent argument. */ 231 | adopt_status_t status; 232 | 233 | /** 234 | * The specification that was provided on the command-line, or 235 | * `NULL` if the argument did not match an `adopt_spec`. 236 | */ 237 | const adopt_spec *spec; 238 | 239 | /** 240 | * The argument as it was specified on the command-line, including 241 | * dashes, eg, `-f` or `--foo`. 242 | */ 243 | char *arg; 244 | 245 | /** 246 | * If the spec is of type `ADOPT_VALUE` or `ADOPT_VALUE_OPTIONAL`, 247 | * this is the value provided to the argument. 248 | */ 249 | char *value; 250 | 251 | /** 252 | * If the argument is of type `ADOPT_ARGS`, this is the number of 253 | * arguments remaining. This value is persisted even when parsing 254 | * is complete and `status` == `ADOPT_STATUS_DONE`. 255 | */ 256 | size_t args_len; 257 | } adopt_opt; 258 | 259 | /* The internal parser state. Callers should not modify this structure. */ 260 | typedef struct adopt_parser { 261 | const adopt_spec *specs; 262 | char **args; 263 | size_t args_len; 264 | unsigned int flags; 265 | 266 | /* Parser state */ 267 | size_t idx; 268 | size_t arg_idx; 269 | size_t in_args; 270 | size_t in_short; 271 | unsigned int needs_sort : 1, 272 | in_literal : 1; 273 | } adopt_parser; 274 | 275 | /** 276 | * Parses all the command-line arguments and updates all the options using 277 | * the pointers provided. Parsing stops on any invalid argument and 278 | * information about the failure will be provided in the opt argument. 279 | * 280 | * This is the simplest way to parse options; it handles the initialization 281 | * (`parser_init`) and looping (`parser_next`). 282 | * 283 | * @param opt The The `adopt_opt` information that failed parsing 284 | * @param specs A NULL-terminated array of `adopt_spec`s that can be parsed 285 | * @param args The arguments that will be parsed 286 | * @param args_len The length of arguments to be parsed 287 | * @param flags The `adopt_flag_t flags for parsing 288 | */ 289 | adopt_status_t adopt_parse( 290 | adopt_opt *opt, 291 | const adopt_spec specs[], 292 | char **args, 293 | size_t args_len, 294 | unsigned int flags); 295 | 296 | /** 297 | * Quickly executes the given callback for each argument. 298 | * 299 | * @param specs A NULL-terminated array of `adopt_spec`s that can be parsed 300 | * @param args The arguments that will be parsed 301 | * @param args_len The length of arguments to be parsed 302 | * @param flags The `adopt_flag_t flags for parsing 303 | * @param callback The callback to invoke for each specified option 304 | * @param callback_data Data to be provided to the callback 305 | */ 306 | int adopt_foreach( 307 | const adopt_spec specs[], 308 | char **args, 309 | size_t args_len, 310 | unsigned int flags, 311 | int (*callback)(adopt_opt *, void *), 312 | void *callback_data); 313 | 314 | /** 315 | * Initializes a parser that parses the given arguments according to the 316 | * given specifications. 317 | * 318 | * @param parser The `adopt_parser` that will be initialized 319 | * @param specs A NULL-terminated array of `adopt_spec`s that can be parsed 320 | * @param args The arguments that will be parsed 321 | * @param args_len The length of arguments to be parsed 322 | * @param flags The `adopt_flag_t flags for parsing 323 | */ 324 | void adopt_parser_init( 325 | adopt_parser *parser, 326 | const adopt_spec specs[], 327 | char **args, 328 | size_t args_len, 329 | unsigned int flags); 330 | 331 | /** 332 | * Parses the next command-line argument and places the information about 333 | * the argument into the given `opt` data. 334 | * 335 | * @param opt The `adopt_opt` information parsed from the argument 336 | * @param parser An `adopt_parser` that has been initialized with 337 | * `adopt_parser_init` 338 | * @return true if the caller should continue iterating, or 0 if there are 339 | * no arguments left to process. 340 | */ 341 | adopt_status_t adopt_parser_next( 342 | adopt_opt *opt, 343 | adopt_parser *parser); 344 | 345 | /** 346 | * Prints the status after parsing the most recent argument. This is 347 | * useful for printing an error message when an unknown argument was 348 | * specified, or when an argument was specified without a value. 349 | * 350 | * @param file The file to print information to 351 | * @param command The name of the command to use when printing (optional) 352 | * @param opt The option that failed to parse 353 | * @return 0 on success, -1 on failure 354 | */ 355 | int adopt_status_fprint( 356 | FILE *file, 357 | const char *command, 358 | const adopt_opt *opt); 359 | 360 | /** 361 | * Prints usage information to the given file handle. 362 | * 363 | * @param file The file to print information to 364 | * @param command The name of the command to use when printing 365 | * @param specs The specifications allowed by the command 366 | * @return 0 on success, -1 on failure 367 | */ 368 | int adopt_usage_fprint( 369 | FILE *file, 370 | const char *command, 371 | const adopt_spec specs[]); 372 | 373 | #endif /* ADOPT_H */ 374 | -------------------------------------------------------------------------------- /examples/loop.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c), Edward Thomson 3 | * All rights reserved. 4 | * 5 | * This file is part of adopt, distributed under the MIT license. 6 | * For full terms and conditions, see the included LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "adopt.h" 13 | 14 | static int verbose = 0; 15 | static int volume = 1; 16 | static char *channel = "default"; 17 | static char *filename1 = NULL; 18 | static char *filename2 = NULL; 19 | static char **other = NULL; 20 | 21 | adopt_spec opt_specs[] = { 22 | { ADOPT_TYPE_BOOL, "verbose", 'v', &verbose, 0, 23 | 0, NULL, "Turn on verbose information" }, 24 | { ADOPT_TYPE_SWITCH, "quiet", 'q', &volume, 0, 25 | ADOPT_USAGE_REQUIRED, NULL, "Emit no output" }, 26 | { ADOPT_TYPE_SWITCH, "loud", 'l', &volume, 2, 27 | ADOPT_USAGE_CHOICE, NULL, "Emit louder than usual output" }, 28 | { ADOPT_TYPE_VALUE, "channel", 'c', &channel, 0, 29 | 0, "channel", "Set the channel" }, 30 | { ADOPT_TYPE_LITERAL }, 31 | { ADOPT_TYPE_ARG, NULL, 0, &filename1, 0, 32 | ADOPT_USAGE_REQUIRED, "file1", "The first filename" }, 33 | { ADOPT_TYPE_ARG, NULL, 0, &filename2, 0, 34 | 0, "file2", "The second (optional) filename" }, 35 | { ADOPT_TYPE_ARGS, NULL, 0, &other, 0, 36 | 0, "other", "The other (optional) arguments" }, 37 | { 0 } 38 | }; 39 | 40 | static const char *volume_tostr(int volume) 41 | { 42 | switch(volume) { 43 | case 0: 44 | return "quiet"; 45 | case 1: 46 | return "normal"; 47 | case 2: 48 | return "loud"; 49 | } 50 | 51 | return "unknown"; 52 | } 53 | 54 | int main(int argc, char **argv) 55 | { 56 | adopt_parser parser; 57 | adopt_opt opt; 58 | size_t i; 59 | unsigned int flags = ADOPT_PARSE_DEFAULT; 60 | 61 | if (getenv("ADOPT_PARSE_GNU") != NULL) 62 | flags |= ADOPT_PARSE_GNU; 63 | if (getenv("ADOPT_PARSE_FORCE_GNU") != NULL) 64 | flags |= ADOPT_PARSE_FORCE_GNU; 65 | 66 | adopt_parser_init(&parser, opt_specs, argv + 1, argc - 1, flags); 67 | 68 | while (adopt_parser_next(&opt, &parser)) { 69 | if (opt.status != ADOPT_STATUS_OK) { 70 | adopt_status_fprint(stderr, argv[0], &opt); 71 | adopt_usage_fprint(stderr, argv[0], opt_specs); 72 | return 129; 73 | } 74 | } 75 | 76 | if (!filename1) { 77 | fprintf(stderr, "filename is required\n"); 78 | adopt_usage_fprint(stderr, argv[0], opt_specs); 79 | return 129; 80 | } 81 | 82 | printf("verbose: %d\n", verbose); 83 | printf("volume: %s\n", volume_tostr(volume)); 84 | printf("channel: %s\n", channel ? channel : "(null)"); 85 | printf("filename one: %s\n", filename1 ? filename1 : "(null)"); 86 | printf("filename two: %s\n", filename2 ? filename2 : "(null)"); 87 | 88 | /* display other args */ 89 | if (other) { 90 | printf("other args [%d]: ", (int)opt.args_len); 91 | 92 | for (i = 0; i < opt.args_len; i++) { 93 | if (i) 94 | printf(", "); 95 | 96 | printf("%s", other[i]); 97 | } 98 | 99 | printf("\n"); 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /examples/parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c), Edward Thomson 3 | * All rights reserved. 4 | * 5 | * This file is part of adopt, distributed under the MIT license. 6 | * For full terms and conditions, see the included LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "adopt.h" 13 | 14 | static int verbose = 0; 15 | static int volume = 1; 16 | static char *channel = "default"; 17 | static char *filename1 = NULL; 18 | static char *filename2 = NULL; 19 | static char **other = NULL; 20 | 21 | adopt_spec opt_specs[] = { 22 | { ADOPT_TYPE_BOOL, "verbose", 'v', &verbose, 0, 23 | 0, NULL, "Turn on verbose information" }, 24 | { ADOPT_TYPE_SWITCH, "quiet", 'q', &volume, 0, 25 | ADOPT_USAGE_REQUIRED, NULL, "Emit no output" }, 26 | { ADOPT_TYPE_SWITCH, "loud", 'l', &volume, 2, 27 | ADOPT_USAGE_CHOICE, NULL, "Emit louder than usual output" }, 28 | { ADOPT_TYPE_VALUE, "channel", 'c', &channel, 0, 29 | 0, "channel", "Set the channel" }, 30 | { ADOPT_TYPE_LITERAL }, 31 | { ADOPT_TYPE_ARG, NULL, 0, &filename1, 0, 32 | ADOPT_USAGE_REQUIRED, "file1", "The first filename" }, 33 | { ADOPT_TYPE_ARG, NULL, 0, &filename2, 0, 34 | 0, "file2", "The second (optional) filename" }, 35 | { ADOPT_TYPE_ARGS, NULL, 0, &other, 0, 36 | 0, "other", "The other (optional) arguments" }, 37 | { 0 } 38 | }; 39 | 40 | static const char *volume_tostr(int volume) 41 | { 42 | switch(volume) { 43 | case 0: 44 | return "quiet"; 45 | case 1: 46 | return "normal"; 47 | case 2: 48 | return "loud"; 49 | } 50 | 51 | return "unknown"; 52 | } 53 | 54 | int main(int argc, char **argv) 55 | { 56 | adopt_opt result; 57 | size_t i; 58 | unsigned int flags = ADOPT_PARSE_DEFAULT; 59 | 60 | if (getenv("ADOPT_PARSE_GNU") != NULL) 61 | flags |= ADOPT_PARSE_GNU; 62 | if (getenv("ADOPT_PARSE_FORCE_GNU") != NULL) 63 | flags |= ADOPT_PARSE_FORCE_GNU; 64 | 65 | if (adopt_parse(&result, opt_specs, argv + 1, argc - 1, flags) != 0) { 66 | adopt_status_fprint(stderr, argv[0], &result); 67 | adopt_usage_fprint(stderr, argv[0], opt_specs); 68 | return 129; 69 | } 70 | 71 | printf("verbose: %d\n", verbose); 72 | printf("volume: %s\n", volume_tostr(volume)); 73 | printf("channel: %s\n", channel ? channel : "(null)"); 74 | printf("filename one: %s\n", filename1 ? filename1 : "(null)"); 75 | printf("filename two: %s\n", filename2 ? filename2 : "(null)"); 76 | 77 | /* display other args */ 78 | if (other) { 79 | printf("other args [%d]: ", (int)result.args_len); 80 | 81 | for (i = 0; i < result.args_len; i++) { 82 | if (i) 83 | printf(", "); 84 | 85 | printf("%s", other[i]); 86 | } 87 | 88 | printf("\n"); 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /rename.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | use strict; 4 | 5 | my $tests = 0; 6 | my $out = '.'; 7 | my($prefix, $filename, $out_tests, $inline, $include, $tests_name, $header_guard, $lowercase_status, $no_usage); 8 | 9 | my $usage = "usage: $0 [--out=] [--filename=] [--out-tests=] [--with-tests=] [--without-usage] [--include=] [--inline=] [--header-guard=] [--lowercase-status] \n"; 10 | 11 | sub die_usage() { die $usage; } 12 | 13 | 14 | foreach my $a (@ARGV) { 15 | my $arg = $a; 16 | 17 | if ($arg =~ /^--with-tests/) { 18 | $tests = 1; 19 | 20 | if ($arg =~ s/^--with-tests=//) { 21 | $tests_name = $arg; 22 | } 23 | } 24 | elsif ($arg =~ s/^--out=//) { 25 | $out = $arg; 26 | } 27 | elsif ($arg =~ s/^--filename=//) { 28 | $filename = $arg; 29 | } 30 | elsif ($arg =~ s/^--out-tests=//) { 31 | $out_tests = $arg; 32 | } 33 | elsif ($arg =~ s/^--include=//) { 34 | $include = $arg; 35 | } 36 | elsif ($arg =~ s/^--inline=//) { 37 | $inline = $arg; 38 | } 39 | elsif ($arg =~ s/^--header-guard=//) { 40 | $header_guard = $arg; 41 | } 42 | elsif ($arg !~ /^--/ && ! $prefix) { 43 | $prefix = $arg; 44 | } 45 | elsif ($arg eq '--lowercase-status') { 46 | $lowercase_status = 1; 47 | } 48 | elsif ($arg eq '--without-usage') { 49 | $no_usage = 1; 50 | } 51 | elsif ($arg eq '--help') { 52 | print STDOUT $usage; 53 | exit; 54 | } 55 | else { 56 | print STDERR "$0: unknown argument: $arg\n"; 57 | die_usage(); 58 | } 59 | } 60 | 61 | die_usage() unless $prefix; 62 | 63 | $filename = $prefix unless($filename); 64 | 65 | my $filename_upper = $filename; 66 | $filename_upper =~ tr/a-z/A-Z/; 67 | 68 | my $prefix_upper = $prefix; 69 | $prefix_upper =~ tr/a-z/A-Z/; 70 | 71 | $header_guard = "${filename_upper}_H" unless($header_guard); 72 | 73 | translate("adopt.c", "${out}/${filename}.c"); 74 | translate("adopt.h", "${out}/${filename}.h"); 75 | 76 | if ($tests) 77 | { 78 | $out_tests = $out unless($out_tests); 79 | 80 | if ($tests_name) { 81 | $tests_name =~ s/::/_/g; 82 | } else { 83 | $tests_name = $prefix; 84 | } 85 | 86 | my $tests_filename = $tests_name; 87 | $tests_filename =~ s/.*_//; 88 | 89 | translate("tests/adopt.c", "${out_tests}/${tests_filename}.c"); 90 | } 91 | 92 | sub translate { 93 | my($in, $out) = @_; 94 | 95 | open(IN, $in) || die "$0: could not open ${in}: $!\n"; 96 | my $contents = join('', ); 97 | close(IN); 98 | 99 | $contents =~ s/\n\/\*\*\n( \*[^\n]*\n)* \*\/\nint adopt_usage_fprint\(.*?\);\n//s 100 | if ($no_usage); 101 | $contents =~ s/\nint adopt_usage_fprint.*}\n//s 102 | if ($no_usage); 103 | 104 | $contents =~ s/test_adopt__/test_${filename}__/g; 105 | 106 | # if a prefix becomes foo_opt, we want to rewrite adopt_opt specially 107 | # to avoid it becoming foo_opt_opt 108 | $contents =~ s/adopt_opt/${prefix}/g if ($prefix =~ /_opt$/); 109 | 110 | $contents =~ s/ifndef ADOPT_H/ifndef ${header_guard}/g; 111 | $contents =~ s/define ADOPT_H/define ${header_guard}/g; 112 | $contents =~ s/endif \/\* ADOPT_H/endif \/* ${header_guard}/g; 113 | $contents =~ s/adopt\.h/${filename}\.h/g; 114 | 115 | $contents =~ s/adopt_/${prefix}_/g; 116 | $contents =~ s/ADOPT_/${prefix_upper}_/g; 117 | 118 | $contents =~ s/fprintf\(file, "([A-Z])/fprintf\(file, "\l$1/g if($lowercase_status); 119 | 120 | if ($include) { 121 | $contents =~ s/^(#include "opt.h")$/#include "${include}"\n$1/mg; 122 | } 123 | 124 | if ($inline) { 125 | $contents =~ s/^INLINE/${inline}/mg; 126 | $contents =~ s/\n#ifdef _MSC_VER\n.*?\n#endif\n//s; 127 | } 128 | 129 | if ($tests) { 130 | $contents =~ s/test_adopt__/test_${tests_name}__/g; 131 | } 132 | 133 | $contents =~ s/\n \*\/\n/\n *\n * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT.\n *\n * This file was produced by using the `rename.pl` script included with\n * adopt. The command-line specified was:\n *\n * $0 @ARGV\n *\/\n/s; 134 | 135 | open(OUT, '>' . $out) || die "$0: could not open ${out}: $!\n"; 136 | print OUT $contents; 137 | close(OUT); 138 | } 139 | 140 | -------------------------------------------------------------------------------- /tests/adopt.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "adopt.h" 3 | 4 | typedef struct { 5 | adopt_spec *spec; 6 | const char *arg; 7 | } adopt_expected; 8 | 9 | static void test_parse( 10 | adopt_spec *specs, 11 | char *args[], 12 | size_t argslen, 13 | adopt_expected expected[], 14 | size_t expectlen) 15 | { 16 | adopt_parser parser; 17 | adopt_opt opt; 18 | size_t i; 19 | 20 | adopt_parser_init(&parser, specs, args, argslen, ADOPT_PARSE_DEFAULT); 21 | 22 | for (i = 0; i < expectlen; ++i) { 23 | cl_assert(adopt_parser_next(&opt, &parser) > 0); 24 | 25 | cl_assert_equal_p(expected[i].spec, opt.spec); 26 | 27 | if (expected[i].arg && expected[i].spec == NULL) 28 | cl_assert_equal_s(expected[i].arg, opt.arg); 29 | else if (expected[i].arg) 30 | cl_assert_equal_s(expected[i].arg, opt.value); 31 | else 32 | cl_assert(opt.value == NULL); 33 | } 34 | 35 | cl_assert(adopt_parser_next(&opt, &parser) == 0); 36 | } 37 | 38 | static void test_returns_missing_value( 39 | adopt_spec *specs, 40 | char *args[], 41 | size_t argslen) 42 | { 43 | adopt_parser parser; 44 | adopt_opt opt; 45 | adopt_status_t status; 46 | 47 | adopt_parser_init(&parser, specs, args, argslen, ADOPT_PARSE_DEFAULT); 48 | 49 | status = adopt_parser_next(&opt, &parser); 50 | cl_assert_equal_i(ADOPT_STATUS_MISSING_VALUE, status); 51 | } 52 | 53 | void test_adopt__empty(void) 54 | { 55 | int foo = 0, bar = 0; 56 | 57 | adopt_spec specs[] = { 58 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 59 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 60 | { 0 } 61 | }; 62 | 63 | /* Parse an empty arg list */ 64 | test_parse(specs, NULL, 0, NULL, 0); 65 | cl_assert_equal_i(0, foo); 66 | cl_assert_equal_i(0, bar); 67 | } 68 | 69 | void test_adopt__args(void) 70 | { 71 | int foo = 0, bar = 0; 72 | 73 | adopt_spec specs[] = { 74 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 75 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 76 | { 0 } 77 | }; 78 | 79 | char *args[] = { "bare1", "bare2" }; 80 | adopt_expected expected[] = { 81 | { NULL, "bare1" }, 82 | { NULL, "bare2" }, 83 | }; 84 | 85 | /* Parse an arg list with only bare arguments */ 86 | test_parse(specs, args, 2, expected, 2); 87 | cl_assert_equal_i(0, foo); 88 | cl_assert_equal_i(0, bar); 89 | } 90 | 91 | void test_adopt__unknown(void) 92 | { 93 | int foo = 0, bar = 0; 94 | 95 | adopt_spec specs[] = { 96 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 97 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 98 | { 0 } 99 | }; 100 | 101 | char *args[] = { "--unknown-long", "-u" }; 102 | adopt_expected expected[] = { 103 | { NULL, "--unknown-long" }, 104 | { NULL, "-u" }, 105 | }; 106 | 107 | /* Parse an arg list with only bare arguments */ 108 | test_parse(specs, args, 2, expected, 2); 109 | cl_assert_equal_i(0, foo); 110 | cl_assert_equal_i(0, bar); 111 | } 112 | 113 | void test_adopt__returns_unknown_option(void) 114 | { 115 | adopt_parser parser; 116 | adopt_opt opt; 117 | adopt_status_t status; 118 | int foo = 0, bar = 0; 119 | 120 | adopt_spec specs[] = { 121 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 122 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 123 | { 0 } 124 | }; 125 | 126 | char *args[] = { "--unknown-long", "-u" }; 127 | 128 | adopt_parser_init(&parser, specs, args, 2, ADOPT_PARSE_DEFAULT); 129 | 130 | status = adopt_parser_next(&opt, &parser); 131 | cl_assert_equal_i(ADOPT_STATUS_UNKNOWN_OPTION, status); 132 | } 133 | 134 | void test_adopt__bool(void) 135 | { 136 | int foo = 0, bar = 0; 137 | 138 | adopt_spec specs[] = { 139 | { ADOPT_TYPE_BOOL, "foo", 0, &foo, 0 }, 140 | { ADOPT_TYPE_BOOL, "bar", 0, &bar, 0 }, 141 | { 0 } 142 | }; 143 | 144 | char *args[] = { "--foo", "-b" }; 145 | adopt_expected expected[] = { 146 | { &specs[0], NULL }, 147 | { NULL, "-b" }, 148 | }; 149 | 150 | /* Parse an arg list with only bare arguments */ 151 | test_parse(specs, args, 2, expected, 2); 152 | cl_assert_equal_i(1, foo); 153 | cl_assert_equal_i(0, bar); 154 | } 155 | 156 | void test_adopt__bool_converse(void) 157 | { 158 | int foo = 1, bar = 0; 159 | 160 | adopt_spec specs[] = { 161 | { ADOPT_TYPE_BOOL, "foo", 0, &foo, 0 }, 162 | { ADOPT_TYPE_BOOL, "bar", 0, &bar, 0 }, 163 | { 0 } 164 | }; 165 | 166 | char *args[] = { "--no-foo", "--bar" }; 167 | adopt_expected expected[] = { 168 | { &specs[0], NULL }, 169 | { &specs[1], NULL }, 170 | }; 171 | 172 | /* Parse an arg list with only bare arguments */ 173 | test_parse(specs, args, 2, expected, 2); 174 | cl_assert_equal_i(0, foo); 175 | cl_assert_equal_i(1, bar); 176 | } 177 | 178 | void test_adopt__bool_converse_overrides(void) 179 | { 180 | int foo = 0, bar = 0; 181 | 182 | adopt_spec specs[] = { 183 | { ADOPT_TYPE_BOOL, "foo", 0, &foo, 0 }, 184 | { ADOPT_TYPE_BOOL, "bar", 0, &bar, 0 }, 185 | { 0 } 186 | }; 187 | 188 | char *args[] = { "--foo", "--bar", "--no-foo" }; 189 | adopt_expected expected[] = { 190 | { &specs[0], NULL }, 191 | { &specs[1], NULL }, 192 | { &specs[0], NULL }, 193 | }; 194 | 195 | /* Parse an arg list with only bare arguments */ 196 | test_parse(specs, args, 3, expected, 3); 197 | cl_assert_equal_i(0, foo); 198 | cl_assert_equal_i(1, bar); 199 | } 200 | 201 | void test_adopt__long_switches1(void) 202 | { 203 | int foo = 0, bar = 0; 204 | 205 | adopt_spec specs[] = { 206 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 207 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 208 | { 0 } 209 | }; 210 | 211 | char *args1[] = { "--foo", "bare1" }; 212 | adopt_expected expected1[] = { 213 | { &specs[0], NULL }, 214 | { NULL, "bare1" }, 215 | }; 216 | 217 | /* Parse --foo bare1 */ 218 | test_parse(specs, args1, 2, expected1, 2); 219 | cl_assert_equal_i('f', foo); 220 | cl_assert_equal_i(0, bar); 221 | } 222 | 223 | void test_adopt__long_switches2(void) 224 | { 225 | int foo = 0, bar = 0; 226 | 227 | adopt_spec specs[] = { 228 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 229 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 230 | { 0 } 231 | }; 232 | 233 | char *args2[] = { "--foo", "--bar" }; 234 | adopt_expected expected2[] = { 235 | { &specs[0], NULL }, 236 | { &specs[1], NULL } 237 | }; 238 | 239 | /* Parse --foo --bar */ 240 | test_parse(specs, args2, 2, expected2, 2); 241 | cl_assert_equal_i('f', foo); 242 | cl_assert_equal_i('b', bar); 243 | } 244 | 245 | 246 | void test_adopt__long_switches3(void) 247 | { 248 | int foo = 0, bar = 0; 249 | 250 | adopt_spec specs[] = { 251 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 252 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 253 | { 0 } 254 | }; 255 | 256 | char *args3[] = { "--foo", "bare2", "--bar", "-u" }; 257 | adopt_expected expected3[] = { 258 | { &specs[0], NULL }, 259 | { NULL, "bare2" }, 260 | { &specs[1], NULL }, 261 | { NULL, "-u" }, 262 | }; 263 | 264 | /* Parse --foo bare2 --bar -u */ 265 | test_parse(specs, args3, 4, expected3, 4); 266 | cl_assert_equal_i('f', foo); 267 | cl_assert_equal_i('b', bar); 268 | } 269 | 270 | void test_adopt__long_values1(void) 271 | { 272 | char *foo = NULL, *bar = NULL; 273 | 274 | adopt_spec specs[] = { 275 | { ADOPT_TYPE_VALUE, "foo", 0, &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 276 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 277 | { 0 } 278 | }; 279 | 280 | char *args1[] = { "--foo", "arg_1" }; 281 | adopt_expected expected1[] = { 282 | { &specs[0], "arg_1" }, 283 | }; 284 | 285 | /* Parse --foo arg_1 */ 286 | test_parse(specs, args1, 2, expected1, 1); 287 | cl_assert_equal_s("arg_1", foo); 288 | cl_assert_equal_s(NULL, bar); 289 | } 290 | 291 | void test_adopt__long_values2(void) 292 | { 293 | char *foo = NULL, *bar = NULL; 294 | 295 | adopt_spec specs[] = { 296 | { ADOPT_TYPE_VALUE, "foo", 0, &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 297 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 298 | { 0 } 299 | }; 300 | 301 | char *args2[] = { "--foo", "--bar" }; 302 | adopt_expected expected2[] = { 303 | { &specs[0], "--bar" }, 304 | }; 305 | 306 | /* Parse --foo --bar */ 307 | test_parse(specs, args2, 2, expected2, 1); 308 | cl_assert_equal_s("--bar", foo); 309 | cl_assert_equal_s(NULL, bar); 310 | } 311 | 312 | void test_adopt__long_values3(void) 313 | { 314 | char *foo = NULL, *bar = NULL; 315 | 316 | adopt_spec specs[] = { 317 | { ADOPT_TYPE_VALUE, "foo", 0, &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 318 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 319 | { 0 } 320 | }; 321 | 322 | char *args3[] = { "--foo", "--arg_1", "--bar", "arg_2" }; 323 | adopt_expected expected3[] = { 324 | { &specs[0], "--arg_1" }, 325 | { &specs[1], "arg_2" }, 326 | }; 327 | 328 | /* Parse --foo --arg_1 --bar arg_2 */ 329 | test_parse(specs, args3, 4, expected3, 2); 330 | cl_assert_equal_s("--arg_1", foo); 331 | cl_assert_equal_s("arg_2", bar); 332 | } 333 | 334 | void test_adopt__long_values4(void) 335 | { 336 | char *foo = NULL, *bar = NULL; 337 | 338 | adopt_spec specs[] = { 339 | { ADOPT_TYPE_VALUE, "foo", 0, &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 340 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 341 | { 0 } 342 | }; 343 | 344 | char *args4[] = { "--foo=--bar" }; 345 | adopt_expected expected4[] = { 346 | { &specs[0], "--bar" }, 347 | }; 348 | 349 | /* Parse --foo=bar */ 350 | test_parse(specs, args4, 1, expected4, 1); 351 | cl_assert_equal_s("--bar", foo); 352 | cl_assert_equal_s(NULL, bar); 353 | } 354 | 355 | void test_adopt__long_values5(void) 356 | { 357 | char *foo = NULL, *bar = NULL; 358 | 359 | adopt_spec specs[] = { 360 | { ADOPT_TYPE_VALUE, "foo", 0, &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 361 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 362 | { 0 } 363 | }; 364 | 365 | char *args5[] = { "--bar=" }; 366 | adopt_expected expected5[] = { 367 | { &specs[1], NULL }, 368 | }; 369 | 370 | /* Parse --bar= */ 371 | test_parse(specs, args5, 1, expected5, 1); 372 | cl_assert_equal_s(NULL, foo); 373 | cl_assert_equal_s(NULL, bar); 374 | } 375 | 376 | void test_adopt__returns_missing_value(void) 377 | { 378 | char *foo = NULL, *bar = NULL; 379 | 380 | adopt_spec specs[] = { 381 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0 }, 382 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0 }, 383 | { 0 } 384 | }; 385 | char *missing1[] = { "--foo" }; 386 | char *missing2[] = { "--foo=" }; 387 | char *missing3[] = { "-f" }; 388 | 389 | test_returns_missing_value(specs, missing1, 1); 390 | test_returns_missing_value(specs, missing2, 1); 391 | test_returns_missing_value(specs, missing3, 1); 392 | } 393 | 394 | void test_adopt__short_switches2(void) 395 | { 396 | int foo = 0, bar = 0; 397 | 398 | adopt_spec specs[] = { 399 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 400 | { ADOPT_TYPE_SWITCH, "bar", 'b', &bar, 'b' }, 401 | { 0 } 402 | }; 403 | 404 | char *args2[] = { "-f", "-b" }; 405 | adopt_expected expected2[] = { 406 | { &specs[0], NULL }, 407 | { &specs[1], NULL } 408 | }; 409 | 410 | /* Parse -f -b */ 411 | test_parse(specs, args2, 2, expected2, 2); 412 | cl_assert_equal_i('f', foo); 413 | cl_assert_equal_i('b', bar); 414 | } 415 | 416 | void test_adopt__short_switches3(void) 417 | { 418 | int foo = 0, bar = 0; 419 | 420 | adopt_spec specs[] = { 421 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 422 | { ADOPT_TYPE_SWITCH, "bar", 'b', &bar, 'b' }, 423 | { 0 } 424 | }; 425 | 426 | char *args3[] = { "-f", "bare2", "-b", "-u" }; 427 | adopt_expected expected3[] = { 428 | { &specs[0], NULL }, 429 | { NULL, "bare2" }, 430 | { &specs[1], NULL }, 431 | { NULL, "-u" }, 432 | }; 433 | 434 | /* Parse -f bare2 -b -u */ 435 | test_parse(specs, args3, 4, expected3, 4); 436 | cl_assert_equal_i('f', foo); 437 | cl_assert_equal_i('b', bar); 438 | } 439 | 440 | void test_adopt__short_values1(void) 441 | { 442 | char *foo = NULL, *bar = NULL; 443 | 444 | adopt_spec specs[] = { 445 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 446 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 447 | { 0 } 448 | }; 449 | 450 | char *args1[] = { "-f", "arg_1" }; 451 | adopt_expected expected1[] = { 452 | { &specs[0], "arg_1" }, 453 | }; 454 | 455 | /* Parse -f arg_1 */ 456 | test_parse(specs, args1, 2, expected1, 1); 457 | cl_assert_equal_s("arg_1", foo); 458 | cl_assert_equal_s(NULL, bar); 459 | } 460 | 461 | void test_adopt__short_values2(void) 462 | { 463 | char *foo = NULL, *bar = NULL; 464 | 465 | adopt_spec specs[] = { 466 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 467 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 468 | { 0 } 469 | }; 470 | 471 | char *args2[] = { "-f", "--bar" }; 472 | adopt_expected expected2[] = { 473 | { &specs[0], "--bar" }, 474 | }; 475 | 476 | /* Parse -f --bar */ 477 | test_parse(specs, args2, 2, expected2, 1); 478 | cl_assert_equal_s("--bar", foo); 479 | cl_assert_equal_s(NULL, bar); 480 | } 481 | 482 | void test_adopt__short_values3(void) 483 | { 484 | char *foo = NULL, *bar = NULL; 485 | 486 | adopt_spec specs[] = { 487 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 488 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 489 | { 0 } 490 | }; 491 | 492 | char *args3[] = { "-f", "--arg_1", "-b", "arg_2" }; 493 | adopt_expected expected3[] = { 494 | { &specs[0], "--arg_1" }, 495 | { &specs[1], "arg_2" }, 496 | }; 497 | 498 | /* Parse -f --arg_1 -b arg_2 */ 499 | test_parse(specs, args3, 4, expected3, 2); 500 | cl_assert_equal_s("--arg_1", foo); 501 | cl_assert_equal_s("arg_2", bar); 502 | } 503 | 504 | void test_adopt__short_values4(void) 505 | { 506 | char *foo = NULL, *bar = NULL; 507 | 508 | adopt_spec specs[] = { 509 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 510 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 511 | { 0 } 512 | }; 513 | 514 | char *args4[] = { "-fbar" }; 515 | adopt_expected expected4[] = { 516 | { &specs[0], "bar" }, 517 | }; 518 | 519 | /* Parse -fbar */ 520 | test_parse(specs, args4, 1, expected4, 1); 521 | cl_assert_equal_s("bar", foo); 522 | cl_assert_equal_s(NULL, bar); 523 | } 524 | 525 | void test_adopt__short_values5(void) 526 | { 527 | char *foo = NULL, *bar = NULL; 528 | 529 | adopt_spec specs[] = { 530 | { ADOPT_TYPE_VALUE, "foo", 'f', &foo, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 531 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 0, ADOPT_USAGE_VALUE_OPTIONAL }, 532 | { 0 } 533 | }; 534 | 535 | char *args5[] = { "-b" }; 536 | adopt_expected expected5[] = { 537 | { &specs[1], NULL }, 538 | }; 539 | 540 | /* Parse -b */ 541 | test_parse(specs, args5, 1, expected5, 1); 542 | cl_assert_equal_s(NULL, foo); 543 | cl_assert_equal_s(NULL, bar); 544 | } 545 | 546 | void test_adopt__literal(void) 547 | { 548 | int foo = 0, bar = 0; 549 | 550 | adopt_spec specs[] = { 551 | { ADOPT_TYPE_SWITCH, "foo", 0, &foo, 'f' }, 552 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 553 | { ADOPT_TYPE_LITERAL }, 554 | { 0 } 555 | }; 556 | 557 | char *args1[] = { "--foo", "--", "--bar" }; 558 | adopt_expected expected1[] = { 559 | { &specs[0], NULL }, 560 | { &specs[2], NULL }, 561 | { NULL, "--bar" }, 562 | }; 563 | 564 | /* Parse --foo -- --bar */ 565 | test_parse(specs, args1, 3, expected1, 3); 566 | cl_assert_equal_i('f', foo); 567 | cl_assert_equal_i(0, bar); 568 | } 569 | 570 | void test_adopt__no_long_argument(void) 571 | { 572 | int foo = 0, bar = 0; 573 | 574 | adopt_spec specs[] = { 575 | { ADOPT_TYPE_SWITCH, NULL, 'f', &foo, 'f' }, 576 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 577 | { 0 }, 578 | }; 579 | 580 | char *args1[] = { "-f", "--bar" }; 581 | adopt_expected expected1[] = { 582 | { &specs[0], NULL }, 583 | { &specs[1], NULL }, 584 | }; 585 | 586 | /* Parse -f --bar */ 587 | test_parse(specs, args1, 2, expected1, 2); 588 | cl_assert_equal_i('f', foo); 589 | cl_assert_equal_i('b', bar); 590 | } 591 | 592 | void test_adopt__parse_oneshot(void) 593 | { 594 | int foo = 0, bar = 0; 595 | adopt_opt result; 596 | 597 | adopt_spec specs[] = { 598 | { ADOPT_TYPE_SWITCH, NULL, 'f', &foo, 'f' }, 599 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 600 | { 0 }, 601 | }; 602 | 603 | char *args[] = { "-f", "--bar" }; 604 | 605 | cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 606 | 607 | cl_assert_equal_i('f', foo); 608 | cl_assert_equal_i('b', bar); 609 | 610 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 611 | cl_assert_equal_p(NULL, result.arg); 612 | cl_assert_equal_p(NULL, result.value); 613 | } 614 | 615 | void test_adopt__parse_oneshot_unknown_option(void) 616 | { 617 | int foo = 0, bar = 0; 618 | adopt_opt result; 619 | 620 | adopt_spec specs[] = { 621 | { ADOPT_TYPE_SWITCH, NULL, 'f', &foo, 'f' }, 622 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 623 | { 0 }, 624 | }; 625 | 626 | char *args[] = { "-f", "--bar", "--asdf" }; 627 | 628 | cl_assert_equal_i(ADOPT_STATUS_UNKNOWN_OPTION, adopt_parse(&result, specs, args, 3, ADOPT_PARSE_DEFAULT)); 629 | } 630 | 631 | void test_adopt__parse_oneshot_missing_value(void) 632 | { 633 | int foo = 0; 634 | char *bar = NULL; 635 | adopt_opt result; 636 | 637 | adopt_spec specs[] = { 638 | { ADOPT_TYPE_SWITCH, NULL, 'f', &foo, 'f' }, 639 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 640 | { 0 }, 641 | }; 642 | 643 | char *args[] = { "-f", "--bar" }; 644 | 645 | cl_assert_equal_i(ADOPT_STATUS_MISSING_VALUE, adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 646 | } 647 | 648 | void test_adopt__parse_arg(void) 649 | { 650 | int foo = 0; 651 | char *bar = NULL, *arg1 = NULL, *arg2 = NULL; 652 | adopt_opt result; 653 | 654 | adopt_spec specs[] = { 655 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 656 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 657 | { ADOPT_TYPE_ARG, "arg1", 0, &arg1, 0 }, 658 | { ADOPT_TYPE_ARG, "arg2", 0, &arg2, 0 }, 659 | { 0 }, 660 | }; 661 | 662 | char *args[] = { "-f", "bar", "baz" }; 663 | 664 | cl_must_pass(adopt_parse(&result, specs, args, 3, ADOPT_PARSE_DEFAULT)); 665 | 666 | cl_assert_equal_i('f', foo); 667 | cl_assert_equal_p(NULL, bar); 668 | cl_assert_equal_s("bar", arg1); 669 | cl_assert_equal_p("baz", arg2); 670 | 671 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 672 | cl_assert_equal_p(NULL, result.arg); 673 | cl_assert_equal_p(NULL, result.value); 674 | } 675 | 676 | void test_adopt__parse_arg_mixed_with_switches(void) 677 | { 678 | int foo = 0, bar = 0; 679 | char *arg1 = NULL, *arg2 = NULL; 680 | adopt_opt result; 681 | 682 | adopt_spec specs[] = { 683 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 684 | { ADOPT_TYPE_ARG, "arg1", 0, &arg1, 0 }, 685 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b' }, 686 | { ADOPT_TYPE_ARG, "arg2", 0, &arg2, 0 }, 687 | { 0 }, 688 | }; 689 | 690 | char *args[] = { "-f", "bar", "baz", "--bar" }; 691 | 692 | cl_must_pass(adopt_parse(&result, specs, args, 4, ADOPT_PARSE_DEFAULT)); 693 | 694 | cl_assert_equal_i('f', foo); 695 | cl_assert_equal_i('b', bar); 696 | cl_assert_equal_s("bar", arg1); 697 | cl_assert_equal_p("baz", arg2); 698 | 699 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 700 | cl_assert_equal_p(NULL, result.arg); 701 | cl_assert_equal_p(NULL, result.value); 702 | } 703 | 704 | void test_adopt__accumulator(void) 705 | { 706 | int foo = 0; 707 | adopt_opt result; 708 | char *argz; 709 | 710 | char *args_zero[] = { "foo", "bar", "baz" }; 711 | char *args_one[] = { "-f", "foo", "bar", "baz" }; 712 | char *args_two[] = { "-f", "-f", "foo", "bar", "baz" }; 713 | char *args_four[] = { "-f", "-f", "-f", "-f", "foo", "bar", "baz" }; 714 | 715 | adopt_spec specs[] = { 716 | { ADOPT_TYPE_ACCUMULATOR, "foo", 'f', &foo, 0 }, 717 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 718 | { 0 }, 719 | }; 720 | 721 | foo = 0; 722 | cl_must_pass(adopt_parse(&result, specs, args_zero, 3, ADOPT_PARSE_DEFAULT)); 723 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 724 | cl_assert_equal_i(0, foo); 725 | 726 | foo = 0; 727 | cl_must_pass(adopt_parse(&result, specs, args_one, 4, ADOPT_PARSE_DEFAULT)); 728 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 729 | cl_assert_equal_i(1, foo); 730 | 731 | foo = 0; 732 | cl_must_pass(adopt_parse(&result, specs, args_two, 5, ADOPT_PARSE_DEFAULT)); 733 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 734 | cl_assert_equal_i(2, foo); 735 | 736 | foo = 0; 737 | cl_must_pass(adopt_parse(&result, specs, args_four, 7, ADOPT_PARSE_DEFAULT)); 738 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 739 | cl_assert_equal_i(4, foo); 740 | } 741 | 742 | void test_adopt__accumulator_with_custom_incrementor(void) 743 | { 744 | int foo = 0; 745 | adopt_opt result; 746 | char *argz; 747 | 748 | char *args_zero[] = { "foo", "bar", "baz" }; 749 | char *args_one[] = { "-f", "foo", "bar", "baz" }; 750 | char *args_two[] = { "-f", "-f", "foo", "bar", "baz" }; 751 | char *args_four[] = { "-f", "-f", "-f", "-f", "foo", "bar", "baz" }; 752 | 753 | adopt_spec specs[] = { 754 | { ADOPT_TYPE_ACCUMULATOR, "foo", 'f', &foo, 42 }, 755 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 756 | { 0 }, 757 | }; 758 | 759 | foo = 0; 760 | cl_must_pass(adopt_parse(&result, specs, args_zero, 3, ADOPT_PARSE_DEFAULT)); 761 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 762 | cl_assert_equal_i(0, foo); 763 | 764 | foo = 0; 765 | cl_must_pass(adopt_parse(&result, specs, args_one, 4, ADOPT_PARSE_DEFAULT)); 766 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 767 | cl_assert_equal_i(42, foo); 768 | 769 | foo = 0; 770 | cl_must_pass(adopt_parse(&result, specs, args_two, 5, ADOPT_PARSE_DEFAULT)); 771 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 772 | cl_assert_equal_i(84, foo); 773 | 774 | foo = 0; 775 | cl_must_pass(adopt_parse(&result, specs, args_four, 7, ADOPT_PARSE_DEFAULT)); 776 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 777 | cl_assert_equal_i(168, foo); 778 | } 779 | 780 | void test_adopt__parse_arg_with_literal(void) 781 | { 782 | int foo = 0; 783 | char *bar = NULL, *arg1 = NULL, *arg2 = NULL; 784 | adopt_opt result; 785 | 786 | adopt_spec specs[] = { 787 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 788 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 789 | { ADOPT_TYPE_LITERAL }, 790 | { ADOPT_TYPE_ARG, "arg1", 0, &arg1, 0 }, 791 | { ADOPT_TYPE_ARG, "arg2", 0, &arg2, 0 }, 792 | { 0 }, 793 | }; 794 | 795 | char *args[] = { "-f", "--", "--bar" }; 796 | 797 | cl_must_pass(adopt_parse(&result, specs, args, 3, ADOPT_PARSE_DEFAULT)); 798 | 799 | cl_assert_equal_i('f', foo); 800 | cl_assert_equal_p(NULL, bar); 801 | cl_assert_equal_s("--bar", arg1); 802 | cl_assert_equal_p(NULL, arg2); 803 | 804 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 805 | cl_assert_equal_p(NULL, result.arg); 806 | cl_assert_equal_p(NULL, result.value); 807 | } 808 | 809 | void test_adopt__parse_args(void) 810 | { 811 | int foo = 0; 812 | char *bar = NULL, **argz = NULL; 813 | adopt_opt result; 814 | 815 | adopt_spec specs[] = { 816 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 817 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 818 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 819 | { 0 }, 820 | }; 821 | 822 | char *args[] = { "-f", "--bar", "BRR", "one", "two", "three", "four" }; 823 | 824 | cl_must_pass(adopt_parse(&result, specs, args, 7, ADOPT_PARSE_DEFAULT)); 825 | 826 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 827 | cl_assert_equal_p(NULL, result.arg); 828 | cl_assert_equal_p(NULL, result.value); 829 | cl_assert_equal_i(4, result.args_len); 830 | 831 | cl_assert_equal_i('f', foo); 832 | cl_assert_equal_s("BRR", bar); 833 | cl_assert(argz); 834 | cl_assert_equal_s("one", argz[0]); 835 | cl_assert_equal_s("two", argz[1]); 836 | cl_assert_equal_s("three", argz[2]); 837 | cl_assert_equal_s("four", argz[3]); 838 | } 839 | 840 | void test_adopt__parse_args_with_literal(void) 841 | { 842 | int foo = 0; 843 | char *bar = NULL, **argz = NULL; 844 | adopt_opt result; 845 | 846 | adopt_spec specs[] = { 847 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 848 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 849 | { ADOPT_TYPE_LITERAL }, 850 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 851 | { 0 }, 852 | }; 853 | 854 | char *args[] = { "-f", "--", "--bar", "asdf", "--baz" }; 855 | 856 | cl_must_pass(adopt_parse(&result, specs, args, 5, ADOPT_PARSE_DEFAULT)); 857 | 858 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 859 | cl_assert_equal_p(NULL, result.arg); 860 | cl_assert_equal_p(NULL, result.value); 861 | cl_assert_equal_i(3, result.args_len); 862 | 863 | cl_assert_equal_i('f', foo); 864 | cl_assert_equal_p(NULL, bar); 865 | cl_assert(argz); 866 | cl_assert_equal_s("--bar", argz[0]); 867 | cl_assert_equal_s("asdf", argz[1]); 868 | cl_assert_equal_s("--baz", argz[2]); 869 | } 870 | 871 | void test_adopt__parse_args_implies_literal(void) 872 | { 873 | int foo = 0; 874 | char *bar = NULL, **argz = NULL; 875 | adopt_opt result; 876 | 877 | adopt_spec specs[] = { 878 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 879 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 880 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 881 | { 0 }, 882 | }; 883 | 884 | char *args[] = { "-f", "foo", "bar", "--bar" }; 885 | 886 | cl_must_pass(adopt_parse(&result, specs, args, 4, ADOPT_PARSE_DEFAULT)); 887 | 888 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 889 | cl_assert_equal_p(NULL, result.arg); 890 | cl_assert_equal_p(NULL, result.value); 891 | cl_assert_equal_i(3, result.args_len); 892 | 893 | cl_assert_equal_i('f', foo); 894 | cl_assert_equal_p(NULL, bar); 895 | cl_assert(argz); 896 | cl_assert_equal_s("foo", argz[0]); 897 | cl_assert_equal_s("bar", argz[1]); 898 | cl_assert_equal_s("--bar", argz[2]); 899 | } 900 | 901 | void test_adopt__parse_options_gnustyle(void) 902 | { 903 | int foo = 0; 904 | char *bar = NULL, **argz = NULL; 905 | char **args; 906 | adopt_opt result; 907 | 908 | adopt_spec specs[] = { 909 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 910 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0 }, 911 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 912 | { 0 }, 913 | }; 914 | 915 | /* allocate so that `adopt_parse` can reorder things */ 916 | cl_assert(args = malloc(sizeof(char *) * 7)); 917 | args[0] = "BRR"; 918 | args[1] = "-f"; 919 | args[2] = "one"; 920 | args[3] = "two"; 921 | args[4] = "--bar"; 922 | args[5] = "three"; 923 | args[6] = "four"; 924 | 925 | cl_must_pass(adopt_parse(&result, specs, args, 7, ADOPT_PARSE_GNU)); 926 | 927 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 928 | cl_assert_equal_p(NULL, result.arg); 929 | cl_assert_equal_p(NULL, result.value); 930 | cl_assert_equal_i(4, result.args_len); 931 | 932 | cl_assert_equal_i('f', foo); 933 | cl_assert_equal_s("three", bar); 934 | cl_assert(argz); 935 | cl_assert_equal_s("BRR", argz[0]); 936 | cl_assert_equal_s("one", argz[1]); 937 | cl_assert_equal_s("two", argz[2]); 938 | cl_assert_equal_s("four", argz[3]); 939 | 940 | free(args); 941 | } 942 | 943 | void test_adopt__parse_options_gnustyle_dangling_value(void) 944 | { 945 | int foo = 0; 946 | char *bar = NULL, **argz = NULL; 947 | char **args; 948 | adopt_opt result; 949 | 950 | adopt_spec specs[] = { 951 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 952 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 0 }, 953 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 954 | { 0 }, 955 | }; 956 | 957 | /* allocate so that `adopt_parse` can reorder things */ 958 | cl_assert(args = malloc(sizeof(char *) * 7)); 959 | args[0] = "BRR"; 960 | args[1] = "-f"; 961 | args[2] = "one"; 962 | args[3] = "two"; 963 | args[4] = "three"; 964 | args[5] = "four"; 965 | args[6] = "--bar"; 966 | 967 | cl_must_pass(adopt_parse(&result, specs, args, 7, ADOPT_PARSE_GNU)); 968 | 969 | cl_assert_equal_i(ADOPT_STATUS_MISSING_VALUE, result.status); 970 | cl_assert_equal_s("--bar", result.arg); 971 | 972 | free(args); 973 | } 974 | 975 | void test_adopt__parse_options_gnustyle_with_compressed_shorts(void) 976 | { 977 | int foo = 0, baz = 0; 978 | char *bar = NULL, **argz = NULL; 979 | char **args; 980 | adopt_opt result; 981 | 982 | adopt_spec specs[] = { 983 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 984 | { ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 }, 985 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' }, 986 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 987 | { 0 }, 988 | }; 989 | 990 | /* allocate so that `adopt_parse` can reorder things */ 991 | cl_assert(args = malloc(sizeof(char *) * 7)); 992 | args[0] = "BRR"; 993 | args[1] = "-fzb"; 994 | args[2] = "bar"; 995 | args[3] = "one"; 996 | args[4] = "two"; 997 | args[5] = "three"; 998 | args[6] = "four"; 999 | 1000 | cl_must_pass(adopt_parse(&result, specs, args, 7, ADOPT_PARSE_GNU)); 1001 | 1002 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1003 | cl_assert_equal_p(NULL, result.arg); 1004 | cl_assert_equal_p(NULL, result.value); 1005 | cl_assert_equal_i(5, result.args_len); 1006 | 1007 | cl_assert_equal_i('f', foo); 1008 | cl_assert_equal_s("bar", bar); 1009 | cl_assert(argz); 1010 | cl_assert_equal_s("BRR", argz[0]); 1011 | cl_assert_equal_s("one", argz[1]); 1012 | cl_assert_equal_s("two", argz[2]); 1013 | cl_assert_equal_s("three", argz[3]); 1014 | cl_assert_equal_s("four", argz[4]); 1015 | 1016 | free(args); 1017 | } 1018 | 1019 | void test_adopt__compressed_shorts1(void) 1020 | { 1021 | int foo = 0, baz = 0; 1022 | char *bar = NULL, **argz = NULL; 1023 | adopt_opt result; 1024 | 1025 | adopt_spec specs[] = { 1026 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 1027 | { ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 }, 1028 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' }, 1029 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 1030 | { 0 }, 1031 | }; 1032 | 1033 | char *args[] = { "-fzb", "asdf", "foobar" }; 1034 | 1035 | cl_must_pass(adopt_parse(&result, specs, args, 3, ADOPT_PARSE_DEFAULT)); 1036 | 1037 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1038 | cl_assert_equal_p(NULL, result.arg); 1039 | cl_assert_equal_p(NULL, result.value); 1040 | cl_assert_equal_i(1, result.args_len); 1041 | 1042 | cl_assert_equal_i('f', foo); 1043 | cl_assert_equal_i(1, baz); 1044 | cl_assert_equal_s("asdf", bar); 1045 | cl_assert(argz); 1046 | cl_assert_equal_s("foobar", argz[0]); 1047 | } 1048 | 1049 | void test_adopt__compressed_shorts2(void) 1050 | { 1051 | int foo = 0, baz = 0; 1052 | char *bar = NULL, **argz = NULL; 1053 | adopt_opt result; 1054 | 1055 | adopt_spec specs[] = { 1056 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 1057 | { ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 }, 1058 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' }, 1059 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 1060 | { 0 }, 1061 | }; 1062 | 1063 | char *args[] = { "-fzbasdf", "foobar" }; 1064 | 1065 | cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1066 | 1067 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1068 | cl_assert_equal_p(NULL, result.arg); 1069 | cl_assert_equal_p(NULL, result.value); 1070 | cl_assert_equal_i(1, result.args_len); 1071 | 1072 | cl_assert_equal_i('f', foo); 1073 | cl_assert_equal_i(1, baz); 1074 | cl_assert_equal_s("asdf", bar); 1075 | cl_assert(argz); 1076 | cl_assert_equal_s("foobar", argz[0]); 1077 | } 1078 | 1079 | void test_adopt__compressed_shorts3(void) 1080 | { 1081 | int foo = 0, baz = 0; 1082 | char *bar = NULL, **argz = NULL; 1083 | adopt_opt result; 1084 | 1085 | adopt_spec specs[] = { 1086 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 1087 | { ADOPT_TYPE_BOOL, "baz", 'z', &baz, 0 }, 1088 | { ADOPT_TYPE_VALUE, "bar", 'b', &bar, 'b' }, 1089 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 1090 | { 0 }, 1091 | }; 1092 | 1093 | char *args[] = { "-fbzasdf", "foobar" }; 1094 | 1095 | cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1096 | 1097 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1098 | cl_assert_equal_p(NULL, result.arg); 1099 | cl_assert_equal_p(NULL, result.value); 1100 | cl_assert_equal_i(1, result.args_len); 1101 | 1102 | cl_assert_equal_i('f', foo); 1103 | cl_assert_equal_i(0, baz); 1104 | cl_assert_equal_s("zasdf", bar); 1105 | cl_assert(argz); 1106 | cl_assert_equal_s("foobar", argz[0]); 1107 | } 1108 | 1109 | void test_adopt__value_required(void) 1110 | { 1111 | int foo = 0; 1112 | char *bar = NULL, **argz = NULL; 1113 | adopt_opt result; 1114 | 1115 | adopt_spec specs[] = { 1116 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f' }, 1117 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b' }, 1118 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0 }, 1119 | { 0 }, 1120 | }; 1121 | 1122 | char *args[] = { "-f", "--bar" }; 1123 | 1124 | cl_must_pass(adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1125 | 1126 | cl_assert_equal_i(ADOPT_STATUS_MISSING_VALUE, result.status); 1127 | } 1128 | 1129 | void test_adopt__required_choice_missing(void) 1130 | { 1131 | int foo = 0; 1132 | char *bar = NULL, **argz = NULL; 1133 | adopt_opt result; 1134 | 1135 | adopt_spec specs[] = { 1136 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f', ADOPT_USAGE_REQUIRED }, 1137 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b', ADOPT_USAGE_CHOICE }, 1138 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0, 0 }, 1139 | { 0 }, 1140 | }; 1141 | 1142 | char *args[] = { "foo", "bar" }; 1143 | 1144 | cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1145 | 1146 | cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, result.status); 1147 | cl_assert_equal_s("foo", result.spec->name); 1148 | cl_assert_equal_i('f', result.spec->alias); 1149 | cl_assert_equal_p(NULL, result.arg); 1150 | cl_assert_equal_p(NULL, result.value); 1151 | cl_assert_equal_i(2, result.args_len); 1152 | } 1153 | 1154 | void test_adopt__required_choice_specified(void) 1155 | { 1156 | int foo = 0; 1157 | char *bar = NULL, *baz = NULL, **argz = NULL; 1158 | adopt_opt result; 1159 | 1160 | adopt_spec specs[] = { 1161 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f', ADOPT_USAGE_REQUIRED }, 1162 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b', ADOPT_USAGE_CHOICE }, 1163 | { ADOPT_TYPE_ARG, "baz", 0, &baz, 0, ADOPT_USAGE_REQUIRED }, 1164 | { ADOPT_TYPE_ARGS, "argz", 0, &argz, 0, 0 }, 1165 | { 0 }, 1166 | }; 1167 | 1168 | char *args[] = { "--bar", "b" }; 1169 | 1170 | cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1171 | 1172 | cl_assert_equal_i(ADOPT_STATUS_MISSING_ARGUMENT, result.status); 1173 | cl_assert_equal_s("baz", result.spec->name); 1174 | cl_assert_equal_i(0, result.spec->alias); 1175 | cl_assert_equal_p(NULL, result.arg); 1176 | cl_assert_equal_p(NULL, result.value); 1177 | cl_assert_equal_i(0, result.args_len); 1178 | } 1179 | 1180 | void test_adopt__choice_switch_or_arg_advances_arg(void) 1181 | { 1182 | int foo = 0; 1183 | char *bar = NULL, *baz = NULL, *final = NULL; 1184 | adopt_opt result; 1185 | 1186 | adopt_spec specs[] = { 1187 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f', 0 }, 1188 | { ADOPT_TYPE_SWITCH, "fooz", 'z', &foo, 'z', ADOPT_USAGE_CHOICE }, 1189 | { ADOPT_TYPE_VALUE, "bar", 0, &bar, 'b', ADOPT_USAGE_CHOICE }, 1190 | { ADOPT_TYPE_ARG, "baz", 0, &baz, 0, ADOPT_USAGE_CHOICE }, 1191 | { ADOPT_TYPE_ARG, "final", 0, &final, 0, 0 }, 1192 | { 0 }, 1193 | }; 1194 | 1195 | char *args[] = { "-z", "actually_final" }; 1196 | 1197 | cl_assert_equal_i(ADOPT_STATUS_DONE, adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1198 | 1199 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1200 | cl_assert_equal_p(NULL, result.arg); 1201 | cl_assert_equal_p(NULL, result.value); 1202 | cl_assert_equal_i(0, result.args_len); 1203 | 1204 | cl_assert_equal_i('z', foo); 1205 | cl_assert_equal_p(NULL, bar); 1206 | cl_assert_equal_p(NULL, baz); 1207 | cl_assert_equal_s("actually_final", final); 1208 | } 1209 | 1210 | void test_adopt__stop(void) 1211 | { 1212 | int foo = 0, bar = 0, help = 0, baz = 0; 1213 | adopt_opt result; 1214 | 1215 | adopt_spec specs[] = { 1216 | { ADOPT_TYPE_SWITCH, "foo", 'f', &foo, 'f', ADOPT_USAGE_REQUIRED }, 1217 | { ADOPT_TYPE_SWITCH, "bar", 0, &bar, 'b', ADOPT_USAGE_REQUIRED }, 1218 | { ADOPT_TYPE_SWITCH, "help", 0, &help, 'h', ADOPT_USAGE_STOP_PARSING }, 1219 | { ADOPT_TYPE_SWITCH, "baz", 0, &baz, 'z', ADOPT_USAGE_REQUIRED }, 1220 | { 0 }, 1221 | }; 1222 | 1223 | char *args[] = { "-f", "--help", "-z" }; 1224 | 1225 | cl_assert_equal_i(ADOPT_STATUS_DONE, adopt_parse(&result, specs, args, 2, ADOPT_PARSE_DEFAULT)); 1226 | 1227 | cl_assert_equal_i(ADOPT_STATUS_DONE, result.status); 1228 | cl_assert_equal_s("--help", result.arg); 1229 | cl_assert_equal_p(NULL, result.value); 1230 | cl_assert_equal_i(0, result.args_len); 1231 | 1232 | cl_assert_equal_i('f', foo); 1233 | cl_assert_equal_p('h', help); 1234 | cl_assert_equal_p(0, bar); 1235 | cl_assert_equal_p(0, baz); 1236 | } 1237 | -------------------------------------------------------------------------------- /tests/adopt_clar.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | 3 | int main(int argc, char **argv) 4 | { 5 | return clar_test(argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /tests/clar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* required for sandboxing */ 17 | #include 18 | #include 19 | 20 | #ifdef _WIN32 21 | # include 22 | # include 23 | # include 24 | # include 25 | 26 | # define _MAIN_CC __cdecl 27 | 28 | # ifndef stat 29 | # define stat(path, st) _stat(path, st) 30 | # endif 31 | # ifndef mkdir 32 | # define mkdir(path, mode) _mkdir(path) 33 | # endif 34 | # ifndef chdir 35 | # define chdir(path) _chdir(path) 36 | # endif 37 | # ifndef access 38 | # define access(path, mode) _access(path, mode) 39 | # endif 40 | # ifndef strdup 41 | # define strdup(str) _strdup(str) 42 | # endif 43 | # ifndef strcasecmp 44 | # define strcasecmp(a,b) _stricmp(a,b) 45 | # endif 46 | 47 | # ifndef __MINGW32__ 48 | # pragma comment(lib, "shell32") 49 | # ifndef strncpy 50 | # define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) 51 | # endif 52 | # ifndef W_OK 53 | # define W_OK 02 54 | # endif 55 | # ifndef S_ISDIR 56 | # define S_ISDIR(x) ((x & _S_IFDIR) != 0) 57 | # endif 58 | # define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) 59 | # else 60 | # define p_snprintf snprintf 61 | # endif 62 | 63 | # ifndef PRIuZ 64 | # define PRIuZ "Iu" 65 | # endif 66 | # ifndef PRIxZ 67 | # define PRIxZ "Ix" 68 | # endif 69 | 70 | # if defined(_MSC_VER) || defined(__MINGW32__) 71 | typedef struct stat STAT_T; 72 | # else 73 | typedef struct _stat STAT_T; 74 | # endif 75 | #else 76 | # include /* waitpid(2) */ 77 | # include 78 | # define _MAIN_CC 79 | # define p_snprintf snprintf 80 | # ifndef PRIuZ 81 | # define PRIuZ "zu" 82 | # endif 83 | # ifndef PRIxZ 84 | # define PRIxZ "zx" 85 | # endif 86 | typedef struct stat STAT_T; 87 | #endif 88 | 89 | #define MAX(x, y) (((x) > (y)) ? (x) : (y)) 90 | 91 | #include "clar.h" 92 | 93 | static void fs_rm(const char *_source); 94 | static void fs_copy(const char *_source, const char *dest); 95 | 96 | #ifdef CLAR_FIXTURE_PATH 97 | static const char * 98 | fixture_path(const char *base, const char *fixture_name); 99 | #endif 100 | 101 | struct clar_error { 102 | const char *file; 103 | const char *function; 104 | size_t line_number; 105 | const char *error_msg; 106 | char *description; 107 | 108 | struct clar_error *next; 109 | }; 110 | 111 | struct clar_explicit { 112 | size_t suite_idx; 113 | const char *filter; 114 | 115 | struct clar_explicit *next; 116 | }; 117 | 118 | struct clar_report { 119 | const char *test; 120 | int test_number; 121 | const char *suite; 122 | 123 | enum cl_test_status status; 124 | 125 | struct clar_error *errors; 126 | struct clar_error *last_error; 127 | 128 | struct clar_report *next; 129 | }; 130 | 131 | struct clar_summary { 132 | const char *filename; 133 | FILE *fp; 134 | }; 135 | 136 | static struct { 137 | enum cl_test_status test_status; 138 | 139 | const char *active_test; 140 | const char *active_suite; 141 | 142 | int total_skipped; 143 | int total_errors; 144 | 145 | int tests_ran; 146 | int suites_ran; 147 | 148 | enum cl_output_format output_format; 149 | 150 | int report_errors_only; 151 | int exit_on_error; 152 | int verbosity; 153 | 154 | int write_summary; 155 | char *summary_filename; 156 | struct clar_summary *summary; 157 | 158 | struct clar_explicit *explicit; 159 | struct clar_explicit *last_explicit; 160 | 161 | struct clar_report *reports; 162 | struct clar_report *last_report; 163 | 164 | void (*local_cleanup)(void *); 165 | void *local_cleanup_payload; 166 | 167 | jmp_buf trampoline; 168 | int trampoline_enabled; 169 | 170 | cl_trace_cb *pfn_trace_cb; 171 | void *trace_payload; 172 | 173 | } _clar; 174 | 175 | struct clar_func { 176 | const char *name; 177 | void (*ptr)(void); 178 | }; 179 | 180 | struct clar_suite { 181 | const char *name; 182 | struct clar_func initialize; 183 | struct clar_func cleanup; 184 | const struct clar_func *tests; 185 | size_t test_count; 186 | int enabled; 187 | }; 188 | 189 | /* From clar_print_*.c */ 190 | static void clar_print_init(int test_count, int suite_count, const char *suite_names); 191 | static void clar_print_shutdown(int test_count, int suite_count, int error_count); 192 | static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); 193 | static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed); 194 | static void clar_print_onsuite(const char *suite_name, int suite_index); 195 | static void clar_print_onabort(const char *msg, ...); 196 | 197 | /* From clar_sandbox.c */ 198 | static void clar_unsandbox(void); 199 | static int clar_sandbox(void); 200 | 201 | /* From summary.h */ 202 | static struct clar_summary *clar_summary_init(const char *filename); 203 | static int clar_summary_shutdown(struct clar_summary *fp); 204 | 205 | /* Load the declarations for the test suite */ 206 | #include "clar.suite" 207 | 208 | 209 | #define CL_TRACE(ev) \ 210 | do { \ 211 | if (_clar.pfn_trace_cb) \ 212 | _clar.pfn_trace_cb(ev, \ 213 | _clar.active_suite, \ 214 | _clar.active_test, \ 215 | _clar.trace_payload); \ 216 | } while (0) 217 | 218 | void cl_trace_register(cl_trace_cb *cb, void *payload) 219 | { 220 | _clar.pfn_trace_cb = cb; 221 | _clar.trace_payload = payload; 222 | } 223 | 224 | 225 | /* Core test functions */ 226 | static void 227 | clar_report_errors(struct clar_report *report) 228 | { 229 | struct clar_error *error; 230 | int i = 1; 231 | 232 | for (error = report->errors; error; error = error->next) 233 | clar_print_error(i++, _clar.last_report, error); 234 | } 235 | 236 | static void 237 | clar_report_all(void) 238 | { 239 | struct clar_report *report; 240 | struct clar_error *error; 241 | int i = 1; 242 | 243 | for (report = _clar.reports; report; report = report->next) { 244 | if (report->status != CL_TEST_FAILURE) 245 | continue; 246 | 247 | for (error = report->errors; error; error = error->next) 248 | clar_print_error(i++, report, error); 249 | } 250 | } 251 | 252 | static void 253 | clar_run_test( 254 | const struct clar_suite *suite, 255 | const struct clar_func *test, 256 | const struct clar_func *initialize, 257 | const struct clar_func *cleanup) 258 | { 259 | _clar.trampoline_enabled = 1; 260 | 261 | CL_TRACE(CL_TRACE__TEST__BEGIN); 262 | 263 | if (setjmp(_clar.trampoline) == 0) { 264 | if (initialize->ptr != NULL) 265 | initialize->ptr(); 266 | 267 | CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); 268 | test->ptr(); 269 | CL_TRACE(CL_TRACE__TEST__RUN_END); 270 | } 271 | 272 | _clar.trampoline_enabled = 0; 273 | 274 | if (_clar.last_report->status == CL_TEST_NOTRUN) 275 | _clar.last_report->status = CL_TEST_OK; 276 | 277 | if (_clar.local_cleanup != NULL) 278 | _clar.local_cleanup(_clar.local_cleanup_payload); 279 | 280 | if (cleanup->ptr != NULL) 281 | cleanup->ptr(); 282 | 283 | CL_TRACE(CL_TRACE__TEST__END); 284 | 285 | _clar.tests_ran++; 286 | 287 | /* remove any local-set cleanup methods */ 288 | _clar.local_cleanup = NULL; 289 | _clar.local_cleanup_payload = NULL; 290 | 291 | if (_clar.report_errors_only) { 292 | clar_report_errors(_clar.last_report); 293 | } else { 294 | clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status); 295 | } 296 | } 297 | 298 | static void 299 | clar_run_suite(const struct clar_suite *suite, const char *filter) 300 | { 301 | const struct clar_func *test = suite->tests; 302 | size_t i, matchlen; 303 | struct clar_report *report; 304 | int exact = 0; 305 | 306 | if (!suite->enabled) 307 | return; 308 | 309 | if (_clar.exit_on_error && _clar.total_errors) 310 | return; 311 | 312 | if (!_clar.report_errors_only) 313 | clar_print_onsuite(suite->name, ++_clar.suites_ran); 314 | 315 | _clar.active_suite = suite->name; 316 | _clar.active_test = NULL; 317 | CL_TRACE(CL_TRACE__SUITE_BEGIN); 318 | 319 | if (filter) { 320 | size_t suitelen = strlen(suite->name); 321 | matchlen = strlen(filter); 322 | if (matchlen <= suitelen) { 323 | filter = NULL; 324 | } else { 325 | filter += suitelen; 326 | while (*filter == ':') 327 | ++filter; 328 | matchlen = strlen(filter); 329 | 330 | if (matchlen && filter[matchlen - 1] == '$') { 331 | exact = 1; 332 | matchlen--; 333 | } 334 | } 335 | } 336 | 337 | for (i = 0; i < suite->test_count; ++i) { 338 | if (filter && strncmp(test[i].name, filter, matchlen)) 339 | continue; 340 | 341 | if (exact && strlen(test[i].name) != matchlen) 342 | continue; 343 | 344 | _clar.active_test = test[i].name; 345 | 346 | report = calloc(1, sizeof(struct clar_report)); 347 | report->suite = _clar.active_suite; 348 | report->test = _clar.active_test; 349 | report->test_number = _clar.tests_ran; 350 | report->status = CL_TEST_NOTRUN; 351 | 352 | if (_clar.reports == NULL) 353 | _clar.reports = report; 354 | 355 | if (_clar.last_report != NULL) 356 | _clar.last_report->next = report; 357 | 358 | _clar.last_report = report; 359 | 360 | clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup); 361 | 362 | if (_clar.exit_on_error && _clar.total_errors) 363 | return; 364 | } 365 | 366 | _clar.active_test = NULL; 367 | CL_TRACE(CL_TRACE__SUITE_END); 368 | } 369 | 370 | static void 371 | clar_usage(const char *arg) 372 | { 373 | printf("Usage: %s [options]\n\n", arg); 374 | printf("Options:\n"); 375 | printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); 376 | printf(" -iname Include the suite with `name`\n"); 377 | printf(" -xname Exclude the suite with `name`\n"); 378 | printf(" -v Increase verbosity (show suite names)\n"); 379 | printf(" -q Only report tests that had an error\n"); 380 | printf(" -Q Quit as soon as a test fails\n"); 381 | printf(" -t Display results in tap format\n"); 382 | printf(" -l Print suite names\n"); 383 | printf(" -r[filename] Write summary file (to the optional filename)\n"); 384 | exit(-1); 385 | } 386 | 387 | static void 388 | clar_parse_args(int argc, char **argv) 389 | { 390 | int i; 391 | 392 | /* Verify options before execute */ 393 | for (i = 1; i < argc; ++i) { 394 | char *argument = argv[i]; 395 | 396 | if (argument[0] != '-' || argument[1] == '\0' 397 | || strchr("sixvqQtlr", argument[1]) == NULL) { 398 | clar_usage(argv[0]); 399 | } 400 | } 401 | 402 | for (i = 1; i < argc; ++i) { 403 | char *argument = argv[i]; 404 | 405 | switch (argument[1]) { 406 | case 's': 407 | case 'i': 408 | case 'x': { /* given suite name */ 409 | int offset = (argument[2] == '=') ? 3 : 2, found = 0; 410 | char action = argument[1]; 411 | size_t j, arglen, suitelen, cmplen; 412 | 413 | argument += offset; 414 | arglen = strlen(argument); 415 | 416 | if (arglen == 0) 417 | clar_usage(argv[0]); 418 | 419 | for (j = 0; j < _clar_suite_count; ++j) { 420 | suitelen = strlen(_clar_suites[j].name); 421 | cmplen = (arglen < suitelen) ? arglen : suitelen; 422 | 423 | if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { 424 | int exact = (arglen >= suitelen); 425 | 426 | /* Do we have a real suite prefix separated by a 427 | * trailing '::' or just a matching substring? */ 428 | if (arglen > suitelen && (argument[suitelen] != ':' 429 | || argument[suitelen + 1] != ':')) 430 | continue; 431 | 432 | ++found; 433 | 434 | if (!exact) 435 | _clar.verbosity = MAX(_clar.verbosity, 1); 436 | 437 | switch (action) { 438 | case 's': { 439 | struct clar_explicit *explicit = 440 | calloc(1, sizeof(struct clar_explicit)); 441 | assert(explicit); 442 | 443 | explicit->suite_idx = j; 444 | explicit->filter = argument; 445 | 446 | if (_clar.explicit == NULL) 447 | _clar.explicit = explicit; 448 | 449 | if (_clar.last_explicit != NULL) 450 | _clar.last_explicit->next = explicit; 451 | 452 | _clar_suites[j].enabled = 1; 453 | _clar.last_explicit = explicit; 454 | break; 455 | } 456 | case 'i': _clar_suites[j].enabled = 1; break; 457 | case 'x': _clar_suites[j].enabled = 0; break; 458 | } 459 | 460 | if (exact) 461 | break; 462 | } 463 | } 464 | 465 | if (!found) { 466 | clar_print_onabort("No suite matching '%s' found.\n", argument); 467 | exit(-1); 468 | } 469 | break; 470 | } 471 | 472 | case 'q': 473 | _clar.report_errors_only = 1; 474 | break; 475 | 476 | case 'Q': 477 | _clar.exit_on_error = 1; 478 | break; 479 | 480 | case 't': 481 | _clar.output_format = CL_OUTPUT_TAP; 482 | break; 483 | 484 | case 'l': { 485 | size_t j; 486 | printf("Test suites (use -s to run just one):\n"); 487 | for (j = 0; j < _clar_suite_count; ++j) 488 | printf(" %3d: %s\n", (int)j, _clar_suites[j].name); 489 | 490 | exit(0); 491 | } 492 | 493 | case 'v': 494 | _clar.verbosity++; 495 | printf("VERBOSITY: _clar.verbosity: %d\n", _clar.verbosity); 496 | break; 497 | 498 | case 'r': 499 | _clar.write_summary = 1; 500 | free(_clar.summary_filename); 501 | _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); 502 | break; 503 | 504 | default: 505 | assert(!"Unexpected commandline argument!"); 506 | } 507 | } 508 | } 509 | 510 | void 511 | clar_test_init(int argc, char **argv) 512 | { 513 | if (argc > 1) 514 | clar_parse_args(argc, argv); 515 | 516 | clar_print_init( 517 | (int)_clar_callback_count, 518 | (int)_clar_suite_count, 519 | "" 520 | ); 521 | 522 | if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { 523 | _clar.write_summary = 1; 524 | _clar.summary_filename = strdup(_clar.summary_filename); 525 | } 526 | 527 | if (_clar.write_summary && 528 | !(_clar.summary = clar_summary_init(_clar.summary_filename))) { 529 | clar_print_onabort("Failed to open the summary file\n"); 530 | exit(-1); 531 | } 532 | 533 | if (clar_sandbox() < 0) { 534 | clar_print_onabort("Failed to sandbox the test runner.\n"); 535 | exit(-1); 536 | } 537 | } 538 | 539 | int 540 | clar_test_run(void) 541 | { 542 | size_t i; 543 | struct clar_explicit *explicit; 544 | 545 | if (_clar.explicit) { 546 | for (explicit = _clar.explicit; explicit; explicit = explicit->next) 547 | clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); 548 | } else { 549 | for (i = 0; i < _clar_suite_count; ++i) 550 | clar_run_suite(&_clar_suites[i], NULL); 551 | } 552 | 553 | return _clar.total_errors; 554 | } 555 | 556 | void 557 | clar_test_shutdown(void) 558 | { 559 | struct clar_explicit *explicit, *explicit_next; 560 | struct clar_report *report, *report_next; 561 | 562 | clar_print_shutdown( 563 | _clar.tests_ran, 564 | (int)_clar_suite_count, 565 | _clar.total_errors 566 | ); 567 | 568 | clar_unsandbox(); 569 | 570 | if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { 571 | clar_print_onabort("Failed to write the summary file\n"); 572 | exit(-1); 573 | } 574 | 575 | for (explicit = _clar.explicit; explicit; explicit = explicit_next) { 576 | explicit_next = explicit->next; 577 | free(explicit); 578 | } 579 | 580 | for (report = _clar.reports; report; report = report_next) { 581 | report_next = report->next; 582 | free(report); 583 | } 584 | 585 | free(_clar.summary_filename); 586 | } 587 | 588 | int 589 | clar_test(int argc, char **argv) 590 | { 591 | int errors; 592 | 593 | clar_test_init(argc, argv); 594 | errors = clar_test_run(); 595 | clar_test_shutdown(); 596 | 597 | return errors; 598 | } 599 | 600 | static void abort_test(void) 601 | { 602 | if (!_clar.trampoline_enabled) { 603 | clar_print_onabort( 604 | "Fatal error: a cleanup method raised an exception."); 605 | clar_report_errors(_clar.last_report); 606 | exit(-1); 607 | } 608 | 609 | CL_TRACE(CL_TRACE__TEST__LONGJMP); 610 | longjmp(_clar.trampoline, -1); 611 | } 612 | 613 | void clar__skip(void) 614 | { 615 | _clar.last_report->status = CL_TEST_SKIP; 616 | _clar.total_skipped++; 617 | abort_test(); 618 | } 619 | 620 | void clar__fail( 621 | const char *file, 622 | const char *function, 623 | size_t line, 624 | const char *error_msg, 625 | const char *description, 626 | int should_abort) 627 | { 628 | struct clar_error *error = calloc(1, sizeof(struct clar_error)); 629 | 630 | if (_clar.last_report->errors == NULL) 631 | _clar.last_report->errors = error; 632 | 633 | if (_clar.last_report->last_error != NULL) 634 | _clar.last_report->last_error->next = error; 635 | 636 | _clar.last_report->last_error = error; 637 | 638 | error->file = file; 639 | error->function = function; 640 | error->line_number = line; 641 | error->error_msg = error_msg; 642 | 643 | if (description != NULL) 644 | error->description = strdup(description); 645 | 646 | _clar.total_errors++; 647 | _clar.last_report->status = CL_TEST_FAILURE; 648 | 649 | if (should_abort) 650 | abort_test(); 651 | } 652 | 653 | void clar__assert( 654 | int condition, 655 | const char *file, 656 | const char *function, 657 | size_t line, 658 | const char *error_msg, 659 | const char *description, 660 | int should_abort) 661 | { 662 | if (condition) 663 | return; 664 | 665 | clar__fail(file, function, line, error_msg, description, should_abort); 666 | } 667 | 668 | void clar__assert_equal( 669 | const char *file, 670 | const char *function, 671 | size_t line, 672 | const char *err, 673 | int should_abort, 674 | const char *fmt, 675 | ...) 676 | { 677 | va_list args; 678 | char buf[4096]; 679 | int is_equal = 1; 680 | 681 | va_start(args, fmt); 682 | 683 | if (!strcmp("%s", fmt)) { 684 | const char *s1 = va_arg(args, const char *); 685 | const char *s2 = va_arg(args, const char *); 686 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); 687 | 688 | if (!is_equal) { 689 | if (s1 && s2) { 690 | int pos; 691 | for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) 692 | /* find differing byte offset */; 693 | p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", 694 | s1, s2, pos); 695 | } else { 696 | p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); 697 | } 698 | } 699 | } 700 | else if(!strcmp("%.*s", fmt)) { 701 | const char *s1 = va_arg(args, const char *); 702 | const char *s2 = va_arg(args, const char *); 703 | int len = va_arg(args, int); 704 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); 705 | 706 | if (!is_equal) { 707 | if (s1 && s2) { 708 | int pos; 709 | for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) 710 | /* find differing byte offset */; 711 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", 712 | len, s1, len, s2, pos); 713 | } else { 714 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); 715 | } 716 | } 717 | } 718 | else if (!strcmp("%ls", fmt)) { 719 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 720 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 721 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); 722 | 723 | if (!is_equal) { 724 | if (wcs1 && wcs2) { 725 | int pos; 726 | for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) 727 | /* find differing byte offset */; 728 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", 729 | wcs1, wcs2, pos); 730 | } else { 731 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); 732 | } 733 | } 734 | } 735 | else if(!strcmp("%.*ls", fmt)) { 736 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 737 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 738 | int len = va_arg(args, int); 739 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); 740 | 741 | if (!is_equal) { 742 | if (wcs1 && wcs2) { 743 | int pos; 744 | for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) 745 | /* find differing byte offset */; 746 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", 747 | len, wcs1, len, wcs2, pos); 748 | } else { 749 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); 750 | } 751 | } 752 | } 753 | else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { 754 | size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); 755 | is_equal = (sz1 == sz2); 756 | if (!is_equal) { 757 | int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); 758 | strncat(buf, " != ", sizeof(buf) - offset); 759 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); 760 | } 761 | } 762 | else if (!strcmp("%p", fmt)) { 763 | void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); 764 | is_equal = (p1 == p2); 765 | if (!is_equal) 766 | p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); 767 | } 768 | else { 769 | int i1 = va_arg(args, int), i2 = va_arg(args, int); 770 | is_equal = (i1 == i2); 771 | if (!is_equal) { 772 | int offset = p_snprintf(buf, sizeof(buf), fmt, i1); 773 | strncat(buf, " != ", sizeof(buf) - offset); 774 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); 775 | } 776 | } 777 | 778 | va_end(args); 779 | 780 | if (!is_equal) 781 | clar__fail(file, function, line, err, buf, should_abort); 782 | } 783 | 784 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque) 785 | { 786 | _clar.local_cleanup = cleanup; 787 | _clar.local_cleanup_payload = opaque; 788 | } 789 | 790 | #include "clar/sandbox.h" 791 | #include "clar/fixtures.h" 792 | #include "clar/fs.h" 793 | #include "clar/print.h" 794 | #include "clar/summary.h" 795 | -------------------------------------------------------------------------------- /tests/clar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #ifndef __CLAR_TEST_H__ 8 | #define __CLAR_TEST_H__ 9 | 10 | #include 11 | 12 | enum cl_test_status { 13 | CL_TEST_OK, 14 | CL_TEST_FAILURE, 15 | CL_TEST_SKIP, 16 | CL_TEST_NOTRUN, 17 | }; 18 | 19 | enum cl_output_format { 20 | CL_OUTPUT_CLAP, 21 | CL_OUTPUT_TAP, 22 | }; 23 | 24 | /** Setup clar environment */ 25 | void clar_test_init(int argc, char *argv[]); 26 | int clar_test_run(void); 27 | void clar_test_shutdown(void); 28 | 29 | /** One shot setup & run */ 30 | int clar_test(int argc, char *argv[]); 31 | 32 | const char *clar_sandbox_path(void); 33 | 34 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque); 35 | void cl_fs_cleanup(void); 36 | 37 | /** 38 | * cl_trace_* is a hook to provide a simple global tracing 39 | * mechanism. 40 | * 41 | * The goal here is to let main() provide clar-proper 42 | * with a callback to optionally write log info for 43 | * test operations into the same stream used by their 44 | * actual tests. This would let them print test names 45 | * and maybe performance data as they choose. 46 | * 47 | * The goal is NOT to alter the flow of control or to 48 | * override test selection/skipping. (So the callback 49 | * does not return a value.) 50 | * 51 | * The goal is NOT to duplicate the existing 52 | * pass/fail/skip reporting. (So the callback 53 | * does not accept a status/errorcode argument.) 54 | * 55 | */ 56 | typedef enum cl_trace_event { 57 | CL_TRACE__SUITE_BEGIN, 58 | CL_TRACE__SUITE_END, 59 | CL_TRACE__TEST__BEGIN, 60 | CL_TRACE__TEST__END, 61 | CL_TRACE__TEST__RUN_BEGIN, 62 | CL_TRACE__TEST__RUN_END, 63 | CL_TRACE__TEST__LONGJMP, 64 | } cl_trace_event; 65 | 66 | typedef void (cl_trace_cb)( 67 | cl_trace_event ev, 68 | const char *suite_name, 69 | const char *test_name, 70 | void *payload); 71 | 72 | /** 73 | * Register a callback into CLAR to send global trace events. 74 | * Pass NULL to disable. 75 | */ 76 | void cl_trace_register(cl_trace_cb *cb, void *payload); 77 | 78 | 79 | #ifdef CLAR_FIXTURE_PATH 80 | const char *cl_fixture(const char *fixture_name); 81 | void cl_fixture_sandbox(const char *fixture_name); 82 | void cl_fixture_cleanup(const char *fixture_name); 83 | const char *cl_fixture_basename(const char *fixture_name); 84 | #endif 85 | 86 | /** 87 | * Assertion macros with explicit error message 88 | */ 89 | #define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) 90 | #define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) 91 | #define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) 92 | 93 | /** 94 | * Check macros with explicit error message 95 | */ 96 | #define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) 97 | #define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) 98 | #define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) 99 | 100 | /** 101 | * Assertion macros with no error message 102 | */ 103 | #define cl_must_pass(expr) cl_must_pass_(expr, NULL) 104 | #define cl_must_fail(expr) cl_must_fail_(expr, NULL) 105 | #define cl_assert(expr) cl_assert_(expr, NULL) 106 | 107 | /** 108 | * Check macros with no error message 109 | */ 110 | #define cl_check_pass(expr) cl_check_pass_(expr, NULL) 111 | #define cl_check_fail(expr) cl_check_fail_(expr, NULL) 112 | #define cl_check(expr) cl_check_(expr, NULL) 113 | 114 | /** 115 | * Forced failure/warning 116 | */ 117 | #define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) 118 | #define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) 119 | 120 | #define cl_skip() clar__skip() 121 | 122 | /** 123 | * Typed assertion macros 124 | */ 125 | #define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) 126 | #define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) 127 | 128 | #define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) 129 | #define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) 130 | 131 | #define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) 132 | #define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) 133 | 134 | #define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 135 | #define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 136 | 137 | #define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) 138 | #define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) 139 | #define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) 140 | 141 | #define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) 142 | 143 | #define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) 144 | 145 | void clar__skip(void); 146 | 147 | void clar__fail( 148 | const char *file, 149 | const char *func, 150 | size_t line, 151 | const char *error, 152 | const char *description, 153 | int should_abort); 154 | 155 | void clar__assert( 156 | int condition, 157 | const char *file, 158 | const char *func, 159 | size_t line, 160 | const char *error, 161 | const char *description, 162 | int should_abort); 163 | 164 | void clar__assert_equal( 165 | const char *file, 166 | const char *func, 167 | size_t line, 168 | const char *err, 169 | int should_abort, 170 | const char *fmt, 171 | ...); 172 | 173 | #endif 174 | -------------------------------------------------------------------------------- /tests/clar/fixtures.h: -------------------------------------------------------------------------------- 1 | #ifdef CLAR_FIXTURE_PATH 2 | static const char * 3 | fixture_path(const char *base, const char *fixture_name) 4 | { 5 | static char _path[4096]; 6 | size_t root_len; 7 | 8 | root_len = strlen(base); 9 | strncpy(_path, base, sizeof(_path)); 10 | 11 | if (_path[root_len - 1] != '/') 12 | _path[root_len++] = '/'; 13 | 14 | if (fixture_name[0] == '/') 15 | fixture_name++; 16 | 17 | strncpy(_path + root_len, 18 | fixture_name, 19 | sizeof(_path) - root_len); 20 | 21 | return _path; 22 | } 23 | 24 | const char *cl_fixture(const char *fixture_name) 25 | { 26 | return fixture_path(CLAR_FIXTURE_PATH, fixture_name); 27 | } 28 | 29 | void cl_fixture_sandbox(const char *fixture_name) 30 | { 31 | fs_copy(cl_fixture(fixture_name), _clar_path); 32 | } 33 | 34 | const char *cl_fixture_basename(const char *fixture_name) 35 | { 36 | const char *p; 37 | 38 | for (p = fixture_name; *p; p++) { 39 | if (p[0] == '/' && p[1] && p[1] != '/') 40 | fixture_name = p+1; 41 | } 42 | 43 | return fixture_name; 44 | } 45 | 46 | void cl_fixture_cleanup(const char *fixture_name) 47 | { 48 | fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /tests/clar/fs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * By default, use a read/write loop to copy files on POSIX systems. 3 | * On Linux, use sendfile by default as it's slightly faster. On 4 | * macOS, we avoid fcopyfile by default because it's slightly slower. 5 | */ 6 | #undef USE_FCOPYFILE 7 | #define USE_SENDFILE 1 8 | 9 | #ifdef _WIN32 10 | 11 | #ifdef CLAR_WIN32_LONGPATHS 12 | # define CLAR_MAX_PATH 4096 13 | #else 14 | # define CLAR_MAX_PATH MAX_PATH 15 | #endif 16 | 17 | #define RM_RETRY_COUNT 5 18 | #define RM_RETRY_DELAY 10 19 | 20 | #ifdef __MINGW32__ 21 | 22 | /* These security-enhanced functions are not available 23 | * in MinGW, so just use the vanilla ones */ 24 | #define wcscpy_s(a, b, c) wcscpy((a), (c)) 25 | #define wcscat_s(a, b, c) wcscat((a), (c)) 26 | 27 | #endif /* __MINGW32__ */ 28 | 29 | static int 30 | fs__dotordotdot(WCHAR *_tocheck) 31 | { 32 | return _tocheck[0] == '.' && 33 | (_tocheck[1] == '\0' || 34 | (_tocheck[1] == '.' && _tocheck[2] == '\0')); 35 | } 36 | 37 | static int 38 | fs_rmdir_rmdir(WCHAR *_wpath) 39 | { 40 | unsigned retries = 1; 41 | 42 | while (!RemoveDirectoryW(_wpath)) { 43 | /* Only retry when we have retries remaining, and the 44 | * error was ERROR_DIR_NOT_EMPTY. */ 45 | if (retries++ > RM_RETRY_COUNT || 46 | ERROR_DIR_NOT_EMPTY != GetLastError()) 47 | return -1; 48 | 49 | /* Give whatever has a handle to a child item some time 50 | * to release it before trying again */ 51 | Sleep(RM_RETRY_DELAY * retries * retries); 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | static void translate_path(WCHAR *path, size_t path_size) 58 | { 59 | size_t path_len, i; 60 | 61 | if (wcsncmp(path, L"\\\\?\\", 4) == 0) 62 | return; 63 | 64 | path_len = wcslen(path); 65 | cl_assert(path_size > path_len + 4); 66 | 67 | for (i = path_len; i > 0; i--) { 68 | WCHAR c = path[i - 1]; 69 | 70 | if (c == L'/') 71 | path[i + 3] = L'\\'; 72 | else 73 | path[i + 3] = path[i - 1]; 74 | } 75 | 76 | path[0] = L'\\'; 77 | path[1] = L'\\'; 78 | path[2] = L'?'; 79 | path[3] = L'\\'; 80 | path[path_len + 4] = L'\0'; 81 | } 82 | 83 | static void 84 | fs_rmdir_helper(WCHAR *_wsource) 85 | { 86 | WCHAR buffer[CLAR_MAX_PATH]; 87 | HANDLE find_handle; 88 | WIN32_FIND_DATAW find_data; 89 | size_t buffer_prefix_len; 90 | 91 | /* Set up the buffer and capture the length */ 92 | wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); 93 | translate_path(buffer, CLAR_MAX_PATH); 94 | wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); 95 | buffer_prefix_len = wcslen(buffer); 96 | 97 | /* FindFirstFile needs a wildcard to match multiple items */ 98 | wcscat_s(buffer, CLAR_MAX_PATH, L"*"); 99 | find_handle = FindFirstFileW(buffer, &find_data); 100 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 101 | 102 | do { 103 | /* FindFirstFile/FindNextFile gives back . and .. 104 | * entries at the beginning */ 105 | if (fs__dotordotdot(find_data.cFileName)) 106 | continue; 107 | 108 | wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName); 109 | 110 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) 111 | fs_rmdir_helper(buffer); 112 | else { 113 | /* If set, the +R bit must be cleared before deleting */ 114 | if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) 115 | cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); 116 | 117 | cl_assert(DeleteFileW(buffer)); 118 | } 119 | } 120 | while (FindNextFileW(find_handle, &find_data)); 121 | 122 | /* Ensure that we successfully completed the enumeration */ 123 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 124 | 125 | /* Close the find handle */ 126 | FindClose(find_handle); 127 | 128 | /* Now that the directory is empty, remove it */ 129 | cl_assert(0 == fs_rmdir_rmdir(_wsource)); 130 | } 131 | 132 | static int 133 | fs_rm_wait(WCHAR *_wpath) 134 | { 135 | unsigned retries = 1; 136 | DWORD last_error; 137 | 138 | do { 139 | if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) 140 | last_error = GetLastError(); 141 | else 142 | last_error = ERROR_SUCCESS; 143 | 144 | /* Is the item gone? */ 145 | if (ERROR_FILE_NOT_FOUND == last_error || 146 | ERROR_PATH_NOT_FOUND == last_error) 147 | return 0; 148 | 149 | Sleep(RM_RETRY_DELAY * retries * retries); 150 | } 151 | while (retries++ <= RM_RETRY_COUNT); 152 | 153 | return -1; 154 | } 155 | 156 | static void 157 | fs_rm(const char *_source) 158 | { 159 | WCHAR wsource[CLAR_MAX_PATH]; 160 | DWORD attrs; 161 | 162 | /* The input path is UTF-8. Convert it to wide characters 163 | * for use with the Windows API */ 164 | cl_assert(MultiByteToWideChar(CP_UTF8, 165 | MB_ERR_INVALID_CHARS, 166 | _source, 167 | -1, /* Indicates NULL termination */ 168 | wsource, 169 | CLAR_MAX_PATH)); 170 | 171 | translate_path(wsource, CLAR_MAX_PATH); 172 | 173 | /* Does the item exist? If not, we have no work to do */ 174 | attrs = GetFileAttributesW(wsource); 175 | 176 | if (INVALID_FILE_ATTRIBUTES == attrs) 177 | return; 178 | 179 | if (FILE_ATTRIBUTE_DIRECTORY & attrs) 180 | fs_rmdir_helper(wsource); 181 | else { 182 | /* The item is a file. Strip the +R bit */ 183 | if (FILE_ATTRIBUTE_READONLY & attrs) 184 | cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); 185 | 186 | cl_assert(DeleteFileW(wsource)); 187 | } 188 | 189 | /* Wait for the DeleteFile or RemoveDirectory call to complete */ 190 | cl_assert(0 == fs_rm_wait(wsource)); 191 | } 192 | 193 | static void 194 | fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) 195 | { 196 | WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; 197 | HANDLE find_handle; 198 | WIN32_FIND_DATAW find_data; 199 | size_t buf_source_prefix_len, buf_dest_prefix_len; 200 | 201 | wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); 202 | wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); 203 | translate_path(buf_source, CLAR_MAX_PATH); 204 | buf_source_prefix_len = wcslen(buf_source); 205 | 206 | wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); 207 | wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); 208 | translate_path(buf_dest, CLAR_MAX_PATH); 209 | buf_dest_prefix_len = wcslen(buf_dest); 210 | 211 | /* Get an enumerator for the items in the source. */ 212 | wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); 213 | find_handle = FindFirstFileW(buf_source, &find_data); 214 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 215 | 216 | /* Create the target directory. */ 217 | cl_assert(CreateDirectoryW(_wdest, NULL)); 218 | 219 | do { 220 | /* FindFirstFile/FindNextFile gives back . and .. 221 | * entries at the beginning */ 222 | if (fs__dotordotdot(find_data.cFileName)) 223 | continue; 224 | 225 | wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName); 226 | wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName); 227 | 228 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) 229 | fs_copydir_helper(buf_source, buf_dest); 230 | else 231 | cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); 232 | } 233 | while (FindNextFileW(find_handle, &find_data)); 234 | 235 | /* Ensure that we successfully completed the enumeration */ 236 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 237 | 238 | /* Close the find handle */ 239 | FindClose(find_handle); 240 | } 241 | 242 | static void 243 | fs_copy(const char *_source, const char *_dest) 244 | { 245 | WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH]; 246 | DWORD source_attrs, dest_attrs; 247 | HANDLE find_handle; 248 | WIN32_FIND_DATAW find_data; 249 | 250 | /* The input paths are UTF-8. Convert them to wide characters 251 | * for use with the Windows API. */ 252 | cl_assert(MultiByteToWideChar(CP_UTF8, 253 | MB_ERR_INVALID_CHARS, 254 | _source, 255 | -1, 256 | wsource, 257 | CLAR_MAX_PATH)); 258 | 259 | cl_assert(MultiByteToWideChar(CP_UTF8, 260 | MB_ERR_INVALID_CHARS, 261 | _dest, 262 | -1, 263 | wdest, 264 | CLAR_MAX_PATH)); 265 | 266 | translate_path(wsource, CLAR_MAX_PATH); 267 | translate_path(wdest, CLAR_MAX_PATH); 268 | 269 | /* Check the source for existence */ 270 | source_attrs = GetFileAttributesW(wsource); 271 | cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); 272 | 273 | /* Check the target for existence */ 274 | dest_attrs = GetFileAttributesW(wdest); 275 | 276 | if (INVALID_FILE_ATTRIBUTES != dest_attrs) { 277 | /* Target exists; append last path part of source to target. 278 | * Use FindFirstFile to parse the path */ 279 | find_handle = FindFirstFileW(wsource, &find_data); 280 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 281 | wcscat_s(wdest, CLAR_MAX_PATH, L"\\"); 282 | wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName); 283 | FindClose(find_handle); 284 | 285 | /* Check the new target for existence */ 286 | cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); 287 | } 288 | 289 | if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) 290 | fs_copydir_helper(wsource, wdest); 291 | else 292 | cl_assert(CopyFileW(wsource, wdest, TRUE)); 293 | } 294 | 295 | void 296 | cl_fs_cleanup(void) 297 | { 298 | fs_rm(fixture_path(_clar_path, "*")); 299 | } 300 | 301 | #else 302 | 303 | #include 304 | #include 305 | #include 306 | #include 307 | #include 308 | #include 309 | #include 310 | #include 311 | 312 | #if defined(__linux__) 313 | # include 314 | #endif 315 | 316 | #if defined(__APPLE__) 317 | # include 318 | #endif 319 | 320 | static void basename_r(const char **out, int *out_len, const char *in) 321 | { 322 | size_t in_len = strlen(in), start_pos; 323 | 324 | for (in_len = strlen(in); in_len; in_len--) { 325 | if (in[in_len - 1] != '/') 326 | break; 327 | } 328 | 329 | for (start_pos = in_len; start_pos; start_pos--) { 330 | if (in[start_pos - 1] == '/') 331 | break; 332 | } 333 | 334 | cl_assert(in_len - start_pos < INT_MAX); 335 | 336 | if (in_len - start_pos > 0) { 337 | *out = &in[start_pos]; 338 | *out_len = (in_len - start_pos); 339 | } else { 340 | *out = "/"; 341 | *out_len = 1; 342 | } 343 | } 344 | 345 | static char *joinpath(const char *dir, const char *base, int base_len) 346 | { 347 | char *out; 348 | int len; 349 | 350 | if (base_len == -1) { 351 | size_t bl = strlen(base); 352 | 353 | cl_assert(bl < INT_MAX); 354 | base_len = (int)bl; 355 | } 356 | 357 | len = strlen(dir) + base_len + 2; 358 | cl_assert(len > 0); 359 | 360 | cl_assert(out = malloc(len)); 361 | cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); 362 | 363 | return out; 364 | } 365 | 366 | static void 367 | fs_copydir_helper(const char *source, const char *dest, int dest_mode) 368 | { 369 | DIR *source_dir; 370 | struct dirent *d; 371 | 372 | mkdir(dest, dest_mode); 373 | 374 | cl_assert_(source_dir = opendir(source), "Could not open source dir"); 375 | while ((d = (errno = 0, readdir(source_dir))) != NULL) { 376 | char *child; 377 | 378 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) 379 | continue; 380 | 381 | child = joinpath(source, d->d_name, -1); 382 | fs_copy(child, dest); 383 | free(child); 384 | } 385 | 386 | cl_assert_(errno == 0, "Failed to iterate source dir"); 387 | 388 | closedir(source_dir); 389 | } 390 | 391 | static void 392 | fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) 393 | { 394 | int in, out; 395 | 396 | cl_must_pass((in = open(source, O_RDONLY))); 397 | cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); 398 | 399 | #if USE_FCOPYFILE && defined(__APPLE__) 400 | ((void)(source_len)); /* unused */ 401 | cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); 402 | #elif USE_SENDFILE && defined(__linux__) 403 | { 404 | ssize_t ret = 0; 405 | 406 | while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { 407 | source_len -= (size_t)ret; 408 | } 409 | cl_assert(ret >= 0); 410 | } 411 | #else 412 | { 413 | char buf[131072]; 414 | ssize_t ret; 415 | 416 | ((void)(source_len)); /* unused */ 417 | 418 | while ((ret = read(in, buf, sizeof(buf))) > 0) { 419 | size_t len = (size_t)ret; 420 | 421 | while (len && (ret = write(out, buf, len)) > 0) { 422 | cl_assert(ret <= (ssize_t)len); 423 | len -= ret; 424 | } 425 | cl_assert(ret >= 0); 426 | } 427 | cl_assert(ret == 0); 428 | } 429 | #endif 430 | 431 | close(in); 432 | close(out); 433 | } 434 | 435 | static void 436 | fs_copy(const char *source, const char *_dest) 437 | { 438 | char *dbuf = NULL; 439 | const char *dest = NULL; 440 | struct stat source_st, dest_st; 441 | 442 | cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); 443 | 444 | if (lstat(_dest, &dest_st) == 0) { 445 | const char *base; 446 | int base_len; 447 | 448 | /* Target exists and is directory; append basename */ 449 | cl_assert(S_ISDIR(dest_st.st_mode)); 450 | 451 | basename_r(&base, &base_len, source); 452 | cl_assert(base_len < INT_MAX); 453 | 454 | dbuf = joinpath(_dest, base, base_len); 455 | dest = dbuf; 456 | } else if (errno != ENOENT) { 457 | cl_fail("Cannot copy; cannot stat destination"); 458 | } else { 459 | dest = _dest; 460 | } 461 | 462 | if (S_ISDIR(source_st.st_mode)) { 463 | fs_copydir_helper(source, dest, source_st.st_mode); 464 | } else { 465 | fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); 466 | } 467 | 468 | free(dbuf); 469 | } 470 | 471 | static void 472 | fs_rmdir_helper(const char *path) 473 | { 474 | DIR *dir; 475 | struct dirent *d; 476 | 477 | cl_assert_(dir = opendir(path), "Could not open dir"); 478 | while ((d = (errno = 0, readdir(dir))) != NULL) { 479 | char *child; 480 | 481 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) 482 | continue; 483 | 484 | child = joinpath(path, d->d_name, -1); 485 | fs_rm(child); 486 | free(child); 487 | } 488 | 489 | cl_assert_(errno == 0, "Failed to iterate source dir"); 490 | closedir(dir); 491 | 492 | cl_must_pass_(rmdir(path), "Could not remove directory"); 493 | } 494 | 495 | static void 496 | fs_rm(const char *path) 497 | { 498 | struct stat st; 499 | 500 | if (lstat(path, &st)) { 501 | if (errno == ENOENT) 502 | return; 503 | 504 | cl_fail("Cannot copy; cannot stat destination"); 505 | } 506 | 507 | if (S_ISDIR(st.st_mode)) { 508 | fs_rmdir_helper(path); 509 | } else { 510 | cl_must_pass(unlink(path)); 511 | } 512 | } 513 | 514 | void 515 | cl_fs_cleanup(void) 516 | { 517 | clar_unsandbox(); 518 | clar_sandbox(); 519 | } 520 | #endif 521 | -------------------------------------------------------------------------------- /tests/clar/print.h: -------------------------------------------------------------------------------- 1 | /* clap: clar protocol, the traditional clar output format */ 2 | 3 | static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names) 4 | { 5 | (void)test_count; 6 | printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); 7 | printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); 8 | } 9 | 10 | static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count) 11 | { 12 | (void)test_count; 13 | (void)suite_count; 14 | (void)error_count; 15 | 16 | printf("\n\n"); 17 | clar_report_all(); 18 | } 19 | 20 | static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error) 21 | { 22 | printf(" %d) Failure:\n", num); 23 | 24 | printf("%s::%s [%s:%"PRIuZ"]\n", 25 | report->suite, 26 | report->test, 27 | error->file, 28 | error->line_number); 29 | 30 | printf(" %s\n", error->error_msg); 31 | 32 | if (error->description != NULL) 33 | printf(" %s\n", error->description); 34 | 35 | printf("\n"); 36 | fflush(stdout); 37 | } 38 | 39 | static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status) 40 | { 41 | (void)test_name; 42 | (void)test_number; 43 | 44 | if (_clar.verbosity > 1) { 45 | printf("%s::%s: ", suite_name, test_name); 46 | 47 | switch (status) { 48 | case CL_TEST_OK: printf("ok\n"); break; 49 | case CL_TEST_FAILURE: printf("fail\n"); break; 50 | case CL_TEST_SKIP: printf("skipped"); break; 51 | case CL_TEST_NOTRUN: printf("notrun"); break; 52 | } 53 | } else { 54 | switch (status) { 55 | case CL_TEST_OK: printf("."); break; 56 | case CL_TEST_FAILURE: printf("F"); break; 57 | case CL_TEST_SKIP: printf("S"); break; 58 | case CL_TEST_NOTRUN: printf("N"); break; 59 | } 60 | 61 | fflush(stdout); 62 | } 63 | } 64 | 65 | static void clar_print_clap_onsuite(const char *suite_name, int suite_index) 66 | { 67 | if (_clar.verbosity == 1) 68 | printf("\n%s", suite_name); 69 | 70 | (void)suite_index; 71 | } 72 | 73 | static void clar_print_clap_onabort(const char *fmt, va_list arg) 74 | { 75 | vfprintf(stderr, fmt, arg); 76 | } 77 | 78 | /* tap: test anywhere protocol format */ 79 | 80 | static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names) 81 | { 82 | (void)test_count; 83 | (void)suite_count; 84 | (void)suite_names; 85 | printf("TAP version 13\n"); 86 | } 87 | 88 | static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count) 89 | { 90 | (void)suite_count; 91 | (void)error_count; 92 | 93 | printf("1..%d\n", test_count); 94 | } 95 | 96 | static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error) 97 | { 98 | (void)num; 99 | (void)report; 100 | (void)error; 101 | } 102 | 103 | static void print_escaped(const char *str) 104 | { 105 | char *c; 106 | 107 | while ((c = strchr(str, '\'')) != NULL) { 108 | printf("%.*s", (int)(c - str), str); 109 | printf("''"); 110 | str = c + 1; 111 | } 112 | 113 | printf("%s", str); 114 | } 115 | 116 | static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status) 117 | { 118 | const struct clar_error *error = _clar.last_report->errors; 119 | 120 | (void)test_name; 121 | (void)test_number; 122 | 123 | switch(status) { 124 | case CL_TEST_OK: 125 | printf("ok %d - %s::%s\n", test_number, suite_name, test_name); 126 | break; 127 | case CL_TEST_FAILURE: 128 | printf("not ok %d - %s::%s\n", test_number, suite_name, test_name); 129 | 130 | printf(" ---\n"); 131 | printf(" reason: |\n"); 132 | printf(" %s\n", error->error_msg); 133 | 134 | if (error->description) 135 | printf(" %s\n", error->description); 136 | 137 | printf(" at:\n"); 138 | printf(" file: '"); print_escaped(error->file); printf("'\n"); 139 | printf(" line: %" PRIuZ "\n", error->line_number); 140 | printf(" function: '%s'\n", error->function); 141 | printf(" ---\n"); 142 | 143 | break; 144 | case CL_TEST_SKIP: 145 | case CL_TEST_NOTRUN: 146 | printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name); 147 | break; 148 | } 149 | 150 | fflush(stdout); 151 | } 152 | 153 | static void clar_print_tap_onsuite(const char *suite_name, int suite_index) 154 | { 155 | printf("# start of suite %d: %s\n", suite_index, suite_name); 156 | } 157 | 158 | static void clar_print_tap_onabort(const char *fmt, va_list arg) 159 | { 160 | printf("Bail out! "); 161 | vprintf(fmt, arg); 162 | fflush(stdout); 163 | } 164 | 165 | /* indirection between protocol output selection */ 166 | 167 | #define PRINT(FN, ...) do { \ 168 | switch (_clar.output_format) { \ 169 | case CL_OUTPUT_CLAP: \ 170 | clar_print_clap_##FN (__VA_ARGS__); \ 171 | break; \ 172 | case CL_OUTPUT_TAP: \ 173 | clar_print_tap_##FN (__VA_ARGS__); \ 174 | break; \ 175 | default: \ 176 | abort(); \ 177 | } \ 178 | } while (0) 179 | 180 | static void clar_print_init(int test_count, int suite_count, const char *suite_names) 181 | { 182 | PRINT(init, test_count, suite_count, suite_names); 183 | } 184 | 185 | static void clar_print_shutdown(int test_count, int suite_count, int error_count) 186 | { 187 | PRINT(shutdown, test_count, suite_count, error_count); 188 | } 189 | 190 | static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) 191 | { 192 | PRINT(error, num, report, error); 193 | } 194 | 195 | static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status) 196 | { 197 | PRINT(ontest, suite_name, test_name, test_number, status); 198 | } 199 | 200 | static void clar_print_onsuite(const char *suite_name, int suite_index) 201 | { 202 | PRINT(onsuite, suite_name, suite_index); 203 | } 204 | 205 | static void clar_print_onabort(const char *msg, ...) 206 | { 207 | va_list argp; 208 | va_start(argp, msg); 209 | PRINT(onabort, msg, argp); 210 | va_end(argp); 211 | } 212 | -------------------------------------------------------------------------------- /tests/clar/sandbox.h: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | #include 3 | #endif 4 | 5 | static char _clar_path[4096 + 1]; 6 | 7 | static int 8 | is_valid_tmp_path(const char *path) 9 | { 10 | STAT_T st; 11 | 12 | if (stat(path, &st) != 0) 13 | return 0; 14 | 15 | if (!S_ISDIR(st.st_mode)) 16 | return 0; 17 | 18 | return (access(path, W_OK) == 0); 19 | } 20 | 21 | static int 22 | find_tmp_path(char *buffer, size_t length) 23 | { 24 | #ifndef _WIN32 25 | static const size_t var_count = 5; 26 | static const char *env_vars[] = { 27 | "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" 28 | }; 29 | 30 | size_t i; 31 | 32 | for (i = 0; i < var_count; ++i) { 33 | const char *env = getenv(env_vars[i]); 34 | if (!env) 35 | continue; 36 | 37 | if (is_valid_tmp_path(env)) { 38 | #ifdef __APPLE__ 39 | if (length >= PATH_MAX && realpath(env, buffer) != NULL) 40 | return 0; 41 | #endif 42 | strncpy(buffer, env, length - 1); 43 | buffer[length - 1] = '\0'; 44 | return 0; 45 | } 46 | } 47 | 48 | /* If the environment doesn't say anything, try to use /tmp */ 49 | if (is_valid_tmp_path("/tmp")) { 50 | #ifdef __APPLE__ 51 | if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) 52 | return 0; 53 | #endif 54 | strncpy(buffer, "/tmp", length - 1); 55 | buffer[length - 1] = '\0'; 56 | return 0; 57 | } 58 | 59 | #else 60 | DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); 61 | if (env_len > 0 && env_len < (DWORD)length) 62 | return 0; 63 | 64 | if (GetTempPath((DWORD)length, buffer)) 65 | return 0; 66 | #endif 67 | 68 | /* This system doesn't like us, try to use the current directory */ 69 | if (is_valid_tmp_path(".")) { 70 | strncpy(buffer, ".", length - 1); 71 | buffer[length - 1] = '\0'; 72 | return 0; 73 | } 74 | 75 | return -1; 76 | } 77 | 78 | static void clar_unsandbox(void) 79 | { 80 | if (_clar_path[0] == '\0') 81 | return; 82 | 83 | cl_must_pass(chdir("..")); 84 | 85 | fs_rm(_clar_path); 86 | } 87 | 88 | static int build_sandbox_path(void) 89 | { 90 | #ifdef CLAR_TMPDIR 91 | const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; 92 | #else 93 | const char path_tail[] = "clar_tmp_XXXXXX"; 94 | #endif 95 | 96 | size_t len; 97 | 98 | if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) 99 | return -1; 100 | 101 | len = strlen(_clar_path); 102 | 103 | #ifdef _WIN32 104 | { /* normalize path to POSIX forward slashes */ 105 | size_t i; 106 | for (i = 0; i < len; ++i) { 107 | if (_clar_path[i] == '\\') 108 | _clar_path[i] = '/'; 109 | } 110 | } 111 | #endif 112 | 113 | if (_clar_path[len - 1] != '/') { 114 | _clar_path[len++] = '/'; 115 | } 116 | 117 | strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); 118 | 119 | #if defined(__MINGW32__) 120 | if (_mktemp(_clar_path) == NULL) 121 | return -1; 122 | 123 | if (mkdir(_clar_path, 0700) != 0) 124 | return -1; 125 | #elif defined(_WIN32) 126 | if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) 127 | return -1; 128 | 129 | if (mkdir(_clar_path, 0700) != 0) 130 | return -1; 131 | #else 132 | if (mkdtemp(_clar_path) == NULL) 133 | return -1; 134 | #endif 135 | 136 | return 0; 137 | } 138 | 139 | static int clar_sandbox(void) 140 | { 141 | if (_clar_path[0] == '\0' && build_sandbox_path() < 0) 142 | return -1; 143 | 144 | if (chdir(_clar_path) != 0) 145 | return -1; 146 | 147 | return 0; 148 | } 149 | 150 | const char *clar_sandbox_path(void) 151 | { 152 | return _clar_path; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /tests/clar/summary.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | int clar_summary_close_tag( 6 | struct clar_summary *summary, const char *tag, int indent) 7 | { 8 | const char *indt; 9 | 10 | if (indent == 0) indt = ""; 11 | else if (indent == 1) indt = "\t"; 12 | else indt = "\t\t"; 13 | 14 | return fprintf(summary->fp, "%s\n", indt, tag); 15 | } 16 | 17 | int clar_summary_testsuites(struct clar_summary *summary) 18 | { 19 | return fprintf(summary->fp, "\n"); 20 | } 21 | 22 | int clar_summary_testsuite(struct clar_summary *summary, 23 | int idn, const char *name, const char *pkg, time_t timestamp, 24 | double elapsed, int test_count, int fail_count, int error_count) 25 | { 26 | struct tm *tm = localtime(×tamp); 27 | char iso_dt[20]; 28 | 29 | if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) 30 | return -1; 31 | 32 | return fprintf(summary->fp, "\t\n", 42 | idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); 43 | } 44 | 45 | int clar_summary_testcase(struct clar_summary *summary, 46 | const char *name, const char *classname, double elapsed) 47 | { 48 | return fprintf(summary->fp, 49 | "\t\t\n", 50 | name, classname, elapsed); 51 | } 52 | 53 | int clar_summary_failure(struct clar_summary *summary, 54 | const char *type, const char *message, const char *desc) 55 | { 56 | return fprintf(summary->fp, 57 | "\t\t\t\n", 58 | type, message, desc); 59 | } 60 | 61 | struct clar_summary *clar_summary_init(const char *filename) 62 | { 63 | struct clar_summary *summary; 64 | FILE *fp; 65 | 66 | if ((fp = fopen(filename, "w")) == NULL) 67 | return NULL; 68 | 69 | if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { 70 | fclose(fp); 71 | return NULL; 72 | } 73 | 74 | summary->filename = filename; 75 | summary->fp = fp; 76 | 77 | return summary; 78 | } 79 | 80 | int clar_summary_shutdown(struct clar_summary *summary) 81 | { 82 | struct clar_report *report; 83 | const char *last_suite = NULL; 84 | 85 | if (clar_summary_testsuites(summary) < 0) 86 | goto on_error; 87 | 88 | report = _clar.reports; 89 | while (report != NULL) { 90 | struct clar_error *error = report->errors; 91 | 92 | if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { 93 | if (clar_summary_testsuite(summary, 0, report->suite, "", 94 | time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) 95 | goto on_error; 96 | } 97 | 98 | last_suite = report->suite; 99 | 100 | clar_summary_testcase(summary, report->test, "what", 0); 101 | 102 | while (error != NULL) { 103 | if (clar_summary_failure(summary, "assert", 104 | error->error_msg, error->description) < 0) 105 | goto on_error; 106 | 107 | error = error->next; 108 | } 109 | 110 | if (clar_summary_close_tag(summary, "testcase", 2) < 0) 111 | goto on_error; 112 | 113 | report = report->next; 114 | 115 | if (!report || strcmp(last_suite, report->suite) != 0) { 116 | if (clar_summary_close_tag(summary, "testsuite", 1) < 0) 117 | goto on_error; 118 | } 119 | } 120 | 121 | if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || 122 | fclose(summary->fp) != 0) 123 | goto on_error; 124 | 125 | printf("written summary file to %s\n", summary->filename); 126 | 127 | free(summary); 128 | return 0; 129 | 130 | on_error: 131 | fclose(summary->fp); 132 | free(summary); 133 | return -1; 134 | } 135 | -------------------------------------------------------------------------------- /tests/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) Vicent Marti. All rights reserved. 4 | # 5 | # This file is part of clar, distributed under the ISC license. 6 | # For full terms see the included COPYING file. 7 | # 8 | 9 | from __future__ import with_statement 10 | from string import Template 11 | import re, fnmatch, os, sys, codecs, pickle 12 | 13 | class Module(object): 14 | class Template(object): 15 | def __init__(self, module): 16 | self.module = module 17 | 18 | def _render_callback(self, cb): 19 | if not cb: 20 | return ' { NULL, NULL }' 21 | return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) 22 | 23 | class DeclarationTemplate(Template): 24 | def render(self): 25 | out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" 26 | 27 | for initializer in self.module.initializers: 28 | out += "extern %s;\n" % initializer['declaration'] 29 | 30 | if self.module.cleanup: 31 | out += "extern %s;\n" % self.module.cleanup['declaration'] 32 | 33 | return out 34 | 35 | class CallbacksTemplate(Template): 36 | def render(self): 37 | out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name 38 | out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) 39 | out += "\n};\n" 40 | return out 41 | 42 | class InfoTemplate(Template): 43 | def render(self): 44 | templates = [] 45 | 46 | initializers = self.module.initializers 47 | if len(initializers) == 0: 48 | initializers = [ None ] 49 | 50 | for initializer in initializers: 51 | name = self.module.clean_name() 52 | if initializer and initializer['short_name'].startswith('initialize_'): 53 | variant = initializer['short_name'][len('initialize_'):] 54 | name += " (%s)" % variant.replace('_', ' ') 55 | 56 | template = Template( 57 | r""" 58 | { 59 | "${clean_name}", 60 | ${initialize}, 61 | ${cleanup}, 62 | ${cb_ptr}, ${cb_count}, ${enabled} 63 | }""" 64 | ).substitute( 65 | clean_name = name, 66 | initialize = self._render_callback(initializer), 67 | cleanup = self._render_callback(self.module.cleanup), 68 | cb_ptr = "_clar_cb_%s" % self.module.name, 69 | cb_count = len(self.module.callbacks), 70 | enabled = int(self.module.enabled) 71 | ) 72 | templates.append(template) 73 | 74 | return ','.join(templates) 75 | 76 | def __init__(self, name): 77 | self.name = name 78 | 79 | self.mtime = None 80 | self.enabled = True 81 | self.modified = False 82 | 83 | def clean_name(self): 84 | return self.name.replace("_", "::") 85 | 86 | def _skip_comments(self, text): 87 | SKIP_COMMENTS_REGEX = re.compile( 88 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 89 | re.DOTALL | re.MULTILINE) 90 | 91 | def _replacer(match): 92 | s = match.group(0) 93 | return "" if s.startswith('/') else s 94 | 95 | return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) 96 | 97 | def parse(self, contents): 98 | TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" 99 | 100 | contents = self._skip_comments(contents) 101 | regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) 102 | 103 | self.callbacks = [] 104 | self.initializers = [] 105 | self.cleanup = None 106 | 107 | for (declaration, symbol, short_name) in regex.findall(contents): 108 | data = { 109 | "short_name" : short_name, 110 | "declaration" : declaration, 111 | "symbol" : symbol 112 | } 113 | 114 | if short_name.startswith('initialize'): 115 | self.initializers.append(data) 116 | elif short_name == 'cleanup': 117 | self.cleanup = data 118 | else: 119 | self.callbacks.append(data) 120 | 121 | return self.callbacks != [] 122 | 123 | def refresh(self, path): 124 | self.modified = False 125 | 126 | try: 127 | st = os.stat(path) 128 | 129 | # Not modified 130 | if st.st_mtime == self.mtime: 131 | return True 132 | 133 | self.modified = True 134 | self.mtime = st.st_mtime 135 | 136 | with codecs.open(path, encoding='utf-8') as fp: 137 | raw_content = fp.read() 138 | 139 | except IOError: 140 | return False 141 | 142 | return self.parse(raw_content) 143 | 144 | class TestSuite(object): 145 | 146 | def __init__(self, path, output): 147 | self.path = path 148 | self.output = output 149 | 150 | def should_generate(self, path): 151 | if not os.path.isfile(path): 152 | return True 153 | 154 | if any(module.modified for module in self.modules.values()): 155 | return True 156 | 157 | return False 158 | 159 | def find_modules(self): 160 | modules = [] 161 | for root, _, files in os.walk(self.path): 162 | module_root = root[len(self.path):] 163 | module_root = [c for c in module_root.split(os.sep) if c] 164 | 165 | tests_in_module = fnmatch.filter(files, "*.c") 166 | 167 | for test_file in tests_in_module: 168 | full_path = os.path.join(root, test_file) 169 | module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") 170 | 171 | modules.append((full_path, module_name)) 172 | 173 | return modules 174 | 175 | def load_cache(self): 176 | path = os.path.join(self.output, '.clarcache') 177 | cache = {} 178 | 179 | try: 180 | fp = open(path, 'rb') 181 | cache = pickle.load(fp) 182 | fp.close() 183 | except (IOError, ValueError): 184 | pass 185 | 186 | return cache 187 | 188 | def save_cache(self): 189 | path = os.path.join(self.output, '.clarcache') 190 | with open(path, 'wb') as cache: 191 | pickle.dump(self.modules, cache) 192 | 193 | def load(self, force = False): 194 | module_data = self.find_modules() 195 | self.modules = {} if force else self.load_cache() 196 | 197 | for path, name in module_data: 198 | if name not in self.modules: 199 | self.modules[name] = Module(name) 200 | 201 | if not self.modules[name].refresh(path): 202 | del self.modules[name] 203 | 204 | def disable(self, excluded): 205 | for exclude in excluded: 206 | for module in self.modules.values(): 207 | name = module.clean_name() 208 | if name.startswith(exclude): 209 | module.enabled = False 210 | module.modified = True 211 | 212 | def suite_count(self): 213 | return sum(max(1, len(m.initializers)) for m in self.modules.values()) 214 | 215 | def callback_count(self): 216 | return sum(len(module.callbacks) for module in self.modules.values()) 217 | 218 | def write(self): 219 | output = os.path.join(self.output, 'clar.suite') 220 | 221 | if not self.should_generate(output): 222 | return False 223 | 224 | with open(output, 'w') as data: 225 | modules = sorted(self.modules.values(), key=lambda module: module.name) 226 | 227 | for module in modules: 228 | t = Module.DeclarationTemplate(module) 229 | data.write(t.render()) 230 | 231 | for module in modules: 232 | t = Module.CallbacksTemplate(module) 233 | data.write(t.render()) 234 | 235 | suites = "static struct clar_suite _clar_suites[] = {" + ','.join( 236 | Module.InfoTemplate(module).render() for module in modules 237 | ) + "\n};\n" 238 | 239 | data.write(suites) 240 | 241 | data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count()) 242 | data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count()) 243 | 244 | self.save_cache() 245 | return True 246 | 247 | if __name__ == '__main__': 248 | from optparse import OptionParser 249 | 250 | parser = OptionParser() 251 | parser.add_option('-f', '--force', action="store_true", dest='force', default=False) 252 | parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) 253 | parser.add_option('-o', '--output', dest='output') 254 | 255 | options, args = parser.parse_args() 256 | if len(args) > 1: 257 | print("More than one path given") 258 | sys.exit(1) 259 | 260 | path = args.pop() if args else '.' 261 | output = options.output or path 262 | suite = TestSuite(path, output) 263 | suite.load(options.force) 264 | suite.disable(options.excluded) 265 | if suite.write(): 266 | print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) 267 | 268 | --------------------------------------------------------------------------------