├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── bam.lua ├── example └── example.cpp ├── include └── getopt │ └── getopt.h ├── src └── getopt.c └── test ├── getopt_tests.cpp └── greatest.h /.gitignore: -------------------------------------------------------------------------------- 1 | .bam 2 | .cproject 3 | .project 4 | local 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - gcc 4 | - clang 5 | 6 | install: 7 | - git clone https://github.com/matricks/bam.git 8 | - cd bam 9 | - ./make_unix.sh 10 | - cd .. 11 | 12 | script: 13 | - bam/bam compiler=$CC config=debug 14 | - bam/bam compiler=$CC config=release 15 | - ./local/linux_x86_64/debug/getopt_tests 16 | - ./local/linux_x86_64/release/getopt_tests 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | a getopt. 2 | 3 | version 0.1, march, 2012 4 | 5 | Copyright (C) 2012- Fredrik Kihlander 6 | 7 | https://github.com/wc-duck/getopt 8 | 9 | This software is provided 'as-is', without any express or implied 10 | warranty. In no event will the authors be held liable for any damages 11 | arising from the use of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, 14 | including commercial applications, and to alter it and redistribute it 15 | freely, subject to the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; you must not 18 | claim that you wrote the original software. If you use this software 19 | in a product, an acknowledgment in the product documentation would be 20 | appreciated but is not required. 21 | 2. Altered source versions must be plainly marked as such, and must not be 22 | misrepresented as being the original software. 23 | 3. This notice may not be removed or altered from any source distribution. 24 | 25 | Fredrik Kihlander 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # getopt - command line options parser in c 2 | 3 | [![Build Status](https://travis-ci.org/wc-duck/getopt.svg?branch=master)](https://travis-ci.org/wc-duck/getopt) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/4twbkbyhj7h7i689)](https://ci.appveyor.com/project/wc-duck/getopt) 5 | 6 | ## About: 7 | This module aims to be a simple drop-in argc/argv-parser for c/cpp-code. getopt support 8 | short ( -s ) and long ( --long ) options, flags and help-generation. 9 | 10 | ## Example: 11 | This is a simple example showing of most of the features: 12 | 13 | ```c 14 | #include 15 | #include 16 | 17 | int verbose = 0; // verbose flag 18 | 19 | static const getopt_option_t option_list[] = 20 | { 21 | { "help", 'h', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'h', "print this help text", 0x0 }, 22 | { "verbose", 'v', GETOPT_OPTION_TYPE_FLAG_SET, &verbose, 1, "verbose logging enabled", 0x0 }, 23 | { "input", 'i', GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'i', "an input file", "FILE" }, 24 | GETOPT_OPTIONS_END 25 | }; 26 | 27 | static void print_help_string( getopt_context_t ctx ) 28 | { 29 | char buffer[2048]; 30 | printf( "%s\n", getopt_create_help_string( &ctx, buffer, sizeof( buffer ) ) ); 31 | } 32 | 33 | int main( int argc, const char** argv ) 34 | { 35 | getopt_context_t ctx; 36 | if( getopt_create_context( &ctx, argc, argv, option_list ) < 0 ) 37 | { 38 | printf( "error while creating getopt ctx, bad options-list?" ); 39 | return 1; 40 | } 41 | 42 | int opt; 43 | 44 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 45 | { 46 | switch( opt ) 47 | { 48 | case '+': printf( "got argument without flag: %s\n", ctx.current_opt_arg ); break; 49 | case '?': printf( "unknown flag %s\n", ctx.current_opt_arg ); break; 50 | case '!': printf( "invalid use of flag %s\n", ctx.current_opt_arg ); break; 51 | case 'i': printf( "got -i or --input with value %s\n", ctx.current_opt_arg ); break; 52 | case 0: printf( "flag was set!\n"); break; 53 | case 'h': print_help_string( ctx ); break; 54 | default: break; 55 | } 56 | } 57 | 58 | if( verbose > 0 ) 59 | printf( "verbose flag was set!" ); 60 | } 61 | 62 | 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | - x86 3 | 4 | environment: 5 | matrix: 6 | - GETOPT_PLATFORM: win32 7 | GETOPT_CONFIG: debug 8 | - GETOPT_PLATFORM: win32 9 | GETOPT_CONFIG: release 10 | - GETOPT_PLATFORM: winx64 11 | GETOPT_CONFIG: debug 12 | - GETOPT_PLATFORM: winx64 13 | GETOPT_CONFIG: release 14 | 15 | install: 16 | - if [%GETOPT_PLATFORM%]==[win32] call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 17 | - if [%GETOPT_PLATFORM%]==[winx64] call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 18 | - if [%GETOPT_PLATFORM%]==[winx64] call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86_amd64 19 | - git clone https://github.com/matricks/bam.git 20 | - cd bam 21 | - make_win64_msvc.bat 22 | - cd .. 23 | - bam\bam.exe platform=%GETOPT_PLATFORM% config=%GETOPT_CONFIG% 24 | - local\%GETOPT_PLATFORM%\%GETOPT_CONFIG%\getopt_tests.exe 25 | 26 | build: OFF 27 | -------------------------------------------------------------------------------- /bam.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | a getopt. 3 | version 0.1, march, 2012 4 | 5 | Copyright (C) 2012- Fredrik Kihlander 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | Fredrik Kihlander 24 | --]] 25 | 26 | BUILD_PATH = "local" 27 | 28 | function get_config() 29 | local config = ScriptArgs["config"] 30 | if config == nil then 31 | return "debug" 32 | end 33 | return config 34 | end 35 | 36 | function get_platform() 37 | local platform = ScriptArgs["platform"] 38 | if platform == nil then 39 | if family == "windows" then 40 | platform = "winx64" 41 | else 42 | platform = "linux_x86_64" 43 | end 44 | end 45 | return platform 46 | end 47 | 48 | function get_base_settings() 49 | local settings = {} 50 | 51 | settings._is_settingsobject = true 52 | settings.invoke_count = 0 53 | settings.debug = 0 54 | settings.optimize = 0 55 | SetCommonSettings(settings) 56 | 57 | -- add all tools 58 | for _, tool in pairs(_bam_tools) do 59 | tool(settings) 60 | end 61 | 62 | return settings 63 | end 64 | 65 | function set_compiler( settings, config ) 66 | if family == "windows" then 67 | compiler = "msvc" 68 | else 69 | compiler = ScriptArgs["compiler"] 70 | if compiler == nil then 71 | compiler = "gcc" 72 | end 73 | end 74 | 75 | InitCommonCCompiler(settings) 76 | if compiler == "msvc" then 77 | SetDriversCL( settings ) 78 | if config == "release" then 79 | settings.cc.flags:Add( "/Ox" ) 80 | settings.cc.flags:Add( "/TP" ) -- forcing c++ compile on windows =/ 81 | end 82 | elseif compiler == "gcc" then 83 | SetDriversGCC( settings ) 84 | settings.cc.flags:Add( "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 85 | if config == "release" then 86 | settings.cc.flags:Add( "-O2" ) 87 | end 88 | elseif compiler == "clang" then 89 | SetDriversClang( settings ) 90 | settings.cc.flags:Add( "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 91 | if config == "release" then 92 | settings.cc.flags:Add( "-O2" ) 93 | end 94 | end 95 | end 96 | 97 | config = get_config() 98 | platform = get_platform() 99 | settings = get_base_settings() 100 | set_compiler( settings, config ) 101 | TableLock( settings ) 102 | 103 | local output_path = PathJoin( BUILD_PATH, PathJoin( platform, config ) ) 104 | local output_func = function(settings, path) return PathJoin(output_path, PathFilename(PathBase(path)) .. settings.config_ext) end 105 | settings.cc.Output = output_func 106 | settings.lib.Output = output_func 107 | settings.link.Output = output_func 108 | 109 | settings.cc.includes:Add( 'include' ) 110 | 111 | local objs = Compile( settings, 'src/getopt.c' ) 112 | local lib = StaticLibrary( settings, 'getopt', objs ) 113 | 114 | local example = Link( settings, 'example', Compile( settings, 'example/example.cpp' ), lib ) 115 | 116 | local test_objs = Compile( settings, 'test/getopt_tests.cpp' ) 117 | local tests = Link( settings, 'getopt_tests', test_objs, lib ) 118 | -------------------------------------------------------------------------------- /example/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int verbose = 0; // verbose flag 5 | 6 | static const getopt_option_t option_list[] = 7 | { 8 | { "help", 'h', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'h', "print this help text", 0x0 }, 9 | { "verbose", 'v', GETOPT_OPTION_TYPE_FLAG_SET, &verbose, 1, "verbose logging enabled", 0x0 }, 10 | { "input", 'i', GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'i', "an input file", "FILE" }, 11 | GETOPT_OPTIONS_END 12 | }; 13 | 14 | static void print_help_string( getopt_context_t ctx ) 15 | { 16 | char buffer[2048]; 17 | printf( "%s\n", getopt_create_help_string( &ctx, buffer, sizeof( buffer ) ) ); 18 | } 19 | 20 | int main( int argc, const char** argv ) 21 | { 22 | getopt_context_t ctx; 23 | if( getopt_create_context( &ctx, argc, argv, option_list ) < 0 ) 24 | { 25 | printf( "error while creating getopt ctx, bad options-list?" ); 26 | return 1; 27 | } 28 | 29 | int opt; 30 | 31 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 32 | { 33 | switch( opt ) 34 | { 35 | case '+': printf( "got argument without flag: %s\n", ctx.current_opt_arg ); break; 36 | case '?': printf( "unknown flag %s\n", ctx.current_opt_arg ); break; 37 | case '!': printf( "invalid use of flag %s\n", ctx.current_opt_arg ); break; 38 | case 'i': printf( "got -i or --input with value %s\n", ctx.current_opt_arg ); break; 39 | case 0: printf( "flag was set!\n"); break; 40 | case 'h': print_help_string( ctx ); break; 41 | default: break; 42 | } 43 | } 44 | 45 | if( verbose > 0 ) 46 | printf( "verbose flag was set!" ); 47 | } 48 | -------------------------------------------------------------------------------- /include/getopt/getopt.h: -------------------------------------------------------------------------------- 1 | /* a getopt. 2 | version 0.1, march, 2012 3 | 4 | Copyright (C) 2012- Fredrik Kihlander 5 | 6 | https://github.com/wc-duck/getopt 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #ifndef GETOPT_GETOPT_H_INCLUDED 28 | #define GETOPT_GETOPT_H_INCLUDED 29 | 30 | #include 31 | 32 | #if defined (__cplusplus) 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * @file getopt.h 38 | * 39 | * Provides functionality to parse standard argc/argv in an easy manner. 40 | * 41 | * @example of simple parse 42 | * 43 | * // ... first we need to define our options, these need to be valid during the entire commandline-parse ... 44 | * const getopt_option_t option_list[] = 45 | * { 46 | * { "help", 'h', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'h', "print this help text", 0x0 }, 47 | * { "verbose", 'v', GETOPT_OPTION_TYPE_FLAG_SET, &verbose, 1, "verbose logging enabled", 0x0 }, 48 | * { "input", 'i', GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'i', "an input file", "FILE" }, 49 | * GETOPT_OPTIONS_END 50 | * }; 51 | * 52 | * // ... next we need to initialize our parse-context ... 53 | * getopt_context_t ctx; 54 | * if( getopt_create_context( &ctx, argc, argv, option_list ) < 0 ) 55 | * { 56 | * printf( "error while creating getopt ctx, bad options-list?" ); 57 | * return 1; 58 | * } 59 | * 60 | * // ... time to get parsin! ... 61 | * int opt; 62 | * 63 | * while( ( opt = getopt_next( &ctx ) ) != -1 ) 64 | * { 65 | * switch( opt ) 66 | * { 67 | * case '+': printf( "got argument without flag: %s\n", ctx.current_opt_arg ); break; 68 | * case '?': printf( "unknown flag %s\n", ctx.current_opt_arg ); break; 69 | * case '!': printf( "invalid use of flag %s\n", ctx.current_opt_arg ); break; 70 | * case 'i': printf( "got -i or --input with value %s\n", ctx.current_opt_arg ); break; 71 | * case 0: printf( "flag was set!\n"); break; 72 | * case 'h': print_help_string( ctx ); break; 73 | * default: break; 74 | * } 75 | * } 76 | */ 77 | 78 | /** 79 | * option types supported by system. 80 | */ 81 | typedef enum getopt_option_type 82 | { 83 | GETOPT_OPTION_TYPE_NO_ARG, ///< The option can have no argument 84 | GETOPT_OPTION_TYPE_REQUIRED, ///< The option requires an argument (--option=arg, -o arg) 85 | GETOPT_OPTION_TYPE_OPTIONAL, ///< The option-argument is optional 86 | GETOPT_OPTION_TYPE_REQUIRED_INT32, ///< The option requires an argument and this argument has to be parseable as an int (--option=arg, -o arg) 87 | GETOPT_OPTION_TYPE_OPTIONAL_INT32, ///< The option-argument is optional, but if it is there it has to be parseable as int. 88 | GETOPT_OPTION_TYPE_REQUIRED_FP32, ///< The option requires an argument and this argument has to be parseable as an float (--option=arg, -o arg) 89 | GETOPT_OPTION_TYPE_OPTIONAL_FP32, ///< The option-argument is optional, but if it is there it has to be parseable as float. 90 | GETOPT_OPTION_TYPE_FLAG_SET, ///< The option is a flag and value will be set to flag 91 | GETOPT_OPTION_TYPE_FLAG_AND, ///< The option is a flag and value will be and:ed with flag 92 | GETOPT_OPTION_TYPE_FLAG_OR ///< The option is a flag and value will be or:ed with flag 93 | } getopt_option_type_t; 94 | 95 | /** 96 | * Helper-macro to define end-element in options-array. 97 | * Mostly helpful on higher warning-level where compiler would complain for { 0 } 98 | */ 99 | #define GETOPT_OPTIONS_END { 0, 0, GETOPT_OPTION_TYPE_NO_ARG, 0, 0, 0, 0 } 100 | 101 | /** 102 | * Option definition in system. 103 | */ 104 | typedef struct getopt_option 105 | { 106 | const char* name; ///< Long name of argument, set to NULL if only short name is valid. 107 | int name_short; ///< Short name of argument, set to 0 if only long name is valid. 108 | getopt_option_type_t type; ///< Type of option, see . 109 | int* flag; ///< Pointer to flag to set if option is of flag-type, set to null NULL if option is not of flag-type. 110 | int value; ///< If option is of flag-type, this value will be set/and:ed/or:ed to the flag, else it will be returned from getopt_next when option is found. 111 | const char* desc; ///< Description of option, used when generating help-text. 112 | const char* value_desc; ///< Short description of valid values to the option, will only be used when generating help-text. example: "--my_option=" 113 | } getopt_option_t; 114 | 115 | /** 116 | * Context used while parsing options. 117 | * Need to be initialized by before usage. If reused a re-initialization by is needed. 118 | * 119 | * @note: Do not modify data in this struct manually! 120 | */ 121 | typedef struct getopt_context 122 | { 123 | int argc; ///< Internal variable 124 | const char** argv; ///< Internal variable 125 | const getopt_option_t* opts; ///< pointer to 'opts' passed in getopt_create_context(). 126 | int num_opts; ///< number of valid options in 'opts' 127 | int current_index; ///< Internal variable 128 | 129 | /** 130 | * Used to return values. Will point to a string that is the argument to the currently parsed option. 131 | * I.e. when parsing '--my-flag whoppa_doppa", this will point to "whoppa doppa". 132 | * 133 | * If the option is of type GETOPT_OPTION_TYPE_OPTIONAL this will be set to NULL if there was no argument passed. 134 | */ 135 | const char* current_opt_arg; 136 | 137 | /** 138 | * Union storeing parsed values if that is requested by the option-type. 139 | * @note on parse-errors '!' will be returned from getopt_next() and current_opt_arg will be set to the name 140 | * of the option that failed to parse. 141 | * @note if the option is 'optional' and there was no arg, current_opt_arg will be 0x0. 142 | */ 143 | union 144 | { 145 | /** 146 | * if the option is on type GETOPT_OPTION_TYPE_OPTIONAL_INT or GETOPT_OPTION_TYPE_REQUIRED_INT and it parsed 147 | * successfully the value will be stored here. 148 | * supported int formats are, decimal, hex and octal (123, 0x123, 0123) 149 | */ 150 | int i32; 151 | 152 | /** 153 | * if the option is on type GETOPT_OPTION_TYPE_OPTIONAL_FP32 or GETOPT_OPTION_TYPE_REQUIRED_FP32 and it parsed 154 | * successfully the value will be stored here. 155 | */ 156 | float fp32; 157 | } current_value; 158 | 159 | } getopt_context_t; 160 | 161 | /** 162 | * Initializes an getopt_context_t-struct to be used by 163 | * 164 | * @param ctx Pointer to context to initialize. 165 | * @param argc argc from "int main(int argc, char** argv)" or equal. 166 | * @param argv argv from "int main(int argc, char** argv)" or equal. Data need to be valid during option-parsing and usage of data. 167 | * @param opts Pointer to array with options that should be looked for. Should end with an option that is all zeroed! 168 | * 169 | * @return 0 on success, otherwise error-code. 170 | */ 171 | int getopt_create_context( getopt_context_t* ctx, int argc, const char** argv, const getopt_option_t* opts ); 172 | 173 | /** 174 | * Used to parse argc/argv with the help of a getopt_context_t. 175 | * Tries to parse the next token in ctx and return id depending on status. 176 | * 177 | * @param ctx Pointer to a initialized 178 | * 179 | * @return '!' on error. ctx->current_opt_arg will be set to flag-name! Errors that can occur, 180 | * Argument missing if argument is required or Argument found when there should be none. 181 | * '?' if item was an unrecognized option, ctx->current_opt_arg will be set to item! 182 | * '+' if item was no option, ctx->current_opt_arg will be set to item! 183 | * '0' if the opt was a flag and it was set. ctx->current_opt_arg will be set to flag-name! 184 | * the value stored is value in the found option. 185 | * -1 no more options to parse! 186 | */ 187 | int getopt_next( getopt_context_t* ctx ); 188 | 189 | /** 190 | * Builds a string that describes all options for use with the --help-flag etc. 191 | * 192 | * @param ctx Pointer to a initialized 193 | * @param buffer Pointer to buffer to build string in. 194 | * @param buffer_size Size of buffer. 195 | * 196 | * @return buffer filled with a help-string. 197 | */ 198 | const char* getopt_create_help_string( getopt_context_t* ctx, char* buffer, size_t buffer_size ); 199 | 200 | #if defined (__cplusplus) 201 | } 202 | #endif 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /src/getopt.c: -------------------------------------------------------------------------------- 1 | /* a getopt. 2 | version 0.1, march, 2012 3 | 4 | Copyright (C) 2012- Fredrik Kihlander 5 | 6 | https://github.com/wc-duck/getopt 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include 28 | 29 | #include /* for vsnprintf */ 30 | #include /* for va_list */ 31 | #include /* atoi */ 32 | #include 33 | #if !defined(_MSC_VER) 34 | # include /* for strncasecmp */ 35 | #else 36 | # include /* tolower */ 37 | #endif 38 | 39 | static int str_case_cmp_len(const char* s1, const char* s2, unsigned int len) 40 | { 41 | #if defined (_MSC_VER) 42 | for(unsigned int i = 0; i < len; i++) 43 | { 44 | int c1 = tolower(s1[i]); 45 | int c2 = tolower(s2[i]); 46 | if(c1 < c2) return -1; 47 | if(c1 > c2) return 1; 48 | if(c1 == '\0' && c1 == c2) return 0; 49 | } 50 | return 0; 51 | #else /* defined (_MSC_VER) */ 52 | return strncasecmp(s1, s2, len); 53 | #endif /* defined (_MSC_VER) */ 54 | } 55 | 56 | static int str_format(char* buf, size_t buf_size, const char* fmt, ...) 57 | { 58 | va_list args; 59 | va_start( args, fmt ); 60 | int ret = vsnprintf( buf, buf_size, fmt, args ); 61 | #if defined(_MSC_VER) 62 | buf[buf_size - 1] = '\0'; 63 | #endif 64 | va_end( args ); 65 | return ret; 66 | } 67 | 68 | int getopt_create_context( getopt_context_t* ctx, int argc, const char** argv, const getopt_option_t* opts ) 69 | { 70 | ctx->argc = (argc > 1) ? (argc - 1) : 0; /* stripping away file-name! */ 71 | ctx->argv = (argc > 1) ? (argv + 1) : argv; /* stripping away file-name! */ 72 | ctx->opts = opts; 73 | ctx->current_index = 0; 74 | ctx->current_opt_arg = 0x0; 75 | 76 | /* count opts */ 77 | ctx->num_opts = 0; 78 | const getopt_option_t* opt = opts; 79 | while( !(opt->name == 0x0 && opt->name_short == 0) ) 80 | { 81 | if( opt->value == '!' || 82 | opt->value == '?' || 83 | opt->value == '+' || 84 | opt->value == -1) 85 | return -1; 86 | 87 | if( opt->name ) 88 | { 89 | if( opt->name[0] == '-' ) 90 | return -1; 91 | } 92 | 93 | ctx->num_opts++; opt++; 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | static int getopt_opt_might_have_arg( const getopt_option_t* opt ) 100 | { 101 | switch(opt->type) 102 | { 103 | case GETOPT_OPTION_TYPE_OPTIONAL: 104 | case GETOPT_OPTION_TYPE_OPTIONAL_INT32: 105 | case GETOPT_OPTION_TYPE_OPTIONAL_FP32: 106 | case GETOPT_OPTION_TYPE_REQUIRED: 107 | case GETOPT_OPTION_TYPE_REQUIRED_INT32: 108 | case GETOPT_OPTION_TYPE_REQUIRED_FP32: 109 | return 1; 110 | default: 111 | return 0; 112 | } 113 | return 0; 114 | } 115 | 116 | static int getopt_read_value(getopt_context_t* ctx, const getopt_option_t* found_opt) 117 | { 118 | char* end = 0x0; 119 | switch(found_opt->type) 120 | { 121 | case GETOPT_OPTION_TYPE_OPTIONAL_INT32: 122 | case GETOPT_OPTION_TYPE_REQUIRED_INT32: 123 | ctx->current_value.i32 = (int)strtol(ctx->current_opt_arg, &end, 0); 124 | break; 125 | case GETOPT_OPTION_TYPE_OPTIONAL_FP32: 126 | case GETOPT_OPTION_TYPE_REQUIRED_FP32: 127 | ctx->current_value.fp32 = strtof(ctx->current_opt_arg, &end); 128 | break; 129 | default: 130 | break; 131 | } 132 | 133 | if(*end != '\0') 134 | { 135 | ctx->current_opt_arg = found_opt->name; 136 | return '!'; 137 | } 138 | return found_opt->value; 139 | } 140 | 141 | int getopt_next( getopt_context_t* ctx ) 142 | { 143 | /* are all options processed? */ 144 | if(ctx->current_index == ctx->argc ) 145 | return -1; 146 | 147 | /* reset opt-arg */ 148 | ctx->current_opt_arg = 0x0; 149 | 150 | const char* curr_token = ctx->argv[ ctx->current_index ]; 151 | 152 | /* this token has been processed! */ 153 | ctx->current_index++; 154 | 155 | /* check if item is no option */ 156 | if( curr_token[0] && curr_token[0] != '-' ) 157 | { 158 | ctx->current_opt_arg = curr_token; 159 | return '+'; /* return '+' as identifier for no option! */ 160 | } 161 | 162 | const getopt_option_t* found_opt = 0x0; 163 | const char* found_arg = 0x0; 164 | 165 | /* short opt */ 166 | if( curr_token[1] != '\0' && curr_token[1] != '-' && curr_token[2] == '\0' ) 167 | { 168 | int i = 0; 169 | for( ; i < ctx->num_opts; i++ ) 170 | { 171 | const getopt_option_t* opt = ctx->opts + i; 172 | 173 | if( opt->name_short == curr_token[1] ) 174 | { 175 | found_opt = opt; 176 | 177 | /* if there is an value when: - current_index < argc and value in argv[current_index] do not start with '-' */ 178 | if( ( ( ctx->current_index != ctx->argc) && ( ctx->argv[ctx->current_index][0] != '-' ) ) && 179 | getopt_opt_might_have_arg(opt) ) 180 | { 181 | found_arg = ctx->argv[ctx->current_index]; 182 | ctx->current_index++; /* next token has been processed aswell! */ 183 | } 184 | break; 185 | } 186 | } 187 | } 188 | /* long opt */ 189 | else if(curr_token[1] == '-' && curr_token[2] != '\0') 190 | { 191 | int i = 0; 192 | for( ; i < ctx->num_opts; i++ ) 193 | { 194 | const char* check_option = curr_token + 2; 195 | 196 | const getopt_option_t* opt = ctx->opts + i; 197 | 198 | if(!opt->name) 199 | continue; 200 | 201 | unsigned int name_len = (unsigned int)strlen( opt->name ); 202 | 203 | if( str_case_cmp_len( opt->name, check_option, name_len ) == 0 ) 204 | { 205 | check_option += name_len; 206 | 207 | /* find arg if there is any */ 208 | if(getopt_opt_might_have_arg(opt)) 209 | { 210 | switch( *check_option ) 211 | { 212 | case '\0': 213 | { 214 | if( ctx->current_index < ctx->argc ) /* are there more tokens that can contain the '='? */ 215 | { 216 | const char* next_token = ctx->argv[ ctx->current_index ]; 217 | if( next_token[0] == '=' ) 218 | { 219 | ctx->current_index++; /* next token has been processed aswell! */ 220 | 221 | if( next_token[1] != '\0' ) /* does this token contain the arg-value? */ 222 | found_arg = next_token + 1; 223 | else if( ctx->current_index < ctx->argc ) 224 | found_arg = ctx->argv[ ctx->current_index++ ]; /* next token has been processed aswell! */ 225 | } 226 | else if( next_token[0] != '-' ) 227 | { 228 | ctx->current_index++; /* next token has been processed aswell! */ 229 | found_arg = next_token; 230 | } 231 | } 232 | } 233 | break; 234 | case '=': 235 | if( check_option[1] != '\0' ) 236 | found_arg = check_option + 1; 237 | else if( ctx->current_index < ctx->argc ) 238 | found_arg = ctx->argv[ ctx->current_index++ ]; /* next token has been processed aswell! */ 239 | break; 240 | default: 241 | /* not found, matched for example --test but it was --testing*/ 242 | continue; 243 | } 244 | } 245 | 246 | found_opt = opt; 247 | break; 248 | } 249 | } 250 | } 251 | /* malformed opt "-", "-xyz" or "--" */ 252 | else 253 | { 254 | ctx->current_opt_arg = curr_token; 255 | return '!'; 256 | } 257 | 258 | /* found no matching option! */ 259 | if(found_opt == 0x0) 260 | { 261 | ctx->current_opt_arg = curr_token; 262 | return '?'; 263 | } 264 | 265 | if(found_arg != 0x0) 266 | { 267 | ctx->current_opt_arg = found_arg; 268 | 269 | switch(found_opt->type) 270 | { 271 | case GETOPT_OPTION_TYPE_FLAG_SET: 272 | case GETOPT_OPTION_TYPE_FLAG_AND: 273 | case GETOPT_OPTION_TYPE_FLAG_OR: 274 | case GETOPT_OPTION_TYPE_NO_ARG: 275 | /* these types should have no argument, usage error! */ 276 | ctx->current_opt_arg = 0x0; 277 | return '+'; 278 | 279 | case GETOPT_OPTION_TYPE_OPTIONAL: 280 | case GETOPT_OPTION_TYPE_REQUIRED: 281 | return found_opt->value; 282 | case GETOPT_OPTION_TYPE_OPTIONAL_INT32: 283 | case GETOPT_OPTION_TYPE_REQUIRED_INT32: 284 | case GETOPT_OPTION_TYPE_OPTIONAL_FP32: 285 | case GETOPT_OPTION_TYPE_REQUIRED_FP32: 286 | return getopt_read_value(ctx, found_opt); 287 | } 288 | } 289 | /* no argument found */ 290 | else 291 | { 292 | switch(found_opt->type) 293 | { 294 | case GETOPT_OPTION_TYPE_FLAG_SET: *found_opt->flag = found_opt->value; return 0; 295 | case GETOPT_OPTION_TYPE_FLAG_AND: *found_opt->flag &= found_opt->value; return 0; 296 | case GETOPT_OPTION_TYPE_FLAG_OR: *found_opt->flag |= found_opt->value; return 0; /* zero is "a flag was set!" */ 297 | 298 | case GETOPT_OPTION_TYPE_NO_ARG: 299 | case GETOPT_OPTION_TYPE_OPTIONAL: 300 | case GETOPT_OPTION_TYPE_OPTIONAL_INT32: 301 | case GETOPT_OPTION_TYPE_OPTIONAL_FP32: 302 | return found_opt->value; 303 | 304 | /* the option requires an argument! (--option=arg, -o arg) */ 305 | case GETOPT_OPTION_TYPE_REQUIRED: 306 | case GETOPT_OPTION_TYPE_REQUIRED_INT32: 307 | case GETOPT_OPTION_TYPE_REQUIRED_FP32: 308 | ctx->current_opt_arg = found_opt->name; 309 | return '!'; 310 | } 311 | } 312 | 313 | return -1; 314 | } 315 | 316 | const char* getopt_create_help_string( getopt_context_t* ctx, char* buffer, size_t buffer_size ) 317 | { 318 | size_t buffer_pos = 0; 319 | int opt_index = 0; 320 | for( ; opt_index < ctx->num_opts; ++opt_index ) 321 | { 322 | const getopt_option_t* opt = ctx->opts + opt_index; 323 | 324 | size_t outpos; 325 | char long_name[64]; 326 | int chars_written = str_format( long_name, 64, "--%s", opt->name ); 327 | if( chars_written < 0 ) 328 | return buffer; 329 | 330 | outpos = (size_t)chars_written; 331 | 332 | switch( opt->type ) 333 | { 334 | case GETOPT_OPTION_TYPE_REQUIRED: 335 | str_format(long_name + outpos, 64 - outpos, "=<%s>", opt->value_desc); 336 | break; 337 | case GETOPT_OPTION_TYPE_OPTIONAL: 338 | str_format(long_name + outpos, 64 - outpos, "(=%s)", opt->value_desc); 339 | break; 340 | default: 341 | break; 342 | } 343 | 344 | if(opt->name_short == 0x0) 345 | chars_written = str_format( buffer + buffer_pos, buffer_size - buffer_pos, " %-32s - %s\n", long_name, opt->desc ); 346 | else 347 | chars_written = str_format( buffer + buffer_pos, buffer_size - buffer_pos, "-%c %-32s - %s\n", opt->name_short, long_name, opt->desc); 348 | 349 | if( chars_written < 0 ) 350 | return buffer; 351 | 352 | buffer_pos += (size_t)chars_written; 353 | } 354 | 355 | return buffer; 356 | } 357 | -------------------------------------------------------------------------------- /test/getopt_tests.cpp: -------------------------------------------------------------------------------- 1 | /* a getopt. 2 | version 0.1, march, 2012 3 | 4 | Copyright (C) 2012- Fredrik Kihlander 5 | 6 | https://github.com/wc-duck/getopt 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #include "greatest.h" 28 | #include 29 | 30 | #define ARRAY_LENGTH( arr ) ( sizeof( arr ) / sizeof( arr[0] ) ) 31 | 32 | int g_flag = -1; 33 | 34 | static const getopt_option_t option_list[] = 35 | { 36 | { "aaaa", 'a', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help a", 0 }, 37 | { "bbbb", 'b', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'b', "help b", 0 }, 38 | { "cccc", 'c', GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'c', "help c", 0 }, 39 | { "dddd", 'd', GETOPT_OPTION_TYPE_OPTIONAL, 0x0, 'd', "help d", 0 }, 40 | { "eeee", 'e', GETOPT_OPTION_TYPE_FLAG_SET, &g_flag, 1337, "help e", 0 }, 41 | { "ffff", 'f', GETOPT_OPTION_TYPE_FLAG_AND, &g_flag, 1, "help f", 0 }, 42 | { "gggg", 'g', GETOPT_OPTION_TYPE_FLAG_OR , &g_flag, 1, "help g", 0 }, 43 | { "hhhh", 'h', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 0, "help h", 0 }, 44 | GETOPT_OPTIONS_END 45 | }; 46 | 47 | static const getopt_option_t parse_option_list[] = 48 | { 49 | { "ri32", 'a', GETOPT_OPTION_TYPE_REQUIRED_INT32, 0x0, 'a', "help a", 0 }, 50 | { "rf32", 'b', GETOPT_OPTION_TYPE_REQUIRED_FP32, 0x0, 'b', "help b", 0 }, 51 | { "oi32", 'c', GETOPT_OPTION_TYPE_OPTIONAL_INT32, 0x0, 'c', "help c", 0 }, 52 | { "of32", 'd', GETOPT_OPTION_TYPE_OPTIONAL_FP32, 0x0, 'd', "help d", 0 }, 53 | GETOPT_OPTIONS_END 54 | }; 55 | 56 | int test_get_opt_simple( int argc, const char** argv ) 57 | { 58 | bool got_a = false; 59 | bool got_b = false; 60 | 61 | getopt_context_t ctx; 62 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 63 | ASSERT_EQ( 0, err ); 64 | 65 | int opt; 66 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 67 | { 68 | switch( opt ) 69 | { 70 | case 'a': ASSERT( !got_a ); got_a = true; break; // TODO: TRY!!!, should be false 71 | case 'b': ASSERT( !got_b ); got_b = true; break; 72 | default: 73 | FAILm( "got an unexpected opt!" ); 74 | break; 75 | } 76 | } 77 | 78 | ASSERT( got_a ); 79 | ASSERT( got_b ); 80 | return 0; 81 | } 82 | 83 | TEST short_opt() 84 | { 85 | const char* argv1[] = { "dummy_prog", "-a", "-b" }; 86 | const char* argv2[] = { "dummy_prog", "--aaaa", "--bbbb" }; 87 | if( test_get_opt_simple( (int)ARRAY_LENGTH( argv1 ), argv1 ) != 0 ) 88 | return -1; 89 | if( test_get_opt_simple( (int)ARRAY_LENGTH( argv2 ), argv2 ) != 0 ) 90 | return -1; 91 | return 0; 92 | } 93 | 94 | TEST unknown_flags() 95 | { 96 | const char* argv[] = { "dummy_prog", "-p", "--pppp", "--ccccc" }; 97 | int argc = (int)ARRAY_LENGTH( argv ); 98 | 99 | int unknow_count = 0; 100 | const char* unknown_flags[3] = { 0x0 }; 101 | 102 | getopt_context_t ctx; 103 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 104 | ASSERT_EQ( 0, err ); 105 | 106 | int opt; 107 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 108 | { 109 | switch( opt ) 110 | { 111 | case '?': unknown_flags[unknow_count++] = ctx.current_opt_arg; break; 112 | default: 113 | FAILm( "got an unexpected opt!" ); 114 | break; 115 | } 116 | } 117 | 118 | ASSERT_EQ(3, unknow_count); 119 | ASSERT_STR_EQ("-p", unknown_flags[0]); 120 | ASSERT_STR_EQ("--pppp", unknown_flags[1]); 121 | ASSERT_STR_EQ("--ccccc", unknown_flags[2]); 122 | 123 | return 0; 124 | } 125 | 126 | int test_with_arg( int argc, const char** argv ) 127 | { 128 | int value_count = 0; 129 | const char* values[2] = { 0x0, 0x0 }; 130 | 131 | getopt_context_t ctx; 132 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 133 | ASSERT_EQ( 0, err ); 134 | 135 | int opt; 136 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 137 | { 138 | switch( opt ) 139 | { 140 | case 'c': values[value_count++] = ctx.current_opt_arg; break; 141 | default: 142 | FAILm( "got an unexpected opt!" ); 143 | break; 144 | } 145 | } 146 | 147 | ASSERT_EQ( 2, value_count ); 148 | ASSERT_STR_EQ( "c_value_1", values[0] ); 149 | ASSERT_STR_EQ( "c_value_2", values[1] ); 150 | return 0; 151 | } 152 | 153 | int test_missing_arg( int argc, const char** argv ) 154 | { 155 | getopt_context_t ctx; 156 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 157 | ASSERT_EQ( 0, err ); 158 | 159 | int opt; 160 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 161 | { 162 | switch( opt ) 163 | { 164 | case 'c': FAILm( "we should not get a call to c since it requires an argument" ); break; 165 | case 'b': break; 166 | case '!': ASSERT_STR_EQ("cccc", ctx.current_opt_arg ); break; // flag should be stored in current_opt_arg 167 | default: 168 | FAILm( "got an unexpected opt!" ); 169 | break; 170 | } 171 | } 172 | 173 | return 0; 174 | } 175 | 176 | int test_zero_args( int argc, const char** argv ) 177 | { 178 | getopt_context_t ctx; 179 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 180 | ASSERT_EQ( 0, err ); 181 | 182 | int opt; 183 | if( ( opt = getopt_next( &ctx ) ) != -1 ) 184 | { 185 | FAILm("No arguments should have been parsed since argc was 0!"); 186 | } 187 | 188 | return 0; 189 | } 190 | 191 | TEST with_zero_args() 192 | { 193 | if (test_zero_args( 0, NULL ) != 0 ) return -1; 194 | if (test_zero_args( 1, NULL ) != 0 ) return -1; 195 | return 0; 196 | } 197 | 198 | 199 | 200 | TEST with_args_short() 201 | { 202 | const char* argv[] = { "dummy_prog", "-c", "c_value_1", "-c", "c_value_2" }; 203 | return test_with_arg( (int)ARRAY_LENGTH( argv ), argv ); 204 | } 205 | 206 | TEST with_args_long() 207 | { 208 | const char* argv1[] = { "dummy_prog", "--cccc=c_value_1", "--cccc=", "c_value_2" }; 209 | const char* argv2[] = { "dummy_prog", "--cccc", "=c_value_1", "--cccc", "=", "c_value_2" }; 210 | const char* argv3[] = { "dummy_prog", "--cccc", "c_value_1", "--cccc", "c_value_2" }; 211 | if( test_with_arg( (int)ARRAY_LENGTH( argv1 ), argv1 ) != 0 ) return -1; 212 | if( test_with_arg( (int)ARRAY_LENGTH( argv2 ), argv2 ) != 0 ) return -1; 213 | if( test_with_arg( (int)ARRAY_LENGTH( argv3 ), argv3 ) != 0 ) return -1; 214 | return 0; 215 | } 216 | 217 | TEST with_args_short_after_long() 218 | { 219 | const char* argv1[] = { "dummy_prog", "--cccc=c_value_1", "-c", "c_value_2" }; 220 | const char* argv2[] = { "dummy_prog", "--cccc", "=c_value_1", "-c", "c_value_2" }; 221 | const char* argv3[] = { "dummy_prog", "--cccc", "=", "c_value_1", "-c", "c_value_2" }; 222 | 223 | if( test_with_arg( (int)ARRAY_LENGTH( argv1 ), argv1 ) != 0 ) return -1; 224 | if( test_with_arg( (int)ARRAY_LENGTH( argv2 ), argv2 ) != 0 ) return -1; 225 | if( test_with_arg( (int)ARRAY_LENGTH( argv3 ), argv3 ) != 0 ) return -1; 226 | 227 | return 0; 228 | } 229 | 230 | TEST missing_arg_short() 231 | { 232 | const char* argv1[] = { "dummy_prog", "-c", "-b" }; 233 | const char* argv2[] = { "dummy_prog", "--cccc", "-b" }; 234 | if( test_missing_arg( (int)ARRAY_LENGTH( argv1 ), argv1 ) != 0 ) return -1; 235 | if( test_missing_arg( (int)ARRAY_LENGTH( argv2 ), argv2 ) != 0 ) return -1; 236 | return 0; 237 | } 238 | 239 | int test_optional_arg( int argc, const char** argv ) 240 | { 241 | const char* arguments[2] = { "put_arg1_here", "put_arg2_here" }; 242 | int d_count = 0; 243 | int b_count = 0; 244 | 245 | getopt_context_t ctx; 246 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 247 | ASSERT_EQ( 0, err ); 248 | 249 | int opt; 250 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 251 | { 252 | switch( opt ) 253 | { 254 | case 'd': arguments[d_count++] = ctx.current_opt_arg; break; 255 | case 'b': ++b_count; break; 256 | default: 257 | FAILm( "got an unexpected opt!" ); 258 | break; 259 | } 260 | } 261 | 262 | ASSERT_EQ(2, d_count); 263 | ASSERT_EQ(2, b_count); 264 | ASSERT_EQ((const char*)0x0, arguments[0] ); 265 | ASSERT_STR_EQ("arg", arguments[1] ); 266 | 267 | return 0; 268 | } 269 | 270 | TEST optional_arg() 271 | { 272 | const char* argv1[] = { "dummy_prog", "-d", "-b", "-d", "arg", "-b" }; 273 | const char* argv2[] = { "dummy_prog", "--dddd", "-b", "--dddd=", "arg", "-b" }; 274 | if( test_optional_arg( (int)ARRAY_LENGTH( argv1 ), argv1 ) != 0 ) return -1; 275 | if( test_optional_arg( (int)ARRAY_LENGTH( argv2 ), argv2 ) != 0 ) return -1; 276 | return 0; 277 | } 278 | 279 | static int test_parse_int(int argc, const char** argv, int expect) 280 | { 281 | int default_value = -2; 282 | int found_int = -1; 283 | 284 | getopt_context_t ctx; 285 | int err = getopt_create_context( &ctx, argc, argv, parse_option_list ); 286 | ASSERT_EQ( 0, err ); 287 | 288 | int opt; 289 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 290 | { 291 | switch( opt ) 292 | { 293 | case 'a': // required int 294 | case 'c': // optional int 295 | ASSERT_EQ(-1, found_int); 296 | 297 | found_int = ctx.current_opt_arg 298 | ? ctx.current_value.i32 299 | : default_value; 300 | 301 | if(opt == 'a') // required 302 | ASSERT_FALSE(ctx.current_opt_arg == 0x0); 303 | 304 | break; 305 | case '!': 306 | break; 307 | default: 308 | FAILm( "got an unexpected opt!" ); 309 | break; 310 | } 311 | } 312 | 313 | ASSERT_EQ(found_int, expect); 314 | 315 | return 0; 316 | } 317 | 318 | TEST int_arg() 319 | { 320 | #define EXPECT_SUCCESS(value, ...) \ 321 | { \ 322 | const char* argv[] = { __VA_ARGS__ }; \ 323 | int res = test_parse_int((int)ARRAY_LENGTH(argv), argv, value); \ 324 | if(res < 0) \ 325 | return res; \ 326 | } 327 | 328 | #define EXPECT_FAIL(...) \ 329 | EXPECT_SUCCESS(-1, __VA_ARGS__) 330 | 331 | // ... required int short ... 332 | EXPECT_SUCCESS( 1337, "dummy_prog", "-a", "1337"); // ... dec ... 333 | EXPECT_SUCCESS(0x1337, "dummy_prog", "-a", "0x1337"); // ... hex ... 334 | EXPECT_SUCCESS( 01337, "dummy_prog", "-a", "01337"); // ... oct ... 335 | 336 | // ... optional int short ... 337 | EXPECT_SUCCESS( 1337, "dummy_prog", "-c", "1337"); // ... dec ... 338 | EXPECT_SUCCESS(0x1337, "dummy_prog", "-c", "0x1337"); // ... hex ... 339 | EXPECT_SUCCESS( 01337, "dummy_prog", "-c", "01337"); // ... oct ... 340 | EXPECT_SUCCESS( -2, "dummy_prog", "-c"); // ... no value ... 341 | 342 | // ... required int long ... 343 | EXPECT_SUCCESS( 1337, "dummy_prog", "--ri32", "1337"); // ... dec ... 344 | EXPECT_SUCCESS(0x1337, "dummy_prog", "--ri32", "0x1337"); // ... hex ... 345 | EXPECT_SUCCESS( 01337, "dummy_prog", "--ri32", "01337"); // ... oct ... 346 | 347 | // ... optional int long ... 348 | EXPECT_SUCCESS( 1337, "dummy_prog", "--oi32", "1337"); // ... dec ... 349 | EXPECT_SUCCESS(0x1337, "dummy_prog", "--oi32", "0x1337"); // ... hex ... 350 | EXPECT_SUCCESS( 01337, "dummy_prog", "--oi32", "01337"); // ... oct ... 351 | EXPECT_SUCCESS( -2, "dummy_prog", "--oi32"); // ... long, optional int ... 352 | 353 | EXPECT_FAIL( "dummy_prog", "-a" ); // ... short, required int, missing arg ... 354 | EXPECT_FAIL( "dummy_prog", "-a", "poop" ); // ... short, required int, invalid arg ... 355 | EXPECT_FAIL( "dummy_prog", "-a", "1337poop" ); // ... short, required int, invalid arg that start of as valid ... 356 | EXPECT_FAIL( "dummy_prog", "--ri32" ); // ... long, required int, missing arg ... 357 | EXPECT_FAIL( "dummy_prog", "--ri32", "poop" ); // ... long, required int, invalid arg ... 358 | EXPECT_FAIL( "dummy_prog", "--ri32", "1337poop" ); // ... long, required int, invalid arg that start of as valid ... 359 | 360 | #undef EXPECT_FAIL 361 | #undef EXPECT_SUCCESS 362 | 363 | return 0; 364 | } 365 | 366 | static int test_parse_fp32(int argc, const char** argv, float expect) 367 | { 368 | float default_value = -2.0f; 369 | float found_fp32 = -1.0f; 370 | 371 | getopt_context_t ctx; 372 | int err = getopt_create_context( &ctx, argc, argv, parse_option_list ); 373 | ASSERT_EQ( 0, err ); 374 | 375 | int opt; 376 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 377 | { 378 | switch( opt ) 379 | { 380 | case 'b': // required fp 381 | case 'd': // optional fp 382 | ASSERT_EQ(-1.0f, found_fp32); 383 | 384 | found_fp32 = ctx.current_opt_arg 385 | ? ctx.current_value.fp32 386 | : default_value; 387 | 388 | if(opt == 'b') // required 389 | ASSERT_FALSE(ctx.current_opt_arg == 0x0); 390 | 391 | break; 392 | case '!': 393 | break; 394 | default: 395 | FAILm( "got an unexpected opt!" ); 396 | break; 397 | } 398 | } 399 | 400 | ASSERT_EQ(found_fp32, expect); 401 | 402 | return 0; 403 | } 404 | 405 | TEST fp32_arg() 406 | { 407 | #define EXPECT_SUCCESS(value, ...) \ 408 | { \ 409 | const char* argv[] = { __VA_ARGS__ }; \ 410 | int res = test_parse_fp32((int)ARRAY_LENGTH(argv), argv, value); \ 411 | if(res < 0) \ 412 | return res; \ 413 | } 414 | 415 | #define EXPECT_FAIL(...) \ 416 | EXPECT_SUCCESS(-1, __VA_ARGS__) 417 | 418 | // ... required fp32 short ... 419 | EXPECT_SUCCESS(1337.0f, "dummy_prog", "-b", "1337"); // ... dec ... 420 | EXPECT_SUCCESS( 13.37f, "dummy_prog", "-b", "13.37"); // ... fp ... 421 | 422 | // ... optional fp32 short ... 423 | EXPECT_SUCCESS(1337.0f, "dummy_prog", "-d", "1337"); // ... dec ... 424 | EXPECT_SUCCESS( 13.37f, "dummy_prog", "-d", "13.37"); // ... hex ... 425 | EXPECT_SUCCESS( -2.0f, "dummy_prog", "-d"); // ... no value ... 426 | 427 | // ... required fp32 long ... 428 | EXPECT_SUCCESS(1337.0f, "dummy_prog", "--rf32", "1337"); // ... dec ... 429 | EXPECT_SUCCESS( 13.37f, "dummy_prog", "--rf32", "13.37"); // ... hex ... 430 | 431 | // ... optional fp32 long ... 432 | EXPECT_SUCCESS(1337.0f, "dummy_prog", "--of32", "1337"); // ... dec ... 433 | EXPECT_SUCCESS( 13.37f, "dummy_prog", "--of32", "13.37"); // ... hex ... 434 | EXPECT_SUCCESS( -2.0f, "dummy_prog", "--of32"); // ... long, optional int ... 435 | 436 | EXPECT_FAIL( "dummy_prog", "-b" ); // ... short, required int, missing arg ... 437 | EXPECT_FAIL( "dummy_prog", "-b", "poop" ); // ... short, required int, invalid arg ... 438 | EXPECT_FAIL( "dummy_prog", "-b", "1337poop" ); // ... short, required int, invalid arg that start of as valid ... 439 | EXPECT_FAIL( "dummy_prog", "--rf32" ); // ... long, required int, missing arg ... 440 | EXPECT_FAIL( "dummy_prog", "--rf32", "poop" ); // ... long, required int, invalid arg ... 441 | EXPECT_FAIL( "dummy_prog", "--rf32", "1337poop" ); // ... long, required int, invalid arg that start of as valid ... 442 | 443 | #undef EXPECT_FAIL 444 | #undef EXPECT_SUCCESS 445 | 446 | return 0; 447 | }; 448 | 449 | TEST non_arguments() 450 | { 451 | const char* argv[] = { "dummy_prog", "-c", "arg1", "non_arg1", "--cccc=arg2", "non_arg2", "non_arg3" }; 452 | int argc = (int)ARRAY_LENGTH( argv ); 453 | 454 | const char* args[2] = { "fill1", "fill2" }; 455 | const char* non_args[3] = { "fill3", "fill4", "fill5" }; 456 | int args_count = 0; 457 | int non_args_count = 0; 458 | 459 | getopt_context_t ctx; 460 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 461 | ASSERT_EQ( 0, err ); 462 | 463 | int opt; 464 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 465 | { 466 | switch( opt ) 467 | { 468 | case 'c': args[args_count++] = ctx.current_opt_arg; break; 469 | case '+': non_args[non_args_count++] = ctx.current_opt_arg; break; 470 | default: 471 | FAILm( "got an unexpected opt!" ); 472 | break; 473 | } 474 | } 475 | 476 | ASSERT_EQ(2, args_count); 477 | ASSERT_EQ(3, non_args_count); 478 | ASSERT_STR_EQ("arg1", args[0]); 479 | ASSERT_STR_EQ("arg2", args[1]); 480 | ASSERT_STR_EQ("non_arg1", non_args[0]); 481 | ASSERT_STR_EQ("non_arg2", non_args[1]); 482 | ASSERT_STR_EQ("non_arg3", non_args[2]); 483 | 484 | return 0; 485 | } 486 | 487 | TEST non_arguments_for_no_args_flags() 488 | { 489 | // test that flag that should have no args pass 'non_args' correctly ... 490 | const char* argv[] = { "dummy_prog", "-a", "non_arg1", "--bbbb", "non_arg2" }; 491 | int argc = (int)ARRAY_LENGTH( argv ); 492 | 493 | const char* args[2] = { "fill1", "fill2" }; 494 | const char* non_args[2] = { "fill3", "fill4" }; 495 | int args_count = 0; 496 | int non_args_count = 0; 497 | 498 | getopt_context_t ctx; 499 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 500 | ASSERT_EQ( 0, err ); 501 | 502 | int opt; 503 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 504 | { 505 | switch( opt ) 506 | { 507 | case 'a': 508 | args[args_count++] = "a"; 509 | ASSERT_EQ(nullptr, ctx.current_opt_arg); 510 | break; 511 | case 'b': 512 | args[args_count++] = "b"; 513 | ASSERT_EQ(nullptr, ctx.current_opt_arg); 514 | break; 515 | case '+': 516 | non_args[non_args_count++] = ctx.current_opt_arg; 517 | break; 518 | default: 519 | printf("opt == '%c'\n", opt); 520 | FAILm("got an unexpected opt!"); 521 | break; 522 | } 523 | } 524 | 525 | ASSERT_EQ(2, args_count); 526 | ASSERT_EQ(2, non_args_count); 527 | ASSERT_STR_EQ("a", args[0]); 528 | ASSERT_STR_EQ("b", args[1]); 529 | ASSERT_STR_EQ("non_arg1", non_args[0]); 530 | ASSERT_STR_EQ("non_arg2", non_args[1]); 531 | 532 | return 0; 533 | } 534 | 535 | TEST set_flag() 536 | { 537 | g_flag = -1; 538 | 539 | const char* argv[] = { "dummy_prog", "-g", "-f", "-g", "-f", "-e" }; // ( ( ( 0xFFFFFFFF | 1 ) & 1 ) | 1 ) & 1 540 | int argc = (int)ARRAY_LENGTH( argv ); 541 | 542 | int flag_values[5] = { 0 }; 543 | int num_flags = 0; 544 | 545 | getopt_context_t ctx; 546 | int err = getopt_create_context( &ctx, argc, argv, option_list ); 547 | ASSERT_EQ( 0, err ); 548 | 549 | int opt; 550 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 551 | { 552 | switch( opt ) 553 | { 554 | case '\0': flag_values[num_flags++] = g_flag; break; 555 | default: 556 | FAILm( "got an unexpected opt!" ); 557 | break; 558 | } 559 | } 560 | 561 | ASSERT_EQ(5, num_flags); 562 | int expect0 = -1; 563 | int expect1 = expect0 | 1; 564 | int expect2 = expect1 & 1; 565 | int expect3 = expect2 | 1; 566 | int expect4 = expect3 & 1; 567 | ASSERT_EQ(expect1, flag_values[0] ); 568 | ASSERT_EQ(expect2, flag_values[1] ); 569 | ASSERT_EQ(expect3, flag_values[2] ); 570 | ASSERT_EQ(expect4, flag_values[3] ); 571 | ASSERT_EQ(1337, flag_values[4] ); 572 | return 0; 573 | } 574 | 575 | TEST same_prefix_long_opt() 576 | { 577 | static const getopt_option_t bug_option_list[] = 578 | { 579 | { "input", 0x0, GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'i', "help input", "input value" }, 580 | { "input-variant", 0x0, GETOPT_OPTION_TYPE_REQUIRED, 0x0, 'I', "help input-variant", "input variant" }, 581 | GETOPT_OPTIONS_END 582 | }; 583 | 584 | const char* argv[] = { "dummy_prog", "--input", "Input Value", "--input-variant", "Input Variant" }; 585 | int argc = (int)ARRAY_LENGTH( argv ); 586 | 587 | getopt_context_t ctx; 588 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 589 | ASSERT_EQ( 0, err ); 590 | 591 | int opt; 592 | 593 | int found_input = 0; 594 | int found_input_variant = 0; 595 | 596 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 597 | { 598 | switch( opt ) 599 | { 600 | case 'i': { found_input = 1; ASSERT_STR_EQ("Input Value", ctx.current_opt_arg); break; } 601 | case 'I': { found_input_variant = 1; ASSERT_STR_EQ("Input Variant", ctx.current_opt_arg); break; } 602 | default: 603 | FAILm( "got an unexpected opt!" ); 604 | break; 605 | } 606 | } 607 | 608 | ASSERT_EQ(1, found_input); 609 | ASSERT_EQ(1, found_input_variant); 610 | 611 | return 0; 612 | } 613 | 614 | TEST no_longopt_with_longopt() 615 | { 616 | static const getopt_option_t bug_option_list[] = 617 | { 618 | { 0x0, 'a', GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 619 | GETOPT_OPTIONS_END 620 | }; 621 | 622 | const char* argv[] = { "dummy_prog", "--input" }; 623 | int argc = (int)ARRAY_LENGTH( argv ); 624 | 625 | getopt_context_t ctx; 626 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 627 | ASSERT_EQ( 0, err ); 628 | 629 | int opt; 630 | 631 | int found_input = 0; 632 | 633 | while( ( opt = getopt_next( &ctx ) ) != -1 ) 634 | { 635 | switch( opt ) 636 | { 637 | case 'a': ++found_input; break; 638 | case '?': break; 639 | default: 640 | FAILm( "got an unexpected opt!" ); 641 | break; 642 | } 643 | } 644 | 645 | ASSERT_EQ(0, found_input); 646 | 647 | return 0; 648 | } 649 | 650 | TEST capture_bad_longopt() 651 | { 652 | const char* argv[] = { "dummy_prog", "--input" }; 653 | int argc = (int)ARRAY_LENGTH( argv ); 654 | 655 | { 656 | static const getopt_option_t bug_option_list[] = 657 | { 658 | // args are not allowed to begin with one '-' 659 | { "-bad", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 660 | GETOPT_OPTIONS_END 661 | }; 662 | 663 | getopt_context_t ctx; 664 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 665 | ASSERT_EQ( -1, err ); 666 | } 667 | 668 | { 669 | static const getopt_option_t bug_option_list[] = 670 | { 671 | // args are not allowed to begin with two '-' 672 | { "--bad", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 673 | GETOPT_OPTIONS_END 674 | }; 675 | 676 | getopt_context_t ctx; 677 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 678 | ASSERT_EQ( -1, err ); 679 | } 680 | 681 | // test for bad args in different positions. 682 | { 683 | static const getopt_option_t bug_option_list[] = 684 | { 685 | { "--bad", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 686 | { "i", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'b', "help input", "input value" }, 687 | GETOPT_OPTIONS_END 688 | }; 689 | 690 | getopt_context_t ctx; 691 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 692 | ASSERT_EQ( -1, err ); 693 | } 694 | 695 | { 696 | static const getopt_option_t bug_option_list[] = 697 | { 698 | { "i", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'b', "help input", "input value" }, 699 | { "--bad", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 700 | GETOPT_OPTIONS_END 701 | }; 702 | 703 | getopt_context_t ctx; 704 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 705 | ASSERT_EQ( -1, err ); 706 | } 707 | 708 | { 709 | static const getopt_option_t bug_option_list[] = 710 | { 711 | { "j", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'c', "help input", "input value" }, 712 | { "--bad", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'a', "help input", "input value" }, 713 | { "i", 0, GETOPT_OPTION_TYPE_NO_ARG, 0x0, 'b', "help input", "input value" }, 714 | GETOPT_OPTIONS_END 715 | }; 716 | 717 | getopt_context_t ctx; 718 | int err = getopt_create_context( &ctx, argc, argv, bug_option_list ); 719 | ASSERT_EQ( -1, err ); 720 | } 721 | 722 | return 0; 723 | } 724 | 725 | GREATEST_SUITE( getopt ) 726 | { 727 | RUN_TEST( short_opt ); 728 | RUN_TEST( unknown_flags ); 729 | RUN_TEST( with_args_short ); 730 | RUN_TEST( with_args_long ); 731 | RUN_TEST( with_args_short_after_long ); 732 | RUN_TEST( missing_arg_short ); 733 | RUN_TEST( optional_arg ); 734 | RUN_TEST( int_arg ); 735 | RUN_TEST( fp32_arg ); 736 | RUN_TEST( non_arguments ); 737 | RUN_TEST( non_arguments_for_no_args_flags ); 738 | RUN_TEST( set_flag ); 739 | RUN_TEST( with_zero_args ); 740 | RUN_TEST( same_prefix_long_opt ); 741 | RUN_TEST( no_longopt_with_longopt ); 742 | RUN_TEST( capture_bad_longopt ); 743 | } 744 | 745 | GREATEST_MAIN_DEFS(); 746 | 747 | int main( int argc, char **argv ) 748 | { 749 | GREATEST_MAIN_BEGIN(); 750 | RUN_SUITE( getopt ); 751 | GREATEST_MAIN_END(); 752 | } 753 | 754 | -------------------------------------------------------------------------------- /test/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #define GREATEST_VERSION_MAJOR 0 21 | #define GREATEST_VERSION_MINOR 9 22 | #define GREATEST_VERSION_PATCH 3 23 | 24 | /* A unit testing system for C, contained in 1 file. 25 | * It doesn't use dynamic allocation or depend on anything 26 | * beyond ANSI C89. */ 27 | 28 | 29 | /********************************************************************* 30 | * Minimal test runner template 31 | *********************************************************************/ 32 | #if 0 33 | 34 | #include "greatest.h" 35 | 36 | TEST foo_should_foo() { 37 | PASS(); 38 | } 39 | 40 | static void setup_cb(void *data) { 41 | printf("setup callback for each test case\n"); 42 | } 43 | 44 | static void teardown_cb(void *data) { 45 | printf("teardown callback for each test case\n"); 46 | } 47 | 48 | SUITE(suite) { 49 | /* Optional setup/teardown callbacks which will be run before/after 50 | * every test case in the suite. 51 | * Cleared when the suite finishes. */ 52 | SET_SETUP(setup_cb, voidp_to_callback_data); 53 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 54 | 55 | RUN_TEST(foo_should_foo); 56 | } 57 | 58 | /* Add all the definitions that need to be in the test runner's main file. */ 59 | GREATEST_MAIN_DEFS(); 60 | 61 | int main(int argc, char **argv) { 62 | GREATEST_MAIN_BEGIN(); /* command-line arguments, initialization. */ 63 | RUN_SUITE(suite); 64 | GREATEST_MAIN_END(); /* display results */ 65 | } 66 | 67 | #endif 68 | /*********************************************************************/ 69 | 70 | 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | 77 | /*********** 78 | * Options * 79 | ***********/ 80 | 81 | /* Default column width for non-verbose output. */ 82 | #ifndef GREATEST_DEFAULT_WIDTH 83 | #define GREATEST_DEFAULT_WIDTH 72 84 | #endif 85 | 86 | /* FILE *, for test logging. */ 87 | #ifndef GREATEST_STDOUT 88 | #define GREATEST_STDOUT stdout 89 | #endif 90 | 91 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 92 | #ifndef GREATEST_USE_ABBREVS 93 | #define GREATEST_USE_ABBREVS 1 94 | #endif 95 | 96 | 97 | /********* 98 | * Types * 99 | *********/ 100 | 101 | /* Info for the current running suite. */ 102 | typedef struct greatest_suite_info { 103 | unsigned int tests_run; 104 | unsigned int passed; 105 | unsigned int failed; 106 | unsigned int skipped; 107 | 108 | /* timers, pre/post running suite and individual tests */ 109 | clock_t pre_suite; 110 | clock_t post_suite; 111 | clock_t pre_test; 112 | clock_t post_test; 113 | } greatest_suite_info; 114 | 115 | /* Type for a suite function. */ 116 | typedef void (greatest_suite_cb)(void); 117 | 118 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 119 | * and passed the pointer to their additional data. */ 120 | typedef void (greatest_setup_cb)(void *udata); 121 | typedef void (greatest_teardown_cb)(void *udata); 122 | 123 | typedef enum { 124 | GREATEST_FLAG_VERBOSE = 0x01, 125 | GREATEST_FLAG_FIRST_FAIL = 0x02, 126 | GREATEST_FLAG_LIST_ONLY = 0x04 127 | } GREATEST_FLAG; 128 | 129 | typedef struct greatest_run_info { 130 | unsigned int flags; 131 | unsigned int tests_run; /* total test count */ 132 | 133 | /* Overall pass/fail/skip counts. */ 134 | unsigned int passed; 135 | unsigned int failed; 136 | unsigned int skipped; 137 | 138 | /* currently running test suite */ 139 | greatest_suite_info suite; 140 | 141 | /* info to print about the most recent failure */ 142 | const char *fail_file; 143 | unsigned int fail_line; 144 | const char *msg; 145 | 146 | /* current setup/teardown hooks and userdata */ 147 | greatest_setup_cb *setup; 148 | void *setup_udata; 149 | greatest_teardown_cb *teardown; 150 | void *teardown_udata; 151 | 152 | /* formatting info for ".....s...F"-style output */ 153 | unsigned int col; 154 | unsigned int width; 155 | 156 | /* only run a specific suite or test */ 157 | char *suite_filter; 158 | char *test_filter; 159 | 160 | /* overall timers */ 161 | clock_t begin; 162 | clock_t end; 163 | } greatest_run_info; 164 | 165 | /* Global var for the current testing context. 166 | * Initialized by GREATEST_MAIN_DEFS(). */ 167 | extern greatest_run_info greatest_info; 168 | 169 | 170 | /********************** 171 | * Exported functions * 172 | **********************/ 173 | 174 | void greatest_do_pass(const char *name); 175 | void greatest_do_fail(const char *name); 176 | void greatest_do_skip(const char *name); 177 | int greatest_pre_test(const char *name); 178 | void greatest_post_test(const char *name, int res); 179 | void greatest_usage(const char *name); 180 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 181 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 182 | 183 | 184 | /********** 185 | * Macros * 186 | **********/ 187 | 188 | /* Define a suite. */ 189 | #define GREATEST_SUITE(NAME) void NAME(void) 190 | 191 | /* Start defining a test function. 192 | * The arguments are not included, to allow parametric testing. */ 193 | #define GREATEST_TEST static int 194 | 195 | /* Run a suite. */ 196 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 197 | 198 | /* Run a test in the current suite. */ 199 | #define GREATEST_RUN_TEST(TEST) \ 200 | do { \ 201 | if (greatest_pre_test(#TEST) == 1) { \ 202 | int res = TEST(); \ 203 | greatest_post_test(#TEST, res); \ 204 | } else if (GREATEST_LIST_ONLY()) { \ 205 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 206 | } \ 207 | } while (0) 208 | 209 | /* Run a test in the current suite with one void* argument, 210 | * which can be a pointer to a struct with multiple arguments. */ 211 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 212 | do { \ 213 | if (greatest_pre_test(#TEST) == 1) { \ 214 | int res = TEST(ENV); \ 215 | greatest_post_test(#TEST, res); \ 216 | } else if (GREATEST_LIST_ONLY()) { \ 217 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 218 | } \ 219 | } while (0) 220 | 221 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 222 | * without needing to manually manage the argument struct. */ 223 | #if __STDC_VERSION__ >= 19901L 224 | #define GREATEST_RUN_TESTp(TEST, ...) \ 225 | do { \ 226 | if (greatest_pre_test(#TEST) == 1) { \ 227 | int res = TEST(__VA_ARGS__); \ 228 | greatest_post_test(#TEST, res); \ 229 | } else if (GREATEST_LIST_ONLY()) { \ 230 | fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ 231 | } \ 232 | } while (0) 233 | #endif 234 | 235 | 236 | /* Check if the test runner is in verbose mode. */ 237 | #define GREATEST_IS_VERBOSE() (greatest_info.flags & GREATEST_FLAG_VERBOSE) 238 | #define GREATEST_LIST_ONLY() (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 239 | #define GREATEST_FIRST_FAIL() (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 240 | #define GREATEST_FAILURE_ABORT() (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) 241 | 242 | /* Message-less forms. */ 243 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 244 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 245 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 246 | #define GREATEST_ASSERT(COND) GREATEST_ASSERTm(#COND, COND) 247 | #define GREATEST_ASSERT_FALSE(COND) GREATEST_ASSERT_FALSEm(#COND, COND) 248 | #define GREATEST_ASSERT_EQ(EXP, GOT) GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 249 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 250 | 251 | /* The following forms take an additional message argument first, 252 | * to be displayed by the test runner. */ 253 | 254 | /* Fail if a condition is not true, with message. */ 255 | #define GREATEST_ASSERTm(MSG, COND) \ 256 | do { \ 257 | greatest_info.msg = MSG; \ 258 | greatest_info.fail_file = __FILE__; \ 259 | greatest_info.fail_line = __LINE__; \ 260 | if (!(COND)) { return -1; } \ 261 | greatest_info.msg = NULL; \ 262 | } while (0) 263 | 264 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 265 | do { \ 266 | greatest_info.msg = MSG; \ 267 | greatest_info.fail_file = __FILE__; \ 268 | greatest_info.fail_line = __LINE__; \ 269 | if ((COND)) { return -1; } \ 270 | greatest_info.msg = NULL; \ 271 | } while (0) 272 | 273 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 274 | do { \ 275 | greatest_info.msg = MSG; \ 276 | greatest_info.fail_file = __FILE__; \ 277 | greatest_info.fail_line = __LINE__; \ 278 | if ((EXP) != (GOT)) { return -1; } \ 279 | greatest_info.msg = NULL; \ 280 | } while (0) 281 | 282 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 283 | do { \ 284 | const char *exp_s = (EXP); \ 285 | const char *got_s = (GOT); \ 286 | greatest_info.msg = MSG; \ 287 | greatest_info.fail_file = __FILE__; \ 288 | greatest_info.fail_line = __LINE__; \ 289 | if (0 != strcmp(exp_s, got_s)) { \ 290 | fprintf(GREATEST_STDOUT, \ 291 | "Expected:\n####\n%s\n####\n", exp_s); \ 292 | fprintf(GREATEST_STDOUT, \ 293 | "Got:\n####\n%s\n####\n", got_s); \ 294 | return -1; \ 295 | } \ 296 | greatest_info.msg = NULL; \ 297 | } while (0) 298 | 299 | #define GREATEST_PASSm(MSG) \ 300 | do { \ 301 | greatest_info.msg = MSG; \ 302 | return 0; \ 303 | } while (0) 304 | 305 | #define GREATEST_FAILm(MSG) \ 306 | do { \ 307 | greatest_info.fail_file = __FILE__; \ 308 | greatest_info.fail_line = __LINE__; \ 309 | greatest_info.msg = MSG; \ 310 | return -1; \ 311 | } while (0) 312 | 313 | #define GREATEST_SKIPm(MSG) \ 314 | do { \ 315 | greatest_info.msg = MSG; \ 316 | return 1; \ 317 | } while (0) 318 | 319 | #define GREATEST_SET_TIME(NAME) \ 320 | NAME = clock(); \ 321 | if (NAME == (clock_t) -1) { \ 322 | fprintf(GREATEST_STDOUT, \ 323 | "clock error: %s\n", #NAME); \ 324 | exit(EXIT_FAILURE); \ 325 | } 326 | 327 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 328 | fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 329 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 330 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) \ 331 | 332 | /* Include several function definitions in the main test file. */ 333 | #define GREATEST_MAIN_DEFS() \ 334 | \ 335 | /* Is FILTER a subset of NAME? */ \ 336 | static int greatest_name_match(const char *name, \ 337 | const char *filter) { \ 338 | size_t offset = 0; \ 339 | size_t filter_len = strlen(filter); \ 340 | while (name[offset] != '\0') { \ 341 | if (name[offset] == filter[0]) { \ 342 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 343 | return 1; \ 344 | } \ 345 | } \ 346 | offset++; \ 347 | } \ 348 | \ 349 | return 0; \ 350 | } \ 351 | \ 352 | int greatest_pre_test(const char *name) { \ 353 | if (!GREATEST_LIST_ONLY() \ 354 | && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ 355 | && (greatest_info.test_filter == NULL || \ 356 | greatest_name_match(name, greatest_info.test_filter))) { \ 357 | GREATEST_SET_TIME(greatest_info.suite.pre_test); \ 358 | if (greatest_info.setup) { \ 359 | greatest_info.setup(greatest_info.setup_udata); \ 360 | } \ 361 | return 1; /* test should be run */ \ 362 | } else { \ 363 | return 0; /* skipped */ \ 364 | } \ 365 | } \ 366 | \ 367 | void greatest_post_test(const char *name, int res) { \ 368 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 369 | if (greatest_info.teardown) { \ 370 | void *udata = greatest_info.teardown_udata; \ 371 | greatest_info.teardown(udata); \ 372 | } \ 373 | \ 374 | if (res < 0) { \ 375 | greatest_do_fail(name); \ 376 | } else if (res > 0) { \ 377 | greatest_do_skip(name); \ 378 | } else if (res == 0) { \ 379 | greatest_do_pass(name); \ 380 | } \ 381 | greatest_info.suite.tests_run++; \ 382 | greatest_info.col++; \ 383 | if (GREATEST_IS_VERBOSE()) { \ 384 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 385 | greatest_info.suite.post_test); \ 386 | fprintf(GREATEST_STDOUT, "\n"); \ 387 | } else if (greatest_info.col % greatest_info.width == 0) { \ 388 | fprintf(GREATEST_STDOUT, "\n"); \ 389 | greatest_info.col = 0; \ 390 | } \ 391 | if (GREATEST_STDOUT == stdout) fflush(stdout); \ 392 | } \ 393 | \ 394 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 395 | const char *suite_name) { \ 396 | if (greatest_info.suite_filter && \ 397 | !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ 398 | return; \ 399 | } \ 400 | if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ 401 | greatest_info.suite.tests_run = 0; \ 402 | greatest_info.suite.failed = 0; \ 403 | greatest_info.suite.passed = 0; \ 404 | greatest_info.suite.skipped = 0; \ 405 | greatest_info.suite.pre_suite = 0; \ 406 | greatest_info.suite.post_suite = 0; \ 407 | greatest_info.suite.pre_test = 0; \ 408 | greatest_info.suite.post_test = 0; \ 409 | greatest_info.col = 0; \ 410 | fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 411 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 412 | suite_cb(); \ 413 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 414 | if (greatest_info.suite.tests_run > 0) { \ 415 | fprintf(GREATEST_STDOUT, \ 416 | "\n%u tests - %u pass, %u fail, %u skipped", \ 417 | greatest_info.suite.tests_run, \ 418 | greatest_info.suite.passed, \ 419 | greatest_info.suite.failed, \ 420 | greatest_info.suite.skipped); \ 421 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 422 | greatest_info.suite.post_suite); \ 423 | fprintf(GREATEST_STDOUT, "\n"); \ 424 | } \ 425 | greatest_info.setup = NULL; \ 426 | greatest_info.setup_udata = NULL; \ 427 | greatest_info.teardown = NULL; \ 428 | greatest_info.teardown_udata = NULL; \ 429 | greatest_info.passed += greatest_info.suite.passed; \ 430 | greatest_info.failed += greatest_info.suite.failed; \ 431 | greatest_info.skipped += greatest_info.suite.skipped; \ 432 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 433 | } \ 434 | \ 435 | void greatest_do_pass(const char *name) { \ 436 | if (GREATEST_IS_VERBOSE()) { \ 437 | fprintf(GREATEST_STDOUT, "PASS %s: %s", \ 438 | name, greatest_info.msg ? greatest_info.msg : ""); \ 439 | } else { \ 440 | fprintf(GREATEST_STDOUT, "."); \ 441 | } \ 442 | greatest_info.suite.passed++; \ 443 | } \ 444 | \ 445 | void greatest_do_fail(const char *name) { \ 446 | if (GREATEST_IS_VERBOSE()) { \ 447 | fprintf(GREATEST_STDOUT, \ 448 | "FAIL %s: %s (%s:%u)", \ 449 | name, greatest_info.msg ? greatest_info.msg : "", \ 450 | greatest_info.fail_file, greatest_info.fail_line); \ 451 | } else { \ 452 | fprintf(GREATEST_STDOUT, "F"); \ 453 | greatest_info.col++; \ 454 | /* add linebreak if in line of '.'s */ \ 455 | if (greatest_info.col != 0) { \ 456 | fprintf(GREATEST_STDOUT, "\n"); \ 457 | greatest_info.col = 0; \ 458 | } \ 459 | fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 460 | name, \ 461 | greatest_info.msg ? greatest_info.msg : "", \ 462 | greatest_info.fail_file, greatest_info.fail_line); \ 463 | } \ 464 | greatest_info.suite.failed++; \ 465 | } \ 466 | \ 467 | void greatest_do_skip(const char *name) { \ 468 | if (GREATEST_IS_VERBOSE()) { \ 469 | fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ 470 | name, \ 471 | greatest_info.msg ? \ 472 | greatest_info.msg : "" ); \ 473 | } else { \ 474 | fprintf(GREATEST_STDOUT, "s"); \ 475 | } \ 476 | greatest_info.suite.skipped++; \ 477 | } \ 478 | \ 479 | void greatest_usage(const char *name) { \ 480 | fprintf(GREATEST_STDOUT, \ 481 | "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ 482 | " -h print this Help\n" \ 483 | " -l List suites and their tests, then exit\n" \ 484 | " -f Stop runner after first failure\n" \ 485 | " -v Verbose output\n" \ 486 | " -s SUITE only run suite named SUITE\n" \ 487 | " -t TEST only run test named TEST\n", \ 488 | name); \ 489 | } \ 490 | \ 491 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 492 | greatest_info.setup = cb; \ 493 | greatest_info.setup_udata = udata; \ 494 | } \ 495 | \ 496 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 497 | void *udata) { \ 498 | greatest_info.teardown = cb; \ 499 | greatest_info.teardown_udata = udata; \ 500 | } \ 501 | \ 502 | greatest_run_info greatest_info 503 | 504 | /* Handle command-line arguments, etc. */ 505 | #define GREATEST_MAIN_BEGIN() \ 506 | do { \ 507 | int i = 0; \ 508 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 509 | if (greatest_info.width == 0) { \ 510 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 511 | } \ 512 | for (i = 1; i < argc; i++) { \ 513 | if (0 == strcmp("-t", argv[i])) { \ 514 | if (argc <= i + 1) { \ 515 | greatest_usage(argv[0]); \ 516 | exit(EXIT_FAILURE); \ 517 | } \ 518 | greatest_info.test_filter = argv[i+1]; \ 519 | i++; \ 520 | } else if (0 == strcmp("-s", argv[i])) { \ 521 | if (argc <= i + 1) { \ 522 | greatest_usage(argv[0]); \ 523 | exit(EXIT_FAILURE); \ 524 | } \ 525 | greatest_info.suite_filter = argv[i+1]; \ 526 | i++; \ 527 | } else if (0 == strcmp("-f", argv[i])) { \ 528 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ 529 | } else if (0 == strcmp("-v", argv[i])) { \ 530 | greatest_info.flags |= GREATEST_FLAG_VERBOSE; \ 531 | } else if (0 == strcmp("-l", argv[i])) { \ 532 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ 533 | } else if (0 == strcmp("-h", argv[i])) { \ 534 | greatest_usage(argv[0]); \ 535 | exit(EXIT_SUCCESS); \ 536 | } else { \ 537 | fprintf(GREATEST_STDOUT, \ 538 | "Unknown argument '%s'\n", argv[i]); \ 539 | greatest_usage(argv[0]); \ 540 | exit(EXIT_FAILURE); \ 541 | } \ 542 | } \ 543 | } while (0); \ 544 | GREATEST_SET_TIME(greatest_info.begin) 545 | 546 | #define GREATEST_MAIN_END() \ 547 | do { \ 548 | if (!GREATEST_LIST_ONLY()) { \ 549 | GREATEST_SET_TIME(greatest_info.end); \ 550 | fprintf(GREATEST_STDOUT, \ 551 | "\nTotal: %u tests", greatest_info.tests_run); \ 552 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 553 | greatest_info.end); \ 554 | fprintf(GREATEST_STDOUT, "\n"); \ 555 | fprintf(GREATEST_STDOUT, \ 556 | "Pass: %u, fail: %u, skip: %u.\n", \ 557 | greatest_info.passed, \ 558 | greatest_info.failed, greatest_info.skipped); \ 559 | } \ 560 | return (greatest_info.failed > 0 \ 561 | ? EXIT_FAILURE : EXIT_SUCCESS); \ 562 | } while (0) 563 | 564 | /* Make abbreviations without the GREATEST_ prefix for the 565 | * most commonly used symbols. */ 566 | #if GREATEST_USE_ABBREVS 567 | #define TEST GREATEST_TEST 568 | #define SUITE GREATEST_SUITE 569 | #define RUN_TEST GREATEST_RUN_TEST 570 | #define RUN_TEST1 GREATEST_RUN_TEST1 571 | #define RUN_SUITE GREATEST_RUN_SUITE 572 | #define ASSERT GREATEST_ASSERT 573 | #define ASSERTm GREATEST_ASSERTm 574 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 575 | #define ASSERT_EQ GREATEST_ASSERT_EQ 576 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 577 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 578 | #define ASSERT_EQm GREATEST_ASSERT_EQm 579 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 580 | #define PASS GREATEST_PASS 581 | #define FAIL GREATEST_FAIL 582 | #define SKIP GREATEST_SKIP 583 | #define PASSm GREATEST_PASSm 584 | #define FAILm GREATEST_FAILm 585 | #define SKIPm GREATEST_SKIPm 586 | #define SET_SETUP GREATEST_SET_SETUP_CB 587 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 588 | 589 | #if __STDC_VERSION__ >= 19901L 590 | #endif /* C99 */ 591 | #define RUN_TESTp GREATEST_RUN_TESTp 592 | #endif /* USE_ABBREVS */ 593 | 594 | #endif 595 | --------------------------------------------------------------------------------