Add example how to remove doctest options from the command line for the program after the tests run #20

Closed
martinmoene opened this Issue Jun 3, 2016 · 5 comments

Projects

None yet

2 participants

@martinmoene
Contributor

When test code is embedded with the application, the application code would preferably not have to deal with commandline options that belong to the test code. See example below.

Currently there seems to be no provision to only provide the application code with the non-doctest options.

#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"

int program(int argc, char** argv);

int main(int argc, char** argv) 
{
    doctest::Context context;  

    context.setOption("--exit", false); // continue after tests, unless --exit

    context.applyCommandLine(argc, argv);

    int test_result = context.run();    // run queries, or run tests unless --no-run

    if (context.shouldExit())           // honor query flags and --exit
        return test_result;

    int app_result = program(argc, argv);

    return test_result + app_result;
}

TEST_CASE("Fail") { REQUIRE(0); }
TEST_CASE("Pass") { REQUIRE(1); }

int program(int argc, char** argv) 
{
    printf("\nProgram code, %sarguments received:\n", argc > 1 ? "":"no ");
    while ( *++argv )
        printf("%s\n", *argv);
    return EXIT_SUCCESS;
}

Compile and run:

prompt> g++ -Wall -Wextra -o main.exe main.cpp && main.exe --no-colors=true --my-option=abc 123
[doctest] doctest version is "1.0.0"
[doctest] run with "--help" for options
===============================================================================
main.cpp(44)
Fail

main.cpp(44) FAILED!
  REQUIRE( 0 )
with expansion:
  REQUIRE( 0 )

===============================================================================
[doctest] test cases:    2 |    1 passed |    1 failed |    0 skipped
[doctest] assertions:    2 |    1 passed |    1 failed |

Program code, arguments received:
--no-colors=true
--my-option=abc
123
@martinmoene martinmoene changed the title from Howto to use commandline options in application code with embedded tests ? to How to to use commandline options in application code with embedded tests ? Jun 3, 2016
@martinmoene martinmoene changed the title from How to to use commandline options in application code with embedded tests ? to How to use commandline options in application code with embedded tests ? Jun 3, 2016
@onqtam
Owner
onqtam commented Jun 3, 2016

Well my idea was for all doctest related options to have also a prefixed version with -dt- so user code can easily distinguish between user options and doctest options. That is also the reason why option values are not separated from the option like --no-colors true but are packed with an equals sign - --no-colors=true.

In the case where the user wants to supply a --version flag but it will clash with doctest - then the DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS identifier should be used to remove all unprefixed versions from doctest and only the prefixed forms - like --dt-version will be parsed by doctest.

If you have a better idea - I'm all ears. Perhaps doctest could also provide argc/argv without doctest-related options after it parses the original argc/argv? But I'm not fond of this idea that much...

@martinmoene
Contributor
martinmoene commented Jun 3, 2016 edited

Depending on the commandline processor someone uses, the presence of --dt-* options in the application part may or may not be acceptable. That option processor may have to accept the --dt-* options and consequently ignore them to prevent 'flagging' them as an error.

This may not necessarily be something doctest should solve, but an example how such a case might be handled in application code would be helpful (See below).

Aside: You may consider adding an option --dt-- (equivalent to --) to explicitly end the doctest options. Then you can have application options with the same name as doctest options without requiring to remove all non-dt--prefixed doctest options, for example: prog --dt-- --version. (Edit: non- added.)

Example of application code that is shielded from the doctest options:

#define DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"

#include <vector>

int program(int argc, char** argv);

bool starts_with(std::string text, std::string with)
{
    return with == text.substr(0, with.length());
}

std::vector<char *> dt_removed(char** argv)
{
    std::vector<char *> vec;

    for ( ; *argv; ++argv )
    {
        if ( !starts_with(*argv, "--dt-"))
        {
            vec.push_back(*argv);
        }
    }
    vec.push_back(NULL); // C++11: nullptr

    return vec;
}

int main(int argc, char** argv) 
{
    doctest::Context context;  

    context.setOption("--exit", false); // continue after tests, unless --exit

    context.applyCommandLine(argc, argv);

    int test_result = context.run();    // run queries, or run tests unless --no-run

    if (context.shouldExit())           // honor query flags and --exit
        return test_result;

    std::vector<char *> args = dt_removed(argv);    // C++11: auto args...
    int app_result = program(args.size(), args.data());

    return test_result + app_result;
}

TEST_CASE("Fail") { REQUIRE(0); }
TEST_CASE("Pass") { REQUIRE(1); }

