/* * Copyright 2015 - 2025 gary@drinkingtea.net * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #undef NDEBUG #include #include #include #include #include union TestUnion { static constexpr auto TypeName = "TestUnion"; static constexpr auto TypeVersion = 1; bool Bool; uint32_t Int; char *CString{}; }; struct TestStructNest { static constexpr auto TypeName = "TestStructNest"; static constexpr auto TypeVersion = 1; bool Bool = false; uint32_t Int = 0; ox::IString<32> IString = ""; }; struct TestStruct { static constexpr auto TypeName = "TestStruct"; static constexpr auto TypeVersion = 1; bool Bool = false; int32_t Int = 0; int32_t Int1 = 0; int32_t Int2 = 0; int32_t Int3 = 0; int32_t Int4 = 0; int32_t Int5 = 0; int32_t Int6 = 0; int32_t Int7 = 0; int32_t Int8 = 0; int unionIdx = 1; TestUnion Union; ox::String String; ox::IString<32> IString = ""; uint32_t List[4] = {0, 0, 0, 0}; ox::Vector Vector = {1, 2, 3, 4, 5}; ox::Vector Vector2 = {1, 2, 3, 4, 5}; ox::HashMap Map; TestStructNest EmptyStruct; TestStructNest Struct; constexpr ~TestStruct() noexcept { if (unionIdx == 2) { ox::safeDelete(Union.CString); } } }; template constexpr ox::Error model(T *io, ox::CommonPtrWith auto *obj) noexcept { OX_RETURN_ERROR(io->template setTypeInfo()); OX_RETURN_ERROR(io->field("Bool", &obj->Bool)); OX_RETURN_ERROR(io->field("Int", &obj->Int)); OX_RETURN_ERROR(io->fieldCString("CString", &obj->CString)); return ox::Error(0); } OX_MODEL_BEGIN(TestStructNest) OX_MODEL_FIELD(Bool) OX_MODEL_FIELD(Int) OX_MODEL_FIELD(IString) OX_MODEL_END() template constexpr ox::Error model(T *io, ox::CommonPtrWith auto *obj) noexcept { OX_RETURN_ERROR(io->template setTypeInfo()); OX_RETURN_ERROR(io->field("Bool", &obj->Bool)); OX_RETURN_ERROR(io->field("Int", &obj->Int)); OX_RETURN_ERROR(io->field("Int1", &obj->Int1)); OX_RETURN_ERROR(io->field("Int2", &obj->Int2)); OX_RETURN_ERROR(io->field("Int3", &obj->Int3)); OX_RETURN_ERROR(io->field("Int4", &obj->Int4)); OX_RETURN_ERROR(io->field("Int5", &obj->Int5)); OX_RETURN_ERROR(io->field("Int6", &obj->Int6)); OX_RETURN_ERROR(io->field("Int7", &obj->Int7)); OX_RETURN_ERROR(io->field("Int8", &obj->Int8)); OX_RETURN_ERROR(io->field("unionIdx", &obj->unionIdx)); if constexpr(T::opType() == ox::OpType::Reflect) { OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, 0})); } else { OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, obj->unionIdx})); } OX_RETURN_ERROR(io->field("String", &obj->String)); OX_RETURN_ERROR(io->field("IString", &obj->IString)); OX_RETURN_ERROR(io->field("List", obj->List, 4)); OX_RETURN_ERROR(io->field("Vector", &obj->Vector)); OX_RETURN_ERROR(io->field("Vector2", &obj->Vector2)); OX_RETURN_ERROR(io->field("Map", &obj->Map)); OX_RETURN_ERROR(io->field("Struct", &obj->Struct)); OX_RETURN_ERROR(io->field("EmptyStruct", &obj->EmptyStruct)); return ox::Error(0); } std::map tests = { { { "MetalClawWriter", [] { // This test doesn't confirm much, but it does show that the writer // doesn't segfault ox::Array buff; TestStruct ts; OX_RETURN_ERROR(ox::writeMC(buff.data(), buff.size(), ts)); OX_RETURN_ERROR(ox::writeMC(ts)); return ox::Error(0); } }, { "MetalClawReader", [] { // setup for tests TestStruct testIn, testOut; testIn.Bool = true; testIn.Int = 42; testIn.IString = "Test String 1"; testIn.String = "Test String 2"; testIn.Vector = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, }; testIn.Vector2 = {}; testIn.List[0] = 1; testIn.List[1] = 2; testIn.List[2] = 3; testIn.List[3] = 4; testIn.Struct.Bool = true; testIn.Struct.Int = 300; testIn.Struct.IString = "Test String 3"; testIn.unionIdx = 1; testIn.Union.Int = 93; // run tests const auto [buff, err] = ox::writeMC(testIn); oxAssert(err, "writeMC failed"); oxAssert(ox::readMC(buff, testOut), "readMC failed"); //std::cout << testIn.Union.Int << "|" << testOut.Union.Int << "|\n"; oxAssert(testIn.Bool == testOut.Bool, "Bool value mismatch"); oxAssert(testIn.Int == testOut.Int, "Int value mismatch"); oxAssert(testIn.Int1 == testOut.Int1, "Int1 value mismatch"); oxAssert(testIn.Int2 == testOut.Int2, "Int2 value mismatch"); oxAssert(testIn.Int3 == testOut.Int3, "Int3 value mismatch"); oxAssert(testIn.Int4 == testOut.Int4, "Int4 value mismatch"); oxAssert(testIn.Int5 == testOut.Int5, "Int5 value mismatch"); oxAssert(testIn.Int6 == testOut.Int6, "Int6 value mismatch"); oxAssert(testIn.Int7 == testOut.Int7, "Int7 value mismatch"); oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch"); oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch"); oxAssert(testIn.String == testOut.String, "String value mismatch"); ox::expect(testIn.IString, testOut.IString); oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch"); oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch"); oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch"); oxAssert(testIn.List[3] == testOut.List[3], "List[3] value mismatch"); oxAssert(testIn.Vector.size() == testOut.Vector.size(), "Vector size mismatch"); for (auto i = 0u; i < testIn.Vector.size(); ++i) { oxAssert(testIn.Vector[i] == testOut.Vector[i], ox::sfmt("Vector[{}] value mismatch", i)); } oxAssert(testIn.Vector2.size() == testOut.Vector2.size(), "Vector2 size mismatch"); oxAssert(testIn.Map["asdf"] == testOut.Map["asdf"], "Map[\"asdf\"] value mismatch"); oxAssert(testIn.Map["aoeu"] == testOut.Map["aoeu"], "Map[\"aoeu\"] value mismatch"); oxAssert(testIn.EmptyStruct.Bool == testOut.EmptyStruct.Bool, "EmptyStruct.Bool value mismatch"); oxAssert(testIn.EmptyStruct.Int == testOut.EmptyStruct.Int, "EmptyStruct.Int value mismatch"); oxAssert(testIn.EmptyStruct.IString == testOut.EmptyStruct.IString, "EmptyStruct.IString value mismatch"); oxAssert(testIn.Struct.Int == testOut.Struct.Int, "Struct.Int value mismatch"); oxAssert(testIn.Struct.IString == testOut.Struct.IString, "Struct.IString value mismatch"); oxAssert(testIn.Struct.Bool == testOut.Struct.Bool, "Struct.Bool value mismatch"); return ox::Error(0); } }, { "encodeInteger", [] { using ox::MaxValue; using ox::mc::McInt; using ox::mc::encodeInteger; static constexpr auto check = [](McInt val, const ox::Vector &expected) { if (val.length != expected.size()) { std::cout << "val.length: " << val.length << ", expected: " << expected.size() << '\n'; return ox::Error(1); } for (std::size_t i = 0; i < expected.size(); i++) { if (expected[i] != val.data[i]) { std::cout << "decoded: " << static_cast(val.data[i]) << ", expected: " << static_cast(expected[i]) << '\n'; std::cout << "decoded: " << i << ": " << static_cast(val.data[i]) << '\n'; return ox::Error(1); } } return ox::Error(0); }; constexpr auto check64 = [](McInt val, auto expected) { if (val.length != 9) { std::cout << "val.length: " << val.length << '\n'; return ox::Error(1); } ox::LittleEndian decoded = *reinterpret_cast(&val.data[1]); if (expected != decoded) { std::cout << "decoded: " << decoded << ", expected: " << expected << '\n'; return ox::Error(1); } return ox::Error(0); }; // signed positive oxAssert(check(encodeInteger(int64_t(1)), {0b000'0001'0}), "Encode 1 fail"); oxAssert(check(encodeInteger(int64_t(2)), {0b000'0010'0}), "Encode 2 fail"); oxAssert(check(encodeInteger(int64_t(3)), {0b000'0011'0}), "Encode 3 fail"); oxAssert(check(encodeInteger(int64_t(4)), {0b000'0100'0}), "Encode 4 fail"); oxAssert(check(encodeInteger(int64_t(64)), {0b00'0000'01, 0b1}), "Encode 64 fail"); oxAssert(check(encodeInteger(int64_t(128)), {0b00'0000'01, 0b10}), "Encode 128 fail"); oxAssert(check(encodeInteger(int64_t(129)), {0b00'0001'01, 0b10}), "Encode 129 fail"); oxAssert(check(encodeInteger(int64_t(130)), {0b00'0010'01, 0b10}), "Encode 130 fail"); oxAssert(check(encodeInteger(int64_t(131)), {0b00'0011'01, 0b10}), "Encode 131 fail"); // signed negative oxAssert(check(encodeInteger( int64_t(-1)), {0b111'1111'0}), "Encode -1 fail"); oxAssert(check(encodeInteger( int64_t(-2)), {0b111'1110'0}), "Encode -2 fail"); oxAssert(check(encodeInteger( int64_t(-3)), {0b111'1101'0}), "Encode -3 fail"); oxAssert(check(encodeInteger( int64_t(-4)), {0b111'1100'0}), "Encode -4 fail"); oxAssert(check(encodeInteger( int64_t(-64)), {0b100'0000'0}), "Encode -64 fail"); oxAssert(check(encodeInteger(int64_t(-128)), {0b00'0000'01, 0b11'1111'10}), "Encode -128 fail"); oxAssert(check(encodeInteger(int64_t(-129)), {0b11'1111'01, 0b11'1111'01}), "Encode -129 fail"); oxAssert(check(encodeInteger(int64_t(-130)), {0b11'1110'01, 0b11'1111'01}), "Encode -130 fail"); oxAssert(check(encodeInteger(int64_t(-131)), {0b11'1101'01, 0b11'1111'01}), "Encode -131 fail"); // unsigned oxAssert(check(encodeInteger(uint32_t(0xffffffff)), {0b11101111, 255, 255, 255, 0b00011111}), "Encode 0xffffffff fail"); oxAssert(check(encodeInteger(uint64_t(1)), {0b0010}), "Encode 1 fail"); oxAssert(check(encodeInteger(uint64_t(2)), {0b0100}), "Encode 2 fail"); oxAssert(check(encodeInteger(uint64_t(3)), {0b0110}), "Encode 3 fail"); oxAssert(check(encodeInteger(uint64_t(4)), {0b1000}), "Encode 4 fail"); oxAssert(check(encodeInteger(uint64_t(64)), {0b1000'000'0}), "Encode 4 fail"); oxAssert(check(encodeInteger(uint64_t(128)), {0b0001, 0b10}), "Encode 128 fail"); oxAssert(check(encodeInteger(uint64_t(129)), {0b0101, 0b10}), "Encode 129 fail"); oxAssert(check(encodeInteger(uint64_t(130)), {0b1001, 0b10}), "Encode 130 fail"); oxAssert(check(encodeInteger(uint64_t(131)), {0b1101, 0b10}), "Encode 131 fail"); // Signed check needs lambda templates to run correctly without // code deduplication oxAssert(check64(encodeInteger(MaxValue), MaxValue), "Encode MaxValue fail"); oxAssert(check64(encodeInteger(MaxValue), MaxValue), "Encode MaxValue fail"); return ox::Error(0); } }, { "decodeInteger", [] { using ox::MaxValue; using ox::mc::McInt; using ox::mc::encodeInteger; using ox::mc::decodeInteger; static constexpr auto check = [](auto val) { auto result = decodeInteger(encodeInteger(val)); OX_RETURN_ERROR(result.error); if (result.value != val) { std::cout << "Bad value: " << result.value << ", expected: " << val << '\n'; return ox::Error(1); } return ox::Error(0); }; oxAssert(check(uint32_t(14)), "Decode of 14 failed."); oxAssert(check(int8_t(-1)), "Decode of -1 failed."); oxAssert(check(int16_t(-1)), "Decode of -1 failed."); oxAssert(check(int32_t(-1)), "Decode of -1 failed."); oxAssert(check(int64_t(-1)), "Decode of -1 failed."); oxAssert(check(int64_t(-2)), "Decode of -2 failed."); oxAssert(check(int64_t(-127)), "Decode of -127 failed."); oxAssert(check(int64_t(-128)), "Decode of -128 failed."); oxAssert(check(int64_t(-129)), "Decode of -129 failed."); oxAssert(check(int64_t(-129000)), "Decode of -129000 failed."); oxAssert(check(int64_t(1)), "Decode of 1 failed."); oxAssert(check(int64_t(2)), "Decode of 2 failed."); oxAssert(check(int64_t(42)), "Decode of 42 failed."); oxAssert(check(int64_t(130)), "Decode of 130 failed."); oxAssert(check(int64_t(131)), "Decode of 131 failed."); oxAssert(check(int64_t(131000)), "Decode of 131000 failed."); oxAssert(check(uint64_t(1)), "Decode of 1 failed."); oxAssert(check(uint64_t(2)), "Decode of 2 failed."); oxAssert(check(uint64_t(42)), "Decode of 42 failed."); oxAssert(check(uint64_t(130)), "Decode of 130 failed."); oxAssert(check(uint64_t(131)), "Decode of 131 failed."); oxAssert(check(0xffffffff), "Decode of 0xffffffff failed."); oxAssert(check(0xffffffffffff), "Decode of 0xffffffffffff failed."); oxAssert(check(0xffffffffffffffff), "Decode of U64 max failed."); return ox::Error(0); } }, { "MetalClawModelValue", [] { static constexpr size_t dataBuffLen = ox::units::MB; ox::Buffer dataBuff(dataBuffLen); TestStruct testIn; testIn.Bool = true; testIn.Int = 42; testIn.IString = "Test String 1"; testIn.List[0] = 1; testIn.List[1] = 2; testIn.List[2] = 3; testIn.List[3] = 4; testIn.Struct.Bool = true; testIn.Struct.Int = 300; testIn.Struct.IString = "Test String 2"; testIn.unionIdx = 1; testIn.Union.Int = 93; oxAssert(ox::writeMC(dataBuff.data(), dataBuff.size(), testIn), "Data generation failed"); ox::TypeStore typeStore; const auto [type, typeErr] = ox::buildTypeDef(typeStore, testIn); oxAssert(typeErr, "Descriptor write failed"); ox::ModelObject testOut; OX_RETURN_ERROR(testOut.setType(type)); oxAssert(ox::readMC(dataBuff, testOut), "Data read failed"); oxAssert(testOut.at("Int").unwrap()->get() == testIn.Int, "testOut.Int failed"); oxAssert(testOut.at("Bool").unwrap()->get() == testIn.Bool, "testOut.Bool failed"); oxAssert(testOut.at("IString").unwrap()->get() == testIn.IString.c_str(), "testOut.String failed"); oxAssert(testOut.at("String").unwrap()->get() == testIn.String, "testOut.String failed"); auto &testOutStruct = testOut.at("Struct").unwrap()->get(); auto &testOutUnion = testOut.at("Union").unwrap()->get(); auto &testOutList = testOut.at("List").unwrap()->get(); auto testOutStructCopy = testOut.at("Struct").unwrap()->get(); auto testOutUnionCopy = testOut.at("Union").unwrap()->get(); auto testOutListCopy = testOut.at("List").unwrap()->get(); oxAssert(testOutStruct.typeName() == TestStructNest::TypeName, "ModelObject TypeName failed"); oxAssert(testOutStruct.typeVersion() == TestStructNest::TypeVersion, "ModelObject TypeVersion failed"); oxAssert(testOutStruct.at("Bool").unwrap()->get() == testIn.Struct.Bool, "testOut.Struct.Bool failed"); oxAssert(testOutStruct.at("IString").unwrap()->get() == testIn.Struct.IString.c_str(), "testOut.Struct.IString failed"); oxAssert(testOut.at("unionIdx").unwrap()->get() == testIn.unionIdx, "testOut.unionIdx failed"); oxAssert(testOutUnion.unionIdx() == testIn.unionIdx, "testOut.Union idx wrong"); oxAssert(testOutUnion.at("Int").unwrap()->get() == testIn.Union.Int, "testOut.Union.Int failed"); oxAssert(testOutList[0].get() == testIn.List[0], "testOut.List[0] failed"); oxAssert(testOutList[1].get() == testIn.List[1], "testOut.Struct.List[1] failed"); oxAssert(testOutStructCopy.at("Bool").unwrap()->get() == testIn.Struct.Bool, "testOut.Struct.Bool (copy) failed"); oxAssert(testOutStructCopy.at("IString").unwrap()->get() == testIn.Struct.IString.c_str(), "testOut.Struct.IString (copy) failed"); oxAssert(testOutListCopy[0].get() == testIn.List[0], "testOut.Struct.List[0] (copy) failed"); oxAssert(testOutListCopy[1].get() == testIn.List[1], "testOut.Struct.List[1] (copy) failed"); return ox::Error(0); } }, { "MetalClawDef", [] { //constexpr size_t descBuffLen = 1024; //uint8_t descBuff[descBuffLen]; static constexpr size_t dataBuffLen = ox::units::MB; char dataBuff[dataBuffLen]; TestStruct testIn, testOut; testIn.Bool = true; testIn.Int = 42; testIn.IString = "Test String 1"; testIn.List[0] = 1; testIn.List[1] = 2; testIn.List[2] = 3; testIn.List[3] = 4; testIn.Struct.Bool = false; testIn.Struct.Int = 300; testIn.Struct.IString = "Test String 2"; oxAssert(ox::writeMC(dataBuff, dataBuffLen, testIn), "Data generation failed"); ox::TypeStore typeStore; const auto [type, typeErr] = ox::buildTypeDef(typeStore, testIn); oxAssert(typeErr, "Descriptor write failed"); ox::BufferReader br({dataBuff, dataBuffLen}); OX_RETURN_ERROR(ox::walkModel(type, br, [](const ox::Vector&, const ox::Vector&, const ox::DescriptorField &f, ox::MetalClawReader *rdr) -> ox::Error { //std::cout << f.fieldName.c_str() << '\n'; auto fieldName = f.fieldName.c_str(); switch (f.type->primitiveType) { case ox::PrimitiveType::UnsignedInteger: std::cout << fieldName << ":\tuint" << f.type->length * 8 << "_t:\t"; switch (f.type->length) { case 1: { uint8_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 2: { uint16_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 4: { uint32_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 8: { uint64_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } } std::cout << '\n'; break; case ox::PrimitiveType::SignedInteger: std::cout << fieldName << ":\tint" << f.type->length * 8 << "_t:\t"; switch (f.type->length) { case 1: { int8_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 2: { int16_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 4: { int32_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } case 8: { int64_t i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << i; break; } } std::cout << '\n'; break; case ox::PrimitiveType::Bool: { bool i = {}; oxAssert(rdr->field(fieldName, &i), "Walking model failed."); std::cout << fieldName << ":\t" << "bool:\t\t" << (i ? "true" : "false") << '\n'; break; } case ox::PrimitiveType::String: { ox::String s; //std::cout << rdr->stringLength() << '\n'; oxAssert(rdr->field(fieldName, &s), "Walking model failed."); oxOutf("{}:\tstring:\t\t{}\n", fieldName, s); break; } case ox::PrimitiveType::Struct: break; case ox::PrimitiveType::Union: break; } return ox::Error(0); } )); return ox::Error(0); } }, } }; int main(int argc, const char **args) { if (argc < 2) { oxError("Must specify test to run"); } auto const testName = args[1]; auto const func = tests.find(testName); if (func != tests.end()) { oxAssert(func->second(), "Test returned Error"); return 0; } return -1; }