CPlusPlus Get code coverage Using CMake - Dieptranivsr/DroneIVSR GitHub Wiki
Table of Contents 👈
dieptt3@DESKTOP-BF5GK0T:~/Developments/UnittestProject$ tree -L 4
.
├── CMakeLists.txt
├── README.md
├── get_codecoverage_report.sh
├── src
│ ├── CMakeLists.txt
│ ├── common
│ │ ├── canonicalpath.cpp
│ │ └── canonicalpath.h
│ ├── main.cpp
│ └── manager
│ ├── coordinate.cpp
│ ├── coordinate.hpp
│ ├── multiply.cpp
│ └── multiply.h
└── unittests
├── CMakeLists.txt
└── tests
├── common
│ └── canonicalpath_tests.cpp
├── main.cpp
└── manager
├── CoordinateTests.cpp
└── ManagerTestSuite.cpp
7 directories, 16 files
#!/bin/bash
UT_TARGET=test_unittest_bin
UT_FILE=test-unittest
start=$(date +%s.%N)
function check_prerequisites {
if ! hash lcov 2> /dev/null; then
echo -e "Please install lcov first: 'sudo apt-get install lcov'"
exit 1
else
lcov -v && gcov -v
fi
}
check_prerequisites
# Build and Run test on Ubuntu
rm -rf build unittest_cov local
mkdir build && cd build && cmake -D BUILD_UT=On -D GCOV_LIB=/usr/lib/gcc/x86_64-linux-gnu/9/libgcov.a -D ENABLE_CODE_COVERAGE=TRUE .. && echo -e '\n' && \
echo "Building ..." && make test_unittest_bin && echo -e '\n' && \
cd .. && GCOV_PREFIX=build/unittest_cov ./build/unittests/test-unittest && echo -e '\n' && \
echo "DEBUG ==> Pulling out data to analyze" && cp -r ${PWD}/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests ${PWD}/build
if [ $? -ne 0 ]; then
echo -e '\n'
echo "[----------] Failed"
exit 0
fi
## gen branch cov
echo "Looks good, generating coverage info..."
lcov --rc lcov_branch_coverage=1 --gcov-tool gcov-9 -i -c -o build/init.cov -d ${PWD}/build/ --include '*/src/*' --exclude '*/test/*'
lcov --rc lcov_branch_coverage=1 --gcov-tool gcov-9 -c -o build/run.cov -d ${PWD}/build/ --include '*/src/*' --exclude '*/test/*'
lcov --rc lcov_branch_coverage=1 --gcov-tool gcov-9 -o build/final.cov -a ${PWD}/build/init.cov -a ${PWD}/build/run.cov
genhtml --rc genhtml_med_limit=80 --function-coverage --branch-coverage --highlight --legend ${PWD}/build/final.cov -o ${PWD}/build/report
echo "Coverage report: ${PWD}/build/report/index.html"
end=$(date +%s.%N)
runtime=$(python -c "print(${end} - ${start})")
if [ $? -eq 0 ]; then
echo -e '\n'
echo "[================== Unittest Project ==================]"
echo "[----------] Time: $(TZ=Asia/Ho_Chi_Minh date +%Y-%m-%d\ %H:%M:%S.%6N)"
echo "[----------] Total Execution Time: $runtime s"
echo "[----------] You could get lcov report files. "
echo "[======================================================]"
exit 0
fi
#!/bin/sh
UT_TARGET=test_unittest_bin
UT_FILE=test-unittest
start=$(date +%s.%N)
# python3 -m pip install gcovr
# sudo apt-get install gcc-8 g++-8 gcc-9 g++-9
# sudo apt-get install gcc-10 g++-10
function check_prerequisites {
if ! hash gcovr 2> /dev/null; then
echo -e "Please install gcovr first: 'python3 -m pip install gcovr'"
exit 1
else
gcovr --version && gcov -v
fi
}
check_prerequisites
# Build and Run test on Ubuntu
rm -rf build unittest_cov local
mkdir build && cd build && cmake -D BUILD_UT=On -D GCOV_LIB=/usr/lib/gcc/x86_64-linux-gnu/9/libgcov.a -D ENABLE_CODE_COVERAGE=TRUE .. && echo -e '\n' && \
echo "Building ..." && make test_unittest_bin && echo -e '\n' && \
cd ..
rm -rf output && mkdir output
echo "${PWD}"
cp -r build/unittests/test-unittest output/.
cd output
export TERM=xterm; GCOV_PREFIX=coverage GCOV_PREFIX_STRIP=5 ./test-unittest && echo -e '\n' && cd .. && \
echo "DEBUG ==> Pulling out data to analyze" && cp -r ${PWD}/output/coverage/unittests ${PWD}/build
if [ $? -ne 0 ]; then
echo -e '\n'
echo "[----------] Failed"
exit 0
fi
## gen branch cov
rm -rf report && mkdir report
gcovr -v -e ".*(mock|stub|unittest).*" --html --html-details -o report/report.html --gcov-executable gcov-9
echo "Coverage report: ${PWD}/report/report.html"
end=$(date +%s.%N)
runtime=$(python3 -c "print(${end} - ${start})")
if [ $? -eq 0 ]; then
echo -e '\n'
echo "[================== Unittest Project ==================]"
echo "[----------] Time: $(TZ=Asia/Ho_Chi_Minh date +%Y-%m-%d\ %H:%M:%S.%6N)"
echo "[----------] Total Execution Time: $runtime s"
echo "[----------] You could get lcov report files. "
echo "[======================================================]"
exit 0
fi
#!/bin/sh
start=$(date +%s.%N)
# echo "\$1 expands to '$1'."
PACKAGE=$1
if [ -z "$PACKAGE" ]; then
echo "Error: PACKAGE is not set."
exit 1
fi
case "$PACKAGE" in
"a")
# do something
;;
"b")
# do something else
;;
*)
# default case
exit 1
;;
esac
UT_TARGET=test_unittest_bin
UT_FILE=test-unittest
# python3 -m pip install gcovr
# sudo apt-get install gcc-8 g++-8 gcc-9 g++-9
# sudo apt-get install gcc-10 g++-10
function check_prerequisites {
if ! hash gcovr 2> /dev/null; then
echo -e "Please install gcovr first: 'python3 -m pip install gcovr'"
exit 1
else
gcovr --version && gcov -v
fi
}
check_prerequisites
# Build and Run test on Ubuntu
rm -rf build unittest_cov local
mkdir build && cd build && cmake -D BUILD_UT=On -D GCOV_LIB=/usr/lib/gcc/x86_64-linux-gnu/9/libgcov.a -D ENABLE_CODE_COVERAGE=TRUE .. && echo -e '\n' && \
echo "Building ..." && make test_unittest_bin && echo -e '\n' && \
cd ..
rm -rf output && mkdir output
echo "${PWD}"
cp -r build/unittests/test-unittest output/.
cd output
export TERM=xterm; GCOV_PREFIX=coverage GCOV_PREFIX_STRIP=5 ./test-unittest && echo -e '\n' && cd .. && \
echo "DEBUG ==> Pulling out data to analyze" && cp -r ${PWD}/output/coverage/unittests ${PWD}/build && echo -e '\n'
if [ $? -eq 0 ]; then
echo "File successfully copied"
echo '\n'
else
echo "Error: Failed to copy files"
exit 1
fi
## gen branch cov
rm -rf report && mkdir report
gcovr -v -e ".*(mock|stub|unittest).*" --html --html-details -o report/report.html --gcov-executable gcov-9 --print-sumary --verbose > logfile.txt 2>&1 && tail -n 3 logfile.txt
echo "Coverage report: ${PWD}/report/report.html"
if [ $? -eq 0 ]; then
end=$(date +%s.%N)
runtime=$(echo "$end - $start" | bc)
echo -e '\n'
echo "[================== Unittest Project ==================]"
echo "[----------] Time: $(TZ=Asia/Ho_Chi_Minh date +%Y-%m-%d\ %H:%M:%S.%6N)"
echo "[----------] Total Execution Time: $runtime s"
echo "[----------] You could get lcov report files. "
echo "[======================================================]"
exit 0
fi
UnittestProject/CMakeLists.txt
cmake_policy(SET CMP0048 NEW)
project(test_project)
cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 17)
include(GNUInstallDirs)
find_package(GTest REQUIRED)
if (${BUILD_UT} MATCHES "On")
message (STATUS "Building test_project unittest")
find_library(GCOV_LIB NAMES gcov)
link_libraries(${GCOV_LIB})
#find_library(GCOV_LIBRARY NAMES gcov)
#link_libraries(${GCOV_LIBRARY})
add_compile_options(-fprofile-arcs)
add_compile_options(-ftest-coverage)
add_compile_options(-DBUILD_UT)
add_subdirectory(unittests)
else()
message (STATUS "Building test_project release/debug")
add_subdirectory(src)
add_subdirectory(unittests)
endif()
option(ENABLE_CODE_COVERAGE "Enable Code Coverage Flags" FALSE)
set(CODE_COVERAGE_CXXFLAGS "-g;O0;-coverage;-ftest-coverage;-fprofile-arcs;-fprofile-abs-path;-nopipe;" CACHE STRING "Code coverage CXX flags")
set(CODE_COVERAGE_LDFLAGS "-coverage;-ftest-coverage;-fprofile-arcs;" CACHE STRING "Code coverage linker flags")
if(ENABLE_CODE_COVERAGE)
foreach(_code_coverage_flag IN LISTS CODE_COVERAGE_CXXFLAGS)
add_compile_options(${_code_coverage_flag})
endforeach()
foreach(_code_coverage_flag IN LISTS CODE_COVERAGE_LDFLAGS)
add_compile_options(${_code_coverage_flag})
endforeach()
unset(_code_coverage_flag)
message(STATUS "Enabling code coverage on all targets, CXX flags: ${CODE_COVERAGE_CXXFLAGS}, LD flags: ${CODE_COVERAGE_LDFLAGS}")
endif()
set(CMAKE_COLOR_DIAGNOSTICS TRUE)
UnittestProject/src/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(project_src VERSION 1.0.0 LANGUAGES CXX)
set(TEST_BINARY_NAME "test_project" CACHE STRING "test_project")
#target_include_directories(test_project_common
# INTERFACE
# manager)
#target_link_libraries(test_project_common
# INTERFACE
# #manager
#)
FILE(GLOB_RECURSE PRJ_SRCS_LIST
${CMAKE_CURRENT_SOURCE_DIR}/common/canonicalpath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/manager/coordinate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/manager/multiply.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/manager/file1.cpp
)
#add_library(test-core-common INTERFACE)
#target_include_directories(test-core-common BEFORE INTERFACE
# $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
# $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/common/include>
#)
add_library(test-core OBJECT ${PRJ_SRCS_LIST})
target_include_directories(test-core BEFORE INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/common/include>
)
# target_link_libraries(test-core PRIVATE
# test-core-common
# )
message (STATUS "Building test_service_app")
add_executable(test_service_app)
target_sources(test_service_app PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
target_link_libraries(test_service_app
PUBLIC
test-core
)
target_include_directories(test_service_app
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/common>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/manager>
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/common/include>
)
target_compile_options(test_service_app
PUBLIC
-Wno-comment
PRIVATE
-Werror=uninitialized
-Werror=return-type
)
install(TARGETS test_service_app RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT test-service)
UnittestProject/unittests/CMakeLists.txt
message (STATUS "Building test_unittest_bin")
cmake_policy(SET CMP0048 NEW)
cmake_minimum_required(VERSION 3.20)
project(project_unittest VERSION 1.0.0 LANGUAGES CXX)
find_package(GTest REQUIRED)
FILE(GLOB_RECURSE PRJ_SRCS_LIST
${CMAKE_CURRENT_SOURCE_DIR}/../src/common/canonicalpath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/manager/coordinate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/manager/multiply.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/../src/file1.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/mocks/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/common/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/manager/*.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/tests/manager/file1.cpp
#${CMAKE_CURRENT_SOURCE_DIR}/manager/file1.cpp
)
add_executable(test_unittest_bin)
target_sources(test_unittest_bin PUBLIC
${PRJ_SRCS_LIST}
)
target_include_directories(test_unittest_bin
PUBLIC
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/common>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/tests>
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/../src/common>
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/../src/common/include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/../src/common>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/../src/manager>
)
target_link_libraries(test_unittest_bin
PUBLIC
GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main
)
set_target_properties(test_unittest_bin PROPERTIES
CXX_STANDARD 17
OUTPUT_NAME test-unittest
)
install(TARGETS test_unittest_bin RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT test_unittest)
UnittestProject/src/common/canonicalpath.h
std::string canonicalpath(const std::string &path);
UnittestProject/src/common/canonicalpath.cpp
#include <iostream>
#include <vector>
#include <regex>
#include <canonicalpath.h>
std::string canonicalpath(const std::string &path)
{
if (path.length() <= 1)
return path;
std::string sep = path[0] == '/' ? "/" : "";
std::vector<std::string> entries;
std::smatch match;
std::regex re("[^/]+");
for (auto p = path; std::regex_search(p, match, re); p = match.suffix()) {
if (match.str() == ".." && !entries.empty()
&& !(sep == "" && entries.back() == ".."))
entries.pop_back();
else
entries.push_back(match.str());
}
std::string cpath;
for (auto s: entries) {
cpath += sep + s;
sep = "/";
}
return cpath;
}
UnittestProject/src/manager/multiply.h
int multiply(int a, int b);
float multiply(float a, float b);
double multiply(double a, double b);
UnittestProject/src/manager/multiply.cpp
#include "multiply.h"
int multiply(int a, int b)
{
return a*b;
}
float multiply(float a, float b)
{
return a*b;
}
double multiply(double a, double b)
{
return a*b;
}
UnittestProject/src/manager/coordinate.cpp
#pragma once
class Coordinate {
private:
public:
Coordinate(double lat, double lng);
bool operator==(const Coordinate &other) const;
bool operator!=(const Coordinate &other) const;
void Offset(const Coordinate &other);
double lat;
double lng;
};
UnittestProject/src/manager/coordinate.cpp
#include <stdexcept>
#include <coordinate.hpp>
Coordinate::Coordinate(double lat, double lng) : lat(lat), lng(lng) {
if (lat > 90.0 || lat < -90.0 || lng > 180.0 || lng < -180.0) {
throw std::runtime_error("ERROR: Failed to parse coordinate coordinate. Value out of range");
}
}
bool Coordinate::operator==(const Coordinate &other) const {
return (lat == other.lat && lng == other.lng);
}
bool Coordinate::operator!=(const Coordinate &other) const {
return !(*this == other);
}
void Coordinate::Offset(const Coordinate &other) {
lat += other.lat;
lng += other.lng;
}
UnittestProject/unittests/tests/main.cpp
#include <gtest/gtest.h>
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
UnittestProject/unittests/tests/common/canonicalpath_tests.cpp
#include <gtest/gtest.h>
#include <canonicalpath.h>
TEST(canonicalTests, relativePath) {
EXPECT_STREQ(canonicalpath("abc/de/").data(), "abc/de");
EXPECT_STREQ(canonicalpath("abc/../de").data(), "de");
EXPECT_STREQ(canonicalpath("../../abc").data(), "../../abc");
EXPECT_STREQ(canonicalpath("abc/../../../de").data(), "../../de");
EXPECT_STREQ(canonicalpath("abc/../de/../fgh").data(), "fgh");
}
TEST(canonicalTests, absolutePath) {
EXPECT_STREQ(canonicalpath("/abc/de/").data(), "/abc/de");
EXPECT_STREQ(canonicalpath("/abc/../de").data(), "/de");
EXPECT_STREQ(canonicalpath("/../../abc").data(), "/abc");
EXPECT_STREQ(canonicalpath("/abc/../../../de").data(), "/de");
EXPECT_STREQ(canonicalpath("/abc/../de/../fgh").data(), "/fgh");
}
TEST(canonicalTests, boundaryCase) {
EXPECT_STREQ(canonicalpath("").data(), "");
EXPECT_STREQ(canonicalpath("/").data(), "/");
}
UnittestProject/unittests/tests/manager/CoordinateTests.cpp
#include <gtest/gtest.h>
#include <coordinate.hpp>
TEST(Coordinate, DoubleConstrutor) {
Coordinate coordinate = Coordinate(-43.454,89.0454);
EXPECT_DOUBLE_EQ(-43.454, coordinate.lat);
EXPECT_DOUBLE_EQ(89.0454, coordinate.lng);
}
TEST(Coordinate, DoubleConstrutorOutOfRange) {
EXPECT_THROW({
Coordinate coordinate = Coordinate(90.100,50.0);
}, std::runtime_error);
EXPECT_THROW({
Coordinate coordinate = Coordinate(-90.1,50.0);
}, std::runtime_error);
EXPECT_THROW({
Coordinate coordinate = Coordinate(45.0,180.1);
}, std::runtime_error);
EXPECT_THROW({
Coordinate coordinate = Coordinate(45.0,-180.1);
}, std::runtime_error);
}
TEST(Coordinate, EqualOverload) {
Coordinate a = Coordinate(10.1,-34.2);
Coordinate b = Coordinate(10.1,-34.2);
Coordinate c = Coordinate(10.1,34.2);
EXPECT_TRUE(a == b);
EXPECT_FALSE(a == c);
}
TEST(Coordinate, NotEqualOverload) {
Coordinate a = Coordinate(-48.0,90.0);
Coordinate b = Coordinate(-48.0,90.0);
Coordinate c = Coordinate(10.1,34.2);
EXPECT_FALSE(a != b);
EXPECT_TRUE(a != c);
}
TEST(Coordinate, OffsetValid) {
Coordinate a = Coordinate(10.0,-10.0);
Coordinate b = Coordinate(0.1, 0.2);
a.Offset(b);
EXPECT_DOUBLE_EQ(10.1, a.lat);
EXPECT_DOUBLE_EQ(-9.8, a.lng);
}
UnittestProject/unittests/tests/manager/ManagerTestSuite.cpp
#include <multiply.h>
#include <gtest/gtest.h>
TEST(MultiplyTests, TestIntegerOne_One)
{
const auto expected = 1;
const auto actual = multiply(1, 1);
ASSERT_EQ(expected, actual);
}
TEST(MultiplyTests, TestIntegerZero_Zero)
{
const auto expected = 0;
const auto actual = multiply(0, 0);
ASSERT_EQ(expected, actual);
}
TEST(MultiplyTests, TestIntegerZero_One)
{
const auto expected = 0;
const auto actual = multiply(0, 1);
ASSERT_EQ(actual, expected);
}
TEST(MultiplyTests, DISABLED_TestFloatValues)
{
const auto expected = 1.1f;
const auto actual = multiply(0.1f, 1.0f);
ASSERT_EQ(actual, expected);
}
TEST(MultiplyTests, TestDoubleValues)
{
const auto expected = 0.1;
const auto actual = multiply(0.1, 1.0);
ASSERT_EQ(actual, expected);
}
Log in Terminal when Getting Code Coverage Report 👈
dieptt3@DESKTOP-BF5GK0T:~/Developments/UnittestProject$ ./get_codecoverage_report.sh
lcov: LCOV version 1.14
gcov (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found GTest: /usr/lib/x86_64-linux-gnu/cmake/GTest/GTestConfig.cmake (found version "1.13.0")
-- Building test_project unittest
-- Building test_unittest_bin
-- Enabling code coverage on all targets, CXX flags: -g;O0;-coverage;-ftest-coverage;-fprofile-arcs;-fprofile-abs-path;-nopipe;, LD flags: -coverage;-ftest-coverage;-fprofile-arcs;
-- Configuring done
-- Generating done
-- Build files have been written to: /home/dieptt3/Developments/UnittestProject/build
Building ...
[ 12%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.o
[ 25%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.o
[ 37%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.o
[ 50%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.o
[ 62%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.o
[ 75%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.o
[ 87%] Building CXX object unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.o
[100%] Linking CXX executable test-unittest
[100%] Built target test_unittest_bin
[==========] Running 12 tests from 3 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from canonicalTests
[ RUN ] canonicalTests.relativePath
[ OK ] canonicalTests.relativePath (1 ms)
[ RUN ] canonicalTests.absolutePath
[ OK ] canonicalTests.absolutePath (0 ms)
[ RUN ] canonicalTests.boundaryCase
[ OK ] canonicalTests.boundaryCase (0 ms)
[----------] 3 tests from canonicalTests (2 ms total)
[----------] 5 tests from Coordinate
[ RUN ] Coordinate.DoubleConstrutor
[ OK ] Coordinate.DoubleConstrutor (0 ms)
[ RUN ] Coordinate.DoubleConstrutorOutOfRange
[ OK ] Coordinate.DoubleConstrutorOutOfRange (0 ms)
[ RUN ] Coordinate.EqualOverload
[ OK ] Coordinate.EqualOverload (0 ms)
[ RUN ] Coordinate.NotEqualOverload
[ OK ] Coordinate.NotEqualOverload (0 ms)
[ RUN ] Coordinate.OffsetValid
[ OK ] Coordinate.OffsetValid (0 ms)
[----------] 5 tests from Coordinate (0 ms total)
[----------] 4 tests from MultiplyTests
[ RUN ] MultiplyTests.TestIntegerOne_One
[ OK ] MultiplyTests.TestIntegerOne_One (0 ms)
[ RUN ] MultiplyTests.TestIntegerZero_Zero
[ OK ] MultiplyTests.TestIntegerZero_Zero (0 ms)
[ RUN ] MultiplyTests.TestIntegerZero_One
[ OK ] MultiplyTests.TestIntegerZero_One (0 ms)
[ DISABLED ] MultiplyTests.DISABLED_TestFloatValues
[ RUN ] MultiplyTests.TestDoubleValues
[ OK ] MultiplyTests.TestDoubleValues (0 ms)
[----------] 4 tests from MultiplyTests (0 ms total)
[----------] Global test environment tear-down
[==========] 12 tests from 3 test suites ran. (3 ms total)
[ PASSED ] 12 tests.
YOU HAVE 1 DISABLED TEST
DEBUG ==> Pulling out data to analyze
Looks good, generating coverage info...
Capturing coverage data from /home/dieptt3/Developments/UnittestProject/build/
Found gcov version: 9.4.0
Using intermediate gcov format
Scanning /home/dieptt3/Developments/UnittestProject/build/ for .gcno files ...
Found 7 graph files in /home/dieptt3/Developments/UnittestProject/build/
Processing __/src/manager/multiply.cpp.gcno
Processing __/src/manager/coordinate.cpp.gcno
Processing __/src/common/canonicalpath.cpp.gcno
Processing tests/manager/ManagerTestSuite.cpp.gcno
Processing tests/manager/CoordinateTests.cpp.gcno
Processing tests/main.cpp.gcno
Processing tests/common/canonicalpath_tests.cpp.gcno
Excluded data for 71 files due to include/exclude options
Finished .info-file creation
Capturing coverage data from /home/dieptt3/Developments/UnittestProject/build/
Found gcov version: 9.4.0
Using intermediate gcov format
Scanning /home/dieptt3/Developments/UnittestProject/build/ for .gcda files ...
Found 14 data files in /home/dieptt3/Developments/UnittestProject/build/
Processing unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.gcda
Processing unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/coordinate.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/manager/multiply.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/__/src/common/canonicalpath.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/ManagerTestSuite.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/manager/CoordinateTests.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/main.cpp.gcda
Processing unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.gcda
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.gcno:cannot open notes file
/home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.gcda:stamp mismatch with notes file
geninfo: WARNING: GCOV did not produce any data for /home/dieptt3/Developments/UnittestProject/build/unittest_cov/home/dieptt3/Developments/UnittestProject/build/unittests/CMakeFiles/test_unittest_bin.dir/tests/common/canonicalpath_tests.cpp.gcda
Excluded data for 71 files due to include/exclude options
Finished .info-file creation
Combining tracefiles.
Reading tracefile /home/dieptt3/Developments/UnittestProject/build/init.cov
Reading tracefile /home/dieptt3/Developments/UnittestProject/build/run.cov
Writing data to build/final.cov
Summary coverage rate:
lines......: 94.3% (33 of 35 lines)
functions..: 87.5% (7 of 8 functions)
branches...: 71.9% (46 of 64 branches)
Reading data file /home/dieptt3/Developments/UnittestProject/build/final.cov
Found 3 entries.
Found common filename prefix "/home/dieptt3/Developments/UnittestProject/src"
Writing .css and .png files.
Generating output.
Processing file common/canonicalpath.cpp
Processing file manager/coordinate.cpp
Processing file manager/multiply.cpp
Writing directory view page.
Overall coverage rate:
lines......: 94.3% (33 of 35 lines)
functions..: 87.5% (7 of 8 functions)
branches...: 71.9% (46 of 64 branches)
Coverage report: /home/dieptt3/Developments/UnittestProject/build/report/index.html
[================== Unittest Project ==================]
[----------] Time: 2023-10-29 16:46:20.126806
[----------] Total Execution Time: 16.5206303596 s
[----------] You could get lcov report files.
[======================================================]
Specific directories For Getting Code Coverage Using CMake 👈
dieptt3@DESKTOP-BF5GK0T:~/Developments/UnittestProject$ tree -L 9
.
├── CMakeLists.txt
├── README.md
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ ├── 3.25.2
│ │ │ ├── CMakeCCompiler.cmake
│ │ │ ├── CMakeCXXCompiler.cmake
│ │ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ │ ├── CMakeSystem.cmake
│ │ │ ├── CompilerIdC
│ │ │ │ ├── CMakeCCompilerId.c
│ │ │ │ ├── a.out
│ │ │ │ └── tmp
│ │ │ └── CompilerIdCXX
│ │ │ ├── CMakeCXXCompilerId.cpp
│ │ │ ├── a.out
│ │ │ └── tmp
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── CMakeError.log
│ │ ├── CMakeOutput.log
│ │ ├── CMakeScratch
│ │ ├── Makefile.cmake
│ │ ├── Makefile2
│ │ ├── TargetDirectories.txt
│ │ ├── cmake.check_cache
│ │ ├── pkgRedirects
│ │ └── progress.marks
│ ├── Makefile
│ ├── cmake_install.cmake
│ ├── final.cov
│ ├── init.cov
│ ├── report
│ │ ├── amber.png
│ │ ├── common
│ │ │ ├── canonicalpath.cpp.func-sort-c.html
│ │ │ ├── canonicalpath.cpp.func.html
│ │ │ ├── canonicalpath.cpp.gcov.html
│ │ │ ├── index-sort-b.html
│ │ │ ├── index-sort-f.html
│ │ │ ├── index-sort-l.html
│ │ │ └── index.html
│ │ ├── emerald.png
│ │ ├── gcov.css
│ │ ├── glass.png
│ │ ├── index-sort-b.html
│ │ ├── index-sort-f.html
│ │ ├── index-sort-l.html
│ │ ├── index.html
│ │ ├── manager
│ │ │ ├── coordinate.cpp.func-sort-c.html
│ │ │ ├── coordinate.cpp.func.html
│ │ │ ├── coordinate.cpp.gcov.html
│ │ │ ├── index-sort-b.html
│ │ │ ├── index-sort-f.html
│ │ │ ├── index-sort-l.html
│ │ │ ├── index.html
│ │ │ ├── multiply.cpp.func-sort-c.html
│ │ │ ├── multiply.cpp.func.html
│ │ │ └── multiply.cpp.gcov.html
│ │ ├── ruby.png
│ │ ├── snow.png
│ │ └── updown.png
│ ├── run.cov
│ ├── unittest_cov
│ │ └── home
│ │ └── dieptt3
│ │ └── Developments
│ │ └── UnittestProject
│ │ └── build
│ │ └── unittests
│ │ └── CMakeFiles
│ └── unittests
│ ├── CMakeFiles
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── progress.marks
│ │ └── test_unittest_bin.dir
│ │ ├── DependInfo.cmake
│ │ ├── __
│ │ │ └── src
│ │ │ ├── common
│ │ │ │ ├── canonicalpath.cpp.gcda
│ │ │ │ ├── canonicalpath.cpp.gcno
│ │ │ │ ├── canonicalpath.cpp.o
│ │ │ │ └── canonicalpath.cpp.o.d
│ │ │ └── manager
│ │ │ ├── coordinate.cpp.gcda
│ │ │ ├── coordinate.cpp.gcno
│ │ │ ├── coordinate.cpp.o
│ │ │ ├── coordinate.cpp.o.d
│ │ │ ├── multiply.cpp.gcda
│ │ │ ├── multiply.cpp.gcno
│ │ │ ├── multiply.cpp.o
│ │ │ └── multiply.cpp.o.d
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── compiler_depend.make
│ │ ├── compiler_depend.ts
│ │ ├── depend.make
│ │ ├── flags.make
│ │ ├── link.txt
│ │ ├── progress.make
│ │ └── tests
│ │ ├── common
│ │ │ ├── canonicalpath_tests.cpp.gcda
│ │ │ ├── canonicalpath_tests.cpp.gcno
│ │ │ ├── canonicalpath_tests.cpp.o
│ │ │ └── canonicalpath_tests.cpp.o.d
│ │ ├── main.cpp.gcda
│ │ ├── main.cpp.gcno
│ │ ├── main.cpp.o
│ │ ├── main.cpp.o.d
│ │ └── manager
│ │ ├── CoordinateTests.cpp.gcda
│ │ ├── CoordinateTests.cpp.gcno
│ │ ├── CoordinateTests.cpp.o
│ │ ├── CoordinateTests.cpp.o.d
│ │ ├── ManagerTestSuite.cpp.gcda
│ │ ├── ManagerTestSuite.cpp.gcno
│ │ ├── ManagerTestSuite.cpp.o
│ │ └── ManagerTestSuite.cpp.o.d
│ ├── Makefile
│ ├── cmake_install.cmake
│ └── test-unittest
├── get_codecoverage_report.sh
├── src
│ ├── CMakeLists.txt
│ ├── common
│ │ ├── canonicalpath.cpp
│ │ └── canonicalpath.h
│ ├── main.cpp
│ └── manager
│ ├── coordinate.cpp
│ ├── coordinate.hpp
│ ├── multiply.cpp
│ └── multiply.h
└── unittests
├── CMakeLists.txt
└── tests
├── common
│ └── canonicalpath_tests.cpp
├── main.cpp
└── manager
├── CoordinateTests.cpp
└── ManagerTestSuite.cpp
37 directories, 109 files
cxxopts.hpp
#ifndef CXXOPTS_HPP_INCLUDED
#define CXXOPTS_HPP_INCLUDED
#include <cstring>
#include <exception>
#include <limits>
#include <initializer_list>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <algorithm>
#include <locale>
#ifdef CXXOPTS_NO_EXCEPTIONS
#include <iostream>
#endif
#if defined(__GNUC__) && !defined(__clang__)
# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49
# define CXXOPTS_NO_REGEX true
# endif
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
#define CXXOPTS_LINKONCE __declspec(selectany) extern
#else
#define CXXOPTS_LINKONCE_CONST
#define CXXOPTS_LINKONCE
#endif
#ifndef CXXOPTS_NO_REGEX
# include <regex>
#endif // CXXOPTS_NO_REGEX
// Nonstandard before C++17, which is coincidentally what we also need for <optional>
#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# ifdef __cpp_lib_optional
# define CXXOPTS_HAS_OPTIONAL
# endif
# 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
#define CXXOPTS_NODISCARD
#endif
#ifndef CXXOPTS_VECTOR_DELIMITER
#define CXXOPTS_VECTOR_DELIMITER ','
#endif
#define CXXOPTS__VERSION_MAJOR 3
#define CXXOPTS__VERSION_MINOR 1
#define CXXOPTS__VERSION_PATCH 1
#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
#define CXXOPTS_NULL_DEREF_IGNORE
#endif
#if defined(__GNUC__)
#define DO_PRAGMA(x) _Pragma(#x)
#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push)
#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop)
#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x)
#else
// define other compilers here if needed
#define CXXOPTS_DIAGNOSTIC_PUSH
#define CXXOPTS_DIAGNOSTIC_POP
#define CXXOPTS_IGNORE_WARNING(x)
#endif
#ifdef CXXOPTS_NO_RTTI
#define CXXOPTS_RTTI_CAST static_cast
#else
#define CXXOPTS_RTTI_CAST dynamic_cast
#endif
namespace cxxopts {
static constexpr struct {
uint8_t major, minor, patch;
} version = {
CXXOPTS__VERSION_MAJOR,
CXXOPTS__VERSION_MINOR,
CXXOPTS__VERSION_PATCH
};
} // namespace cxxopts
//when we ask cxxopts to use Unicode, help strings are processed using ICU,
//which results in the correct lengths being computed for strings when they
//are formatted for the help output
//it is necessary to make sure that <unicode/unistr.h> can be found by the
//compiler, and that icu-uc is linked in to the binary.
#ifdef CXXOPTS_USE_UNICODE
#include <unicode/unistr.h>
namespace cxxopts {
using String = icu::UnicodeString;
inline String toLocalString(std::string s)
{
return icu::UnicodeString::fromUTF8(std::move(s));
}
// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:
// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
// This will be ignored under other compilers like LLVM clang.
class UnicodeStringIterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = int32_t;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) : s(string), i(pos) { }
value_type operator*() const {
return s->char32At(i);
}
bool operator==(const UnicodeStringIterator& rhs) const {
return s == rhs.s && i == rhs.i;
}
bool operator!=(const UnicodeStringIterator& rhs) const {
return !(*this == rhs);
}
UnicodeStringIterator& operator++() {
++i;
return *this;
}
UnicodeStringIterator operator+(int32_t v) {
return UnicodeStringIterator(s, i + v);
}
private:
const icu::UnicodeString* s;
int32_t i;
};
CXXOPTS_DIAGNOSTIC_POP
inline String& stringAppend(String&s, String a) {
return s.append(std::move(a));
}
inline String& stringAppend(String& s, std::size_t n, UChar32 c) {
for (std::size_t i = 0; i != n; ++i) {
s.append(c);
}
return s;
}
template <typename Iterator>
String& stringAppend(String& s, Iterator begin, Iterator end) {
while (begin != end) {
s.append(*begin);
++begin;
}
return s;
}
inline size_t stringLength(const String& s) {
return static_cast<size_t>(s.length());
}
inline std::string toUTF8String(const String& s) {
std::string result;
s.toUTF8String(result);
return result;
}
inline bool empty(const String& s) {
return s.isEmpty();
}
} // namespace cxxopts
namespace std {
inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) {
return cxxopts::UnicodeStringIterator(&s, 0);
}
inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s)
{
return cxxopts::UnicodeStringIterator(&s, s.length());
}
} // namespace std
//ifdef CXXOPTS_USE_UNICODE
#else
namespace cxxopts {
using String = std::string;
template <typename T>
T toLocalString(T&& t) {
return std::forward<T>(t);
}
inline std::size_t stringLength(const String& s) {
return s.length();
}
inline String& stringAppend(String&s, const String& a)
{
return s.append(a);
}
inline String& stringAppend(String& s, std::size_t n, char c) {
return s.append(n, c);
}
template <typename Iterator>
String& stringAppend(String& s, Iterator begin, Iterator end) {
return s.append(begin, end);
}
template <typename T>
std::string toUTF8String(T&& t) {
return std::forward<T>(t);
}
inline bool empty(const std::string& s) {
return s.empty();
}
} // namespace cxxopts
//ifdef CXXOPTS_USE_UNICODE
#endif
namespace cxxopts {
namespace {
CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
} // namespace
// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
// want to silence it: warning: base class 'class
// std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual
// destructor This will be ignored under other compilers like LLVM clang.
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor")
// some older versions of GCC warn under this warning
CXXOPTS_IGNORE_WARNING("-Weffc++")
class Value : public std::enable_shared_from_this<Value> {
public:
virtual ~Value() = default;
virtual 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;
virtual void parse() const = 0;
virtual bool has_default() const = 0;
virtual bool is_container() const = 0;
virtual bool has_implicit() const = 0;
virtual std::string get_default_value() const = 0;
virtual std::string get_implicit_value() const = 0;
virtual std::shared_ptr<Value> default_value(const std::string& value) = 0;
virtual std::shared_ptr<Value> implicit_value(const std::string& value) = 0;
virtual std::shared_ptr<Value> no_implicit_value() = 0;
virtual bool is_boolean() const = 0;
};
CXXOPTS_DIAGNOSTIC_POP
namespace exceptions {
class exception : public std::exception {
public:
explicit exception(std::string message) : m_message(std::move(message)) { }
CXXOPTS_NODISCARD
const char* what() const noexcept override {
return m_message.c_str();
}
private:
std::string m_message;
};
class specification : public exception {
public:
explicit specification(const std::string& message) : exception(message) { }
};
class parsing : public exception {
public:
explicit parsing(const std::string& message) : exception(message) { }
};
class option_already_exists : public specification {
public:
explicit option_already_exists(const std::string& option)
: specification("Option " + LQUOTE + option + RQUOTE + " already exists") { }
};
class invalid_option_format : public specification {
public:
explicit invalid_option_format(const std::string& format)
: specification("Invalid option format " + LQUOTE + format + RQUOTE) { }
};
class invalid_option_syntax : public parsing {
public:
explicit invalid_option_syntax(const std::string& text)
: parsing("Argument " + LQUOTE + text + RQUOTE + " starts with a - but has incorrect syntax") { }
};
class no_such_option : public parsing {
public:
explicit no_such_option(const std::string& option)
: parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") { }
};
class missing_argument : public parsing {
public:
explicit missing_argument(const std::string& option)
: parsing("Option " + LQUOTE + option + RQUOTE + " is missing an argument") { }
};
class option_requires_argument : public parsing {
public:
explicit option_requires_argument(const std::string& option)
: parsing("Option " + LQUOTE + option + RQUOTE + " requires an argument") { }
};
class gratuitous_argument_for_option : public parsing {
public:
gratuitous_argument_for_option(const std::string& option, const std::string& arg)
: parsing("Option " + LQUOTE + option + RQUOTE +
" does not take an argument, but argument " +
LQUOTE + arg + RQUOTE + " given")
{ }
};
class requested_option_not_present : public parsing {
public:
explicit requested_option_not_present(const std::string& option)
: parsing("Option " + LQUOTE + option + RQUOTE + " not present")
{ }
};
class option_has_no_value : public exception {
public:
explicit option_has_no_value(const std::string& option)
: exception(!option.empty() ?
("Option " + LQUOTE + option + RQUOTE + " has no value") :
"Option has no value")
{ }
};
class incorrect_argument_type : public parsing {
public:
explicit incorrect_argument_type (const std::string& arg)
: parsing("Argument " + LQUOTE + arg + RQUOTE + " failed to parse") { }
};
} // namespace exceptions
template <typename T> void throw_or_mimic(const std::string& text) {
static_assert(std::is_base_of<std::exception, T>::value,
"throw_or_mimic only works on std::exception and "
"deriving classes");
#ifndef CXXOPTS_NO_EXCEPTIONS
// If CXXOPTS_NO_EXCEPTIONS is not defined, just throw
throw T{text};
#else
// Otherwise manually instantiate the exception, print what() to stderr,
// and exit
T exception{text};
std::cerr << exception.what() << std::endl;
std::exit(EXIT_FAILURE);
#endif
}
using OptionNames = std::vector<std::string>;
namespace values {
namespace parser_tool {
struct IntegerDesc
{
std::string negative = "";
std::string base = "";
std::string value = "";
};
struct ArguDesc {
std::string arg_name = "";
bool grouping = false;
bool set_value = false;
std::string value = "";
};
#ifdef CXXOPTS_NO_REGEX
inline IntegerDesc SplitInteger(const std::string &text) {
if (text.empty()) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
IntegerDesc desc;
const char *pdata = text.c_str();
if (*pdata == '-') {
pdata += 1;
desc.negative = "-";
}
if (strncmp(pdata, "0x", 2) == 0) {
pdata += 2;
desc.base = "0x";
}
if (*pdata != '\0') {
desc.value = std::string(pdata);
}
else {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
return desc;
}
inline bool IsTrueText(const std::string &text) {
const char *pdata = text.c_str();
if (*pdata == 't' || *pdata == 'T') {
pdata += 1;
if (strncmp(pdata, "rue\0", 4) == 0) {
return true;
}
}
else if (strncmp(pdata, "1\0", 2) == 0) {
return true;
}
return false;
}
inline bool IsFalseText(const std::string &text) {
const char *pdata = text.c_str();
if (*pdata == 'f' || *pdata == 'F') {
pdata += 1;
if (strncmp(pdata, "alse\0", 5) == 0) {
return true;
}
}
else if (strncmp(pdata, "0\0", 2) == 0) {
return true;
}
return false;
}
inline OptionNames split_option_names(const std::string &text) {
OptionNames split_names;
std::string::size_type token_start_pos = 0;
auto length = text.length();
if (length == 0) {
throw_or_mimic<exceptions::invalid_option_format>(text);
}
while (token_start_pos < length) {
const auto &npos = std::string::npos;
auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
if (next_non_space_pos == npos) {
throw_or_mimic<exceptions::invalid_option_format>(text);
}
token_start_pos = next_non_space_pos;
auto next_delimiter_pos = text.find(',', token_start_pos);
if (next_delimiter_pos == token_start_pos) {
throw_or_mimic<exceptions::invalid_option_format>(text);
}
if (next_delimiter_pos == npos) {
next_delimiter_pos = length;
}
auto token_length = next_delimiter_pos - token_start_pos;
// validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
{
const char* option_name_valid_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"_-.?";
if (!std::isalnum(text[token_start_pos], std::locale::classic()) ||
text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
throw_or_mimic<exceptions::invalid_option_format>(text);
}
}
split_names.emplace_back(text.substr(token_start_pos, token_length));
token_start_pos = next_delimiter_pos + 1;
}
return split_names;
}
inline ArguDesc ParseArgument(const char *arg, bool &matched) {
ArguDesc argu_desc;
const char *pdata = arg;
matched = false;
if (strncmp(pdata, "--", 2) == 0) {
pdata += 2;
if (isalnum(*pdata, std::locale::classic())) {
argu_desc.arg_name.push_back(*pdata);
pdata += 1;
while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') {
argu_desc.arg_name.push_back(*pdata);
pdata += 1;
}
if (argu_desc.arg_name.length() > 1) {
if (*pdata == '=') {
argu_desc.set_value = true;
pdata += 1;
if (*pdata != '\0') {
argu_desc.value = std::string(pdata);
}
matched = true;
}
else if (*pdata == '\0') {
matched = true;
}
}
}
}
else if (strncmp(pdata, "-", 1) == 0) {
pdata += 1;
argu_desc.grouping = true;
while (isalnum(*pdata, std::locale::classic())) {
argu_desc.arg_name.push_back(*pdata);
pdata += 1;
}
matched = !argu_desc.arg_name.empty() && *pdata == '\0';
}
return argu_desc;
}
#else // CXXOPTS_NO_REGEX
namespace {
CXXOPTS_LINKONCE
const char* const integer_pattern = "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
CXXOPTS_LINKONCE
const char* const truthy_pattern = "(t|T)(rue)?|1";
CXXOPTS_LINKONCE
const char* const falsy_pattern = "(f|F)(alse)?|0";
CXXOPTS_LINKONCE
const char* const option_pattern = "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
CXXOPTS_LINKONCE
const char* const option_specifier_pattern = "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
CXXOPTS_LINKONCE
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_matcher);
if (match.length() == 0) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
IntegerDesc desc;
desc.negative = match[1];
desc.base = match[2];
desc.value = match[3];
if (match.length(4) > 0) {
desc.base = match[5];
desc.value = "0";
return desc;
}
return desc;
}
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_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_matcher);
return !result.empty();
}
// Gets the option names specified via a single, comma-separated string,
// and returns the separate, space-discarded, non-empty names
// (without considering which or how many are single-character)
inline OptionNames split_option_names(const std::string &text) {
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_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();
ArguDesc argu_desc;
if (matched) {
argu_desc.arg_name = result[1].str();
argu_desc.set_value = result[2].length() > 0;
argu_desc.value = result[3].str();
if (result[4].length() > 0) {
argu_desc.grouping = true;
argu_desc.arg_name = result[4].str();
}
}
return argu_desc;
}
#endif // CXXOPTS_NO_REGEX
#undef CXXOPTS_NO_REGEX
} // namespace parser_tool
namespace detail {
template <typename T, bool B>
struct SignedCheck;
template <typename T>
struct SignedCheck<T, true> {
template <typename U> void operator()(bool negative, U u, const std::string& text) {
if (negative) {
if (u > static_cast<U>((std::numeric_limits<T>::min)())) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
}
else {
if (u > static_cast<U>((std::numeric_limits<T>::max)())) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
}
}
};
template <typename T>
struct SignedCheck<T, false> {
template <typename U> void operator()(bool, U, const std::string&) const {}
};
template <typename T, typename U>
void check_signed_range(bool negative, U value, const std::string& text) {
SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);
}
} // namespace detail
template <typename R, typename T>
void checked_negate(R& r, T&& t, const std::string&, std::true_type) {
// if we got to here, then `t` is a positive number that fits into
// `R`. So to avoid MSVC C4146, we first cast it to `R`.
// See https://github.com/jarro2783/cxxopts/issues/62 for more details.
r = static_cast<R>(-static_cast<R>(t-1)-1);
}
template <typename R, typename T>
void checked_negate(R&, T&&, const std::string& text, std::false_type) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
template <typename T>
void integer_parser(const std::string& text, T& value) {
parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);
using US = typename std::make_unsigned<T>::type;
constexpr bool is_signed = std::numeric_limits<T>::is_signed;
const bool negative = int_desc.negative.length() > 0;
const uint8_t base = int_desc.base.length() > 0 ? 16 : 10;
const std::string & value_match = int_desc.value;
US result = 0;
for (char ch : value_match) {
US digit = 0;
if (ch >= '0' && ch <= '9') {
digit = static_cast<US>(ch - '0');
}
else if (base == 16 && ch >= 'a' && ch <= 'f') {
digit = static_cast<US>(ch - 'a' + 10);
}
else if (base == 16 && ch >= 'A' && ch <= 'F') {
digit = static_cast<US>(ch - 'A' + 10);
}
else {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
const US next = static_cast<US>(result * base + digit);
if (result > next) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
result = next;
}
detail::check_signed_range<T>(negative, result, text);
if (negative) {
checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
}
else {
value = static_cast<T>(result);
}
}
template <typename T>
void stringstream_parser(const std::string& text, T& value) {
std::stringstream in(text);
in >> value;
if (!in) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
}
template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void parse_value(const std::string& text, T& value) {
integer_parser(text, value);
}
inline void parse_value(const std::string& text, bool& value) {
if (parser_tool::IsTrueText(text)) {
value = true;
return;
}
if (parser_tool::IsFalseText(text)) {
value = false;
return;
}
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
inline void parse_value(const std::string& text, std::string& value) {
value = text;
}
// The fallback parser. It uses the stringstream parser to parse all types
// that have not been overloaded explicitly. It has to be placed in the
// source code before all other more specialized templates.
template <typename T, typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr>
void 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));
}
}
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));
}
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
void parse_value(const std::string& text, std::optional<T>& value) {
T result;
parse_value(text, result);
value = std::move(result);
}
#endif
inline void parse_value(const std::string& text, char& c) {
if (text.length() != 1) {
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
c = text[0];
}
template <typename T>
struct type_is_container {
static constexpr bool value = false;
};
template <typename T>
struct type_is_container<std::vector<T>> {
static constexpr bool value = true;
};
template <typename T>
class abstract_value : public Value
{
using Self = abstract_value<T>;
public:
abstract_value()
: m_result(std::make_shared<T>()), m_store(m_result.get()) { }
explicit abstract_value(T* t) : m_store(t) { }
~abstract_value() override = default;
abstract_value& operator=(const abstract_value&) = default;
abstract_value(const abstract_value& rhs) {
if (rhs.m_result) {
m_result = std::make_shared<T>();
m_store = m_result.get();
}
else {
m_store = rhs.m_store;
}
m_default = rhs.m_default;
m_implicit = rhs.m_implicit;
m_default_value = rhs.m_default_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 {
parse_value(text, *m_store);
}
bool is_container() const override {
return type_is_container<T>::value;
}
void parse() const override {
parse_value(m_default_value, *m_store);
}
bool has_default() const override {
return m_default;
}
bool has_implicit() const override {
return m_implicit;
}
std::shared_ptr<Value> default_value(const std::string& value) override {
m_default = true;
m_default_value = value;
return shared_from_this();
}
std::shared_ptr<Value> implicit_value(const std::string& value) override {
m_implicit = true;
m_implicit_value = value;
return shared_from_this();
}
std::shared_ptr<Value> no_implicit_value() override {
m_implicit = false;
return shared_from_this();
}
std::string get_default_value() const override {
return m_default_value;
}
std::string get_implicit_value() const override {
return m_implicit_value;
}
bool is_boolean() const override {
return std::is_same<T, bool>::value;
}
const T& get() const {
if (m_store == nullptr) {
return *m_result;
}
return *m_store;
}
protected:
std::shared_ptr<T> m_result{};
T* m_store{};
bool m_default = false;
bool m_implicit = false;
std::string m_default_value{};
std::string m_implicit_value{};
};
template <typename T>
class standard_value : public abstract_value<T> {
public:
using abstract_value<T>::abstract_value;
CXXOPTS_NODISCARD
std::shared_ptr<Value> clone() const override {
return std::make_shared<standard_value<T>>(*this);
}
};
template <>
class standard_value<bool> : public abstract_value<bool>
{
public:
~standard_value() override = default;
standard_value() {
set_default_and_implicit();
}
explicit standard_value(bool* b) : abstract_value(b) {
m_implicit = true;
m_implicit_value = "true";
}
std::shared_ptr<Value> clone() const override {
return std::make_shared<standard_value<bool>>(*this);
}
private:
void set_default_and_implicit() {
m_default = true;
m_default_value = "false";
m_implicit = true;
m_implicit_value = "true";
}
};
} // namespace values
template <typename T>
std::shared_ptr<Value> value() {
return std::make_shared<values::standard_value<T>>();
}
template <typename T>
std::shared_ptr<Value> value(T& t) {
return std::make_shared<values::standard_value<T>>(&t);
}
class OptionAdder;
CXXOPTS_NODISCARD
inline const std::string& first_or_empty(const OptionNames& long_names) {
static const std::string empty{""};
return long_names.empty() ? empty : long_names.front();
}
class OptionDetails
{
public:
OptionDetails (std::string short_, OptionNames long_, String desc, std::shared_ptr<const Value> val)
: m_short(std::move(short_)), m_long(std::move(long_)), m_desc(std::move(desc)), m_value(std::move(val)), m_count(0) {
m_hash = std::hash<std::string>{}(first_long_name() + m_short);
}
OptionDetails(const OptionDetails& rhs)
: m_desc(rhs.m_desc)
, m_value(rhs.m_value->clone())
, m_count(rhs.m_count) { }
OptionDetails(OptionDetails&& rhs) = default;
CXXOPTS_NODISCARD
const String& description() const {
return m_desc;
}
CXXOPTS_NODISCARD
const Value& value() const {
return *m_value;
}
CXXOPTS_NODISCARD
std::shared_ptr<Value> make_storage() const {
return m_value->clone();
}
CXXOPTS_NODISCARD
const std::string& short_name() const {
return m_short;
}
CXXOPTS_NODISCARD
const std::string& first_long_name() const {
return first_or_empty(m_long);
}
CXXOPTS_NODISCARD
const std::string& essential_name() const {
return m_long.empty() ? m_short : m_long.front();
}
CXXOPTS_NODISCARD
const OptionNames & long_names() const {
return m_long;
}
std::size_t hash() const {
return m_hash;
}
private:
std::string m_short{};
OptionNames m_long{};
String m_desc{};
std::shared_ptr<const Value> m_value{};
int m_count;
std::size_t m_hash{};
};
struct HelpOptionDetails {
std::string s;
OptionNames l;
String desc;
bool has_default;
std::string default_value;
bool has_implicit;
std::string implicit_value;
std::string arg_help;
bool is_container;
bool is_boolean;
};
struct HelpGroupDetails {
std::string name{};
std::string description{};
std::vector<HelpOptionDetails> options{};
};
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(const std::shared_ptr<const OptionDetails>& details, const std::string& text) {
ensure_value(details);
++m_count;
m_value->parse(text);
m_long_names = &details->long_names();
}
void parse_default(const std::shared_ptr<const OptionDetails>& details) {
ensure_value(details);
m_default = true;
m_long_names = &details->long_names();
m_value->parse();
}
void parse_no_value(const std::shared_ptr<const OptionDetails>& details) {
m_long_names = &details->long_names();
}
#if defined(CXXOPTS_NULL_DEREF_IGNORE)
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Wnull-dereference")
#endif
CXXOPTS_NODISCARD
std::size_t count() const noexcept {
return m_count;
}
#if defined(CXXOPTS_NULL_DEREF_IGNORE)
CXXOPTS_DIAGNOSTIC_POP
#endif
// TODO: maybe default options should count towards the number of arguments
CXXOPTS_NODISCARD
bool has_default() const noexcept {
return m_default;
}
template <typename T>
const T& as() const {
if (m_value == nullptr) {
throw_or_mimic<exceptions::option_has_no_value>(
m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
}
return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
}
private:
void ensure_value(const std::shared_ptr<const OptionDetails>& details) {
if (m_value == nullptr) {
m_value = details->make_storage();
}
}
const OptionNames * m_long_names = nullptr;
// Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
// where the key has the string we point to.
std::shared_ptr<Value> m_value{};
std::size_t m_count = 0;
bool m_default = false;
};
class KeyValue
{
public:
KeyValue(std::string key_, std::string value_) noexcept
: m_key(std::move(key_)), m_value(std::move(value_)) { }
CXXOPTS_NODISCARD
const std::string& key() const {
return m_key;
}
CXXOPTS_NODISCARD
const std::string& value() const {
return m_value;
}
template <typename T>
T as() const {
T result;
values::parse_value(m_value, result);
return result;
}
private:
std::string m_key;
std::string m_value;
};
using ParsedHashMap = std::unordered_map<std::size_t, OptionValue>;
using NameHashMap = std::unordered_map<std::string, std::size_t>;
class ParseResult {
public:
class Iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = KeyValue;
using difference_type = void;
using pointer = const KeyValue*;
using reference = const KeyValue&;
Iterator() = default;
Iterator(const Iterator&) = default;
// GCC complains about m_iter not being initialised in the member
// initializer list
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Weffc++")
Iterator(const ParseResult *pr, bool end=false)
: m_pr(pr) {
if (end) {
m_sequential = false;
m_iter = m_pr->m_defaults.end();
}
else {
m_sequential = true;
m_iter = m_pr->m_sequential.begin();
if (m_iter == m_pr->m_sequential.end()) {
m_sequential = false;
m_iter = m_pr->m_defaults.begin();
}
}
}
CXXOPTS_DIAGNOSTIC_POP
Iterator& operator++() {
++m_iter;
if(m_sequential && m_iter == m_pr->m_sequential.end()) {
m_sequential = false;
m_iter = m_pr->m_defaults.begin();
return *this;
}
return *this;
}
Iterator operator++(int) {
Iterator retval = *this;
++(*this);
return retval;
}
bool operator==(const Iterator& other) const {
return (m_sequential == other.m_sequential) && (m_iter == other.m_iter);
}
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
const KeyValue& operator*() {
return *m_iter;
}
const KeyValue* operator->() {
return m_iter.operator->();
}
private:
const ParseResult* m_pr;
std::vector<KeyValue>::const_iterator m_iter;
bool m_sequential = true;
};
ParseResult() = default;
ParseResult(const ParseResult&) = default;
ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential,
std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args)
: m_keys(std::move(keys))
, m_values(std::move(values))
, m_sequential(std::move(sequential))
, m_defaults(std::move(default_opts))
, m_unmatched(std::move(unmatched_args))
{
}
ParseResult& operator=(ParseResult&&) = default;
ParseResult& operator=(const ParseResult&) = default;
Iterator begin() const {
return Iterator(this);
}
Iterator end() const {
return Iterator(this, true);
}
std::size_t count(const std::string& o) const {
auto iter = m_keys.find(o);
if (iter == m_keys.end()) {
return 0;
}
auto viter = m_values.find(iter->second);
if (viter == m_values.end()) {
return 0;
}
return viter->second.count();
}
const OptionValue& operator[](const std::string& option) const {
auto iter = m_keys.find(option);
if (iter == m_keys.end()) {
throw_or_mimic<exceptions::requested_option_not_present>(option);
}
auto viter = m_values.find(iter->second);
if (viter == m_values.end()) {
throw_or_mimic<exceptions::requested_option_not_present>(option);
}
return viter->second;
}
const std::vector<KeyValue>& arguments() const {
return m_sequential;
}
const std::vector<std::string>& unmatched() const {
return m_unmatched;
}
const std::vector<KeyValue>& defaults() const {
return m_defaults;
}
const std::string arguments_string() const{
std::string result;
for(const auto& kv: m_sequential) {
result += kv.key() + " = " + kv.value() + "\n";
}
for(const auto& kv: m_defaults) {
result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n";
}
return result;
}
private:
NameHashMap m_keys{};
ParsedHashMap m_values{};
std::vector<KeyValue> m_sequential{};
std::vector<KeyValue> m_defaults{};
std::vector<std::string> m_unmatched{};
};
struct Option
{
Option(std::string opts,
std::string desc,
std::shared_ptr<const Value> value = ::cxxopts::value<bool>(),
std::string arg_help = ""
)
: opts_(std::move(opts))
, desc_(std::move(desc))
, value_(std::move(value))
, arg_help_(std::move(arg_help)) { }
std::string opts_;
std::string desc_;
std::shared_ptr<const Value> value_;
std::string arg_help_;
};
using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
using PositionalList = std::vector<std::string>;
using PositionalListIterator = PositionalList::const_iterator;
class OptionParser
{
public:
OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
: m_options(options)
, m_positional(positional)
, m_allow_unrecognised(allow_unrecognised) { }
ParseResult parse(int argc, const char* const* argv);
bool consume_positional(const std::string& a, PositionalListIterator& next);
void checked_parse_arg(
int argc,
const char* const* argv,
int& current,
const std::shared_ptr<OptionDetails>& value,
const std::string& name
);
void add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg);
void parse_option(const std::shared_ptr<OptionDetails>& value,
const std::string& name,
const std::string& arg = "");
void parse_default(const std::shared_ptr<OptionDetails>& details);
void parse_no_value(const std::shared_ptr<OptionDetails>& details);
private:
void finalise_aliases();
const OptionMap& m_options;
const PositionalList& m_positional;
std::vector<KeyValue> m_sequential{};
std::vector<KeyValue> m_defaults{};
bool m_allow_unrecognised;
ParsedHashMap m_parsed{};
NameHashMap m_keys{};
};
class Options
{
public:
explicit Options(std::string program_name, std::string help_string = "")
: m_program(std::move(program_name))
, m_help_string(toLocalString(std::move(help_string)))
, m_custom_help("[OPTION...]")
, m_positional_help("positional parameters")
, m_show_positional(false)
, m_allow_unrecognised(false)
, m_width(76)
, m_tab_expansion(false)
, m_options(std::make_shared<OptionMap>())
{
}
Options& positional_help(std::string help_text) {
m_positional_help = std::move(help_text);
return *this;
}
Options& custom_help(std::string help_text) {
m_custom_help = std::move(help_text);
return *this;
}
Options& show_positional_help() {
m_show_positional = true;
return *this;
}
Options& allow_unrecognised_options() {
m_allow_unrecognised = true;
return *this;
}
Options& set_width(std::size_t width) {
m_width = width;
return *this;
}
Options& set_tab_expansion(bool expansion=true) {
m_tab_expansion = expansion;
return *this;
}
ParseResult parse(int argc, const char* const* argv);
OptionAdder add_options(std::string group = "");
void add_options(const std::string& group, std::initializer_list<Option> options);
void add_option(const std::string& group, const Option& option);
void add_option(const std::string& group,
const std::string& s,
const OptionNames& l,
std::string desc,
const std::shared_ptr<const Value>& value,
std::string arg_help);
void add_option(const std::string& group,
const std::string& short_name,
const std::string& single_long_name,
std::string desc,
const std::shared_ptr<const Value>& value,
std::string arg_help) {
OptionNames long_names;
long_names.emplace_back(single_long_name);
add_option(group, short_name, long_names, desc, value, arg_help);
}
//parse positional arguments into the given option
void parse_positional(std::string option);
void parse_positional(std::vector<std::string> options);
void parse_positional(std::initializer_list<std::string> options);
template <typename Iterator>
void parse_positional(Iterator begin, Iterator end) {
parse_positional(std::vector<std::string>{begin, end});
}
std::string help(const std::vector<std::string>& groups = {}, bool print_usage=true) const;
std::vector<std::string> groups() const;
const HelpGroupDetails& group_help(const std::string& group) const;
const std::string& program() const {
return m_program;
}
private:
void add_one_option( const std::string& option, const std::shared_ptr<OptionDetails>& details);
String help_one_group(const std::string& group) const;
void generate_group_help(String& result, const std::vector<std::string>& groups) const;
void generate_all_groups_help(String& result) const;
std::string m_program{};
String m_help_string{};
std::string m_custom_help{};
std::string m_positional_help{};
bool m_show_positional;
bool m_allow_unrecognised;
std::size_t m_width;
bool m_tab_expansion;
std::shared_ptr<OptionMap> m_options;
std::vector<std::string> m_positional{};
std::unordered_set<std::string> m_positional_set{};
//mapping from groups to help options
std::map<std::string, HelpGroupDetails> m_help{};
};
class OptionAdder
{
public:
OptionAdder(Options& options, std::string group)
: m_options(options), m_group(std::move(group)) { }
OptionAdder& operator() (const std::string& opts,
const std::string& desc,
const std::shared_ptr<const Value>& value = ::cxxopts::value<bool>(),
std::string arg_help = "");
private:
Options& m_options;
std::string m_group;
};
namespace {
constexpr std::size_t OPTION_LONGEST = 30;
constexpr std::size_t OPTION_DESC_GAP = 2;
String format_option(const HelpOptionDetails& o) {
const auto& s = o.s;
const auto& l = first_or_empty(o.l);
String result = " ";
if (!s.empty()) {
result += "-" + toLocalString(s);
if (!l.empty()) {
result += ",";
}
}
else {
result += " ";
}
if (!l.empty()) {
result += " --" + toLocalString(l);
}
auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg";
if (!o.is_boolean) {
if (o.has_implicit) {
result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
}
else {
result += " " + arg;
}
}
return result;
}
String format_description(const HelpOptionDetails& o, std::size_t start, std::size_t allowed, bool tab_expansion) {
auto desc = o.desc;
if (o.has_default && (!o.is_boolean || o.default_value != "false")) {
if(!o.default_value.empty()) {
desc += toLocalString(" (default: " + o.default_value + ")");
}
else {
desc += toLocalString(" (default: \"\")");
}
}
String result;
if (tab_expansion) {
String desc2;
auto size = std::size_t{ 0 };
for (auto c = std::begin(desc); c != std::end(desc); ++c) {
if (*c == '\n') {
desc2 += *c;
size = 0;
}
else if (*c == '\t') {
auto skip = 8 - size % 8;
stringAppend(desc2, skip, ' ');
size += skip;
}
else {
desc2 += *c;
++size;
}
}
desc = desc2;
}
desc += " ";
auto current = std::begin(desc);
auto previous = current;
auto startLine = current;
auto lastSpace = current;
auto size = std::size_t{};
bool appendNewLine;
bool onlyWhiteSpace = true;
while (current != std::end(desc)) {
appendNewLine = false;
if (*previous == ' ' || *previous == '\t') {
lastSpace = current;
}
if (*current != ' ' && *current != '\t') {
onlyWhiteSpace = false;
}
while (*current == '\n') {
previous = current;
++current;
appendNewLine = true;
}
if (!appendNewLine && size >= allowed) {
if (lastSpace != startLine) {
current = lastSpace;
previous = current;
}
appendNewLine = true;
}
if (appendNewLine) {
stringAppend(result, startLine, current);
startLine = current;
lastSpace = current;
if (*previous != '\n') {
stringAppend(result, "\n");
}
stringAppend(result, start, ' ');
if (*previous != '\n') {
stringAppend(result, lastSpace, current);
}
onlyWhiteSpace = true;
size = 0;
}
previous = current;
++current;
++size;
}
//append whatever is left but ignore whitespace
if (!onlyWhiteSpace) {
stringAppend(result, startLine, previous);
}
return result;
}
} // namespace
inline void Options::add_options(const std::string &group, std::initializer_list<Option> options) {
OptionAdder option_adder(*this, group);
for (const auto &option: options) {
option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);
}
}
inline OptionAdder Options::add_options(std::string group) {
return OptionAdder(*this, std::move(group));
}
inline OptionAdder& OptionAdder::operator() (const std::string& opts,
const std::string& desc,
const std::shared_ptr<const Value>& value,
std::string arg_help) {
OptionNames option_names = values::parser_tool::split_option_names(opts);
// Note: All names will be non-empty; but we must separate the short
// (length-1) and longer names
std::string short_name {""};
auto first_short_name_iter =
std::partition(option_names.begin(), option_names.end(),
[&](const std::string& name) { return name.length() > 1; }
);
auto num_length_1_names = (option_names.end() - first_short_name_iter);
switch(num_length_1_names) {
case 1:
short_name = *first_short_name_iter;
option_names.erase(first_short_name_iter);
CXXOPTS_FALLTHROUGH;
case 0:
break;
default:
throw_or_mimic<exceptions::invalid_option_format>(opts);
};
m_options.add_option(m_group, short_name, option_names, desc, value, std::move(arg_help));
return *this;
}
inline void OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details) {
// TODO: remove the duplicate code here
auto& store = m_parsed[details->hash()];
store.parse_default(details);
m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());
}
inline void OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details) {
auto& store = m_parsed[details->hash()];
store.parse_no_value(details);
}
inline void OptionParser::parse_option(const std::shared_ptr<OptionDetails>& value,
const std::string& /*name*/,
const std::string& arg) {
auto hash = value->hash();
auto& result = m_parsed[hash];
result.parse(value, arg);
m_sequential.emplace_back(value->essential_name(), arg);
}
inline void OptionParser::checked_parse_arg(int argc,
const char* const* argv,
int& current,
const std::shared_ptr<OptionDetails>& value,
const std::string& name) {
if (current + 1 >= argc) {
if (value->value().has_implicit()) {
parse_option(value, name, value->value().get_implicit_value());
}
else {
throw_or_mimic<exceptions::missing_argument>(name);
}
}
else {
if (value->value().has_implicit()) {
parse_option(value, name, value->value().get_implicit_value());
}
else {
parse_option(value, name, argv[current + 1]);
++current;
}
}
}
inline void OptionParser::add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg) {
auto hash = value->hash();
auto& result = m_parsed[hash];
result.add(value, arg);
m_sequential.emplace_back(value->essential_name(), arg);
}
inline bool OptionParser::consume_positional(const std::string& a, PositionalListIterator& next) {
while (next != m_positional.end()) {
auto iter = m_options.find(*next);
if (iter != m_options.end()) {
if (!iter->second->value().is_container()) {
auto& result = m_parsed[iter->second->hash()];
if (result.count() == 0) {
add_to_option(iter->second, a);
++next;
return true;
}
++next;
continue;
}
add_to_option(iter->second, a);
return true;
}
throw_or_mimic<exceptions::no_such_option>(*next);
}
return false;
}
inline void Options::parse_positional(std::string option) {
parse_positional(std::vector<std::string>{std::move(option)});
}
inline void Options::parse_positional(std::vector<std::string> options) {
m_positional = std::move(options);
m_positional_set.insert(m_positional.begin(), m_positional.end());
}
inline void Options::parse_positional(std::initializer_list<std::string> options) {
parse_positional(std::vector<std::string>(options));
}
inline ParseResult Options::parse(int argc, const char* const* argv) {
OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
return parser.parse(argc, argv);
}
inline ParseResult OptionParser::parse(int argc, const char* const* argv) {
int current = 1;
bool consume_remaining = false;
auto next_positional = m_positional.begin();
std::vector<std::string> unmatched;
while (current != argc) {
if (strcmp(argv[current], "--") == 0) {
consume_remaining = true;
++current;
break;
}
bool matched = false;
values::parser_tool::ArguDesc argu_desc =
values::parser_tool::ParseArgument(argv[current], matched);
if (!matched) {
//not a flag
// but if it starts with a `-`, then it's an error
if (argv[current][0] == '-' && argv[current][1] != '\0') {
if (!m_allow_unrecognised) {
throw_or_mimic<exceptions::invalid_option_syntax>(argv[current]);
}
}
//if true is returned here then it was consumed, otherwise it is
//ignored
if (consume_positional(argv[current], next_positional)) {
}
else {
unmatched.emplace_back(argv[current]);
}
//if we return from here then it was parsed successfully, so continue
}
else {
//short or long option?
if (argu_desc.grouping) {
const std::string& s = argu_desc.arg_name;
for (std::size_t i = 0; i != s.size(); ++i) {
std::string name(1, s[i]);
auto iter = m_options.find(name);
if (iter == m_options.end()) {
if (m_allow_unrecognised) {
unmatched.push_back(std::string("-") + s[i]);
continue;
}
//error
throw_or_mimic<exceptions::no_such_option>(name);
}
auto value = iter->second;
if (i + 1 == s.size()) {
//it must be the last argument
checked_parse_arg(argc, argv, current, value, name);
}
else if (value->value().has_implicit()) {
parse_option(value, name, value->value().get_implicit_value());
}
else if (i + 1 < s.size()) {
std::string arg_value = s.substr(i + 1);
parse_option(value, name, arg_value);
break;
}
else {
//error
throw_or_mimic<exceptions::option_requires_argument>(name);
}
}
}
else if (argu_desc.arg_name.length() != 0) {
const std::string& name = argu_desc.arg_name;
auto iter = m_options.find(name);
if (iter == m_options.end()) {
if (m_allow_unrecognised) {
// keep unrecognised options in argument list, skip to next argument
unmatched.emplace_back(argv[current]);
++current;
continue;
}
//error
throw_or_mimic<exceptions::no_such_option>(name);
}
auto opt = iter->second;
//equals provided for long option?
if (argu_desc.set_value) {
//parse the option given
parse_option(opt, name, argu_desc.value);
}
else {
//parse the next argument
checked_parse_arg(argc, argv, current, opt, name);
}
}
}
++current;
}
for (auto& opt : m_options) {
auto& detail = opt.second;
const auto& value = detail->value();
auto& store = m_parsed[detail->hash()];
if (value.has_default()) {
if (!store.count() && !store.has_default()) {
parse_default(detail);
}
}
else {
parse_no_value(detail);
}
}
if (consume_remaining) {
while (current < argc) {
if (!consume_positional(argv[current], next_positional)) {
break;
}
++current;
}
//adjust argv for any that couldn't be swallowed
while (current != argc) {
unmatched.emplace_back(argv[current]);
++current;
}
}
finalise_aliases();
ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));
return parsed;
}
inline void OptionParser::finalise_aliases() {
for (auto& option: m_options) {
auto& detail = *option.second;
auto hash = detail.hash();
m_keys[detail.short_name()] = hash;
for(const auto& long_name : detail.long_names()) {
m_keys[long_name] = hash;
}
m_parsed.emplace(hash, OptionValue());
}
}
inline void Options::add_option (const std::string& group, const Option& option) {
add_options(group, {option});
}
inline void Options::add_option(const std::string& group,
const std::string& s,
const OptionNames& l,
std::string desc,
const std::shared_ptr<const Value>& value,
std::string arg_help) {
auto stringDesc = toLocalString(std::move(desc));
auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);
if (!s.empty()) {
add_one_option(s, option);
}
for(const auto& long_name : l) {
add_one_option(long_name, option);
}
//add the help details
auto& options = m_help[group];
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
value->has_default(), value->get_default_value(),
value->has_implicit(), value->get_implicit_value(),
std::move(arg_help),
value->is_container(),
value->is_boolean()});
}
inline void Options::add_one_option(const std::string& option, const std::shared_ptr<OptionDetails>& details) {
auto in = m_options->emplace(option, details);
if (!in.second) {
throw_or_mimic<exceptions::option_already_exists>(option);
}
}
inline String Options::help_one_group(const std::string& g) const
{
using OptionHelp = std::vector<std::pair<String, String>>;
auto group = m_help.find(g);
if (group == m_help.end()) {
return "";
}
OptionHelp format;
std::size_t longest = 0;
String result;
if (!g.empty()) {
result += toLocalString(" " + g + " options:\n");
}
for (const auto& o : group->second.options) {
if (o.l.size() &&
m_positional_set.find(o.l.front()) != m_positional_set.end() &&
!m_show_positional) {
continue;
}
auto s = format_option(o);
longest = (std::max)(longest, stringLength(s));
format.push_back(std::make_pair(s, String()));
}
longest = (std::min)(longest, OPTION_LONGEST);
//widest allowed description -- min 10 chars for helptext/line
std::size_t allowed = 10;
if (m_width > allowed + longest + OPTION_DESC_GAP)
{
allowed = m_width - longest - OPTION_DESC_GAP;
}
auto fiter = format.begin();
for (const auto& o : group->second.options){
if (o.l.size() &&
m_positional_set.find(o.l.front()) != m_positional_set.end() &&
!m_show_positional) {
continue;
}
auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
result += fiter->first;
if (stringLength(fiter->first) > longest) {
result += '\n';
result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
}
else {
result += toLocalString(std::string(longest + OPTION_DESC_GAP -
stringLength(fiter->first),
' '));
}
result += d;
result += '\n';
++fiter;
}
return result;
}
inline void Options::generate_group_help(String& result, const std::vector<std::string>& print_groups) const {
for (std::size_t i = 0; i != print_groups.size(); ++i) {
const String& group_help_text = help_one_group(print_groups[i]);
if (empty(group_help_text)) {
continue;
}
result += group_help_text;
if (i < print_groups.size() - 1) {
result += '\n';
}
}
}
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);
}
inline std::string Options::help(const std::vector<std::string>& help_groups, bool print_usage) const {
String result = m_help_string;
if(print_usage) {
result+= "\nUsage:\n " + toLocalString(m_program);
}
if (!m_custom_help.empty()) {
result += " " + toLocalString(m_custom_help);
}
if (!m_positional.empty() && !m_positional_help.empty()) {
result += " " + toLocalString(m_positional_help);
}
result += "\n\n";
if (help_groups.empty()) {
generate_all_groups_help(result);
}
else {
generate_group_help(result, help_groups);
}
return toUTF8String(result);
}
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;
}
inline const HelpGroupDetails& Options::group_help(const std::string& group) const {
return m_help.at(group);
}
} // namespace cxxopts
#endif //CXXOPTS_HPP_INCLUDED
cxxopts_tests.cpp
#include <gtest/gtest.h>
#include <iostream>
#include <initializer_list>
#include "cxxopts.hpp"
class Argv {
public:
Argv(std::initializer_list<const char*> args)
: m_argv(new const char*[args.size()])
, m_argc(static_cast<int>(args.size())) {
int i = 0;
auto iter = args.begin();
while (iter != args.end()) {
auto len = strlen(*iter) + 1;
auto ptr = std::unique_ptr<char[]>(new char[len]);
strcpy(ptr.get(), *iter);
m_args.push_back(std::move(ptr));
m_argv.get()[i] = m_args.back().get();
++iter;
++i;
}
}
const char** argv() const {
return m_argv.get();
}
int argc() const {
return m_argc;
}
private:
std::vector<std::unique_ptr<char[]>> m_args{};
std::unique_ptr<const char*[]> m_argv;
int m_argc;
};
TEST(OptionTestSuite, BasicOptions)
{
cxxopts::Options options("tester", " - test basic options");
options.add_options()
("long", "a long option")
("s,short", "a short option")
("quick,brown", "An option with multiple long names and no short name")
("f,ox,jumped", "An option with multiple long names and a short name")
("over,z,lazy,dog", "An option with multiple long names and a short name, not listed first")
("value", "an option with a value", cxxopts::value<std::string>())
("a,av", "a short option with a value", cxxopts::value<std::string>())
("6,six", "a short number option")
("p, space", "an option with space between short and long")
("period.delimited", "an option with a period in the long name")
("nothing", "won't exist", cxxopts::value<std::string>());
Argv argv({
"tester",
"--long",
"-s",
"--value",
"value",
"-a",
"b",
"-6",
"-p",
"--space",
"--quick",
"--ox",
"-f",
"--brown",
"-z",
"--over",
"--dog",
"--lazy",
"--period.delimited",});
auto** actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
EXPECT_EQ(result.count("long"), 1);
EXPECT_EQ(result.count("s"), 1);
EXPECT_EQ(result.count("value"), 1);
EXPECT_EQ(result.count("a"), 1);
EXPECT_STREQ(result["value"].as<std::string>().c_str(), "value");
EXPECT_STREQ(result["a"].as<std::string>().c_str(), "b");
EXPECT_EQ(result.count("6"), 1);
EXPECT_EQ(result.count("p"), 2);
EXPECT_EQ(result.count("space"), 2);
EXPECT_EQ(result.count("quick"), 2);
EXPECT_EQ(result.count("f"), 2);
EXPECT_EQ(result.count("z"), 4);
EXPECT_EQ(result.count("period.delimited"), 1);
auto& arguments = result.arguments();
EXPECT_EQ(arguments.size(), 16);
EXPECT_EQ(arguments[0].key(), "long");
EXPECT_EQ(arguments[0].value(), "true");
EXPECT_EQ(arguments[0].as<bool>(), true);
EXPECT_EQ(arguments[1].key(), "short");
EXPECT_EQ(arguments[2].key(), "value");
EXPECT_EQ(arguments[3].key(), "av");
EXPECT_THROW(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value);
EXPECT_EQ(options.program(), "tester");
}
TEST(OptionTestSuite, ShortOptions)
{
cxxopts::Options options("test_short", " - test short options");
options.add_options()
("a", "a short option", cxxopts::value<std::string>())
("b", "b option")
("c", "c option", cxxopts::value<std::string>());
Argv argv({"test_short", "-a", "value", "-bcfoo=something"});
auto actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
EXPECT_EQ(result.count("a"), 1);
EXPECT_STREQ(result["a"].as<std::string>().c_str(), "value");
auto& arguments = result.arguments();
EXPECT_EQ(arguments.size(), 3);
// EXPECT_EQ(arguments[0].key().c_str(), "a");
EXPECT_STREQ(arguments[0].value().c_str(), "value");
EXPECT_EQ(result.count("b"), 1);
EXPECT_EQ(result.count("c"), 1);
EXPECT_STREQ(result["c"].as<std::string>().c_str(), "foo=something");
EXPECT_THROW(options.add_options()("", "nothing option"), cxxopts::exceptions::invalid_option_format);
}
TEST(OptionTestSuite, NoPositional)
{
cxxopts::Options options("test_no_positional",
" - test no positional options");
Argv av({"tester", "a", "b", "def"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(argc, 4);
EXPECT_EQ(strcmp(argv[1], "a"), 0);
}
TEST(OptionTestSuite, AllPositional)
{
std::vector<std::string> positional;
cxxopts::Options options("test_all_positional", " - test all positional");
options.add_options()
("positional", "Positional parameters",
cxxopts::value<std::vector<std::string>>(positional));
Argv av({"tester", "a", "b", "c"});
auto argc = av.argc();
auto argv = av.argv();
std::vector<std::string> pos_names = {"positional"};
options.parse_positional(pos_names.begin(), pos_names.end());
auto result = options.parse(argc, argv);
EXPECT_EQ(result.unmatched().size(), 0);
EXPECT_EQ(positional.size(), 3);
EXPECT_STREQ(positional[0].c_str(), "a");
EXPECT_STREQ(positional[1].c_str(), "b");
EXPECT_STREQ(positional[2].c_str(), "c");
}
TEST(OptionTestSuite, SomePositionalExplicit)
{
cxxopts::Options options("positional_explicit", " - test positional");
options.add_options()
("input", "Input file", cxxopts::value<std::string>())
("output", "Output file", cxxopts::value<std::string>())
("positional", "Positional parameters",
cxxopts::value<std::vector<std::string>>());
options.parse_positional({"input", "output", "positional"});
Argv av({"tester", "--output", "a", "b", "c", "d"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.unmatched().size(), 0);
EXPECT_EQ(result.count("output"), 1);
EXPECT_STREQ(result["input"].as<std::string>().c_str(), "b");
EXPECT_STREQ(result["output"].as<std::string>().c_str(), "a");
auto& positional = result["positional"].as<std::vector<std::string>>();
EXPECT_EQ(positional.size(), 2);
EXPECT_STREQ(positional[0].c_str(), "c");
EXPECT_STREQ(positional[1].c_str(), "d");
}
TEST(OptionTestSuite, NoPositionalWithExtras)
{
cxxopts::Options options("posargmaster", "shows incorrect handling");
options.add_options()
("dummy", "oh no", cxxopts::value<std::string>());
Argv av({"extras", "--", "a", "b", "c", "d"});
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();
std::vector<std::string> expectedValue{"a", "b", "c", "d"};
EXPECT_EQ(unmatched, expectedValue);
}
TEST(OptionTestSuite, PositionalNotValid)
{
cxxopts::Options options("positional_invalid", "invalid positional argument");
options.add_options()
("long", "a long option", cxxopts::value<std::string>());
options.parse_positional("something");
Argv av({"foobar", "bar", "baz"});
auto** argv = av.argv();
auto argc = av.argc();
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
}
TEST(OptionTestSuite, PositionalWithEmptyArguments)
{
cxxopts::Options options("positional_with_empty_arguments", "positional with empty argument");
options.add_options()
("long", "a long option", cxxopts::value<std::string>())
("program", "program to run", cxxopts::value<std::string>())
("programArgs", "program arguments", cxxopts::value<std::vector<std::string>>());
options.parse_positional("program", "programArgs");
Argv av({"foobar", "--long", "long_value", "--", "someProgram", "ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", });
std::vector<std::string> expected({"ab", "-c", "d", "--ef", "gh", "--ijk=lm", "n", "", "o", });
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
auto actual = result["programArgs"].as<std::vector<std::string>>();
EXPECT_EQ(result.count("program"), 1);
EXPECT_STREQ(result["program"].as<std::string>().c_str(), "someProgram");
EXPECT_EQ(result.count("programArgs"), expected.size());
EXPECT_EQ(actual, expected);
}
TEST(OptionTestSuite, PositionalWithListDelimiter)
{
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);
EXPECT_EQ(result.unmatched().size(), 0);
EXPECT_EQ(positional.size(), 2);
EXPECT_STREQ(single.c_str(), "a,b");
EXPECT_STREQ(positional[0].c_str(), "c,d");
EXPECT_STREQ(positional[1].c_str(), "e");
}
TEST(OptionTestSuite, EmptyWithImplicitValue)
{
cxxopts::Options options("empty_implicit", "doesn't handle empty");
options.add_options()
("implicit", "Has implicit", cxxopts::value<std::string>()
->implicit_value("foo"));
Argv av({"implicit", "--implicit="});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("implicit"), 1);
EXPECT_STREQ(result["implicit"].as<std::string>().c_str(), "");
}
TEST(OptionTestSuite, BooleanWithoutImplicitValue_tc1)
{
// When no value provided
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
Argv av({"no_implicit", "--bool"});
auto** argv = av.argv();
auto argc = av.argc();
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::missing_argument);
}
TEST(OptionTestSuite, BooleanWithoutImplicitValue_tc2)
{
// With equal-separated true
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
Argv av({"no_implicit", "--bool=true"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("bool"), 1);
EXPECT_EQ(result["bool"].as<bool>(), true);
}
TEST(OptionTestSuite, BooleanWithoutImplicitValue_tc3)
{
// With space-separated false
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
Argv av({"no_implicit", "--bool=false"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("bool"), 1);
EXPECT_EQ(result["bool"].as<bool>(), false);
}
TEST(OptionTestSuite, BooleanWithoutImplicitValue_tc4)
{
// With space-separated true
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
Argv av({"no_implicit", "--bool", "true"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("bool"), 1);
EXPECT_EQ(result["bool"].as<bool>(), true);
}
TEST(OptionTestSuite, BooleanWithoutImplicitValue_tc5)
{
// With space-separated false
cxxopts::Options options("no_implicit", "bool without an implicit value");
options.add_options()
("bool", "Boolean without implicit", cxxopts::value<bool>()
->no_implicit_value());
Argv av({"no_implicit", "--bool", "false"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("bool"), 1);
EXPECT_EQ(result["bool"].as<bool>(), false);
}
TEST(OptionTestSuite, Default_Values_tc1)
{
// Sets defaults
cxxopts::Options options("defaults", "has defaults");
options.add_options()
("default", "Has implicit", cxxopts::value<int>()->default_value("42"))
("v,vector", "Default vector", cxxopts::value<std::vector<int>>()
->default_value("1,4"));
Argv av({"implicit"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("default"), 0);
EXPECT_EQ(result["default"].as<int>(), 42);
auto& v = result["vector"].as<std::vector<int>>();
EXPECT_EQ(v.size(), 2);
EXPECT_EQ(v[0], 1);
EXPECT_EQ(v[1], 4);
}
TEST(OptionTestSuite, Default_Values_tc2)
{
// When values provided
cxxopts::Options options("defaults", "has defaults");
options.add_options()
("default", "Has implicit", cxxopts::value<int>()->default_value("42"))
("v,vector", "Default vector", cxxopts::value<std::vector<int>>()
->default_value("1,4"));
Argv av({"implicit", "--default", "5"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("default"), 1);
EXPECT_EQ(result["default"].as<int>(), 5);
}
TEST(OptionTestSuite, ParseIntoAReference)
{
int value = 0;
bool b_value = true;
cxxopts::Options options("into_reference", "parses into a reference");
options.add_options()
("ref", "A reference", cxxopts::value(value))
("bool", "A bool", cxxopts::value(b_value));
Argv av({"into_reference", "--ref", "42"});
auto argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("ref"), 1);
EXPECT_EQ(value, 42);
EXPECT_EQ(result.count("bool"), 0);
EXPECT_EQ(b_value, true);
}
TEST(OptionTestSuite, Integers)
{
cxxopts::Options options("parses_integers", "parses integers correctly");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int>>());
Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("positional"), 7);
auto& positional = result["positional"].as<std::vector<int>>();
EXPECT_EQ(positional.size(), 7);
EXPECT_EQ(positional[0], 5);
EXPECT_EQ(positional[1], 6);
EXPECT_EQ(positional[2], -6);
EXPECT_EQ(positional[3], 0);
EXPECT_EQ(positional[4], 0xab);
EXPECT_EQ(positional[5], 0xaf);
EXPECT_EQ(positional[6], 0x0);
}
TEST(OptionTestSuite, LeadingZeroIntegers)
{
cxxopts::Options options("parses_integers", "parses integers correctly");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int>>());
Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("positional"), 4);
auto& positional = result["positional"].as<std::vector<int>>();
EXPECT_EQ(positional.size(), 4);
EXPECT_EQ(positional[0], 5);
EXPECT_EQ(positional[1], 6);
EXPECT_EQ(positional[2], 0xab);
EXPECT_EQ(positional[3], 0x1);
}
TEST(OptionTestSuite, UnsignedIntegers)
{
cxxopts::Options options("parses_unsigned", "detects unsigned errors");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<unsigned int>>());
Argv av({"ints", "--", "-2"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
}
TEST(OptionTestSuite, IntegerBounds)
{
// No overflow
cxxopts::Options options("integer_boundaries", "check min/max integer");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int8_t>>());
Argv av({"ints", "--", "127", "-128", "0x7f", "-0x80", "0x7e"});
auto argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("positional"), 5);
auto& positional = result["positional"].as<std::vector<int8_t>>();
EXPECT_EQ(positional[0], 127);
EXPECT_EQ(positional[1], -128);
EXPECT_EQ(positional[2], 0x7f);
EXPECT_EQ(positional[3], -0x80);
EXPECT_EQ(positional[4], 0x7e);
}
TEST(OptionTestSuite, OverflowOnBoundary)
{
using namespace cxxopts::values;
int8_t si;
uint8_t ui;
EXPECT_THROW((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type);
}
TEST(OptionTestSuite, IntegerOverflow)
{
using namespace cxxopts::values;
cxxopts::Options options("reject_overflow", "rejects overflowing integers");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int8_t>>());
Argv av({"ints", "--", "128"});
auto argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
int integer = 0;
EXPECT_THROW((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type);
EXPECT_THROW((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type);
}
TEST(OptionTestSuite, Floats)
{
cxxopts::Options options("parses_floats", "parses floats correctly");
options.add_options()
("double", "Double precision", cxxopts::value<double>())
("positional", "Floats", cxxopts::value<std::vector<float>>());
Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("double"), 1);
EXPECT_EQ(result.count("positional"), 4);
EXPECT_EQ(result["double"].as<double>(), 0.5);
auto& positional = result["positional"].as<std::vector<float>>();
EXPECT_EQ(positional[0], 4);
EXPECT_EQ(positional[1], -4);
EXPECT_EQ(positional[2], 1.5e6);
EXPECT_EQ(positional[3], -1.5e6);
}
TEST(OptionTestSuite, InvalidIntegers)
{
cxxopts::Options options("invalid_integers", "rejects invalid integers");
options.add_options()
("positional", "Integers", cxxopts::value<std::vector<int>>());
Argv av({"ints", "--", "Ae"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse_positional("positional");
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
}
TEST(OptionTestSuite, Booleans)
{
cxxopts::Options options("parses_floats", "parses floats correctly");
options.add_options()
("bool", "A Boolean", cxxopts::value<bool>())
("debug", "Debugging", cxxopts::value<bool>())
("timing", "Timing", cxxopts::value<bool>())
("verbose", "Verbose", cxxopts::value<bool>())
("dry-run", "Dry Run", cxxopts::value<bool>())
("noExplicitDefault", "No Explicit Default", cxxopts::value<bool>())
("defaultTrue", "Timing", cxxopts::value<bool>()->default_value("true"))
("defaultFalse", "Timing", cxxopts::value<bool>()->default_value("false"))
("others", "Other arguments", cxxopts::value<std::vector<std::string>>())
;
options.parse_positional("others");
Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("bool"), 1);
EXPECT_EQ(result.count("debug"), 1);
EXPECT_EQ(result.count("timing"), 1);
EXPECT_EQ(result.count("verbose"), 1);
EXPECT_EQ(result.count("dry-run"), 1);
EXPECT_EQ(result.count("noExplicitDefault"), 0);
EXPECT_EQ(result.count("defaultTrue"), 0);
EXPECT_EQ(result.count("defaultFalse"), 0);
EXPECT_EQ(result["bool"].as<bool>(), false);
EXPECT_EQ(result["debug"].as<bool>(), true);
EXPECT_EQ(result["timing"].as<bool>(), true);
EXPECT_EQ(result["verbose"].as<bool>(), true);
EXPECT_EQ(result["dry-run"].as<bool>(), false);
EXPECT_EQ(result["noExplicitDefault"].as<bool>(), false);
EXPECT_EQ(result["defaultTrue"].as<bool>(), true);
EXPECT_EQ(result["defaultFalse"].as<bool>(), false);
EXPECT_EQ(result.count("others"), 1);
}
TEST(OptionTestSuite, StdVector)
{
std::vector<double> vector;
cxxopts::Options options("vector", " - tests vector");
options.add_options()
("vector", "an vector option", cxxopts::value<std::vector<double>>(vector));
Argv av({"vector", "--vector", "1,-2.1,3,4.5"});
auto** argv = av.argv();
auto argc = av.argc();
options.parse(argc, argv);
EXPECT_EQ(vector.size(), 4);
EXPECT_EQ(vector[0], 1);
EXPECT_EQ(vector[1], -2.1);
EXPECT_EQ(vector[2], 3);
EXPECT_EQ(vector[3], 4.5);
}
// #ifdef CXXOPTS_HAS_OPTIONAL
// TEST(OptionTestSuite, StdOptional)
// {
// std::optional<std::string> optional;
// cxxopts::Options options("optional", " - tests optional");
// options.add_options()
// ("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional));
// Argv av({"optional", "--optional", "foo"});
// auto** argv = av.argv();
// auto argc = av.argc();
// options.parse(argc, argv);
// EXPECT_TRUE(optional.has_value());
// // EXPECT_STREQ(*optional, "foo");
// }
// #endif
TEST(OptionTestSuite, UnrecognisedOptions)
{
cxxopts::Options options("unknown_options", " - test unknown options");
options.add_options()
("long", "a long option")
("s,short", "a short option");
Argv av({
"unknown_options",
"--unknown",
"--long",
"-su",
"--another_unknown",
"-a",
});
auto** argv = av.argv();
auto argc = av.argc();
// Default behaviour
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
// After allowing unrecognised options
options.allow_unrecognised_options();
auto result = options.parse(argc, argv);
auto& unmatched = result.unmatched();
std::vector<std::string> expectedValue{"--unknown", "-u", "--another_unknown", "-a"};
EXPECT_EQ(unmatched, expectedValue);
}
TEST(OptionTestSuite, AllowBadShortSyntax)
{
cxxopts::Options options("unknown_options", " - test unknown options");
options.add_options()
("long", "a long option")
("s,short", "a short option");
Argv av({
"--ab?",
"-?b?#@"
});
auto** argv = av.argv();
auto argc = av.argc();
// Default behaviour
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
// After allowing unrecognised options
options.allow_unrecognised_options();
EXPECT_NO_THROW(options.parse(argc, argv));
EXPECT_EQ(argc, 2);
// EXPECT_EQ(argv[1], Catch::Equals("-?b?#@"));
}
TEST(OptionTestSuite, InvalidOptionSyntax)
{
cxxopts::Options options("invalid_syntax", " - test invalid syntax");
Argv av({
"invalid_syntax",
"--a",
});
auto** argv = av.argv();
auto argc = av.argc();
// Default behaviour
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
}
TEST(OptionTestSuite, OptionsEmpty)
{
cxxopts::Options options("Options list empty", " - test empty option list");
options.add_options();
options.add_options("");
options.add_options("", {});
options.add_options("test");
Argv argv_({
"test",
"--unknown"
});
auto argc = argv_.argc();
auto** argv = argv_.argv();
EXPECT_TRUE(options.groups().empty());
EXPECT_THROW(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
}
TEST(OptionTestSuite, InitializerListWithGroup)
{
cxxopts::Options options("Initializer list group", " - test initializer list with group");
options.add_options("", {
{"a, address", "server address", cxxopts::value<std::string>()->default_value("127.0.0.1")},
{"p, port", "server port", cxxopts::value<std::string>()->default_value("7110"), "PORT"},
});
cxxopts::Option help{"h,help", "Help"};
options.add_options("TEST_GROUP", {
{"t, test", "test option"},
help
});
Argv argv({
"test",
"--address",
"10.0.0.1",
"-p",
"8000",
"-t",
});
auto** actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
EXPECT_EQ(options.groups().size(), 2);
EXPECT_EQ(result.count("address"), 1);
EXPECT_EQ(result.count("port"), 1);
EXPECT_EQ(result.count("test"), 1);
EXPECT_EQ(result.count("help"), 0);
EXPECT_EQ(result["address"].as<std::string>(), "10.0.0.1");
EXPECT_EQ(result["port"].as<std::string>(), "8000");
EXPECT_TRUE(result["test"].as<bool>());
}
TEST(OptionTestSuite, Option_add_with_add_option_string_Option)
{
cxxopts::Options options("Option add with add_option", " - test Option add with add_option(string, Option)");
cxxopts::Option option_1("t,test", "test option", cxxopts::value<int>()->default_value("7"), "TEST");
options.add_option("", option_1);
options.add_option("TEST", {"a,aggregate", "test option 2", cxxopts::value<int>(), "AGGREGATE"});
options.add_option("TEST", {"multilong,m,multilong-alias", "test option 3", cxxopts::value<int>(), "An option with multiple long names"});
Argv argv_({
"test",
"--test",
"5",
"-a",
"4",
"--multilong-alias",
"6"
});
auto argc = argv_.argc();
auto** argv = argv_.argv();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.arguments().size(), 3);
EXPECT_EQ(options.groups().size(), 2);
EXPECT_EQ(result.count("address"), 0);
EXPECT_EQ(result.count("aggregate"), 1);
EXPECT_EQ(result.count("test"), 1);
EXPECT_EQ(result["aggregate"].as<int>(), 4);
EXPECT_EQ(result["multilong"].as<int>(), 6);
EXPECT_EQ(result["multilong-alias"].as<int>(), 6);
EXPECT_EQ(result["m"].as<int>(), 6);
EXPECT_EQ(result["test"].as<int>(), 5);
}
TEST(OptionTestSuite, ConstArray)
{
const char* const option_list[] = {"empty", "options"};
cxxopts::Options options("Empty options", " - test constness");
auto result = options.parse(2, option_list);
}
TEST(OptionTestSuite, ParameterFollowOption)
{
cxxopts::Options options("param_follow_opt", " - test parameter follow option without space.");
options.add_options()
("j,job", "Job", cxxopts::value<std::vector<unsigned>>());
Argv av({"implicit",
"-j", "9",
"--job", "7",
"--job=10",
"-j5",
});
auto ** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
EXPECT_EQ(result.count("job"), 4);
auto job_values = result["job"].as<std::vector<unsigned>>();
EXPECT_EQ(job_values[0], 9);
EXPECT_EQ(job_values[1], 7);
EXPECT_EQ(job_values[2], 10);
EXPECT_EQ(job_values[3], 5);
}
TEST(OptionTestSuite, Iterator)
{
cxxopts::Options options("tester", " - test iterating over parse result");
options.add_options()
("long", "a long option")
("s,short", "a short option")
("a", "a short-only option")
("value", "an option with a value", cxxopts::value<std::string>())
("default", "an option with default value", cxxopts::value<int>()->default_value("42"))
("nothing", "won't exist", cxxopts::value<std::string>())
;
Argv argv({
"tester",
"--long",
"-s",
"-a",
"--value",
"value",
});
auto** actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
auto iter = result.begin();
EXPECT_NE(iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "long");
EXPECT_STREQ(iter->value().c_str(), "true");
EXPECT_NE(++iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "short");
EXPECT_STREQ(iter->value().c_str(), "true");
EXPECT_NE(++iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "a");
EXPECT_STREQ(iter->value().c_str(), "true");
EXPECT_NE(++iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "value");
EXPECT_STREQ(iter->value().c_str(), "value");
EXPECT_NE(++iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "default");
EXPECT_STREQ(iter->value().c_str(), "42");
EXPECT_EQ(++iter, result.end());
}
TEST(OptionTestSuite, IteratorNoArgs) {
cxxopts::Options options("tester", " - test iterating over parse result");
options.add_options()
("value", "an option with a value", cxxopts::value<std::string>())
("default", "an option with default value", cxxopts::value<int>()->default_value("42"))
("nothing", "won't exist", cxxopts::value<std::string>())
;
Argv argv({
"tester",
});
auto** actual_argv = argv.argv();
auto argc = argv.argc();
auto result = options.parse(argc, actual_argv);
auto iter = result.begin();
EXPECT_NE(iter, result.end());
EXPECT_STREQ(iter->key().c_str(), "default");
EXPECT_STREQ(iter->value().c_str(), "42");
++iter;
EXPECT_EQ(iter, result.end());
}
TEST(OptionTestSuite, NoOptionsHelp) {
std::vector<std::string> positional;
cxxopts::Options options("test", "test no options help");
// explicitly setting custom help empty to overwrite
// default "[OPTION...]" when there are no options
options.positional_help("<posArg1>...<posArgN>")
.custom_help("")
.add_options()
("positional", "", cxxopts::value<std::vector<std::string>>(positional));
Argv av({"test", "posArg1", "posArg2", "posArg3"});
auto argc = av.argc();
auto** argv = av.argv();
options.parse_positional({"positional"});
EXPECT_NO_THROW(options.parse(argc, argv));
EXPECT_NE(options.help().find("test <posArg1>...<posArgN>"), std::string::npos);
}
DataBaseTests.cpp
#include <iostream>
#include <vector>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::_;
using ::testing::Invoke;
using ::testing::DoDefault;
using ::testing::DoAll;
class DataBaseConnect {
public:
// virtual bool login(std::string username, std::string password) { return true;}
virtual bool login(std::string username, std::string password) {
std:: cout << "ORIGINAL LOGIN ..." << std::endl;
return false;
}
virtual bool logout(std::string username) { return true;}
virtual int fetchRecord() { return -1;}
};
class MockDataBaseConnect : public DataBaseConnect {
public:
MOCK_METHOD2(login, bool (std::string username, std::string password));
MOCK_METHOD1(logout, bool (std::string username));
MOCK_METHOD0(fetchRecord, int ());
};
class MyDataBase {
public:
MyDataBase(DataBaseConnect &_dbC): dbc(_dbC) {}
int Init(std::string username, std::string password) {
if (dbc.login(username, password) != true) {
std::cout << "DB FAILURE" << std::endl;
return -1;
} else {
std::cout << "DB SUCCESS" << std::endl;
return 1;
}
}
private:
DataBaseConnect &dbc;
};
struct testABC {
bool dummyLogin(std::string a, std::string b) {
std::cout << "Dummy Login get called." << std::endl;
return true;
}
};
TEST(MyDataBaseTestSuite, DISABLED_LoginSuccess_tc1) {
// Arrange
MockDataBaseConnect mDB;
MyDataBase db(mDB);
// EXPECT_CALL(mDB, login("Terminator", "I'm back"))
// .Times(AtLeast(1))
// .WillOnce(Return(true));
EXPECT_CALL(mDB, login(_, _))
.Times(AtLeast(1))
.WillOnce(Return(true));
// Action
int retValue = db.Init("Terminator", "I'm back");
// Assert
EXPECT_EQ(retValue, 1);
}
TEST(MyDataBaseTestSuite, LoginSuccess_tc2) {
// Arrange
MockDataBaseConnect mDB;
MyDataBase db(mDB);
testABC dbTest;
ON_CALL(mDB, login(_, _)).WillByDefault(Invoke(&dbTest, &testABC::dummyLogin));
EXPECT_CALL(mDB, login(_, _))
.Times(AtLeast(1))
.WillOnce(DoDefault());
// Action
int retValue = db.Init("Terminator", "I'm back");
// Assert
EXPECT_EQ(retValue, 1);
}
TEST(MyDataBaseTestSuite, LoginSuccess_tc3) {
// Arrange
MockDataBaseConnect mDB;
MyDataBase db(mDB);
testABC dbTest;
EXPECT_CALL(mDB, login(_, _))
.Times(AtLeast(1))
.WillOnce(DoAll(Invoke(&dbTest, &testABC::dummyLogin), Return(true)));
// Action
int retValue = db.Init("Terminator", "I'm back");
// Assert
EXPECT_EQ(retValue, 1);
}
TEST(MyDataBaseTestSuite, LoginFailure) {
// Arrange
MockDataBaseConnect mDB;
MyDataBase db(mDB);
EXPECT_CALL(mDB, login(_, _))
.Times(AtLeast(1))
.WillOnce(Return(false));
// Action
int retValue = db.Init("Terminator", "I'm back");
// Assert
EXPECT_EQ(retValue, -1);
}