Files
archived-Reclass/tests/test_format.cpp
IChooseYOu cae599a0c6 fix: fixed-width float formatting, fix test_32bit_support on Linux CI
Float values now use a fixed 7-char body (digits.decimals + f suffix)
that adapts decimal places to the integer magnitude. Removes the
variable-width 'g' format and sign-space prefix.

Set QT_QPA_PLATFORM=offscreen for test_32bit_support so it no longer
crashes on headless Linux CI without an X display.
2026-03-01 14:02:40 -07:00

336 lines
10 KiB
C++

#include <QtTest/QTest>
#include "core.h"
using namespace rcx;
class TestFormat : public QObject {
Q_OBJECT
private slots:
void testTypeName() {
QString s = fmt::typeName(NodeKind::Float);
QVERIFY(s.trimmed() == "float");
QCOMPARE(s.size(), 14); // kColType
}
void testFmtInt32() {
// fmtInt32 outputs decimal representation
QCOMPARE(fmt::fmtInt32(-42), QString("-42"));
QCOMPARE(fmt::fmtInt32(0), QString("0"));
}
void testFmtFloat() {
// Positive: 7 chars body. Negative: '-' + 7 chars = 8.
auto check = [](float v, const char* expected) {
QString s = fmt::fmtFloat(v);
QCOMPARE(s, QString(expected));
};
// Basic positive/negative
check( 3.14159f, "3.1416f");
check(-3.14159f, "-3.1416f");
// Zero
check( 0.f, "0.0000f");
// Small values
check( 0.02f, "0.0200f");
check(-0.069f, "-0.0690f");
// Values >= 10 — 3 decimal places
check( 15.6543f, "15.654f");
check(-77.6624f, "-77.662f");
// Values >= 100 — 2 decimal places
check( 500.f, "500.00f");
// Values >= 1000 — 1 decimal place
check( 5000.f, "5000.0f");
// Values >= 10000 — 0 decimal places + "."
check( 50000.f, "50000.f");
// Overflow cap
check( 100000.f, "99999+f");
check(-100000.f, "-99999+f");
// Special values
check( 1.f / 0.f, "inff");
check(-1.f / 0.f, "-inff");
QCOMPARE(fmt::fmtFloat(std::nanf("")), QString("NaN"));
// 1.0 exactly
check( 1.f, "1.0000f");
check(-1.f, "-1.0000f");
}
void testFmtBool() {
QCOMPARE(fmt::fmtBool(1), QString("true"));
QCOMPARE(fmt::fmtBool(0), QString("false"));
}
void testFmtPointer64_null() {
QCOMPARE(fmt::fmtPointer64(0), QString("0x0"));
}
void testFmtPointer64_nonNull() {
QString s = fmt::fmtPointer64(0x400000);
QVERIFY(s.startsWith("0x"));
QVERIFY(s.contains("400000"));
}
void testFmtOffsetMargin_primary() {
QCOMPARE(fmt::fmtOffsetMargin(0x10, false), QString("00000010 "));
QCOMPARE(fmt::fmtOffsetMargin(0, false), QString("00000000 "));
}
void testFmtOffsetMargin_continuation() {
QCOMPARE(fmt::fmtOffsetMargin(0x10, true), QString(" \u00B7 "));
}
void testFmtOffsetMargin_kernelAddr() {
QCOMPARE(fmt::fmtOffsetMargin(0xFFFFF80012345678ULL, false, 16),
QString("FFFFF80012345678 "));
QCOMPARE(fmt::fmtOffsetMargin(0x10, false, 16),
QString("0000000000000010 "));
QCOMPARE(fmt::fmtOffsetMargin(0x10, false, 4),
QString("0010 "));
}
void testFmtStructHeader() {
Node n;
n.kind = NodeKind::Struct;
n.name = "Test";
// Expanded header should contain opening brace
QString s = fmt::fmtStructHeader(n, 0, /*collapsed=*/false);
QVERIFY(s.contains("struct"));
QVERIFY(s.contains("Test"));
QVERIFY(s.contains("{"));
// Collapsed header should not contain opening brace
QString collapsed = fmt::fmtStructHeader(n, 0, /*collapsed=*/true);
QVERIFY(collapsed.contains("struct"));
QVERIFY(collapsed.contains("Test"));
QVERIFY(!collapsed.contains("{"));
}
void testFmtStructFooter() {
Node n;
n.kind = NodeKind::Struct;
n.name = "Test";
QString s = fmt::fmtStructFooter(n, 0);
QVERIFY(s.contains("};"));
// When no size, footer is just "};" without name
}
void testIndent() {
QCOMPARE(fmt::indent(0), QString(""));
QCOMPARE(fmt::indent(1), QString(" "));
QCOMPARE(fmt::indent(3), QString(" "));
}
void testParseValueInt32() {
bool ok;
QByteArray b = fmt::parseValue(NodeKind::Int32, "-42", &ok);
QVERIFY(ok);
QCOMPARE(b.size(), 4);
int32_t v;
memcpy(&v, b.data(), 4);
QCOMPARE(v, -42);
}
void testParseValueFloat() {
bool ok;
QByteArray b = fmt::parseValue(NodeKind::Float, "3.14", &ok);
QVERIFY(ok);
QCOMPARE(b.size(), 4);
float v;
memcpy(&v, b.data(), 4);
QVERIFY(qAbs(v - 3.14f) < 0.01f);
}
void testParseValueHex32() {
bool ok;
// Hex parsing produces native-endian bytes (matches display which reads native-endian)
QByteArray b = fmt::parseValue(NodeKind::Hex32, "DEADBEEF", &ok);
QVERIFY(ok);
QCOMPARE(b.size(), 4);
// Value 0xDEADBEEF stored as native-endian (little-endian on x86)
uint32_t v;
memcpy(&v, b.data(), 4);
QCOMPARE(v, (uint32_t)0xDEADBEEF);
}
void testParseValueBool() {
bool ok;
QByteArray b = fmt::parseValue(NodeKind::Bool, "true", &ok);
QVERIFY(ok);
QCOMPARE(b.size(), 1);
QCOMPARE((uint8_t)b[0], (uint8_t)1);
b = fmt::parseValue(NodeKind::Bool, "false", &ok);
QVERIFY(ok);
QCOMPARE((uint8_t)b[0], (uint8_t)0);
// Unknown token should fail
fmt::parseValue(NodeKind::Bool, "banana", &ok);
QVERIFY(!ok);
}
void testParseValueHex0xPrefix() {
bool ok;
// Hex32 with 0x prefix should work (native-endian, matches display)
QByteArray b = fmt::parseValue(NodeKind::Hex32, "0xDEADBEEF", &ok);
QVERIFY(ok);
uint32_t v32;
memcpy(&v32, b.data(), 4);
QCOMPARE(v32, (uint32_t)0xDEADBEEF);
// Pointer64 with 0x prefix
b = fmt::parseValue(NodeKind::Pointer64, "0x0000000000400000", &ok);
QVERIFY(ok);
uint64_t v64;
memcpy(&v64, b.data(), 8);
QCOMPARE(v64, (uint64_t)0x400000);
}
void testParseValueOverflow() {
bool ok;
// UInt8: 300 exceeds uint8_t max (255) → should fail
fmt::parseValue(NodeKind::UInt8, "300", &ok);
QVERIFY(!ok);
// UInt8: 255 should succeed
QByteArray b = fmt::parseValue(NodeKind::UInt8, "255", &ok);
QVERIFY(ok);
QCOMPARE((uint8_t)b[0], (uint8_t)255);
// Int8: 200 exceeds int8_t max (127) → should fail
fmt::parseValue(NodeKind::Int8, "200", &ok);
QVERIFY(!ok);
// Int8: -129 below min → should fail
fmt::parseValue(NodeKind::Int8, "-129", &ok);
QVERIFY(!ok);
// Int8: -128 is valid
b = fmt::parseValue(NodeKind::Int8, "-128", &ok);
QVERIFY(ok);
int8_t sv;
memcpy(&sv, b.data(), 1);
QCOMPARE(sv, (int8_t)-128);
// UInt16: 70000 exceeds uint16_t max → should fail
fmt::parseValue(NodeKind::UInt16, "70000", &ok);
QVERIFY(!ok);
// Hex8: 0x1FF exceeds uint8_t → should fail
fmt::parseValue(NodeKind::Hex8, "1FF", &ok);
QVERIFY(!ok);
// Hex16: 0x1FFFF exceeds uint16_t → should fail
fmt::parseValue(NodeKind::Hex16, "1FFFF", &ok);
QVERIFY(!ok);
}
void testSignedHexRoundTrip() {
bool ok;
// Int8: 0xFF should parse as -1 (two's complement)
QByteArray b = fmt::parseValue(NodeKind::Int8, "0xFF", &ok);
QVERIFY(ok);
int8_t sv8;
memcpy(&sv8, b.data(), 1);
QCOMPARE(sv8, (int8_t)-1);
// Int8: 0x80 should parse as -128
b = fmt::parseValue(NodeKind::Int8, "0x80", &ok);
QVERIFY(ok);
memcpy(&sv8, b.data(), 1);
QCOMPARE(sv8, (int8_t)-128);
// Int16: 0xFFFF should parse as -1
b = fmt::parseValue(NodeKind::Int16, "0xFFFF", &ok);
QVERIFY(ok);
int16_t sv16;
memcpy(&sv16, b.data(), 2);
QCOMPARE(sv16, (int16_t)-1);
// Int32: 0xFFFFFFFF should parse as -1
b = fmt::parseValue(NodeKind::Int32, "0xFFFFFFFF", &ok);
QVERIFY(ok);
int32_t sv32;
memcpy(&sv32, b.data(), 4);
QCOMPARE(sv32, (int32_t)-1);
// Int8: 0x1FF should fail (exceeds byte range)
fmt::parseValue(NodeKind::Int8, "0x1FF", &ok);
QVERIFY(!ok);
// Int16: 0x1FFFF should fail (exceeds 16-bit range)
fmt::parseValue(NodeKind::Int16, "0x1FFFF", &ok);
QVERIFY(!ok);
}
void testReadValueBoundsCheck() {
// Vec2 single-line: subLine=0 returns all components
QByteArray data(16, '\0');
BufferProvider prov(data);
Node n;
n.kind = NodeKind::Vec2;
n.name = "v";
QVERIFY(fmt::readValue(n, prov, 0, 0).contains(","));
// Vec3 single-line: subLine=0 returns 3 comma-separated values
n.kind = NodeKind::Vec3;
QCOMPARE(fmt::readValue(n, prov, 0, 0).count(','), 2);
// Vec4 single-line: subLine=0 returns 4 comma-separated values
n.kind = NodeKind::Vec4;
QCOMPARE(fmt::readValue(n, prov, 0, 0).count(','), 3);
}
void testEditableValueBasic() {
QByteArray data(16, '\0');
// Write a known float value
float val = 3.14f;
memcpy(data.data(), &val, 4);
BufferProvider prov(data);
Node n;
n.kind = NodeKind::Float;
n.name = "f";
QString s = fmt::editableValue(n, prov, 0, 0);
QVERIFY(s.contains("3.14"));
// Vec2 single-line: returns comma-separated values
n.kind = NodeKind::Vec2;
QString vec2 = fmt::editableValue(n, prov, 0, 0);
QVERIFY(vec2.contains(","));
}
void testParseValueEmptyString() {
bool ok;
// Empty UTF8 should succeed (caller pads)
QByteArray b = fmt::parseValue(NodeKind::UTF8, "", &ok);
QVERIFY(ok);
QVERIFY(b.isEmpty());
// Empty non-string should fail
fmt::parseValue(NodeKind::Int32, "", &ok);
QVERIFY(!ok);
}
void testFmtStructFooterSimple() {
Node n;
n.kind = NodeKind::Struct;
n.name = "Test";
// Footer is always just "};" (no sizeof comment)
QString s = fmt::fmtStructFooter(n, 0, 0x14);
QVERIFY(s.contains("};"));
QVERIFY(!s.contains("sizeof")); // No sizeof comment
}
};
QTEST_MAIN(TestFormat)
#include "test_format.moc"