tropf
ABSTRACT
presentation of short helper to more easily test 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.
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.
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).
For usage please have a look at the test code.
› argv[0] has to be intialized with the binary name when using w/ argp.
This code is licensed under CC0 (public domain).
› 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; };
#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])); } }
18 April
2021
Home