Testing Argp

1. Intro
1.1. Argp
1.2. Testing
2. Concept
3. Code
3.1. License
3.2. Full Text
3.3. Tests
4. See also

tropf

ABSTRACT

presentation of short helper to more easily test argp

1. Intro

1.1. Argp

Argp is one of the more advanced parsers for command-line arguments out there. As it is part of glibc, it is available on all of the major distributions.

You only see it rarely, as most applications don’t care too much about the output of --help or move large parts of it into their man page. And as Argp is a C-Library most C++-devs don’t fancy it too much.

Once set up it’s usage is very straight forward, just pass argc and argv directly into Argp and everything will be handled.

1.2. Testing

When building applications I often spend a large part of my time checking that the configuration works properly. Part of that is testing that command-line arguments are parsed correctly and are associated to their internal variables.

Argp, however, has fairly high (and strange) demands for its inputs. First of all, it requires the typical format of argc and argv: While the former is just an integer, the latter is an array of pointers to c-style strings, i.e. null-terminated char arrays. Also the memory that argv is pointing to might be overwritten during Argp’s operation, so it has to be re-initialized for each test case. Finally, the memory must not be on the heap. This means, argv may only point to regions of the stack, otherwise there will be malloc-errors.

2. Concept

The helper class below accepts a string. This will be exploded into a list of words. From there, all words will be stored consecutively separated by null bytes.

Realtabs are not regarded as separator!

All data resides on the stack, thus there is a limit for the total number of characters stored (4096 bytes including separator bytes) and a maximum number of arguments (31).

3. Code

For usage please have a look at the test code.

argv[0] has to be intialized with the binary name when using w/ argp.

3.1. License

This code is licensed under CC0 (public domain).

3.2. Full Text

download

› Due to fun quirks of groff, any ' in the code will be displayed as (and produces a syntax error when compiled). Just download the files directly.

#include <vector>
#include <string>
#include <sstream>
#include <cstring>

/**
 * argp requires an authentic argc & argv as in a main method, and also for that memory to be writable
 * this class can create that out of thin air
 */
class argv_helper {
private:
    /// max memory used to store args
    static const size_t MAX_ARGS_MEM = 4096;
    /// max number of args
    static const size_t MAX_ARGC = 32;

    /// memory for actual args
    char args_stack[MAX_ARGS_MEM];
    /// memory for pointers to strings
    char* argv_stack[MAX_ARGC];

public:
    /**
     * init w/ a list of args separated by a SINGLE space.
     * Note: is initialized on the stack, because argp doesn’t work w/ args on the heap
     * @param s "--arg value --arg2=value"
     */
    argv_helper(const std::string& s) {
        std::vector<std::string> args;
        size_t full_length = 0;
        std::stringstream given_args_stream(s);
        for (std::string arg; getline(given_args_stream, arg, ’ ’);) {
            if (arg.empty()) {
                continue;
            }
            args.push_back(arg);
            full_length += args.size();
        }

        // argv: array of args, separated by nullbytes
        argc = args.size();
        argv = argv_stack;

        if (argc > MAX_ARGC || full_length > MAX_ARGS_MEM) {
            argc = 0;
            return;
        }

        // copy strings
        size_t cursor = 0;
        for (size_t i = 0; i < args.size(); i++) {
            std::strcpy(args_stack + cursor, args[i].c_str());
            argv_stack[i] = args_stack + cursor;
            cursor += args[i].size() + 1;
        }
        argv_stack[argc] = nullptr;
    }

    int argc;
    char** argv;
};

3.3. Tests

download

#define CATCH_CONFIG_MAIN
#include <catch.hpp>

#include <argv_helper.cc>

#include <string>

TEST_CASE("argv_helper") {
    SECTION("empty string") {
        REQUIRE_NOTHROW(argv_helper(""));
    }

    SECTION("argc correct") {
        argv_helper h("one two three 4");
        REQUIRE(4 == h.argc);
        REQUIRE(h.argv[h.argc] == nullptr);
    }

    SECTION("simple example") {
        argv_helper h("hello world");
        REQUIRE(’h’ == h.argv[0][0]);
        REQUIRE(’e’ == h.argv[0][1]);
        REQUIRE(’l’ == h.argv[0][2]);
        REQUIRE(’l’ == h.argv[0][3]);
        REQUIRE(’o’ == h.argv[0][4]);
        REQUIRE(0x0 == h.argv[0][5]);

        REQUIRE(’w’ == h.argv[1][0]);
        REQUIRE(’o’ == h.argv[1][1]);
        REQUIRE(’r’ == h.argv[1][2]);
        REQUIRE(’l’ == h.argv[1][3]);
        REQUIRE(’d’ == h.argv[1][4]);
        REQUIRE(0x0 == h.argv[1][5]);

        REQUIRE(’w’ == h.argv[0][6]);
        REQUIRE(’o’ == h.argv[0][7]);
        REQUIRE(’r’ == h.argv[0][8]);
        REQUIRE(’l’ == h.argv[0][9]);
        REQUIRE(’d’ == h.argv[0][10]);
        REQUIRE(0x0 == h.argv[0][11]);
    }

    SECTION("ignore multiple spaces") {
        argv_helper h(" one  two   three   ");
        REQUIRE(3 == h.argc);
    }


    SECTION("complex example") {
        argv_helper h("-nt1 --tasks=2 -- bla");
        REQUIRE(4 == h.argc);
        REQUIRE("-nt1" == std::string(h.argv[0]));
        REQUIRE("--tasks=2" == std::string(h.argv[1]));
        REQUIRE("--" == std::string(h.argv[2]));
        REQUIRE("bla" == std::string(h.argv[3]));
    }
}

4. See also


18 April 2021
Home