Compare commits

...

30 Commits

Author SHA1 Message Date
Nigel Stewart
70fb4e4814
Extend cxxopts API to support as_optional in addition to as query of values (#421)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2024-02-29 20:27:19 +11:00
Jarryd Beck
4bf61f0869 Add changes for 3.2.1 2024-02-20 19:39:52 +11:00
jarro2783
1e175bc1db
Fix ordering of parse_value functions (#420)
Fixes #419. Put the definitions of parse_value functions before they
are called so that the right ones are chosen.
2024-02-20 19:38:44 +11:00
jarro2783
3bf268481d
Make cxxopts.hpp ready for 3.2
Update version number in header, ready for 3.2
2024-02-15 16:24:38 +11:00
jarro2783
f7194e9c59
Update CHANGELOG.md
Make changelog ready for 3.2
2024-02-15 16:23:58 +11:00
jarro2783
71d5bf7265
Update CHANGELOG.md 2024-02-15 16:23:15 +11:00
jarro2783
d3de64f26e
Update CHANGELOG.md 2024-02-15 16:21:30 +11:00
ololuki
e84ab5f67c
Fix overflow error in integer_parser. (#417)
Fixes #290.
Checking for overflow should be done before integer overflows.
There are two checks:
(result > limit / base) is used for limits greater than rounded up to base,
e.g. for 65535 it will activate for 65540 and higher.
(result * base > limit - digit) is used for limit+1 to limit+n below
next base rounded number, e.g. 65536 up to 65539.
2024-02-01 16:51:32 +11:00
Jarryd Beck
7bf29108d5 Update catch2 to latest 2.x 2024-01-15 21:04:41 +11:00
Nigel Stewart
cd61c685eb
Ordered vector of group names to preserve order of groups (#416)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2024-01-15 20:52:54 +11:00
Blake-Madden
554396be3b
Minor typo in README.md (#411)
Fix grammar in README
2023-11-25 11:49:16 +11:00
Jarryd Beck
78b90d8f0c Remove incorrect sentence in README
Fixes #410. Removes sentence about ParseResult scope that is no longer correct.
2023-11-13 16:49:18 +11:00
Jarryd Beck
c8c932f891 Add CHANGELOG for #398 2023-06-15 07:35:14 +10:00
Alexander Galanin
ddc695ebac
Don't split by list delimiter in positional lists (#398) 2023-06-15 07:34:26 +10:00
Jarryd Beck
bf1b5a96e0 Update changelog 2023-06-15 07:18:53 +10:00
Jarryd Beck
90b318105f Fix unannoted fallthrough
Fixes #402.
2023-06-15 07:18:30 +10:00
Maximilian Knespel
a526762eb8
[fix] Avoid std::regex initialization during dlopen (#406) 2023-05-31 07:26:34 +10:00
Jarryd Beck
beda973ec6 Remove incorrect todo.
Fixes #404.
2023-05-22 17:31:17 +10:00
Julien Voisin
714a105fe6
Bump rules_fuzzing to the latest available version (#403) 2023-05-01 06:45:55 +10:00
Nigel Stewart
16cc254919
Output same exception messages for both Linux and Windows (#395)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2023-04-03 17:15:01 +10:00
Jarryd Beck
120205ac5a Fix cast warning in Unicode function
Fixes #339. Fixes a sign conversion warning in a function compiled by
-DCXXOPTS_USE_UNICODE_HELP=1.
2023-03-15 07:09:09 +11:00
Jarryd Beck
89a9d334f2 Add note about exceptions in Changelog 2023-03-13 07:52:48 +11:00
Nathaniel Brough
44739d3023
feat(fuzz): Adds trigger for fuzzing in the CI (#392) 2023-02-22 07:03:38 +11:00
Demian Hespe
134f60f973
Fix noexcept warning (#391) 2023-02-20 07:25:13 +11:00
Jarryd Beck
eb787304d6 Fix version number 2023-02-16 07:02:16 +11:00
Jarryd Beck
c12ce65503 Update CHANGELOG for 3.1 release 2023-02-13 07:31:30 +11:00
Jarryd Beck
b80bdfddf0 Added missing CHANGELOG entry 2023-02-13 07:31:30 +11:00
Martijn Courteaux
799ee0e3e8
Add <locale> to be spec-compliant for std::isalnum (didn't work on MSVC). (#385) 2023-02-11 15:47:48 +11:00
Nathaniel Brough
58daccc945
feat(fuzzer): Adds fuzz tests (#386) 2023-02-06 07:30:51 +11:00
Jarryd Beck
e1f8c16702 Fix diagnostics pop error 2023-02-02 07:25:12 +11:00
11 changed files with 16714 additions and 8831 deletions

24
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: CIFuzz
on: [pull_request]
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'cxxopts'
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'cxxopts'
language: c++
fuzz-seconds: 300
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@ -20,11 +20,9 @@ jobs:
build-ubuntu:
strategy:
matrix:
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ]
os: [ ubuntu-20.04, ubuntu-22.04 ]
compiler: [ g++-9, g++-10, clang++ ]
include:
- os: ubuntu-18.04
compiler: g++-7
name: Build and Test on Ubuntu
runs-on: ${{matrix.os}}
steps:

View File

@ -6,3 +6,11 @@ cc_library(
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
cc_fuzz_test(
name = "cxxopts_fuzz_test",
srcs = ["test/fuzz.cpp"],
deps = [":cxxopts"],
)

View File

@ -3,17 +3,51 @@
This is the changelog for `cxxopts`, a C++11 library for parsing command line
options. The project adheres to semantic versioning.
## Unreleased
## 3.2.1
### Bug fixes
* Fix compilation with optional on C++20.
## 3.2
### Bug fixes
* Fix unannotated fallthrough.
* Fix sign conversion with Unicode output.
* Don't initialize regex in static initialiser.
* Fix incorrect integer overflow checks.
### Added
* Add fuzzing to CI
### Changed
* Change quote output to '' to match Windows.
* Don't split positional arguments by the list delimiter.
* Order help groups by the order they were added.
## 3.1.1
### Bug Fixes
* Fixed version number in header.
* Fixed cast warning in Unicode function.
## 3.1
### Added
* Support for multiple long names for the same option (= multiple long aliases)
* Add a `program()` function to retrieve the program name.
* Added a .clang-format file.
* Added iterator and printing for a ParseResult.
### Changed
* Cleanup exception code, add cxxopts::exceptions namespace.
* Renamed several exceptions to be more descriptive, and added to a nested namespace.
### Bug Fixes

View File

@ -85,9 +85,6 @@ result["opt"].as<type>()
to get its value. If "opt" doesn't exist, or isn't of the right type, then an
exception will be thrown.
Note that the result of `options.parse` should only be used as long as the
`options` object that created it is in scope.
## Unrecognised arguments
You can allow unrecognised arguments to be skipped. This applies to both
@ -196,8 +193,8 @@ therefore, `-o false` does not work.
## `std::vector<T>` values
Parsing of list of values in form of an `std::vector<T>` is also supported, as long as `T`
can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER`
Parsing a list of values into a `std::vector<T>` is also supported, as long as `T`
can be parsed. To separate single values in a list the define symbol `CXXOPTS_VECTOR_DELIMITER`
is used, which is ',' by default. Ensure that you use no whitespaces between values because
those would be interpreted as the next command line option. Example for a command line option
that can be parsed as a `std::vector<double>`:
@ -278,7 +275,3 @@ GCC >= 4.9 or clang >= 3.1 with libc++ are known to work.
The following compilers are known not to work:
* MSVC 2013
# TODO list
* Allow unrecognised options.

View File

@ -0,0 +1,16 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_fuzzing",
sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118",
strip_prefix = "rules_fuzzing-0.3.2",
urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"],
)
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
rules_fuzzing_dependencies()
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
rules_fuzzing_init()

View File

@ -27,6 +27,7 @@ THE SOFTWARE.
#ifndef CXXOPTS_HPP_INCLUDED
#define CXXOPTS_HPP_INCLUDED
#include <cstdlib>
#include <cstring>
#include <exception>
#include <limits>
@ -40,6 +41,7 @@ THE SOFTWARE.
#include <utility>
#include <vector>
#include <algorithm>
#include <locale>
#ifdef CXXOPTS_NO_EXCEPTIONS
#include <iostream>
@ -54,8 +56,8 @@ THE SOFTWARE.
#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
#define CXXOPTS_LINKONCE __declspec(selectany) extern
#else
#define CXXOPTS_LINKONCE_CONST
#define CXXOPTS_LINKONCE
#define CXXOPTS_LINKONCE_CONST
#define CXXOPTS_LINKONCE
#endif
#ifndef CXXOPTS_NO_REGEX
@ -72,6 +74,14 @@ THE SOFTWARE.
# endif
#endif
#define CXXOPTS_FALLTHROUGH
#ifdef __has_cpp_attribute
#if __has_cpp_attribute(fallthrough)
#undef CXXOPTS_FALLTHROUGH
#define CXXOPTS_FALLTHROUGH [[fallthrough]]
#endif
#endif
#if __cplusplus >= 201603L
#define CXXOPTS_NODISCARD [[nodiscard]]
#else
@ -83,8 +93,8 @@ THE SOFTWARE.
#endif
#define CXXOPTS__VERSION_MAJOR 3
#define CXXOPTS__VERSION_MINOR 0
#define CXXOPTS__VERSION_PATCH 0
#define CXXOPTS__VERSION_MINOR 2
#define CXXOPTS__VERSION_PATCH 1
#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
#define CXXOPTS_NULL_DEREF_IGNORE
@ -229,10 +239,10 @@ stringAppend(String& s, Iterator begin, Iterator end)
}
inline
std::size_t
size_t
stringLength(const String& s)
{
return s.length();
return static_cast<size_t>(s.length());
}
inline
@ -336,13 +346,8 @@ empty(const std::string& s)
namespace cxxopts {
namespace {
#ifdef _WIN32
CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
#else
CXXOPTS_LINKONCE_CONST std::string LQUOTE("");
CXXOPTS_LINKONCE_CONST std::string RQUOTE("");
#endif
} // namespace
// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
@ -364,6 +369,9 @@ class Value : public std::enable_shared_from_this<Value>
std::shared_ptr<Value>
clone() const = 0;
virtual void
add(const std::string& text) const = 0;
virtual void
parse(const std::string& text) const = 0;
@ -757,29 +765,31 @@ inline ArguDesc ParseArgument(const char *arg, bool &matched)
namespace {
CXXOPTS_LINKONCE
std::basic_regex<char> integer_pattern
("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
const char* const integer_pattern =
"(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
CXXOPTS_LINKONCE
std::basic_regex<char> truthy_pattern
("(t|T)(rue)?|1");
const char* const truthy_pattern =
"(t|T)(rue)?|1";
CXXOPTS_LINKONCE
std::basic_regex<char> falsy_pattern
("(f|F)(alse)?|0");
const char* const falsy_pattern =
"(f|F)(alse)?|0";
CXXOPTS_LINKONCE
std::basic_regex<char> option_matcher
("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)");
const char* const option_pattern =
"--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
CXXOPTS_LINKONCE
std::basic_regex<char> option_specifier
("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
const char* const option_specifier_pattern =
"([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
CXXOPTS_LINKONCE
std::basic_regex<char> option_specifier_separator(", *");
const char* const option_specifier_separator_pattern = ", *";
} // namespace
inline IntegerDesc SplitInteger(const std::string &text)
{
static const std::basic_regex<char> integer_matcher(integer_pattern);
std::smatch match;
std::regex_match(text, match, integer_pattern);
std::regex_match(text, match, integer_matcher);
if (match.length() == 0)
{
@ -803,15 +813,17 @@ inline IntegerDesc SplitInteger(const std::string &text)
inline bool IsTrueText(const std::string &text)
{
static const std::basic_regex<char> truthy_matcher(truthy_pattern);
std::smatch result;
std::regex_match(text, result, truthy_pattern);
std::regex_match(text, result, truthy_matcher);
return !result.empty();
}
inline bool IsFalseText(const std::string &text)
{
static const std::basic_regex<char> falsy_matcher(falsy_pattern);
std::smatch result;
std::regex_match(text, result, falsy_pattern);
std::regex_match(text, result, falsy_matcher);
return !result.empty();
}
@ -820,22 +832,25 @@ inline bool IsFalseText(const std::string &text)
// (without considering which or how many are single-character)
inline OptionNames split_option_names(const std::string &text)
{
if (!std::regex_match(text.c_str(), option_specifier))
static const std::basic_regex<char> option_specifier_matcher(option_specifier_pattern);
if (!std::regex_match(text.c_str(), option_specifier_matcher))
{
throw_or_mimic<exceptions::invalid_option_format>(text);
}
OptionNames split_names;
static const std::basic_regex<char> option_specifier_separator_matcher(option_specifier_separator_pattern);
constexpr int use_non_matches { -1 };
auto token_iterator = std::sregex_token_iterator(
text.begin(), text.end(), option_specifier_separator, use_non_matches);
text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches);
std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
return split_names;
}
inline ArguDesc ParseArgument(const char *arg, bool &matched)
{
static const std::basic_regex<char> option_matcher(option_pattern);
std::match_results<const char*> result;
std::regex_match(arg, result, option_matcher);
matched = !result.empty();
@ -958,13 +973,26 @@ integer_parser(const std::string& text, T& value)
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
const US next = static_cast<US>(result * base + digit);
if (result > next)
US limit = 0;
if (negative)
{
limit = static_cast<US>(std::abs(static_cast<intmax_t>(std::numeric_limits<T>::min())));
}
else
{
limit = std::numeric_limits<T>::max();
}
if (base != 0 && result > limit / base)
{
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
if (result * base > limit - digit)
{
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
result = next;
result = static_cast<US>(result * base + digit);
}
detail::check_signed_range<T>(negative, result, text);
@ -1034,25 +1062,6 @@ parse_value(const std::string& text, T& value) {
stringstream_parser(text, value);
}
template <typename T>
void
parse_value(const std::string& text, std::vector<T>& value)
{
if (text.empty()) {
T v;
parse_value(text, v);
value.emplace_back(std::move(v));
return;
}
std::stringstream in(text);
std::string token;
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v;
parse_value(token, v);
value.emplace_back(std::move(v));
}
}
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
void
@ -1075,6 +1084,41 @@ void parse_value(const std::string& text, char& c)
c = text[0];
}
template <typename T>
void
parse_value(const std::string& text, std::vector<T>& value)
{
if (text.empty()) {
T v;
parse_value(text, v);
value.emplace_back(std::move(v));
return;
}
std::stringstream in(text);
std::string token;
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v;
parse_value(token, v);
value.emplace_back(std::move(v));
}
}
template <typename T>
void
add_value(const std::string& text, T& value)
{
parse_value(text, value);
}
template <typename T>
void
add_value(const std::string& text, std::vector<T>& value)
{
T v;
add_value(text, v);
value.emplace_back(std::move(v));
}
template <typename T>
struct type_is_container
{
@ -1126,6 +1170,12 @@ class abstract_value : public Value
m_implicit_value = rhs.m_implicit_value;
}
void
add(const std::string& text) const override
{
add_value(text, *m_store);
}
void
parse(const std::string& text) const override
{
@ -1411,6 +1461,19 @@ struct HelpGroupDetails
class OptionValue
{
public:
void
add
(
const std::shared_ptr<const OptionDetails>& details,
const std::string& text
)
{
ensure_value(details);
++m_count;
m_value->add(text);
m_long_names = &details->long_names();
}
void
parse
(
@ -1475,6 +1538,15 @@ CXXOPTS_DIAGNOSTIC_POP
return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
}
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
std::optional<T>
as_optional() const
{
return as<T>();
}
#endif
private:
void
ensure_value(const std::shared_ptr<const OptionDetails>& details)
@ -1497,7 +1569,7 @@ CXXOPTS_DIAGNOSTIC_POP
class KeyValue
{
public:
KeyValue(std::string key_, std::string value_)
KeyValue(std::string key_, std::string value_) noexcept
: m_key(std::move(key_))
, m_value(std::move(value_))
{
@ -1550,7 +1622,8 @@ class ParseResult
Iterator(const Iterator&) = default;
// GCC complains about m_iter not being initialised in the member
// initializer list
// initializer list
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Weffc++")
Iterator(const ParseResult *pr, bool end=false)
: m_pr(pr)
@ -1686,6 +1759,24 @@ CXXOPTS_DIAGNOSTIC_POP
return viter->second;
}
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
std::optional<T>
as_optional(const std::string& option) const
{
auto iter = m_keys.find(option);
if (iter != m_keys.end())
{
auto viter = m_values.find(iter->second);
if (viter != m_values.end())
{
return viter->second.as_optional<T>();
}
}
return std::nullopt;
}
#endif
const std::vector<KeyValue>&
arguments() const
{
@ -1780,7 +1871,7 @@ class OptionParser
);
void
add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg);
void
parse_option
@ -1983,6 +2074,7 @@ class Options
std::unordered_set<std::string> m_positional_set{};
//mapping from groups to help options
std::vector<std::string> m_group{};
std::map<std::string, HelpGroupDetails> m_help{};
};
@ -2235,6 +2327,7 @@ OptionAdder::operator()
case 1:
short_name = *first_short_name_iter;
option_names.erase(first_short_name_iter);
CXXOPTS_FALLTHROUGH;
case 0:
break;
default:
@ -2326,9 +2419,13 @@ OptionParser::checked_parse_arg
inline
void
OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
OptionParser::add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg)
{
parse_option(iter->second, option, arg);
auto hash = value->hash();
auto& result = m_parsed[hash];
result.add(value, arg);
m_sequential.emplace_back(value->essential_name(), arg);
}
inline
@ -2345,14 +2442,14 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n
auto& result = m_parsed[iter->second->hash()];
if (result.count() == 0)
{
add_to_option(iter, *next, a);
add_to_option(iter->second, a);
++next;
return true;
}
++next;
continue;
}
add_to_option(iter, *next, a);
add_to_option(iter->second, a);
return true;
}
throw_or_mimic<exceptions::no_such_option>(*next);
@ -2616,6 +2713,12 @@ Options::add_option
}
//add the help details
if (m_help.find(group) == m_help.end())
{
m_group.push_back(group);
}
auto& options = m_help[group];
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
@ -2747,19 +2850,7 @@ inline
void
Options::generate_all_groups_help(String& result) const
{
std::vector<std::string> all_groups;
std::transform(
m_help.begin(),
m_help.end(),
std::back_inserter(all_groups),
[] (const std::map<std::string, HelpGroupDetails>::value_type& group)
{
return group.first;
}
);
generate_group_help(result, all_groups);
generate_group_help(result, m_group);
}
inline
@ -2799,19 +2890,7 @@ inline
std::vector<std::string>
Options::groups() const
{
std::vector<std::string> g;
std::transform(
m_help.begin(),
m_help.end(),
std::back_inserter(g),
[] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
{
return pair.first;
}
);
return g;
return m_group;
}
inline

View File

@ -51,3 +51,10 @@ add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND}
add_executable(link_test link_a.cpp link_b.cpp)
target_link_libraries(link_test cxxopts)
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND ("${CMAKE_SYSTEM}" MATCHES "Linux"))
add_executable(fuzzer fuzz.cpp)
target_link_libraries(fuzzer PRIVATE cxxopts)
target_compile_options(fuzzer PRIVATE -fsanitize=fuzzer)
target_link_options(fuzzer PRIVATE -fsanitize=fuzzer)
endif()

File diff suppressed because it is too large Load Diff

107
test/fuzz.cpp Normal file
View File

@ -0,0 +1,107 @@
#include <cassert>
#include <cxxopts.hpp>
#include <fuzzer/FuzzedDataProvider.h>
constexpr int kMaxOptions = 1024;
constexpr int kMaxArgSize = 1024;
enum class ParseableTypes
{
kInt,
kString,
kVectorString,
kFloat,
kDouble,
// Marker for fuzzer.
kMaxValue,
};
template <typename T>
void
add_fuzzed_option(cxxopts::Options* options, FuzzedDataProvider* provider)
{
assert(options);
assert(provider);
options->add_options()(provider->ConsumeRandomLengthString(kMaxArgSize),
provider->ConsumeRandomLengthString(kMaxArgSize),
cxxopts::value<T>());
}
extern "C" int
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
try
{
FuzzedDataProvider provider(data, size);
// Randomly generate a usage string.
cxxopts::Options options(provider.ConsumeRandomLengthString(kMaxArgSize),
provider.ConsumeRandomLengthString(kMaxArgSize));
// Randomly generate a set of flags configurations.
for (int i = 0; i < provider.ConsumeIntegralInRange<int>(0, kMaxOptions);
i++)
{
switch (provider.ConsumeEnum<ParseableTypes>())
{
case ParseableTypes::kInt:
add_fuzzed_option<int>(&options, &provider);
break;
case ParseableTypes::kString:
add_fuzzed_option<std::string>(&options, &provider);
break;
case ParseableTypes::kVectorString:
add_fuzzed_option<std::vector<std::string>>(&options, &provider);
break;
case ParseableTypes::kFloat:
add_fuzzed_option<float>(&options, &provider);
break;
case ParseableTypes::kDouble:
add_fuzzed_option<double>(&options, &provider);
break;
default:
break;
}
}
// Sometimes allow unrecognised options.
if (provider.ConsumeBool())
{
options.allow_unrecognised_options();
}
// Sometimes allow trailing positional arguments.
if (provider.ConsumeBool())
{
std::string positional_option_name =
provider.ConsumeRandomLengthString(kMaxArgSize);
options.add_options()(positional_option_name,
provider.ConsumeRandomLengthString(kMaxArgSize),
cxxopts::value<std::vector<std::string>>());
options.parse_positional({positional_option_name});
}
// Build command line input.
const int argc = provider.ConsumeIntegralInRange<int>(1, kMaxOptions);
std::vector<std::string> command_line_container;
command_line_container.reserve(argc);
std::vector<const char*> argv;
argv.reserve(argc);
for (int i = 0; i < argc; i++)
{
command_line_container.push_back(
provider.ConsumeRandomLengthString(kMaxArgSize));
argv.push_back(command_line_container[i].c_str());
}
// Parse command line;
auto result = options.parse(argc, argv.data());
} catch (...)
{
}
return 0;
}

View File

@ -112,7 +112,7 @@ TEST_CASE("Basic options", "[options]")
CHECK(arguments[2].key() == "value");
CHECK(arguments[3].key() == "av");
CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value&);
CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value);
CHECK(options.program() == "tester");
}
@ -147,7 +147,7 @@ TEST_CASE("Short options", "[options]")
CHECK(result["c"].as<std::string>() == "foo=something");
REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
cxxopts::exceptions::invalid_option_format&);
cxxopts::exceptions::invalid_option_format);
}
TEST_CASE("No positional", "[positional]")
@ -238,9 +238,6 @@ TEST_CASE("No positional with extras", "[positional]")
auto** argv = av.argv();
auto argc = av.argc();
auto old_argv = argv;
auto old_argc = argc;
auto result = options.parse(argc, argv);
auto& unmatched = result.unmatched();
@ -260,7 +257,7 @@ TEST_CASE("Positional not valid", "[positional]") {
auto** argv = av.argv();
auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
}
TEST_CASE("Positional with empty arguments", "[positional]") {
@ -288,6 +285,37 @@ TEST_CASE("Positional with empty arguments", "[positional]") {
REQUIRE(actual == expected);
}
TEST_CASE("Positional with list delimiter", "[positional]") {
std::string single;
std::vector<std::string> positional;
cxxopts::Options options("test_all_positional_list_delimiter", " - test all positional with list delimiters");
options.add_options()
("single", "Single positional param",
cxxopts::value<std::string>(single))
("positional", "Positional parameters vector",
cxxopts::value<std::vector<std::string>>(positional))
;
Argv av({"tester", "a,b", "c,d", "e"});
auto argc = av.argc();
auto argv = av.argv();
std::vector<std::string> pos_names = {"single", "positional"};
options.parse_positional(pos_names.begin(), pos_names.end());
auto result = options.parse(argc, argv);
CHECK(result.unmatched().size() == 0);
REQUIRE(positional.size() == 2);
CHECK(single == "a,b");
CHECK(positional[0] == "c,d");
CHECK(positional[1] == "e");
}
TEST_CASE("Empty with implicit value", "[implicit]")
{
cxxopts::Options options("empty_implicit", "doesn't handle empty");
@ -319,7 +347,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
auto** argv = av.argv();
auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument);
}
SECTION("With equal-separated true") {
@ -489,7 +517,7 @@ TEST_CASE("Unsigned integers", "[options]")
auto argc = av.argc();
options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
}
TEST_CASE("Integer bounds", "[integer]")
@ -524,14 +552,32 @@ TEST_CASE("Overflow on boundary", "[integer]")
using namespace cxxopts::values;
int8_t si;
int16_t si16;
int64_t si64;
uint8_t ui;
uint16_t ui16;
uint64_t ui64;
CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("65536", ui16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("75536", ui16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("32768", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-32769", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-42769", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-75536", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("18446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("28446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("9223372036854775808", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-9223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-10223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-28446744073709551616", si64)), cxxopts::exceptions::incorrect_argument_type);
}
TEST_CASE("Integer overflow", "[options]")
@ -548,11 +594,11 @@ TEST_CASE("Integer overflow", "[options]")
auto argc = av.argc();
options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
int integer = 0;
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type);
}
TEST_CASE("Floats", "[options]")
@ -593,7 +639,7 @@ TEST_CASE("Invalid integers", "[integer]") {
auto argc = av.argc();
options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
}
TEST_CASE("Booleans", "[boolean]") {
@ -663,11 +709,13 @@ TEST_CASE("std::vector", "[vector]") {
#ifdef CXXOPTS_HAS_OPTIONAL
TEST_CASE("std::optional", "[optional]") {
std::optional<std::string> optional;
std::optional<bool> opt_bool;
cxxopts::Options options("optional", " - tests optional");
options.add_options()
("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional));
("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional))
("optional_bool", "an boolean optional", cxxopts::value<std::optional<bool>>(opt_bool)->default_value("false"));
Argv av({"optional", "--optional", "foo"});
Argv av({"optional", "--optional", "foo", "--optional_bool", "true"});
auto** argv = av.argv();
auto argc = av.argc();
@ -676,6 +724,8 @@ TEST_CASE("std::optional", "[optional]") {
REQUIRE(optional.has_value());
CHECK(*optional == "foo");
CHECK(opt_bool.has_value());
CHECK(*opt_bool);
}
#endif
@ -699,7 +749,7 @@ TEST_CASE("Unrecognised options", "[options]") {
auto argc = av.argc();
SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
}
SECTION("After allowing unrecognised options") {
@ -726,7 +776,7 @@ TEST_CASE("Allow bad short syntax", "[options]") {
auto argc = av.argc();
SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
}
SECTION("After allowing unrecognised options") {
@ -749,7 +799,7 @@ TEST_CASE("Invalid option syntax", "[options]") {
auto argc = av.argc();
SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
}
}
@ -768,9 +818,60 @@ TEST_CASE("Options empty", "[options]") {
auto** argv = argv_.argv();
CHECK(options.groups().empty());
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
}
#ifdef CXXOPTS_HAS_OPTIONAL
TEST_CASE("Optional value", "[optional]")
{
cxxopts::Options options("options", "query as std::optional");
options.add_options()
("int", "Integer", cxxopts::value<int>())
("float", "Float", cxxopts::value<float>())
("string", "String", cxxopts::value<std::string>())
;
SECTION("Available") {
Argv av({
"--int",
"42",
"--float",
"3.141",
"--string",
"Hello"
});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.as_optional<int>("int"));
CHECK(result.as_optional<float>("float"));
CHECK(result.as_optional<string>("string"));
CHECK(*result.as_optional<int>("int") == 42);
CHECK(*result.as_optional<float>("float") == 3.141);
CHECK(*result.as_optional<string>("string") == "Hello");
}
SECTION("Unavailable") {
Argv av({
});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(!result.as_optional<int>("int"));
CHECK(!result.as_optional<float>("float"));
CHECK(!result.as_optional<string>("string"));
}
}
#endif
TEST_CASE("Initializer list with group", "[options]") {
cxxopts::Options options("Initializer list group", " - test initializer list with group");