From: Michael D. Lowis Date: Mon, 4 Jun 2012 00:11:28 +0000 (-0400) Subject: Version 0.1 complete and fully unit tested X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=0fd4135d6ed05bb0f8f010644de0c36c58e1acb3;p=projs%2Fopts.git Version 0.1 complete and fully unit tested --- diff --git a/Makefile b/Makefile index eaa613b..fe8b590 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ INC_DIRS = $(call incdirs, $(SRC_ROOT)) \ # Compiler and Linker Options #---------------------------- CXXFLAGS = $(INC_DIRS) -Wall -fPIC -TEST_CXXFLAGS = $(INC_DIRS) -Wall +TEST_CXXFLAGS = $(INC_DIRS) ARFLAGS = rcs # Build Rules diff --git a/README.md b/README.md index 5846355..1488c58 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,32 @@ Opts About This Project ---------------------------------------------- +This project is an implementation of a very simple command-line options parser. +It is capable of interpreting the Unix/GNU style command line argument flags. + License ---------------------------------------------- +Unless explicitly stated otherwise, all code and documentation contained within +this repository is released under the BSD 2-Clause license. The text for this +license can be found in the LICENSE.md file. Requirements For Building ---------------------------------------------- + * GNU Make + * Unix sed and find utilities + * A C and C++ compiler such as GCC + Build Instructions ---------------------------------------------- -Installation ----------------------------------------------- +You can build the project and run all unit tests with the following command: + + make + +You can execute just the unit tests with the following command: + + make test Project Files and Directories ---------------------------------------------- @@ -31,15 +46,3 @@ Project Files and Directories LICENSE.md The software license notification. README.md You're reading this file right now! -Known Issues or Bugs ----------------------------------------------- - -Version History ----------------------------------------------- - -Feature Wish List ----------------------------------------------- - -More Info ----------------------------------------------- - diff --git a/source/opts.c b/source/opts.c index db41d0f..9c81f49 100644 --- a/source/opts.c +++ b/source/opts.c @@ -13,30 +13,31 @@ Result_T* OPTS_ParseOptions( OptionConfig_T* opts, int argc, char** argv ) // Until we run out of characters while( ctx.current != EOF ) { - // Get rid of any whitespace - while ( ' ' == ctx.current ) - { - (void)OPTS_NextCharacter( &ctx ); - } + OPTS_ConsumeWhitespace( &ctx ); // If we have an option if ( '-' == ctx.current ) { - OPTS_ParseShortOption( &ctx ); - //// And its a long one - //if( '-' == OPTS_NextCharacter( &ctx ) ) - //{ - // (void)OPTS_ParseLongOption( &ctx ); - //} - //// or a short one - //else - //{ - //} + // And it's a long one + if( '-' == OPTS_NextCharacter( &ctx ) ) + { + // Consume the second '-' + OPTS_NextCharacter( &ctx ); + + // Parse the option + OPTS_ParseLongOption( &ctx ); + } + // Or a short one + else + { + // Parse the option + OPTS_ParseShortOption( &ctx ); + } } // Or we have a floating argument else { - (void)OPTS_ParseArgument( &ctx ); + OPTS_ParseArgument( &ctx ); } } @@ -65,31 +66,80 @@ void OPTS_InitContext( StreamContext_T* ctx, int argc, char** argv ) void OPTS_ParseShortOption( StreamContext_T* ctx ) { // Get Config - // if( current != ' ' ) - // { - // Get Config - // if( config != NULL ) - // { - // Save option - // if( has arg ) - // { - // Save arg - // } - // Add option to list - // } - // else - // { - // Exit: "Unknown option: %c\n" - // } - // } + char opt_name[2] = { ctx->current, '\0' }; + OptionConfig_T* config = OPTS_GetOptConfig( ctx->options, SHORT, opt_name ); + if( config != NULL ) + { + char* opt_arg = NULL; + (void)OPTS_NextCharacter( ctx ); + if( 1 == config->has_arg ) + { + OPTS_ConsumeWhitespace( ctx ); + if( ('-' == ctx->current) || (EOF == ctx->current) ) + { + printf("Expected an argument for option '%s'\n", opt_name); + exit(1); + } + opt_arg = OPTS_NextToken( ctx ); + } + else if( (' ' != ctx->current) && (EOF != ctx->current) ) + { + OPTS_ParseShortOption( ctx ); + } + OPTS_AddOption( ctx->results, opt_name, opt_arg ); + } + else + { + printf("Unknown Option '%s'\n", opt_name); + exit(1); + } } void OPTS_ParseLongOption( StreamContext_T* ctx ) { + char* opt_name = OPTS_NextToken( ctx ); + OptionConfig_T* config = OPTS_GetOptConfig( ctx->options, LONG, opt_name ); + if( config != NULL ) + { + char* opt_arg = NULL; + if( 1 == config->has_arg ) + { + OPTS_ConsumeWhitespace( ctx ); + if( ('-' == ctx->current) || (EOF == ctx->current) ) + { + printf("Expected an argument for option '%s'\n", opt_name); + exit(1); + } + opt_arg = OPTS_NextToken( ctx ); + } + OPTS_AddOption( ctx->results, opt_name, opt_arg ); + } + else + { + printf("Unknown Option '%s'\n", opt_name); + exit(1); + } } void OPTS_ParseArgument( StreamContext_T* ctx ) { + char* arg_val = OPTS_NextToken( ctx ); + if( NULL != arg_val ) + { + Argument_T* arg = (Argument_T*)malloc( sizeof( Argument_T ) ); + arg->val = arg_val; + arg->next = NULL; + if( ctx->results->arguments->head == NULL ) + { + ctx->results->arguments->head = arg; + ctx->results->arguments->tail = arg; + } + else + { + ctx->results->arguments->tail->next = arg; + ctx->results->arguments->tail = arg; + } + } } OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char* name ) @@ -111,6 +161,8 @@ OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char* char* OPTS_NextToken( StreamContext_T* ctx ) { char* tok = NULL; + + OPTS_ConsumeWhitespace( ctx ); if ( EOF != ctx->current ) { // Setup the string @@ -119,7 +171,7 @@ char* OPTS_NextToken( StreamContext_T* ctx ) tok[1] = '\0'; (void)OPTS_NextCharacter( ctx ); - while( (EOF != ctx->current) && (' ' != ctx->current) && ('=' != ctx->current) ) + while( (EOF != ctx->current) && (' ' != ctx->current) ) { OPTS_AppendCharacter( tok, ctx->current ); (void)OPTS_NextCharacter( ctx ); @@ -128,6 +180,35 @@ char* OPTS_NextToken( StreamContext_T* ctx ) return tok; } +void OPTS_AddOption( Result_T* res, char* name, char* arg ) +{ + if( (NULL != res) && (NULL != res->options) ) + { + Option_T* opt = (Option_T*)malloc( sizeof( Option_T ) ); + opt->key = strdup( name ); + opt->val = arg; + opt->next = NULL; + if( res->options->head == NULL ) + { + res->options->head = opt; + res->options->tail = opt; + } + else + { + res->options->tail->next = opt; + res->options->tail = opt; + } + } +} + +char OPTS_ConsumeWhitespace( StreamContext_T* ctx ) +{ + while( ' ' == ctx->current ) + { + (void)OPTS_NextCharacter( ctx ); + } +} + char OPTS_NextCharacter( StreamContext_T* ctx ) { char current = EOF; @@ -139,7 +220,8 @@ char OPTS_NextCharacter( StreamContext_T* ctx ) if( ctx->line_idx < ctx->arg_count ) { - if(ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ] == '\0') + char temp = ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ]; + if( temp == '\0' ) { ctx->col_idx = -1; ctx->line_idx++; @@ -148,7 +230,8 @@ char OPTS_NextCharacter( StreamContext_T* ctx ) } else { - current = ctx->arg_vect[ ctx->line_idx ][ ctx->col_idx ]; + temp = (temp == '=') ? ' ' : temp; + current = temp; ctx->current = current; } } diff --git a/source/opts.h b/source/opts.h index 50c6bed..08dc785 100644 --- a/source/opts.h +++ b/source/opts.h @@ -65,6 +65,10 @@ OptionConfig_T* OPTS_GetOptConfig( OptionConfig_T* opts, OptionType_T typ, char* char* OPTS_NextToken( StreamContext_T* ctx ); +void OPTS_AddOption( Result_T* res, char* name, char* arg ); + +char OPTS_ConsumeWhitespace( StreamContext_T* ctx ); + char OPTS_NextCharacter( StreamContext_T* ctx ); char* OPTS_AppendCharacter( char* str, char ch ); diff --git a/tests/test_opts.cpp b/tests/test_opts.cpp index 47dc209..cd548cd 100644 --- a/tests/test_opts.cpp +++ b/tests/test_opts.cpp @@ -3,6 +3,7 @@ #include #include #include +#include // File To Test #include "opts.h" @@ -13,13 +14,28 @@ using namespace UnitTest; // Sample Option Configuration //----------------------------------------------------------------------------- OptionConfig_T Options_Config[] = { - { SHORT, "a", "test_a", 0, "A simple test option" }, - { SHORT, "b", "test_b", 1, "A simple test option" }, - { LONG, "foo", "test_d", 0, "A simple test option" }, - { LONG, "bar", "test_e", 1, "A simple test option" }, - { END, NULL, NULL, 0, NULL } + { SHORT, (char*)"a", (char*)"test_a", 0, (char*)"A simple test option" }, + { SHORT, (char*)"b", (char*)"test_b", 1, (char*)"A simple test option" }, + { LONG, (char*)"foo", (char*)"test_d", 0, (char*)"A simple test option" }, + { LONG, (char*)"bar", (char*)"test_e", 1, (char*)"A simple test option" }, + { END, (char*)NULL, (char*)NULL, 0, (char*)NULL } }; +//----------------------------------------------------------------------------- +// Global Test Variables +//----------------------------------------------------------------------------- +static int Expected_Exit_Code = -1; +static jmp_buf Exit_Point = {0}; + +//----------------------------------------------------------------------------- +// Helper Functions +//----------------------------------------------------------------------------- +void exit(int code) +{ + // Simulate an exit by longjmping to the jmp_buf with the desired error code + longjmp( Exit_Point, Expected_Exit_Code); +} + //----------------------------------------------------------------------------- // Begin Unit Tests //----------------------------------------------------------------------------- @@ -30,7 +46,7 @@ namespace { TEST(Verify_InitContext_Initializes_The_StreamContext) { StreamContext_T ctx; - char* args[] = { "-a", "b", "--foo", "--foo=bar" }; + char* args[] = { (char*)"-a", (char*)"b", (char*)"--foo", (char*)"--foo=bar" }; OPTS_InitContext( &ctx, 4, args ); CHECK( 0 == ctx.line_idx ); @@ -50,60 +66,488 @@ namespace { //------------------------------------------------------------------------- // Test ParseOptions Function //------------------------------------------------------------------------- - //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_No_Param) - //{ - // Result_T* results = NULL; - // char* args[] = { "-a" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} - - //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_A_Space) - //{ - // Result_T* results = NULL; - // char* args[] = { "-b5" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} - - //TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_No_Space) - //{ - // Result_T* results = NULL; - // char* args[] = { "-b", "5" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} - - //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_No_Param) - //{ - // Result_T* results = NULL; - // char* args[] = { "--foo" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} - - //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_A_Space) - //{ - // Result_T* results = NULL; - // char* args[] = { "--bar=5" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} - - //TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_An_Equals_Sign) - //{ - // Result_T* results = NULL; - // char* args[] = { "--bar", "5" }; - // results = OPTS_ParseOptions( Options_Config, 1, args ); - // CHECK( 0 == 1 ); - // free(results); - //} + TEST(Verify_ParseOptions_Parses_A_Short_Option_With_No_Param) + { + char* args[] = { (char*)"-a" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 1, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "a" ) ); + CHECK( NULL == result->tail->val ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_A_Space) + { + char* args[] = { (char*)"-b", (char*)"5" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 2, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "b" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "5" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Short_Option_With_A_Param_And_No_Space) + { + char* args[] = { (char*)"-b5" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 1, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "b" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "5" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Long_Option_With_No_Param) + { + char* args[] = { (char*)"--foo" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 1, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "foo" ) ); + CHECK( NULL == result->tail->val ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_A_Space) + { + char* args[] = { (char*)"--bar", (char*)"baz" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 2, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "bar" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "baz" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Long_Option_With_A_Param_And_An_Equals_Sign) + { + char* args[] = { (char*)"--bar=baz" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 1, args ); + + OptionList_T* result = results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "bar" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "baz" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseOptions_Parses_A_Floating_Argument) + { + char* args[] = { (char*)"baz1" }; + + Result_T* results = OPTS_ParseOptions( Options_Config, 1, args ); + + ArgumentList_T* result = results->arguments; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) ); + CHECK( NULL == result->tail->next ); + } + + //------------------------------------------------------------------------- + // Test ParseShortOption Function + //------------------------------------------------------------------------- + TEST(Verify_ParseShortOption_Exits_With_Error_On_Invalid_Option) + { + int exit_code = 0; + char* args[] = { (char*)"z" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + Expected_Exit_Code = 1; + exit_code = setjmp( Exit_Point ); + + if( 0 == exit_code ) + { + OPTS_ParseShortOption( &ctx ); + // If we fail to call exit then this breaks our test + CHECK( false ); + } + else + { + OptionList_T* result = ctx.results->options; + CHECK( 1 == exit_code ); + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + } + + TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_No_Arg) + { + char* args[] = { (char*)"a", (char*)"a" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 2, args ); + + OPTS_ParseShortOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "a" ) ); + CHECK( NULL == result->tail->val ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_Arg_And_No_Space) + { + char* args[] = { (char*)"b5" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseShortOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "b" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "5" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseShortOption_Parses_A_Single_Valid_Option_With_Arg_And_Space) + { + char* args[] = { (char*)"b", (char*)"5" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 2, args ); + OPTS_ParseShortOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "b" ) ); + CHECK( NULL != result->tail->val ); + CHECK( 0 == strcmp( result->tail->val, "5" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseShortOption_Exits_With_Error_When_No_Arg_Received_When_Expected) + { + int exit_code = 0; + char* args[] = { (char*)"b" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + Expected_Exit_Code = 1; + exit_code = setjmp( Exit_Point ); + + if( 0 == exit_code ) + { + OPTS_ParseShortOption( &ctx ); + // If we fail to call exit then this breaks our test + CHECK( false ); + } + else + { + OptionList_T* result = ctx.results->options; + CHECK( 1 == exit_code ); + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + } + + TEST(Verify_ParseShortOption_Parses_A_Group_Of_Valid_Options_With_No_Arg) + { + char* args[] = { (char*)"aa" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseShortOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head != result->tail ); + // First option + CHECK( NULL != result->head->key ); + CHECK( 0 == strcmp( result->head->key, "a" ) ); + CHECK( NULL == result->head->val ); + CHECK( NULL != result->head->next ); + // Second option + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "a" ) ); + CHECK( NULL == result->tail->val ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseShortOption_Parses_A_Group_Of_Valid_Options_With_The_Last_Having_An_Arg) + { + char* args[] = { (char*)"ab5" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseShortOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head != result->tail ); + // First option + CHECK( NULL != result->head->key ); + CHECK( 0 == strcmp( result->head->key, "b" ) ); + CHECK( NULL != result->head->val ); + CHECK( 0 == strcmp( result->head->val, "5" ) ); + CHECK( NULL != result->head->next ); + // Second option + CHECK( NULL != result->tail->key ); + CHECK( 0 == strcmp( result->tail->key, "a" ) ); + CHECK( NULL == result->tail->val ); + CHECK( NULL == result->tail->next ); + } + + //------------------------------------------------------------------------- + // Test ParseLongOption Function + //------------------------------------------------------------------------- + TEST(Verify_ParseLongOption_Errors_And_Exits_On_Invalid_Option) + { + int exit_code = 0; + char* args[] = { (char*)"baz" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + Expected_Exit_Code = 1; + exit_code = setjmp( Exit_Point ); + + if( 0 == exit_code ) + { + OPTS_ParseLongOption( &ctx ); + // If we fail to call exit then this breaks our test + CHECK( false ); + } + else + { + OptionList_T* result = ctx.results->options; + CHECK( 1 == exit_code ); + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + } + + TEST(Verify_ParseLongOption_Errors_And_Exits_Next_Option_Instead_Of_Argument) + { + int exit_code = 0; + char* args[] = { (char*)"bar", (char*)"--foo" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 2, args ); + Expected_Exit_Code = 1; + exit_code = setjmp( Exit_Point ); + + if( 0 == exit_code ) + { + OPTS_ParseLongOption( &ctx ); + // If we fail to call exit then this breaks our test + CHECK( false ); + } + else + { + OptionList_T* result = ctx.results->options; + CHECK( 1 == exit_code ); + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + } + + TEST(Verify_ParseLongOption_Errors_And_Exits_EOF_Instead_Of_Argument) + { + int exit_code = 0; + char* args[] = { (char*)"bar" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + Expected_Exit_Code = 1; + exit_code = setjmp( Exit_Point ); + + if( 0 == exit_code ) + { + OPTS_ParseLongOption( &ctx ); + // If we fail to call exit then this breaks our test + CHECK( false ); + } + else + { + OptionList_T* result = ctx.results->options; + CHECK( 1 == exit_code ); + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + } + + TEST(Verify_ParseLongOption_Parses_Option_With_No_Arg) + { + char* args[] = { (char*)"foo" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseLongOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( 0 == strcmp( result->head->key, (char*)"foo") ); + CHECK( NULL == result->tail->val ); + } + + TEST(Verify_ParseLongOption_Parses_Option_With_Arg_Using_Equals_Sign) + { + char* args[] = { (char*)"bar=baz" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseLongOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( 0 == strcmp( result->head->key, (char*)"bar") ); + CHECK( 0 == strcmp( result->head->val, (char*)"baz") ); + } + + TEST(Verify_ParseLongOption_Parses_Option_With_Arg_Not_Using_Equals_Sign) + { + char* args[] = { (char*)"bar", (char*)"baz" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 2, args ); + + OPTS_ParseLongOption( &ctx ); + + OptionList_T* result = ctx.results->options; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( 0 == strcmp( result->head->key, (char*)"bar") ); + CHECK( 0 == strcmp( result->head->val, (char*)"baz") ); + } + + //------------------------------------------------------------------------- + // Test ParseArgument Function + //------------------------------------------------------------------------- + TEST(Verify_ParseArgument_Does_Nothing_If_No_Tokens_Left) + { + char* args[] = { (char*)"" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseArgument( &ctx ); + + ArgumentList_T* result = ctx.results->arguments; + CHECK( NULL != result ); + CHECK( NULL == result->head ); + CHECK( NULL == result->tail ); + } + + TEST(Verify_ParseArgument_Adds_Argument_To_Empty_List) + { + char* args[] = { (char*)"baz1" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + + OPTS_ParseArgument( &ctx ); + + ArgumentList_T* result = ctx.results->arguments; + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head == result->tail ); + CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) ); + CHECK( NULL == result->tail->next ); + } + + TEST(Verify_ParseArgument_Appends_Argument_To_End_Of_List) + { + char* args[] = { (char*)"baz1" }; + StreamContext_T ctx; + ctx.options = Options_Config; + OPTS_InitContext( &ctx, 1, args ); + ArgumentList_T* result = ctx.results->arguments; + Argument_T arg = { 0 }; + result->head = &arg; + result->tail = &arg; + + OPTS_ParseArgument( &ctx ); + + CHECK( NULL != result ); + CHECK( NULL != result->head ); + CHECK( NULL != result->tail ); + CHECK( result->head != result->tail ); + CHECK( 0 == strcmp( result->tail->val, (char*)"baz1" ) ); + CHECK( NULL == result->tail->next ); + } //------------------------------------------------------------------------- // Test GetOptConfig Function @@ -111,21 +555,21 @@ namespace { TEST(Verify_GetOptConfig_Can_Retrieve_A_Short_Config_By_Name_And_Type) { OptionConfig_T* result = NULL; - result = OPTS_GetOptConfig( Options_Config, SHORT, "a" ); + result = OPTS_GetOptConfig( Options_Config, SHORT, (char*)"a" ); CHECK( result == &(Options_Config[0]) ); } TEST(Verify_GetOptConfig_Can_Retrieve_A_Long_Config_By_Name_And_Type) { OptionConfig_T* result = NULL; - result = OPTS_GetOptConfig( Options_Config, LONG, "foo" ); + result = OPTS_GetOptConfig( Options_Config, LONG, (char*)"foo" ); CHECK( result == &(Options_Config[2]) ); } TEST(Verify_GetOptConfig_Returns_Null_If_No_Config_Found) { OptionConfig_T* result = NULL; - result = OPTS_GetOptConfig( Options_Config, LONG, "baz" ); + result = OPTS_GetOptConfig( Options_Config, LONG, (char*)"baz" ); CHECK( result == NULL ); } @@ -135,17 +579,98 @@ namespace { { END, NULL, NULL, 0, NULL } }; OptionConfig_T* result = NULL; - result = OPTS_GetOptConfig( config, LONG, "foo" ); + result = OPTS_GetOptConfig( config, LONG, (char*)"foo" ); CHECK( result == NULL ); } + //------------------------------------------------------------------------- + // Test AddOption Function + //------------------------------------------------------------------------- + TEST(Verify_AddOption_Ignores_NULL_Result_Set) + { + Result_T results; + results.options = NULL; + + // If we don't segfault here I call it a win + OPTS_AddOption( NULL, NULL, NULL ); + OPTS_AddOption( &results, NULL, NULL ); + } + + TEST(Verify_AddOption_Adds_Result_To_Empty_List) + { + Result_T results; + char name[] = "foo"; + char arg[] = "bar"; + results.options = (OptionList_T*)malloc( sizeof( OptionList_T ) ); + results.options->head = NULL; + results.options->tail = NULL; + OPTS_AddOption( &results, name, arg ); + + CHECK( NULL != results.options->head ); + CHECK( NULL != results.options->tail ); + CHECK( NULL != results.options->head->key ); + CHECK( name != results.options->head->key ); + CHECK( 0 == strcmp( name, results.options->head->key ) ); + CHECK( NULL != results.options->head->val ); + CHECK( arg == results.options->head->val ); + CHECK( NULL == results.options->head->next ); + CHECK( results.options->head == results.options->tail ); + + delete results.options->head->key; + delete results.options->head; + delete results.options; + } + + TEST(Verify_AddOption_Appends_Result_To_Tail_Of_List) + { + Result_T results; + Option_T option = {0}; + char name[] = "foo"; + char arg[] = "bar"; + results.options = (OptionList_T*)malloc( sizeof( OptionList_T ) ); + results.options->head = &option; + results.options->tail = &option; + OPTS_AddOption( &results, name, arg ); + + CHECK( NULL != results.options->head ); + CHECK( NULL != results.options->tail ); + CHECK( results.options->head != results.options->tail ); + CHECK( NULL != results.options->tail->key ); + CHECK( name != results.options->tail->key ); + CHECK( 0 == strcmp( name, results.options->tail->key ) ); + CHECK( NULL != results.options->tail->val ); + CHECK( arg == results.options->tail->val ); + CHECK( NULL == results.options->tail->next ); + + delete results.options->tail->key; + delete results.options->tail; + delete results.options; + } + + //------------------------------------------------------------------------- + // Test ConsumeWhitespaace Function + //------------------------------------------------------------------------- + TEST(Verify_ConsumeWhitespace_Removes_Whitespace_From_The_Stream_Till_The_Next_Token) + { + char* args[] = { (char*)" " }; + StreamContext_T ctx; + ctx.line_idx = 0; + ctx.col_idx = -1; + ctx.arg_count = 1; + ctx.arg_vect = args; + (void)OPTS_NextCharacter( &ctx ); + + OPTS_ConsumeWhitespace( &ctx ); + CHECK( EOF == OPTS_NextCharacter( &ctx ) ); + } + //------------------------------------------------------------------------- // Test NextToken Function //------------------------------------------------------------------------- TEST(Verify_NextToken_Properly_Tokenizes_Stream) { char* token = NULL; - char* args[] = { "-a", "b", "--foo", "--foo=bar" }; + char* args[] = { (char*)"-a", (char*)"b", (char*)"--foo", (char*)"--foo=bar" }; StreamContext_T ctx; ctx.line_idx = 0; ctx.col_idx = -1; @@ -154,27 +679,27 @@ namespace { (void)OPTS_NextCharacter( &ctx ); token = OPTS_NextToken( &ctx ); - CHECK( 0 == strcmp( token, "-a" ) ); + CHECK( 0 == strcmp( token, (char*)"-a" ) ); free( token ); (void)OPTS_NextCharacter( &ctx ); token = OPTS_NextToken( &ctx ); - CHECK( 0 == strcmp( token, "b" ) ); + CHECK( 0 == strcmp( token, (char*)"b" ) ); free( token ); (void)OPTS_NextCharacter( &ctx ); token = OPTS_NextToken( &ctx ); - CHECK( 0 == strcmp( token, "--foo" ) ); + CHECK( 0 == strcmp( token, (char*)"--foo" ) ); free( token ); (void)OPTS_NextCharacter( &ctx ); token = OPTS_NextToken( &ctx ); - CHECK( 0 == strcmp( token, "--foo" ) ); + CHECK( 0 == strcmp( token, (char*)"--foo" ) ); free( token ); (void)OPTS_NextCharacter( &ctx ); token = OPTS_NextToken( &ctx ); - CHECK( 0 == strcmp( token, "bar" ) ); + CHECK( 0 == strcmp( token, (char*)"bar" ) ); free( token ); } @@ -183,7 +708,7 @@ namespace { //------------------------------------------------------------------------- TEST(Verify_NextCharacter_Can_Loop_Through_Characters) { - char* args[] = { "abc", "123" }; + char* args[] = { (char*)"abc", (char*)"123=4" }; StreamContext_T ctx; ctx.line_idx = 0; ctx.col_idx = -1; @@ -206,6 +731,10 @@ namespace { CHECK( '3' == ctx.current ); CHECK( ' ' == OPTS_NextCharacter( &ctx ) ); CHECK( ' ' == ctx.current ); + CHECK( '4' == OPTS_NextCharacter( &ctx ) ); + CHECK( '4' == ctx.current ); + CHECK( ' ' == OPTS_NextCharacter( &ctx ) ); + CHECK( ' ' == ctx.current ); CHECK( EOF == OPTS_NextCharacter( &ctx ) ); CHECK( EOF == ctx.current ); CHECK( EOF == OPTS_NextCharacter( &ctx ) ); @@ -237,6 +766,6 @@ namespace { str[1] = '\0'; str = OPTS_AppendCharacter( str, 'b' ); - CHECK( 0 == strcmp( str, "ab" ) ); + CHECK( 0 == strcmp( str, (char*)"ab" ) ); } }