ZeroTierOne/ext/libpqxx-7.7.3/test/unit/test_range.cxx
2022-06-24 10:12:36 -07:00

556 lines
20 KiB
C++

#include <pqxx/range>
#include <pqxx/strconv>
#include "../test_helpers.hxx"
namespace
{
void test_range_construct()
{
using optint = std::optional<int>;
using oibound = pqxx::inclusive_bound<std::optional<int>>;
using oxbound = pqxx::inclusive_bound<std::optional<int>>;
PQXX_CHECK_THROWS(
(pqxx::range<optint>{oibound{optint{}}, oibound{optint{}}}),
pqxx::argument_error, "Inclusive bound accepted a null.");
PQXX_CHECK_THROWS(
(pqxx::range<optint>{oxbound{optint{}}, oxbound{optint{}}}),
pqxx::argument_error, "Exclusive bound accepted a null.");
using ibound = pqxx::inclusive_bound<int>;
PQXX_CHECK_THROWS(
(pqxx::range<int>{ibound{1}, ibound{0}}), pqxx::range_error,
"Range constructor accepted backwards range.");
PQXX_CHECK_THROWS(
(pqxx::range<float>{
pqxx::inclusive_bound<float>{-1000.0},
pqxx::inclusive_bound<float>{-std::numeric_limits<float>::infinity()}}),
pqxx::range_error,
"Was able to construct range with infinity bound at the wrong end.");
}
void test_range_equality()
{
using range = pqxx::range<int>;
using ibound = pqxx::inclusive_bound<int>;
using xbound = pqxx::exclusive_bound<int>;
using ubound = pqxx::no_bound;
PQXX_CHECK_EQUAL(
range{}, range{}, "Default-constructed range is not consistent.");
PQXX_CHECK_EQUAL(
(range{xbound{0}, xbound{0}}), (range{xbound{5}, xbound{5}}),
"Empty ranges at different values are not equal.");
PQXX_CHECK_EQUAL(
(range{ubound{}, ubound{}}), (range{ubound{}, ubound{}}),
"Universal range is inconsistent.");
PQXX_CHECK_EQUAL(
(range{ibound{5}, ibound{8}}), (range{ibound{5}, ibound{8}}),
"Inclusive range is inconsistent.");
PQXX_CHECK_EQUAL(
(range{xbound{5}, xbound{8}}), (range{xbound{5}, xbound{8}}),
"Exclusive range is inconsistent.");
PQXX_CHECK_EQUAL(
(range{xbound{5}, ibound{8}}), (range{xbound{5}, ibound{8}}),
"Left-exclusive interval is not equal to itself.");
PQXX_CHECK_EQUAL(
(range{ibound{5}, xbound{8}}), (range{ibound{5}, xbound{8}}),
"Right-exclusive interval is not equal to itself.");
PQXX_CHECK_EQUAL(
(range{ubound{}, ibound{8}}), (range{ubound{}, ibound{8}}),
"Unlimited lower bound does not compare equal to same.");
PQXX_CHECK_EQUAL(
(range{ibound{8}, ubound{}}), (range{ibound{8}, ubound{}}),
"Unlimited upper bound does not compare equal to same.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, ibound{8}}), (range{xbound{5}, ibound{8}}),
"Equality does not detect inclusive vs. exclusive lower bound.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, ibound{8}}), (range{ubound{}, ibound{8}}),
"Equality does not detect inclusive vs. unlimited lower bound.");
PQXX_CHECK_NOT_EQUAL(
(range{xbound{5}, ibound{8}}), (range{ubound{}, ibound{8}}),
"Equality does not detect exclusive vs. unlimited lower bound.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, ibound{8}}), (range{ibound{5}, xbound{8}}),
"Equality does not detect inclusive vs. exclusive upper bound.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, ibound{8}}), (range{ibound{5}, ubound{}}),
"Equality does not detect inclusive vs. unlimited upper bound.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, xbound{8}}), (range{ibound{5}, ubound{}}),
"Equality does not detect exclusive vs. unlimited upper bound.");
PQXX_CHECK_NOT_EQUAL(
(range{ibound{5}, ibound{8}}), (range{ibound{4}, ibound{8}}),
"Equality does not compare lower inclusive bound value.");
PQXX_CHECK_NOT_EQUAL(
(range{xbound{5}, ibound{8}}), (range{xbound{4}, ibound{8}}),
"Equality does not compare lower exclusive bound value.");
PQXX_CHECK_NOT_EQUAL(
(range{xbound{5}, ibound{8}}), (range{xbound{5}, ibound{7}}),
"Equality does not compare upper inclusive bound value.");
PQXX_CHECK_NOT_EQUAL(
(range{xbound{5}, xbound{8}}), (range{xbound{5}, xbound{7}}),
"Equality does not compare lower exclusive bound value.");
}
void test_range_empty()
{
using range = pqxx::range<int>;
using ibound = pqxx::inclusive_bound<int>;
using xbound = pqxx::exclusive_bound<int>;
using ubound = pqxx::no_bound;
PQXX_CHECK((range{}.empty()), "Default-constructed range is not empty.");
PQXX_CHECK(
(range{ibound{10}, xbound{10}}).empty(),
"Right-exclusive zero-length interval is not empty.");
PQXX_CHECK(
(range{xbound{10}, ibound{10}}).empty(),
"Left-exclusive zero-length interval is not empty.");
PQXX_CHECK(
(range{xbound{10}, xbound{10}}).empty(),
"Exclusive zero-length interval is not empty.");
PQXX_CHECK(
not(range{ibound{10}, ibound{10}}).empty(),
"Inclusive zero-length interval is empty.");
PQXX_CHECK(
not(range{xbound{10}, ibound{11}}.empty()),
"Interval is incorrectly empty.");
PQXX_CHECK(
not(range{ubound{}, ubound{}}.empty()),
"Double-unlimited interval is empty.");
PQXX_CHECK(
not(range{ubound{}, xbound{0}}.empty()),
"Left-unlimited interval is empty.");
PQXX_CHECK(
not(range{xbound{0}, ubound{}}.empty()),
"Right-unlimited interval is empty.");
}
void test_range_contains()
{
using range = pqxx::range<int>;
using ibound = pqxx::inclusive_bound<int>;
using xbound = pqxx::exclusive_bound<int>;
using ubound = pqxx::no_bound;
PQXX_CHECK(not(range{}.contains(-1)), "Empty range contains a value.");
PQXX_CHECK(not(range{}.contains(0)), "Empty range contains a value.");
PQXX_CHECK(not(range{}.contains(1)), "Empty range contains a value.");
PQXX_CHECK(
not(range{ibound{5}, ibound{8}}.contains(4)),
"Inclusive range contains value outside its left bound.");
PQXX_CHECK(
(range{ibound{5}, ibound{8}}.contains(5)),
"Inclusive range does not contain value on its left bound.");
PQXX_CHECK(
(range{ibound{5}, ibound{8}}.contains(6)),
"Inclusive range does not contain value inside it.");
PQXX_CHECK(
(range{ibound{5}, ibound{8}}.contains(8)),
"Inclusive range does not contain value on its right bound.");
PQXX_CHECK(
not(range{ibound{5}, ibound{8}}.contains(9)),
"Inclusive range contains value outside its right bound.");
PQXX_CHECK(
not(range{ibound{5}, xbound{8}}.contains(4)),
"Left-inclusive range contains value outside its left bound.");
PQXX_CHECK(
(range{ibound{5}, xbound{8}}.contains(5)),
"Left-inclusive range does not contain value on its left bound.");
PQXX_CHECK(
(range{ibound{5}, xbound{8}}.contains(6)),
"Left-inclusive range does not contain value inside it.");
PQXX_CHECK(
not(range{ibound{5}, xbound{8}}.contains(8)),
"Left-inclusive range contains value on its right bound.");
PQXX_CHECK(
not(range{ibound{5}, xbound{8}}.contains(9)),
"Left-inclusive range contains value outside its right bound.");
PQXX_CHECK(
not(range{xbound{5}, ibound{8}}.contains(4)),
"Right-inclusive range contains value outside its left bound.");
PQXX_CHECK(
not(range{xbound{5}, ibound{8}}.contains(5)),
"Right-inclusive range does contains value on its left bound.");
PQXX_CHECK(
(range{xbound{5}, ibound{8}}.contains(6)),
"Right-inclusive range does not contain value inside it.");
PQXX_CHECK(
(range{xbound{5}, ibound{8}}.contains(8)),
"Right-inclusive range does not contain value on its right bound.");
PQXX_CHECK(
not(range{xbound{5}, ibound{8}}.contains(9)),
"Right-inclusive range contains value outside its right bound.");
PQXX_CHECK(
not(range{xbound{5}, xbound{8}}.contains(4)),
"Exclusive range contains value outside its left bound.");
PQXX_CHECK(
not(range{xbound{5}, xbound{8}}.contains(5)),
"Exclusive range contains value on its left bound.");
PQXX_CHECK(
(range{xbound{5}, xbound{8}}.contains(6)),
"Exclusive range does not contain value inside it.");
PQXX_CHECK(
not(range{xbound{5}, xbound{8}}.contains(8)),
"Exclusive range does contains value on its right bound.");
PQXX_CHECK(
not(range{xbound{5}, xbound{8}}.contains(9)),
"Exclusive range contains value outside its right bound.");
PQXX_CHECK(
(range{ubound{}, ibound{8}}.contains(7)),
"Right-inclusive range does not contain value inside it.");
PQXX_CHECK(
(range{ubound{}, ibound{8}}.contains(8)),
"Right-inclusive range does not contain value on its right bound.");
PQXX_CHECK(
not(range{ubound{}, ibound{8}}.contains(9)),
"Right-inclusive range contains value outside its right bound.");
PQXX_CHECK(
(range{ubound{}, xbound{8}}.contains(7)),
"Right-exclusive range does not contain value inside it.");
PQXX_CHECK(
not(range{ubound{}, xbound{8}}.contains(8)),
"Right-exclusive range contains value on its right bound.");
PQXX_CHECK(
not(range{ubound{}, xbound{8}}.contains(9)),
"Right-exclusive range contains value outside its right bound.");
PQXX_CHECK(
not(range{ibound{5}, ubound{}}.contains(4)),
"Left-inclusive range contains value outside its left bound.");
PQXX_CHECK(
(range{ibound{5}, ubound{}}.contains(5)),
"Left-inclusive range does not contain value on its left bound.");
PQXX_CHECK(
(range{ibound{5}, ubound{}}.contains(6)),
"Left-inclusive range does not contain value inside it.");
PQXX_CHECK(
not(range{xbound{5}, ubound{}}.contains(4)),
"Left-exclusive range contains value outside its left bound.");
PQXX_CHECK(
not(range{xbound{5}, ubound{}}.contains(5)),
"Left-exclusive range contains value on its left bound.");
PQXX_CHECK(
(range{xbound{5}, ubound{}}.contains(6)),
"Left-exclusive range does not contain value inside it.");
PQXX_CHECK(
(range{ubound{}, ubound{}}.contains(-1)), "Value not in universal range.");
PQXX_CHECK(
(range{ubound{}, ubound{}}.contains(0)), "Value not in universal range.");
PQXX_CHECK(
(range{ubound{}, ubound{}}.contains(1)), "Value not in universal range.");
}
void test_float_range_contains()
{
using range = pqxx::range<double>;
using ibound = pqxx::inclusive_bound<double>;
using xbound = pqxx::exclusive_bound<double>;
using ubound = pqxx::no_bound;
using limits = std::numeric_limits<double>;
constexpr auto inf{limits::infinity()};
PQXX_CHECK(
not(range{ibound{4.0}, ibound{8.0}}.contains(3.9)),
"Float inclusive range contains value beyond its lower bound.");
PQXX_CHECK(
(range{ibound{4.0}, ibound{8.0}}.contains(4.0)),
"Float inclusive range does not contain its lower bound value.");
PQXX_CHECK(
(range{ibound{4.0}, ibound{8.0}}.contains(5.0)),
"Float inclusive range does not contain value inside it.");
PQXX_CHECK(
(range{ibound{0}, ibound{inf}}).contains(9999.0),
"Range to infinity did not include large number.");
PQXX_CHECK(
not(range{ibound{0}, ibound{inf}}.contains(-0.1)),
"Range to infinity includes number outside it.");
PQXX_CHECK(
(range{ibound{0}, xbound{inf}}.contains(9999.0)),
"Range to exclusive infinity did not include large number.");
PQXX_CHECK(
(range{ibound{0}, ibound{inf}}).contains(inf),
"Range to inclusive infinity does not include infinity.");
PQXX_CHECK(
not(range{ibound{0}, xbound{inf}}.contains(inf)),
"Range to exclusive infinity includes infinity.");
PQXX_CHECK(
(range{ibound{0}, ubound{}}).contains(inf),
"Right-unlimited range does not include infinity.");
PQXX_CHECK(
(range{ibound{-inf}, ibound{0}}).contains(-9999.0),
"Range from infinity did not include large negative number.");
PQXX_CHECK(
not(range{ibound{-inf}, ibound{0}}.contains(0.1)),
"Range from infinity includes number outside it.");
PQXX_CHECK(
(range{xbound{-inf}, ibound{0}}).contains(-9999.0),
"Range from exclusive infinity did not include large negative number.");
PQXX_CHECK(
(range{ibound{-inf}, ibound{0}}).contains(-inf),
"Range from inclusive infinity does not include negative infinity.");
PQXX_CHECK(
not(range{xbound{-inf}, ibound{0}}).contains(-inf),
"Range to infinity exclusive includes negative infinity.");
PQXX_CHECK(
(range{ubound{}, ibound{0}}).contains(-inf),
"Left-unlimited range does not include negative infinity.");
}
void test_range_subset()
{
using range = pqxx::range<int>;
using traits = pqxx::string_traits<range>;
std::string_view subsets[][2]{
{"empty", "empty"}, {"(,)", "empty"}, {"(0,1)", "empty"},
{"(,)", "[-10,10]"}, {"(,)", "(-10,10)"}, {"(,)", "(,)"},
{"(,10)", "(,10)"}, {"(,10)", "(,9)"}, {"(,10]", "(,10)"},
{"(,10]", "(,10]"}, {"(1,)", "(10,)"}, {"(1,)", "(9,)"},
{"[1,)", "(10,)"}, {"[1,)", "[10,)"}, {"[0,5]", "[1,4]"},
{"(0,5)", "[1,4]"},
};
for (auto const [super, sub] : subsets)
PQXX_CHECK(
traits::from_string(super).contains(traits::from_string(sub)),
pqxx::internal::concat(
"Range '", super, "' did not contain '", sub, "'."));
std::string_view non_subsets[][2]{
{"empty", "[0,0]"}, {"empty", "(,)"}, {"[-10,10]", "(,)"},
{"(-10,10)", "(,)"}, {"(,9)", "(,10)"}, {"(,10)", "(,10]"},
{"[1,4]", "[0,4]"}, {"[1,4]", "[1,5]"}, {"(0,10)", "[0,10]"},
{"(0,10)", "(0,10]"}, {"(0,10)", "[0,10)"},
};
for (auto const [super, sub] : non_subsets)
PQXX_CHECK(
not traits::from_string(super).contains(traits::from_string(sub)),
pqxx::internal::concat("Range '", super, "' contained '", sub, "'."));
}
void test_range_to_string()
{
using range = pqxx::range<int>;
using ibound = pqxx::inclusive_bound<int>;
using xbound = pqxx::exclusive_bound<int>;
using ubound = pqxx::no_bound;
PQXX_CHECK_EQUAL(
pqxx::to_string(range{}), "empty", "Empty range came out wrong.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ibound{5}, ibound{8}}), "[5,8]",
"Inclusive range came out wrong.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{xbound{5}, ibound{8}}), "(5,8]",
"Left-exclusive range came out wrong.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ibound{5}, xbound{8}}), "[5,8)",
"Right-exclusive range came out wrong.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{xbound{5}, xbound{8}}), "(5,8)",
"Exclusive range came out wrong.");
// Unlimited boundaries can use brackets or parentheses. Doesn't matter.
// We cheat and use some white-box knowledge of our implementation here.
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ubound{}, ubound{}}), "(,)",
"Universal range came out unexpected.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ubound{}, ibound{8}}), "(,8]",
"Left-unlimited range came out unexpected.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ubound{}, xbound{8}}), "(,8)",
"Left-unlimited range came out unexpected.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{ibound{5}, ubound{}}), "[5,)",
"Right-unlimited range came out unexpected.");
PQXX_CHECK_EQUAL(
pqxx::to_string(range{xbound{5}, ubound{}}), "(5,)",
"Right-unlimited range came out unexpected.");
}
void test_parse_range()
{
using range = pqxx::range<int>;
using ubound = pqxx::no_bound;
using traits = pqxx::string_traits<range>;
constexpr std::string_view empties[]{"empty", "EMPTY", "eMpTy"};
for (auto empty : empties)
PQXX_CHECK(
traits::from_string(empty).empty(),
pqxx::internal::concat(
"This was supposed to produce an empty range: '", empty, "'"));
constexpr std::string_view universals[]{"(,)", "[,)", "(,]", "[,]"};
for (auto univ : universals)
PQXX_CHECK_EQUAL(
traits::from_string(univ), (range{ubound{}, ubound{}}),
pqxx::internal::concat(
"This was supposed to produce a universal range: '", univ, "'"));
PQXX_CHECK(
traits::from_string("(0,10]").lower_bound().is_exclusive(),
"Exclusive lower bound did not parse right.");
PQXX_CHECK(
traits::from_string("[0,10]").lower_bound().is_inclusive(),
"Inclusive lower bound did not parse right.");
PQXX_CHECK(
traits::from_string("(0,10)").upper_bound().is_exclusive(),
"Exclusive upper bound did not parse right.");
PQXX_CHECK(
traits::from_string("[0,10]").upper_bound().is_inclusive(),
"Inclusive upper bound did not parse right.");
PQXX_CHECK_EQUAL(
*traits::from_string("(\"0\",\"10\")").lower_bound().value(), 0,
"Quoted range boundary did not parse right.");
PQXX_CHECK_EQUAL(
*traits::from_string("(\"0\",\"10\")").upper_bound().value(), 10,
"Quoted upper boundary did not parse right.");
auto floats{
pqxx::string_traits<pqxx::range<double>>::from_string("(0,1.0)")};
PQXX_CHECK_GREATER(
*floats.lower_bound().value(), -0.001,
"Float lower bound is out of range.");
PQXX_CHECK_LESS(
*floats.lower_bound().value(), 0.001,
"Float lower bound is out of range.");
PQXX_CHECK_GREATER(
*floats.upper_bound().value(), 0.999,
"Float upper bound is out of range.");
PQXX_CHECK_LESS(
*floats.upper_bound().value(), 1.001,
"Float upper bound is out of range.");
}
void test_parse_bad_range()
{
using range = pqxx::range<int>;
using conv_err = pqxx::conversion_error;
using traits = pqxx::string_traits<range>;
constexpr std::string_view bad_ranges[]{
"", "x", "e", "empt", "emptyy", "()",
"[]", "(empty)", "(empty, 0)", "(0, empty)", ",", "(,",
",)", "(1,2,3)", "(4,5x)", "(null, 0)", "[0, 1.0]", "[1.0, 0]",
};
for (auto bad : bad_ranges)
PQXX_CHECK_THROWS(
pqxx::ignore_unused(traits::from_string(bad)), conv_err,
pqxx::internal::concat(
"This range wasn't supposed to parse: '", bad, "'"));
}
/// Parse ranges lhs and rhs, return their intersection as a string.
template<typename TYPE>
std::string intersect(std::string_view lhs, std::string_view rhs)
{
using traits = pqxx::string_traits<pqxx::range<TYPE>>;
return pqxx::to_string(traits::from_string(lhs) & traits::from_string(rhs));
}
void test_range_intersection()
{
// Intersections and their expected results, in text form.
// Each row contains two ranges, and their intersection.
std::string_view intersections[][3]{
{"empty", "empty", "empty"},
{"(,)", "empty", "empty"},
{"[,]", "empty", "empty"},
{"empty", "[0,10]", "empty"},
{"(,)", "(,)", "(,)"},
{"(,)", "(5,8)", "(5,8)"},
{"(,)", "[5,8)", "[5,8)"},
{"(,)", "(5,8]", "(5,8]"},
{"(,)", "[5,8]", "[5,8]"},
{"(-1000,10)", "(0,1000)", "(0,10)"},
{"[-1000,10)", "(0,1000)", "(0,10)"},
{"(-1000,10]", "(0,1000)", "(0,10]"},
{"[-1000,10]", "(0,1000)", "(0,10]"},
{"[0,100]", "[0,100]", "[0,100]"},
{"[0,100]", "[0,100)", "[0,100)"},
{"[0,100]", "(0,100]", "(0,100]"},
{"[0,100]", "(0,100)", "(0,100)"},
{"[0,10]", "[11,20]", "empty"},
{"[0,10]", "(11,20]", "empty"},
{"[0,10]", "[11,20)", "empty"},
{"[0,10]", "(11,20)", "empty"},
{"[0,10]", "[10,11]", "[10,10]"},
{"[0,10)", "[10,11]", "empty"},
{"[0,10]", "(10,11]", "empty"},
{"[0,10)", "(10,11]", "empty"},
};
for (auto [left, right, expected] : intersections)
{
PQXX_CHECK_EQUAL(
intersect<int>(left, right), expected,
pqxx::internal::concat(
"Intersection of '", left, "' and '", right,
" produced unexpected result."));
PQXX_CHECK_EQUAL(
intersect<int>(right, left), expected,
pqxx::internal::concat(
"Intersection of '", left, "' and '", right, " was asymmetric."));
}
}
void test_range_conversion()
{
std::string_view const ranges[]{
"empty", "(,)", "(,10)", "(0,)", "[0,10]", "[0,10)", "(0,10]", "(0,10)",
};
for (auto r : ranges)
{
auto const shortr{pqxx::from_string<pqxx::range<short>>(r)};
pqxx::range<int> intr{shortr};
PQXX_CHECK_EQUAL(
pqxx::to_string(intr), r, "Converted range looks different.");
}
}
PQXX_REGISTER_TEST(test_range_construct);
PQXX_REGISTER_TEST(test_range_equality);
PQXX_REGISTER_TEST(test_range_empty);
PQXX_REGISTER_TEST(test_range_contains);
PQXX_REGISTER_TEST(test_float_range_contains);
PQXX_REGISTER_TEST(test_range_subset);
PQXX_REGISTER_TEST(test_range_to_string);
PQXX_REGISTER_TEST(test_parse_range);
PQXX_REGISTER_TEST(test_parse_bad_range);
PQXX_REGISTER_TEST(test_range_intersection);
PQXX_REGISTER_TEST(test_range_conversion);
} // namespace