diff --git a/BUILD.rapidcheck b/BUILD.rapidcheck new file mode 100644 index 0000000..04c6869 --- /dev/null +++ b/BUILD.rapidcheck @@ -0,0 +1,65 @@ + +cc_library( + name = "headers", + includes = ["include"], + visibility = ["//visibility:private"], + copts = ["-DRC_DONT_USE_RTTI"], + hdrs = glob([ + "include/**/*.hpp", + "include/**/*.h", + ]), +) + +cc_library( + name = "random", + deps = [":headers"], + visibility = ["//visibility:private"], + copts = ["-DRC_DONT_USE_RTTI", "-O3"], + srcs = [ + "src/Random.cpp", + ], +) + + +cc_library( + name = "rapidcheck", + visibility = ["//visibility:public"], + deps = [":headers", ":random"], + copts = ["-DRC_DONT_USE_RTTI"], + includes = ["include"], + srcs = [ + "src/BeforeMinimalTestCase.cpp", + "src/Check.cpp", + "src/Classify.cpp", + "src/GenerationFailure.cpp", + "src/Log.cpp", + "src/Show.cpp", + "src/detail/Any.cpp", + "src/detail/Assertions.cpp", + "src/detail/Base64.cpp", + "src/detail/Configuration.cpp", + "src/detail/DefaultTestListener.cpp", + "src/detail/FrequencyMap.cpp", + "src/detail/ImplicitParam.cpp", + "src/detail/LogTestListener.cpp", + "src/detail/MapParser.cpp", + "src/detail/MulticastTestListener.cpp", + "src/detail/ParseException.cpp", + "src/detail/Platform.cpp", + "src/detail/Property.cpp", + "src/detail/PropertyContext.cpp", + "src/detail/ReproduceListener.cpp", + "src/detail/Results.cpp", + "src/detail/Serialization.cpp", + "src/detail/StringSerialization.cpp", + "src/detail/TestMetadata.cpp", + "src/detail/TestParams.cpp", + "src/detail/Testing.cpp", + "src/gen/Numeric.cpp", + "src/gen/Text.cpp", + "src/gen/detail/ExecHandler.cpp", + "src/gen/detail/GenerationHandler.cpp", + "src/gen/detail/Recipe.cpp", + "src/gen/detail/ScaleInteger.cpp", + ] + glob(["src/detail/*.h"]), +) diff --git a/WORKSPACE b/WORKSPACE index 66e1c28..9071cac 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -101,7 +101,7 @@ cc_library( ) #------------------------------------------------------------------------------- -# ut: Unit test framework. +# ut: Testing framework. # TODO(kss): Only if tests are needed? #------------------------------------------------------------------------------- ut_version = "1.1.9" @@ -124,6 +124,22 @@ cc_library( strip_prefix = "ut-" + ut_version, ) +#------------------------------------------------------------------------------- +# rapidcheck: Property-based-testing framework. +# TODO(kss): Only if tests are needed? +#------------------------------------------------------------------------------- +rapidcheck_commit = "ff6af6fc683159deb51c543b065eba14dfcf329b" +rapidcheck_base_url = "https://github.com/emil-e/rapidcheck" +rapidcheck_branch = "master" + +git_repository( + name = "rapidcheck", + remote = rapidcheck_base_url, + commit = rapidcheck_commit, + build_file = "@//:BUILD.rapidcheck" +) + + #------------------------------------------------------------------------------- # Support compile_commands.json generation for LSP. #------------------------------------------------------------------------------- diff --git a/bazel/BUILD b/bazel/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/tests/msgpack/BUILD b/tests/msgpack/BUILD index 179dfbb..225c7e3 100644 --- a/tests/msgpack/BUILD +++ b/tests/msgpack/BUILD @@ -33,6 +33,15 @@ cc_test( deps = ["test_deps"], ) +cc_test( + name = "packer_rc", + size = "small", + srcs = [ + "test_packer_rc.cpp", + ], + deps = ["test_deps", "@rapidcheck"], +) + cc_test( name = "writer", size = "small", diff --git a/tests/msgpack/test_packer_rc.cpp b/tests/msgpack/test_packer_rc.cpp new file mode 100644 index 0000000..1f31d4e --- /dev/null +++ b/tests/msgpack/test_packer_rc.cpp @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Default packer tests. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- + +#include "parselink/msgpack/core/packer.h" + +#include +#include + +#include "rng.h" +#include +#include +#include +#include + +using namespace boost::ut; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::nil const&, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{{msgpack::invalid}}"); + } +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::nil const&, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{{msgpack::nil}}"); + } +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::map_desc const& value, FormatContext& ctx) const { + return fmt::format_to( + ctx.out(), "{{msgpack::map_desc: {}}}", value.count); + } +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::array_desc const& value, FormatContext& ctx) const { + return fmt::format_to( + ctx.out(), "{{msgpack::array_desc: {}}}", value.count); + } +}; + +namespace { + +template +constexpr std::array make_bytes(Bytes&&... bytes) { + return {static_cast(std::forward(bytes))...}; +} + +constexpr auto equal(auto a, auto b) { + return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b)); +} + +template +consteval auto within() noexcept { + return std::numeric_limits::max() <= std::numeric_limits::max(); +} + +template +constexpr auto verify_packed(auto const& packer, auto value) { + std::array::digits / 8> raw; + if (packer.tell() != 1 + raw.size()) return false; + + auto packed = packer.subspan().subspan(1); + be_to_host(packed.begin(), packed.end(), raw.begin()); + auto actual = std::bit_cast(raw); + return actual == value; +} + +template +auto check_unsigned() { + return rc::check([] { + std::array payload; + msgpack::packer packer(payload); + const auto value = *rc::gen::positive(); + if (!packer.pack(value)) return false; + + if (value < 0x80) { + // positive_fixint + return packer.tell() == 1 + && payload[0] == static_cast(value); + } else if (value <= std::numeric_limits::max()) { + // uint8 + return packer.tell() == 2 && payload[0] == std::byte{0xcc} + && T(payload[1]) == value; + } else if (value <= std::numeric_limits::max()) { + // uint16 + return payload[0] == std::byte{0xcd} + && verify_packed(packer, value); + } else if (value <= std::numeric_limits::max()) { + // uint32 + return payload[0] == std::byte{0xce} + && verify_packed(packer, value); + } else { + // uint64 + return payload[0] == std::byte{0xcf} + && verify_packed(packer, value); + } + }); +} + +} // anonymous namespace + +suite packer_single_format = [] { + "packer::pack"_test = [] { + std::array payload; + msgpack::packer packer(payload); + expect(!!packer.pack(msgpack::nil{})); + expect(packer.tell() == 1); + expect(payload[0] == std::byte{0xc0}); + }; + + "packer::pack"_test = [] { + std::array payload; + msgpack::packer packer(payload); + expect(!!packer.pack(msgpack::invalid{})); + expect(packer.tell() == 1); + expect(payload[0] == std::byte{0xc1}); + }; + + "packer::pack"_test = [] { + std::array payload; + msgpack::packer packer(payload); + expect(!!packer.pack(false)); + expect(packer.tell() == 1); + expect(payload[0] == std::byte{0xc2}); + expect(!!packer.pack(true)); + expect(packer.tell() == 2); + expect(payload[1] == std::byte{0xc3}); + }; + + "packer::pack"_test = [] { + expect(check_unsigned()); + }; + + "packer::pack"_test = [] { + expect(check_unsigned()); + }; + + "packer::pack"_test = [] { + expect(check_unsigned()); + }; + + "packer::pack"_test = [] { + expect(check_unsigned()); + }; +}; +#if 0 +"packer::pack"_test = [] { expect(test_deduced()); }; + "packer::pack"_test = [] { expect(test_deduced()); }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + + // Unsigned ints + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + + // Signed ints + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + "packer::pack"_test = [] { + expect(test_deduced()); + }; + + // Strings + "packer::pack"_test = [] { + expect(test_deduced()); + }; +#if 0 + "packer::pack"_test = [] { + expect(test_deduced()); + }; +#endif + + // Byte ranges -> Binary + + "packer::pack>"_test = [] { + expect(test_deduced>()); + }; + + "packer::pack>"_test = [] { + { + constexpr auto data = make_bytes(0x01, 0x02, 0x03, 0x04); + constexpr auto expected = + make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04); + std::array payload; + + msgpack::packer packer(payload); + expect(!!packer.pack(data)); + expect(std::ranges::equal(payload, expected)); + } + { + constexpr std::array data{}; + constexpr std::array expected{ + std::byte(0xc5), std::byte(0x01), std::byte(0x00)}; + std::array payload; + + msgpack::packer packer(payload); + expect(!!packer.pack(data)); + expect(std::ranges::equal(payload, expected)); + } + { + constexpr std::array data{}; + constexpr std::array expected{ + std::byte(0xc6), std::byte(0x00), std::byte(0x01), + std::byte(0x00), std::byte(0x00)}; + std::array payload; + + msgpack::packer packer(payload); + expect(!!packer.pack(data)); + expect(std::ranges::equal(payload, expected)); + } + }; + + // array_desc - Just the header of the array. + "packer::pack"_test = [] { + expect(test_deduced()); + }; + + // map_desc - Just the header of the map. + "packer::pack"_test = [] { + expect(test_deduced()); + }; +}; + +suite packer_ranges = [] { + "packer::pack>"_test = [] { + constexpr auto data_array = std::to_array({5, 10, 15, 20}); + constexpr auto expected = + make_bytes(0x94, 0xd0, 0x05, 0xd0, 0xa, 0xd0, 0xf, 0xd0, 0x14); + + { + std::array payload; + msgpack::packer packer(payload); + expect(!!packer.pack(data_array)); + expect(std::ranges::equal(payload, expected)); + } + + { + std::span data_span{data_array}; + std::array payload; + msgpack::packer packer(payload); + expect(!!packer.pack(data_span)); + expect(std::ranges::equal(payload, expected)); + } + }; +}; +#endif