int program(int argc, char** argv) 
{
    printf("\nProgram code, %sarguments received:\n", argc > 1 ? "":"no ");
    while ( *++argv )
        printf("%s\n", *argv);
    return EXIT_SUCCESS;
}

Edit: In int app_result = program(args.size(), args.data()); data() is C++11, replace with &args[0] for pre-C++11. /Edit.

Compile and run:

prompt> g++ -std=c++98 -Wall -o main.exe main.cpp && main.exe --dt-no-colors=true --version --my-option=abc 123

[doctest] doctest version is "1.0.0"
[doctest] run with "--help" for options
===============================================================================
main.cpp(68)
Fail

main.cpp(68) FAILED!
  REQUIRE( 0 )
with expansion:
  REQUIRE( 0 )

===============================================================================
[doctest] test cases:    2 |    1 passed |    1 failed |    0 skipped
[doctest] assertions:    2 |    1 passed |    1 failed |

Program code, arguments received:
--version
--my-option=abc
123
@onqtam
Owner
onqtam commented Jun 3, 2016

thanks! will add as an example

@martinmoene
Contributor

mmm, before you do: there's a little snatch: because of the trailing null argument, args.size() is one higher than argc must be.

Corrected:

int app_result = program(args.size() - 1, &args[0]);

Perhaps ok for a barebones example to show how it can be done; maybe nicer to wrap it into a class of its own (will try).

@martinmoene
Contributor

Something like:

// Provide commandline processor of application with 
// embedded tests without doctest options.

#define DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"

#include <string>
#include <vector>

int program(int argc, char** argv);

class dt_removed
{
public:
    dt_removed(char** argv)
    {
        for ( ; *argv; ++argv )
        {
            if ( !starts_with(*argv, "--dt-"))
            {
                vec.push_back(*argv);
            }
        }
        vec.push_back(NULL); // C++11: nullptr
    }

    // Note: non-const char **:
    int     argc() { return vec.size() - 1; }
    char ** argv() { return &vec[0]; }

private:
    static bool starts_with(std::string text, std::string with)
    {
        return with == text.substr(0, with.length());
    }

private:
    std::vector<char *> vec;
};

int main(int argc, char** argv) 
{
    doctest::Context context;  

    context.setOption("--exit", false); // continue after tests, unless --exit

    context.applyCommandLine(argc, argv);

    int test_result = context.run();    // run queries, or run tests unless --no-run

    if (context.shouldExit())           // honor query flags and --exit
        return test_result;

    dt_removed args(argv);
    int app_result = program(args.argc(), args.argv());

    return test_result + app_result;
}

TEST_CASE("Fail") { REQUIRE(0); }
TEST_CASE("Pass") { REQUIRE(1); }

int program(int argc, char** argv) 
{
    printf("\nProgram '%s': %d arguments received:\n", *argv, argc - 1);
    while ( *++argv )
        printf("'%s'\n", *argv);
    return EXIT_SUCCESS;
}

// cl -EHsc main.cpp && main.exe --dt-no-colors=true  --version --my-option="abc def" 123
// g++ -std=c++98 -Wall -Wextra -o main.exe main.cpp && main.exe --dt-no-colors=true --version --my-option="abc def" 123

Compile and run:

prompt> g++ -std=c++98 -Wall -Wextra -o main.exe main.cpp && main.exe --dt-no-colors=true --version --my-option="abc def" 123
[doctest] doctest version is "1.0.0"
[doctest] run with "--help" for options
===============================================================================
main.cpp(61)
Fail

main.cpp(61) FAILED!
  REQUIRE( 0 )
with expansion:
  REQUIRE( 0 )

===============================================================================
[doctest] test cases:    2 |    1 passed |    1 failed |    0 skipped
[doctest] assertions:    2 |    1 passed |    1 failed |

Program 'main.exe': 3 arguments received:
'--version'
'--my-option=abc def'
'123'
@onqtam onqtam changed the title from How to use commandline options in application code with embedded tests ? to Add example how to remove doctest options from the command line for the program after the tests run Sep 19, 2016
@onqtam onqtam added a commit that referenced this issue Sep 19, 2016
@onqtam Added example how to remove doctest options from the command line for…
… the program after the tests run - fixes #20
51a7e12
@onqtam onqtam added a commit that closed this issue Sep 21, 2016
@onqtam Added example how to remove doctest options from the command line for…
… the program after the tests run - fixes #20
a1ede61
@onqtam onqtam closed this in a1ede61 Sep 21, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment