From 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Tue, 1 Jul 2025 17:07:04 +0100 Subject: add nihil.std --- nihil.ucl/array.test.cc | 479 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 nihil.ucl/array.test.cc (limited to 'nihil.ucl/array.test.cc') diff --git a/nihil.ucl/array.test.cc b/nihil.ucl/array.test.cc new file mode 100644 index 0000000..89394a0 --- /dev/null +++ b/nihil.ucl/array.test.cc @@ -0,0 +1,479 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: array: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(array<>::ucl_type == object_type::array); + REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); + + static_assert(std::destructible>); + static_assert(std::default_initializable>); + static_assert(std::move_constructible>); + static_assert(std::copy_constructible>); + static_assert(std::equality_comparable>); + static_assert(std::totally_ordered>); + static_assert(std::swappable>); + + static_assert(std::ranges::sized_range>); + static_assert(std::same_as>, integer>); +} + +TEST_CASE("ucl: array: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default") + { + auto arr = array(); + REQUIRE(arr.size() == 0); + REQUIRE(str(arr.type()) == "array"); + } + + SECTION("from range") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(vec); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from iterator pair") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(std::ranges::begin(vec), std::ranges::end(vec)); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from initializer_list") + { + auto arr = array{integer(1), integer(42)}; + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } +} + +TEST_CASE("ucl: array: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); + + auto arr = array(ref, uarr); + REQUIRE(arr[0] == 42); + + ::ucl_object_unref(uarr); + } + + SECTION("noref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); + + auto arr = array(noref, uarr); + REQUIRE(arr[0] == 42); + } + + SECTION("ref, wrong element type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_frombool(true); + ::ucl_array_append(uarr, uint); + + auto arr = array(noref, uarr); + REQUIRE_THROWS_AS(arr[0], type_mismatch); + } + + SECTION("ref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: array: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto arr1 = nihil::ucl::array{nihil::ucl::integer(1), + nihil::ucl::integer(2)}; + + auto arr2 = nihil::ucl::array{ + nihil::ucl::integer(3), + }; + + swap(arr1, arr2); + + REQUIRE(arr1.size() == 1); + REQUIRE(arr1[0] == 3); + + REQUIRE(arr2.size() == 2); + REQUIRE(arr2[0] == 1); +} + +TEST_CASE("ucl: array: push_back", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + REQUIRE(arr.size() == 0); + + arr.push_back(integer(1)); + arr.push_back(integer(42)); + arr.push_back(integer(666)); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); + + REQUIRE_THROWS_AS(arr[3], std::out_of_range); + + REQUIRE(arr.front() == 1); + REQUIRE(arr.back() == 666); +} + +TEST_CASE("ucl: array: compare", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto arr2 = array(); + REQUIRE(arr != arr2); + + arr2.push_back(integer(1)); + arr2.push_back(integer(42)); + arr2.push_back(integer(666)); + REQUIRE(arr == arr2); + + auto arr3 = array{integer(1), integer(1), integer(1)}; + + REQUIRE(arr != arr3); +} + +TEST_CASE("ucl: array: iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto it = arr.begin(); + REQUIRE(*it == 1); + auto end = arr.end(); + REQUIRE(it != end); + REQUIRE(it < end); + + ++it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(*it == 666); + + --it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(it != end); + ++it; + REQUIRE(it == end); +} + +TEST_CASE("ucl: array: parse", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj = parse("value = [1, 42, 666]"sv).value(); + + auto arr = object_cast>(obj["value"]).value(); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); +} + +TEST_CASE("ucl: array: emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("array = [1, 42, 666];").value(); + + auto output = std::format("{:c}", ucl); + REQUIRE(output == "array [\n" + " 1,\n" + " 42,\n" + " 666,\n" + "]\n"); +} + +TEST_CASE("ucl: array: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + REQUIRE(std::format("{}", arr) == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array is a sized_range", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto size = std::ranges::size(arr); + REQUIRE(size == 3); + + auto begin = std::ranges::begin(arr); + static_assert(std::random_access_iterator); + + auto end = std::ranges::end(arr); + static_assert(std::sentinel_for); + + REQUIRE(std::distance(begin, end) == 3); + + auto vec = std::vector(); + std::ranges::copy(arr, std::back_inserter(vec)); + REQUIRE(std::ranges::equal(arr, vec)); + + auto arr_as_ints = arr | std::views::transform(&integer::value); + auto int_vec = std::vector(); + std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); + REQUIRE(int_vec == std::vector{1, 42, 666}); +} + +TEST_CASE("ucl: array: bad object_cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + + auto cast_ok = object_cast(arr); + REQUIRE(!cast_ok); +} + +TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj_err = parse("array [ 42, true, \"test\" ];"); + REQUIRE(obj_err); + auto obj = *obj_err; + + auto err = object_cast>(obj["array"]); + REQUIRE(err); + + auto arr = *err; + REQUIRE(arr.size() == 3); + + auto int_obj = object_cast(arr[0]); + REQUIRE(int_obj); + REQUIRE(*int_obj == 42); + + auto bool_obj = object_cast(arr[1]); + REQUIRE(bool_obj); + REQUIRE(*bool_obj == true); + + auto string_obj = object_cast(arr[2]); + REQUIRE(string_obj); + REQUIRE(*string_obj == "test"); +} + +TEST_CASE("ucl: array: heterogenous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(42)); + arr.push_back(boolean(true)); + + // Converting to an array should fail. + auto cast_ok = object_cast>(arr); + REQUIRE(!cast_ok); + + // Converting to array should succeed. + auto err = object_cast>(arr); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == integer(42)); +} + +TEST_CASE("ucl: array: homogeneous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(1)); + arr.push_back(integer(42)); + + auto obj = object(ref, arr.get_ucl_object()); + + // Converting to array should fail. + auto cast_ok = object_cast>(obj); + REQUIRE(!cast_ok); + + // Converting to an array should succeed. + auto err = object_cast>(obj); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == 1); + REQUIRE(obj_arr[1] == 42); +} + +TEST_CASE("array iterator: empty iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto it = array_iterator(); + + REQUIRE_THROWS_AS(*it, std::logic_error); + REQUIRE_THROWS_AS(it[0], std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(++it, std::logic_error); + + auto it2 = array_iterator(); + REQUIRE(it == it2); + REQUIRE((it < it2) == false); + REQUIRE((it > it2) == false); +} + +TEST_CASE("array iterator: invalid operations", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(42)}; + auto it = arr.begin(); + + SECTION("decrement before start") + { + REQUIRE_THROWS_AS(--it, std::logic_error); + REQUIRE_THROWS_AS(it--, std::logic_error); + REQUIRE_THROWS_AS(it - 1, std::logic_error); + } + + SECTION("increment past end") + { + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(++it, std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(it + 1, std::logic_error); + } + + SECTION("dereference iterator at end") + { + REQUIRE_THROWS_AS(it[1], std::logic_error); + + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(*it, std::logic_error); + } + + SECTION("compare with different array") + { + auto arr2 = array{integer(42)}; + REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error); + REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error); + REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error); + } + + SECTION("compare with empty iterator") + { + auto it2 = array_iterator(); + REQUIRE_THROWS_AS(it == it2, std::logic_error); + REQUIRE_THROWS_AS(it > it2, std::logic_error); + REQUIRE_THROWS_AS(it - it2, std::logic_error); + } +} + +} // anonymous namespace -- cgit v1.2.3