diff options
Diffstat (limited to 'tests/auto/corelib/serialization')
12 files changed, 3567 insertions, 28 deletions
diff --git a/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro b/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro new file mode 100644 index 0000000000..5df331314a --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamreader/qcborstreamreader.pro @@ -0,0 +1,11 @@ +QT = core testlib +TARGET = tst_qcborstreamreader +CONFIG += testcase +SOURCES += \ + tst_qcborstreamreader.cpp + +INCLUDEPATH += \ + ../../../../../src/3rdparty/tinycbor/src \ + ../../../../../src/3rdparty/tinycbor/tests/parser + +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp new file mode 100644 index 0000000000..24d9c7409e --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp @@ -0,0 +1,1091 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qcborstream.h> +#include <QtTest> + +class tst_QCborStreamReader : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase_data(); + void basics(); + void clear_data(); + void clear(); + void integers_data(); + void integers(); + void fixed_data(); + void fixed(); + void strings_data(); + void strings(); + void tags_data(); + void tags() { fixed(); } + void emptyContainers_data(); + void emptyContainers(); + + void arrays_data(); + void arrays(); + void maps_data() { arrays_data(); } + void maps(); + void undefLengthArrays_data() { arrays_data(); } + void undefLengthArrays(); + void undefLengthMaps_data() { arrays_data(); } + void undefLengthMaps(); + + void next_data() { arrays_data(); } + void next(); + void validation_data(); + void validation(); + void recursionLimit_data(); + void recursionLimit(); + + void addData_singleElement_data(); + void addData_singleElement(); + void addData_complex_data() { arrays_data(); } + void addData_complex(); +}; + +#define FOR_CBOR_TYPE(F) \ + F(QCborStreamReader::UnsignedInteger) \ + F(QCborStreamReader::NegativeInteger) \ + F(QCborStreamReader::ByteArray) \ + F(QCborStreamReader::String) \ + F(QCborStreamReader::Array) \ + F(QCborStreamReader::Map) \ + F(QCborStreamReader::Tag) \ + F(QCborStreamReader::SimpleType) \ + F(QCborStreamReader::Float16) \ + F(QCborStreamReader::Float) \ + F(QCborStreamReader::Double) \ + F(QCborStreamReader::Invalid) + +QT_BEGIN_NAMESPACE +namespace QTest { +template<> char *toString<QCborStreamReader::Type>(const QCborStreamReader::Type &t) +{ + return qstrdup([=]() { + switch (t) { +#define TYPE(t) \ + case t: return QT_STRINGIFY(t); + FOR_CBOR_TYPE(TYPE) +#undef TYPE + } + return "<huh?>"; + }()); +} +} // namespace QTest +QT_END_NAMESPACE + +// Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) +#include "data.cpp" + +void tst_QCborStreamReader::initTestCase_data() +{ + QTest::addColumn<bool>("useDevice"); + QTest::newRow("QByteArray") << false; + QTest::newRow("QBuffer") << true; +} + +void tst_QCborStreamReader::basics() +{ + QFETCH_GLOBAL(bool, useDevice); + QBuffer buffer; + QCborStreamReader reader; + + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + QVERIFY(reader.device() != nullptr); + } else { + QCOMPARE(reader.device(), nullptr); + } + + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QCOMPARE(reader.type(), QCborStreamReader::Invalid); + QVERIFY(!reader.isUnsignedInteger()); + QVERIFY(!reader.isNegativeInteger()); + QVERIFY(!reader.isByteArray()); + QVERIFY(!reader.isString()); + QVERIFY(!reader.isArray()); + QVERIFY(!reader.isContainer()); + QVERIFY(!reader.isMap()); + QVERIFY(!reader.isTag()); + QVERIFY(!reader.isSimpleType()); + QVERIFY(!reader.isBool()); + QVERIFY(!reader.isNull()); + QVERIFY(!reader.isUndefined()); + QVERIFY(!reader.isFloat16()); + QVERIFY(!reader.isFloat()); + QVERIFY(!reader.isDouble()); + QVERIFY(!reader.isValid()); + QVERIFY(reader.isInvalid()); + + QCOMPARE(reader.containerDepth(), 0); + QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid); + QVERIFY(!reader.hasNext()); + QVERIFY(!reader.next()); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QVERIFY(reader.isLengthKnown()); // well, it's not unknown + QCOMPARE(reader.length(), ~0ULL); + + if (useDevice) { + reader.reparse(); + QVERIFY(reader.device() != nullptr); + } else { + reader.addData(QByteArray()); + QCOMPARE(reader.device(), nullptr); + } + + // nothing changes, we added nothing + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + + QCOMPARE(reader.type(), QCborStreamReader::Invalid); + QVERIFY(!reader.isUnsignedInteger()); + QVERIFY(!reader.isNegativeInteger()); + QVERIFY(!reader.isByteArray()); + QVERIFY(!reader.isString()); + QVERIFY(!reader.isArray()); + QVERIFY(!reader.isContainer()); + QVERIFY(!reader.isMap()); + QVERIFY(!reader.isTag()); + QVERIFY(!reader.isSimpleType()); + QVERIFY(!reader.isBool()); + QVERIFY(!reader.isNull()); + QVERIFY(!reader.isUndefined()); + QVERIFY(!reader.isFloat16()); + QVERIFY(!reader.isFloat()); + QVERIFY(!reader.isDouble()); + QVERIFY(!reader.isValid()); + QVERIFY(reader.isInvalid()); + + QVERIFY(reader.isLengthKnown()); // well, it's not unknown + QCOMPARE(reader.length(), ~0ULL); + + QCOMPARE(reader.containerDepth(), 0); + QCOMPARE(reader.parentContainerType(), QCborStreamReader::Invalid); + QVERIFY(!reader.hasNext()); + QVERIFY(!reader.next()); + + reader.clear(); + QCOMPARE(reader.device(), nullptr); + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); +} + +void tst_QCborStreamReader::clear_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QCborError>("firstError"); + QTest::addColumn<int>("offsetAfterSkip"); + QTest::newRow("invalid") << QByteArray(512, '\xff') << QCborError{QCborError::UnexpectedBreak} << 0; + QTest::newRow("valid") << QByteArray(512, '\0') << QCborError{QCborError::NoError} << 0; + QTest::newRow("skipped") << QByteArray(512, '\0') << QCborError{QCborError::NoError} << 1; +} + +void tst_QCborStreamReader::clear() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + QFETCH(QCborError, firstError); + QFETCH(int, offsetAfterSkip); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + QCOMPARE(reader.isValid(), !firstError); + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), firstError); + + if (offsetAfterSkip) { + reader.next(); + QVERIFY(!reader.isValid()); + QCOMPARE(reader.currentOffset(), 1); + QCOMPARE(reader.lastError(), QCborError::NoError); + } + + reader.clear(); + QCOMPARE(reader.device(), nullptr); + QCOMPARE(reader.currentOffset(), 0); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); +} + +void tst_QCborStreamReader::integers_data() +{ + addIntegers(); +} + +void tst_QCborStreamReader::integers() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + QFETCH(bool, isNegative); + QFETCH(quint64, expectedRaw); + QFETCH(qint64, expectedValue); + QFETCH(bool, inInt64Range); + quint64 absolute = (isNegative ? expectedRaw + 1 : expectedRaw); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QVERIFY(reader.isInteger()); + + if (inInt64Range) + QCOMPARE(reader.toInteger(), expectedValue); + if (isNegative) + QCOMPARE(quint64(reader.toNegativeInteger()), absolute); + else + QCOMPARE(reader.toUnsignedInteger(), absolute); +} + +void escapedAppendTo(QString &result, const QByteArray &data) +{ + result += "h'" + QString::fromLatin1(data.toHex()) + '\''; +} + +void escapedAppendTo(QString &result, const QString &data) +{ + result += '"'; + for (int i = 0; i <= data.size(); i += 245) { + // hopefully we won't have a surrogate pair split here + QScopedArrayPointer<char> escaped(QTest::toPrettyUnicode(data.mid(i, 245))); + QLatin1String s(escaped.data() + 1); // skip opening " + s.chop(1); // drop the closing " + result += s; + } + result += '"'; +} + +template <typename S, QCborStreamReader::StringResult<S> (QCborStreamReader:: *Decoder)()> +static QString parseOneString_helper(QCborStreamReader &reader) +{ + QString result; + bool parens = !reader.isLengthKnown(); + if (parens) + result += '('; + + auto r = (reader.*Decoder)(); + const char *comma = ""; + while (r.status == QCborStreamReader::Ok) { + result += comma; + escapedAppendTo(result, r.data); + + r = (reader.*Decoder)(); + comma = ", "; + } + + if (r.status == QCborStreamReader::Error) + return QString(); + + if (parens) + result += ')'; + return result; +} + +static QString parseOneByteArray(QCborStreamReader &reader) +{ + return parseOneString_helper<QByteArray, &QCborStreamReader::readByteArray>(reader); +} + +static QString parseOneString(QCborStreamReader &reader) +{ + return parseOneString_helper<QString, &QCborStreamReader::readString>(reader); +} + +static QString makeNegativeString(QCborNegativeInteger n) +{ + return n == QCborNegativeInteger(0) ? + QString("-18446744073709551616") : + QString("-%1").arg(quint64(n)); +} + +template <typename T> static inline bool canConvertTo(double v) +{ + // The [conv.fpint] (7.10 Floating-integral conversions) section of the + // standard says only exact conversions are guaranteed. Converting + // integrals to floating-point with loss of precision has implementation- + // defined behavior whether the next higher or next lower is returned; + // converting FP to integral is UB if it can't be represented.; + Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer); + + double supremum = ldexp(1, std::numeric_limits<T>::digits); + if (v >= supremum) + return false; + + if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact + return false; + + // we're in range + return v == floor(v); +} + +static QString makeFpString(double v) +{ + if (canConvertTo<qint64>(v)) + return QString::number(qint64(v)) + '.'; + if (canConvertTo<quint64>(v)) + return QString::number(quint64(v)) + '.'; + + QString s = QString::number(v, 'g', std::numeric_limits<double>::digits10 + 2); + if (!s.contains('.') && !s.contains('e') && !qIsInf(v) && !qIsNaN(v)) + s += '.'; + return s; +} + +static QString makeFpString(float v) +{ + if (qIsInf(v)) + return v > 0 ? "inf" : "-inf"; + if (qIsNaN(v)) + return "nan"; + return makeFpString(double(v)) + 'f'; +} + +static QString makeFpString(qfloat16 v) +{ + if (qIsInf(v)) + return v > 0 ? "inf" : "-inf"; + if (qIsNaN(v)) + return "nan"; + return makeFpString(double(v)) + "f16"; +} + +static QString parseOne(QCborStreamReader &reader) +{ + QString result; + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + result = QString::number(reader.toUnsignedInteger()); + break; + case QCborStreamReader::NegativeInteger: + result = makeNegativeString(reader.toNegativeInteger()); + break; + case QCborStreamReader::ByteArray: + return parseOneByteArray(reader); + case QCborStreamReader::String: + return parseOneString(reader); + case QCborStreamReader::Array: + case QCborStreamReader::Map: { + const char *delimiters = (reader.isArray() ? "[]" : "{}"); + result += delimiters[0]; + reader.enterContainer(); + + QLatin1String comma(""); + while (reader.lastError() == QCborError::NoError && reader.hasNext()) { + result += comma + parseOne(reader); + comma = QLatin1String(", "); + + if (reader.parentContainerType() == QCborStreamReader::Map + && reader.lastError() == QCborError::NoError) + result += ": " + parseOne(reader); + } + + if (reader.isValid()) + return QString(); + if (reader.lastError() != QCborError::NoError) + return QString(); + reader.leaveContainer(); + result += delimiters[1]; + return result; + } + case QCborStreamReader::Tag: { + QCborTag tag = reader.toTag(); + if (!reader.next()) + return QString(); + return QString("%1(%2)").arg(quint64(tag)).arg(parseOne(reader)); + } + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + result = QStringLiteral("false"); + break; + case QCborSimpleType::True: + result = QStringLiteral("true"); + break; + case QCborSimpleType::Null: + result = QStringLiteral("null"); + break; + case QCborSimpleType::Undefined: + result = QStringLiteral("undefined"); + break; + default: + result = QString("simple(%1)").arg(quint8(reader.toSimpleType())); + break; + } + break; + case QCborStreamReader::Float16: + result = makeFpString(reader.toFloat16()); + break; + case QCborStreamReader::Float: + result = makeFpString(reader.toFloat()); + break; + case QCborStreamReader::Double: + result = makeFpString(reader.toDouble()); + break; + case QCborStreamReader::Invalid: + return QStringLiteral("<invalid>"); + } + + if (!reader.next()) + return QString(); + return result; +} + +bool parseNonRecursive(QString &result, bool &printingStringChunks, QCborStreamReader &reader) +{ + while (reader.lastError() == QCborError::NoError) { + if (!reader.hasNext()) { + if (result.endsWith(", ")) + result.chop(2); + if (reader.containerDepth() == 0) + return true; + result += reader.parentContainerType() == QCborStreamReader::Map ? "}, " : "], "; + reader.leaveContainer(); + continue; + } + + switch (reader.type()) { + case QCborStreamReader::UnsignedInteger: + result += QString::number(reader.toUnsignedInteger()); + break; + case QCborStreamReader::NegativeInteger: + result += makeNegativeString(reader.toNegativeInteger()); + break; + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: { + QCborStreamReader::StringResultCode status; + if (!printingStringChunks && !reader.isLengthKnown()) { + printingStringChunks = true; + result += '('; + } + if (reader.isByteArray()) { + auto r = reader.readByteArray(); + status = r.status; + if (r.status == QCborStreamReader::Ok) + escapedAppendTo(result, r.data); + } else { + auto r = reader.readString(); + status = r.status; + if (r.status == QCborStreamReader::Ok) + escapedAppendTo(result, r.data); + } + + if (status == QCborStreamReader::EndOfString) { + if (result.endsWith(", ")) + result.chop(2); + if (printingStringChunks) + result += ')'; + result += ", "; + printingStringChunks = false; + } + if (status == QCborStreamReader::Ok && printingStringChunks) + result += ", "; + + continue; + } + case QCborStreamReader::Array: + result += '['; + reader.enterContainer(); + continue; + case QCborStreamReader::Map: + result += '{'; + reader.enterContainer(); + continue; + case QCborStreamReader::Tag: + result += QString("Tag:%1:").arg(quint64(reader.toTag())); + reader.next(); + continue; // skip the comma + case QCborStreamReader::SimpleType: + switch (reader.toSimpleType()) { + case QCborSimpleType::False: + result += QStringLiteral("false"); + break; + case QCborSimpleType::True: + result += QStringLiteral("true"); + break; + case QCborSimpleType::Null: + result += QStringLiteral("null"); + break; + case QCborSimpleType::Undefined: + result += QStringLiteral("undefined"); + break; + default: + result += QString("simple(%1)").arg(quint8(reader.toSimpleType())); + break; + } + break; + case QCborStreamReader::Float16: + result += makeFpString(reader.toFloat16()); + break; + case QCborStreamReader::Float: + result += makeFpString(reader.toFloat()); + break; + case QCborStreamReader::Double: + result += makeFpString(reader.toDouble()); + break; + case QCborStreamReader::Invalid: + break; + } + + reader.next(); + result += ", "; + } + return false; +}; + + +static QString &removeIndicators(QString &str) +{ + // remove any CBOR encoding indicators from the string, since parseOne above + // doesn't produce them + static QRegularExpression rx("_(\\d+)? ?"); + return str.replace(rx, QString()); +} + +void tst_QCborStreamReader::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_QCborStreamReader::fixed() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::strings_data() +{ + addColumns(); + addStringsData(); +} + +void tst_QCborStreamReader::strings() +{ + fixed(); + if (QTest::currentTestFailed()) + return; + + // Extra string checks: + // We'll compare the reads using readString() and readByteArray() + // (henceforth "control data" because fixed() above tested them) with those + // obtained with readStringChunk(). + + QFETCH(QByteArray, data); + QFETCH(QString, expected); + QFETCH_GLOBAL(bool, useDevice); + bool isChunked = expected.startsWith('('); + + QBuffer buffer(&data), controlBuffer(&data); + QCborStreamReader reader(data), controlReader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + controlBuffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + controlReader.setDevice(&controlBuffer); + } + QVERIFY(reader.isString() || reader.isByteArray()); + QCOMPARE(reader.isLengthKnown(), !isChunked); + + if (!isChunked) + QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length())); + + int chunks = 0; + forever { + QCborStreamReader::StringResult<QByteArray> controlData; + if (reader.isString()) { + auto r = controlReader.readString(); + controlData.data = r.data.toUtf8(); + controlData.status = r.status; + } else { + controlData = controlReader.readByteArray(); + } + QVERIFY(controlData.status != QCborStreamReader::Error); + + for (int i = 0; i < 10; ++i) { + // this call must work several times with the same result + QCOMPARE(reader.currentStringChunkSize(), controlData.data.size()); + } + + QByteArray chunk(controlData.data.size(), Qt::Uninitialized); + auto r = reader.readStringChunk(chunk.data(), chunk.size()); + QCOMPARE(r.status, controlData.status); + if (r.status == QCborStreamReader::Ok) + QCOMPARE(r.data, controlData.data.size()); + else + QCOMPARE(r.data, 0); + QCOMPARE(chunk, controlData.data); + + if (r.status == QCborStreamReader::EndOfString) + break; + ++chunks; + } + + if (!isChunked) + QCOMPARE(chunks, 1); +} + +void tst_QCborStreamReader::tags_data() +{ + addColumns(); + addTagsData(); +} + +void tst_QCborStreamReader::emptyContainers_data() +{ + addColumns(); + addEmptyContainersData(); +} + +void tst_QCborStreamReader::emptyContainers() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (reader.isLengthKnown()) + QCOMPARE(reader.length(), 0U); + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (reader.isLengthKnown()) + QCOMPARE(reader.length(), 0U); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::arrays_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addTagsData(); + addEmptyContainersData(); +} + +static void checkContainer(int len, const QByteArray &data, const QString &expected) +{ + QFETCH_GLOBAL(bool, useDevice); + + QByteArray copy = data; + QBuffer buffer(©); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (len >= 0) { + QVERIFY(reader.isLengthKnown()); + QCOMPARE(reader.length(), uint(len)); + } + QCOMPARE(parseOne(reader), expected); + + // verify that we can re-read + reader.reset(); + QVERIFY(reader.isValid()); + QCOMPARE(reader.lastError(), QCborError::NoError); + if (len >= 0) { + QVERIFY(reader.isLengthKnown()); + QCOMPARE(reader.length(), uint(len)); + } + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::arrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + checkContainer(1, '\x81' + data, '[' + expected + ']'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, '\x82' + data + data, '[' + expected + ", " + expected + ']'); +} + +void tst_QCborStreamReader::maps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // int keys + checkContainer(1, "\xa1\1" + data, "{1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, "\xa2\1" + data + '\x20' + data, + "{1: " + expected + ", -1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + // string keys + checkContainer(1, "\xa1\x65Hello" + data, "{\"Hello\": " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(2, "\xa2\x65World" + data + "\x65Hello" + data, + "{\"World\": " + expected + ", \"Hello\": " + expected + '}'); +} + +void tst_QCborStreamReader::undefLengthArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + checkContainer(-1, '\x9f' + data + '\xff', '[' + expected + ']'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, '\x9f' + data + data + '\xff', '[' + expected + ", " + expected + ']'); +} + +void tst_QCborStreamReader::undefLengthMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // int keys + checkContainer(-1, "\xbf\1" + data + '\xff', "{1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, "\xbf\1" + data + '\x20' + data + '\xff', + "{1: " + expected + ", -1: " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + // string keys + checkContainer(-1, "\xbf\x65Hello" + data + '\xff', "{\"Hello\": " + expected + '}'); + if (QTest::currentTestFailed()) + return; + + checkContainer(-2, "\xbf\x65World" + data + "\x65Hello" + data + '\xff', + "{\"World\": " + expected + ", \"Hello\": " + expected + '}'); +} + +void tst_QCborStreamReader::next() +{ + QFETCH(QByteArray, data); + + auto doit = [](QByteArray data) { + QFETCH_GLOBAL(bool, useDevice); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + return reader.next(); + }; + + QVERIFY(doit('\x81' + data)); + QVERIFY(doit('\x82' + data + data)); + QVERIFY(doit('\x9f' + data + '\xff')); + QVERIFY(doit("\x81\x9f" + data + '\xff')); + QVERIFY(doit("\x9f\x81" + data + '\xff')); + + QVERIFY(doit("\xa1\1" + data)); + QVERIFY(doit("\xa2\1" + data + '\x20' + data)); + QVERIFY(doit("\xbf\1" + data + '\xff')); + QVERIFY(doit("\xbf\x9f\1\xff\x9f" + data + "\xff\xff")); +} + +void tst_QCborStreamReader::validation_data() +{ + addValidationColumns(); + addValidationData(); +} + +void tst_QCborStreamReader::validation() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + parseOne(reader); + QVERIFY(reader.lastError() != QCborError::NoError); + + // next() should fail + reader.reset(); + QVERIFY(!reader.next()); + QVERIFY(reader.lastError() != QCborError::NoError); +} + +static const int Recursions = 3; +void tst_QCborStreamReader::recursionLimit_data() +{ + static const int recursions = Recursions + 2; + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("array") << QByteArray(recursions, '\x81') + '\x20'; + QTest::newRow("_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff'); + + QByteArray data; + for (int i = 0; i < recursions; ++i) + data += "\xa1\x65Hello"; + data += '\2'; + QTest::newRow("map-recursive-values") << data; + + data.clear(); + for (int i = 0; i < recursions; ++i) + data += "\xbf\x65World"; + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\xff"; + QTest::newRow("_map-recursive-values") << data; + + data = QByteArray(recursions, '\xa1'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\x7f\x64quux\xff"; + QTest::newRow("map-recursive-keys") << data; + + data = QByteArray(recursions, '\xbf'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\1\xff"; + QTest::newRow("_map-recursive-keys") << data; + + data.clear(); + for (int i = 0; i < recursions / 2; ++i) + data += "\x81\xa1\1"; + data += '\2'; + QTest::newRow("mixed") << data; +} + +void tst_QCborStreamReader::recursionLimit() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + + data.prepend('\x81'); + QBuffer buffer(&data); + QCborStreamReader reader(data); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + + // verify that it works normally: + QVERIFY(reader.enterContainer()); + QVERIFY(reader.next()); + QVERIFY(!reader.hasNext()); + + reader.reset(); + QVERIFY(reader.enterContainer()); + QVERIFY(!reader.next(Recursions)); + QCOMPARE(reader.lastError(), QCborError::NestingTooDeep); +} + +void tst_QCborStreamReader::addData_singleElement_data() +{ + addColumns(); + addFixedData(); + addNonChunkedStringsData(); +} + +void tst_QCborStreamReader::addData_singleElement() +{ + QFETCH_GLOBAL(bool, useDevice); + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + QByteArray growing; + QBuffer buffer(&growing); + QCborStreamReader reader; + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + for (int i = 0; i < data.size() - 1; ++i) { + // add one byte from the data + if (useDevice) { + growing.append(data.at(i)); + reader.reparse(); + } else { + reader.addData(data.constData() + i, 1); + } + + parseOne(reader); + QCOMPARE(reader.lastError(), QCborError::EndOfFile); + } + + // add the last byte + if (useDevice) { + growing.append(data.right(1)); + reader.reparse(); + } else { + reader.addData(data.right(1)); + } + QCOMPARE(reader.lastError(), QCborError::NoError); + QCOMPARE(parseOne(reader), expected); +} + +void tst_QCborStreamReader::addData_complex() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + removeIndicators(expected); + + // transform tags (parseNonRecursive can't produce the usual form) + expected.replace(QRegularExpression(R"/((\d+)\(([^)]+)\))/"), "Tag:\\1:\\2"); + + auto doit = [](const QByteArray &data) { + QFETCH_GLOBAL(bool, useDevice); + + // start with one byte + int added = 1; + QByteArray growing = data.left(added); + QBuffer buffer(&growing); + QCborStreamReader reader(growing); + if (useDevice) { + buffer.open(QIODevice::ReadOnly); + reader.setDevice(&buffer); + } + + QString result; + bool printingStringChunks = false; + forever { + if (parseNonRecursive(result, printingStringChunks, reader)) + return result; + if (reader.lastError() != QCborError::EndOfFile) + return reader.lastError().toString(); + + while (reader.lastError() == QCborError::EndOfFile) { + // add more data + if (added == data.size()) + return QStringLiteral("Couldn't parse even with all data"); + + if (useDevice) { + growing.append(data.at(added)); + reader.reparse(); + } else { + reader.addData(data.constData() + added, 1); + } + ++added; + } + } + }; + + // plain: + QCOMPARE(doit(data), expected); + + // in an array + QCOMPARE(doit('\x81' + data), '[' + expected + ']'); + QCOMPARE(doit('\x82' + data + data), '[' + expected + ", " + expected + ']'); + + QCOMPARE(doit('\x9f' + data + '\xff'), '[' + expected + ']'); + QCOMPARE(doit('\x9f' + data + data + '\xff'), '[' + expected + ", " + expected + ']'); + + // in a map + QCOMPARE(doit("\xa1\x01" + data), "{1, " + expected + '}'); + QCOMPARE(doit("\xa1\x65Hello" + data), "{\"Hello\", " + expected + '}'); + QCOMPARE(doit("\xa1\x7f\x65Hello\x65World\xff" + data), "{(\"Hello\", \"World\"), " + expected + '}'); + QCOMPARE(doit("\xa2\x01" + data + "\x65Hello" + data), + "{1, " + expected + ", \"Hello\", " + expected + '}'); + + QCOMPARE(doit("\xbf\x01" + data + '\xff'), "{1, " + expected + '}'); + + // mixed + QCOMPARE(doit("\xbf\x01\x81" + data + '\xff'), "{1, [" + expected + "]}"); + QCOMPARE(doit("\xbf\x01\x82" + data + data + '\xff'), + "{1, [" + expected + ", " + expected + "]}"); + QCOMPARE(doit("\xbf\x01\x9f" + data + data + "\xff\xff"), + "{1, [" + expected + ", " + expected + "]}"); +} + + +QTEST_MAIN(tst_QCborStreamReader) + +#include "tst_qcborstreamreader.moc" diff --git a/tests/auto/corelib/serialization/qcborstreamwriter/qcborstreamwriter.pro b/tests/auto/corelib/serialization/qcborstreamwriter/qcborstreamwriter.pro new file mode 100644 index 0000000000..3391b5a296 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamwriter/qcborstreamwriter.pro @@ -0,0 +1,8 @@ +QT = core testlib +TARGET = tst_qcborstreamwriter +CONFIG += testcase +SOURCES += \ + tst_qcborstreamwriter.cpp + +INCLUDEPATH += ../../../../../src/3rdparty/tinycbor/tests/encoder +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/corelib/serialization/qcborstreamwriter/tst_qcborstreamwriter.cpp b/tests/auto/corelib/serialization/qcborstreamwriter/tst_qcborstreamwriter.cpp new file mode 100644 index 0000000000..6995b4d08b --- /dev/null +++ b/tests/auto/corelib/serialization/qcborstreamwriter/tst_qcborstreamwriter.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> + +class tst_QCborStreamWriter : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase_data(); + void fixed_data(); + void fixed(); + void strings_data(); + void strings() { fixed(); } + void nonAsciiStrings_data(); + void nonAsciiStrings(); + void arraysAndMaps_data(); + void arraysAndMaps() { fixed(); } + void tags_data(); + void tags(); + void arrays_data() { tags_data(); } + void arrays(); + void maps_data() { tags_data(); } + void maps(); +}; + +// Get the data from TinyCBOR (see src/3rdparty/tinycbor/tests/encoder/data.cpp) +typedef quint64 CborTag; +#include "data.cpp" + +void encodeVariant(QCborStreamWriter &writer, const QVariant &v) +{ + int type = v.userType(); + switch (type) { + case QVariant::Int: + case QVariant::LongLong: + return writer.append(v.toLongLong()); + + case QVariant::UInt: + case QVariant::ULongLong: + return writer.append(v.toULongLong()); + + case QVariant::Bool: + return writer.append(v.toBool()); + + case QVariant::Invalid: + return writer.appendUndefined(); + + case QMetaType::VoidStar: + return writer.append(nullptr); + + case QVariant::Double: + return writer.append(v.toDouble()); + + case QMetaType::Float: + return writer.append(v.toFloat()); + + case QVariant::String: + return writer.append(v.toString()); + + case QVariant::ByteArray: + return writer.append(v.toByteArray()); + + default: + if (type == qMetaTypeId<NegativeInteger>()) + return writer.append(QCborNegativeInteger(v.value<NegativeInteger>().abs)); + if (type == qMetaTypeId<SimpleType>()) + return writer.append(QCborSimpleType(v.value<SimpleType>().type)); + if (type == qMetaTypeId<qfloat16>()) + return writer.append(v.value<qfloat16>()); + if (type == qMetaTypeId<Tag>()) { + writer.append(QCborTag(v.value<Tag>().tag)); + return encodeVariant(writer, v.value<Tag>().tagged); + } + if (type == QVariant::List || type == qMetaTypeId<IndeterminateLengthArray>()) { + QVariantList list = v.toList(); + if (type == qMetaTypeId<IndeterminateLengthArray>()) { + list = v.value<IndeterminateLengthArray>(); + writer.startArray(); + } else { + writer.startArray(list.length()); + } + for (const QVariant &v2 : qAsConst(list)) + encodeVariant(writer, v2); + QVERIFY(writer.endArray()); + return; + } + if (type == qMetaTypeId<Map>() || type == qMetaTypeId<IndeterminateLengthMap>()) { + Map map = v.value<Map>(); + if (type == qMetaTypeId<IndeterminateLengthMap>()) { + map = v.value<IndeterminateLengthMap>(); + writer.startMap(); + } else { + writer.startMap(map.length()); + } + for (auto pair : qAsConst(map)) { + encodeVariant(writer, pair.first); + encodeVariant(writer, pair.second); + } + QVERIFY(writer.endMap()); + return; + } + } + QFAIL("Shouldn't have got here"); +} + +void compare(const QVariant &input, const QByteArray &output) +{ + QFETCH_GLOBAL(bool, useDevice); + + if (useDevice) { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QCborStreamWriter writer(&buffer); + encodeVariant(writer, input); + QCOMPARE(buffer.data(), output); + } else { + QByteArray buffer; + QCborStreamWriter writer(&buffer); + encodeVariant(writer, input); + QCOMPARE(buffer, output); + } +} + +void tst_QCborStreamWriter::initTestCase_data() +{ + QTest::addColumn<bool>("useDevice"); + QTest::newRow("QByteArray") << false; + QTest::newRow("QIODevice") << true; +} + +void tst_QCborStreamWriter::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_QCborStreamWriter::fixed() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + compare(input, output); +} + +void tst_QCborStreamWriter::strings_data() +{ + addColumns(); + addStringsData(); +} + +void tst_QCborStreamWriter::nonAsciiStrings_data() +{ + QTest::addColumn<QByteArray>("output"); + QTest::addColumn<QString>("input"); + QTest::addColumn<bool>("isLatin1"); + + QByteArray latin1 = u8"Résumé"; + QTest::newRow("shortlatin1") + << ("\x68" + latin1) << QString::fromUtf8(latin1) << true; + + // replicate it 5 times (total 40 bytes) + latin1 += latin1 + latin1 + latin1 + latin1; + QTest::newRow("longlatin1") + << ("\x78\x28" + latin1) << QString::fromUtf8(latin1) << true; + + QByteArray nonlatin1 = u8"Χαίρετε"; + QTest::newRow("shortnonlatin1") + << ("\x6e" + nonlatin1) << QString::fromUtf8(nonlatin1) << false; + + // replicate it 4 times (total 56 bytes) + nonlatin1 = nonlatin1 + nonlatin1 + nonlatin1 + nonlatin1; + QTest::newRow("longnonlatin1") + << ("\x78\x38" + nonlatin1) << QString::fromUtf8(nonlatin1) << false; +} + +void tst_QCborStreamWriter::nonAsciiStrings() +{ + QFETCH(QByteArray, output); + QFETCH(QString, input); + QFETCH(bool, isLatin1); + QFETCH_GLOBAL(bool, useDevice); + + // will be wrong if !isLatin1 + QByteArray latin1 = input.toLatin1(); + + if (useDevice) { + { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QCborStreamWriter writer(&buffer); + writer.append(input); + QCOMPARE(buffer.data(), output); + } + + if (isLatin1) { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QCborStreamWriter writer(&buffer); + writer.append(QLatin1String(latin1.constData(), latin1.size())); + QCOMPARE(buffer.data(), output); + } + } else { + { + QByteArray buffer; + QCborStreamWriter writer(&buffer); + encodeVariant(writer, input); + QCOMPARE(buffer, output); + } + + if (isLatin1) { + QByteArray buffer; + QCborStreamWriter writer(&buffer); + writer.append(QLatin1String(latin1.constData(), latin1.size())); + QCOMPARE(buffer, output); + } + } +} + +void tst_QCborStreamWriter::arraysAndMaps_data() +{ + addColumns(); + addArraysAndMaps(); +} + +void tst_QCborStreamWriter::tags_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addArraysAndMaps(); +} + +void tst_QCborStreamWriter::tags() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(QVariant::fromValue(Tag{1, input}), "\xc1" + output); +} + +void tst_QCborStreamWriter::arrays() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(make_list(input), "\x81" + output); + if (QTest::currentTestFailed()) + return; + + compare(make_list(input, input), "\x82" + output + output); + if (QTest::currentTestFailed()) + return; + + // nested lists + compare(make_list(make_list(input)), "\x81\x81" + output); + if (QTest::currentTestFailed()) + return; + + compare(make_list(make_list(input), make_list(input)), "\x82\x81" + output + "\x81" + output); +} + +void tst_QCborStreamWriter::maps() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(make_map({{1, input}}), "\xa1\1" + output); + if (QTest::currentTestFailed()) + return; + + compare(make_map({{1, input}, {input, 24}}), "\xa2\1" + output + output + "\x18\x18"); +} + +QTEST_MAIN(tst_QCborStreamWriter) + +#include "tst_qcborstreamwriter.moc" diff --git a/tests/auto/corelib/serialization/qcborvalue/qcborvalue.pro b/tests/auto/corelib/serialization/qcborvalue/qcborvalue.pro new file mode 100644 index 0000000000..9dd67da1f0 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborvalue/qcborvalue.pro @@ -0,0 +1,11 @@ +QT = core testlib +TARGET = tst_qcborvalue +CONFIG += testcase +SOURCES += \ + tst_qcborvalue.cpp + +INCLUDEPATH += \ + ../../../../../src/3rdparty/tinycbor/src \ + ../../../../../src/3rdparty/tinycbor/tests/parser + +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp new file mode 100644 index 0000000000..38b26e7de4 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -0,0 +1,1695 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qcborvalue.h> +#include <QtTest> + +Q_DECLARE_METATYPE(QCborValue) +Q_DECLARE_METATYPE(QCborValue::EncodingOptions) + +class tst_QCborValue : public QObject +{ + Q_OBJECT + +private slots: + void basics_data(); + void basics(); + void tagged_data() { basics_data(); } + void tagged(); + void extendedTypes_data(); + void extendedTypes(); + void copyCompare_data() { basics_data(); } + void copyCompare(); + + void arrayDefaultInitialization(); + void arrayEmptyInitializerList(); + void arrayEmptyDetach(); + void arrayInitializerList(); + void arrayMutation(); + void arrayPrepend(); + void arrayInsertRemove_data() { basics_data(); } + void arrayInsertRemove(); + void arrayInsertTagged_data() { basics_data(); } + void arrayInsertTagged(); + void arrayStringElements(); + void arraySelfAssign_data() { basics_data(); } + void arraySelfAssign(); + + void mapDefaultInitialization(); + void mapEmptyInitializerList(); + void mapEmptyDetach(); + void mapSimpleInitializerList(); + void mapMutation(); + void mapStringValues(); + void mapStringKeys(); + void mapInsertRemove_data() { basics_data(); } + void mapInsertRemove(); + void mapInsertTagged_data() { basics_data(); } + void mapInsertTagged(); + void mapSelfAssign_data() { basics_data(); } + void mapSelfAssign(); + void mapComplexKeys_data() { basics_data(); } + void mapComplexKeys(); + + void sorting(); + + void toCbor_data(); + void toCbor(); + void fromCbor_data(); + void fromCbor(); + void validation_data(); + void validation(); + void toDiagnosticNotation_data(); + void toDiagnosticNotation(); +}; + +// Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) +#include "data.cpp" + +struct SimpleTypeWrapper +{ + // QCborSimpleType is an enum, so QVariant knows how to convert it to + // integer and we don't want it to do that. + SimpleTypeWrapper(QCborSimpleType type = {}) : st(type) {} + QCborSimpleType st; +}; +Q_DECLARE_METATYPE(SimpleTypeWrapper) + +void tst_QCborValue::basics_data() +{ + QTest::addColumn<QCborValue::Type>("type"); + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<QVariant>("expectedValue"); + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + + QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>(); + auto add = [me](QCborValue::Type t, const QCborValue &v, const QVariant &exp) { + auto addRow = [=]() -> QTestData & { + const char *typeString = me.valueToKey(t); + if (t == QCborValue::Integer) + return QTest::addRow("Integer:%lld", exp.toLongLong()); + if (t == QCborValue::Double) + return QTest::addRow("Double:%g", exp.toDouble()); + if (t == QCborValue::ByteArray || t == QCborValue::String) + return QTest::addRow("%s:%d", typeString, exp.toString().size()); + return QTest::newRow(typeString); + }; + addRow() << t << v << exp; + }; + auto st = [](QCborSimpleType t) { return QVariant::fromValue<SimpleTypeWrapper>(t); }; + + add(QCborValue::Undefined, QCborValue(), st(QCborSimpleType::Undefined)); + add(QCborValue::Null, QCborValue::Null, st(QCborSimpleType::Null)); + QTest::newRow("nullptr") << QCborValue::Null << QCborValue(nullptr) + << st(QCborSimpleType::Null); + add(QCborValue::False, false, st(QCborSimpleType::False)); + QTest::newRow("false") << QCborValue::False << QCborValue(QCborValue::False) + << st(QCborSimpleType::False); + add(QCborValue::True, true, st(QCborSimpleType::True)); + QTest::newRow("true") << QCborValue::True << QCborValue(QCborValue::True) + << st(QCborSimpleType::True); + QTest::newRow("simpletype") << QCborValue::Type(QCborValue::SimpleType + 255) + << QCborValue(QCborSimpleType(255)) + << st(QCborSimpleType(255)); + add(QCborValue::Integer, 0, 0); + add(QCborValue::Integer, 1, 1); + add(QCborValue::Integer, -1, -1); + add(QCborValue::Integer, std::numeric_limits<qint64>::min(), std::numeric_limits<qint64>::min()); + add(QCborValue::Integer, std::numeric_limits<qint64>::max(), std::numeric_limits<qint64>::max()); + add(QCborValue::Double, 0., 0.); + add(QCborValue::Double, 1.25, 1.25); + add(QCborValue::Double, -1.25, -1.25); + add(QCborValue::Double, qInf(), qInf()); + add(QCborValue::Double, -qInf(), -qInf()); + add(QCborValue::Double, qQNaN(), qQNaN()); + add(QCborValue::ByteArray, QByteArray("Hello"), QByteArray("Hello")); + add(QCborValue::ByteArray, QByteArray(), QByteArray()); + add(QCborValue::String, "Hello", "Hello"); + add(QCborValue::String, QLatin1String(), QString()); + add(QCborValue::DateTime, QCborValue(dt), dt); + add(QCborValue::Url, QCborValue(QUrl("http://example.com")), QUrl("http://example.com")); + add(QCborValue::RegularExpression, QCborValue(QRegularExpression("^.*$")), QRegularExpression("^.*$")); + add(QCborValue::Uuid, QCborValue(uuid), uuid); + + // empty arrays and maps + add(QCborValue::Array, QCborArray(), QVariantList()); + add(QCborValue::Map, QCborMap(), QVariantMap()); +} + +static void basicTypeCheck(QCborValue::Type type, const QCborValue &v, const QVariant &expectedValue) +{ + bool isSimpleType = (expectedValue.userType() == qMetaTypeId<SimpleTypeWrapper>()); + QCborSimpleType st = expectedValue.value<SimpleTypeWrapper>().st; + + QCOMPARE(v.type(), type); + QCOMPARE(v.isInteger(), type == QCborValue::Integer); + QCOMPARE(v.isByteArray(), type == QCborValue::ByteArray); + QCOMPARE(v.isString(), type == QCborValue::String); + QCOMPARE(v.isArray(), type == QCborValue::Array); + QCOMPARE(v.isMap(), type == QCborValue::Map); + QCOMPARE(v.isFalse(), type == QCborValue::False); + QCOMPARE(v.isTrue(), type == QCborValue::True); + QCOMPARE(v.isBool(), type == QCborValue::False || type == QCborValue::True); + QCOMPARE(v.isNull(), type == QCborValue::Null); + QCOMPARE(v.isUndefined(), type == QCborValue::Undefined); + QCOMPARE(v.isDouble(), type == QCborValue::Double); + QCOMPARE(v.isDateTime(), type == QCborValue::DateTime); + QCOMPARE(v.isUrl(), type == QCborValue::Url); + QCOMPARE(v.isUuid(), type == QCborValue::Uuid); + QCOMPARE(v.isInvalid(), type == QCborValue::Invalid); + QCOMPARE(v.isContainer(), type == QCborValue::Array || type == QCborValue::Map); + QCOMPARE(v.isSimpleType(), isSimpleType); + QCOMPARE(v.isSimpleType(QCborSimpleType::False), st == QCborSimpleType::False); + QCOMPARE(v.isSimpleType(QCborSimpleType::True), st == QCborSimpleType::True); + QCOMPARE(v.isSimpleType(QCborSimpleType::Null), st == QCborSimpleType::Null); + QCOMPARE(v.isSimpleType(QCborSimpleType::Undefined), st == QCborSimpleType::Undefined); + QCOMPARE(v.isSimpleType(QCborSimpleType(255)), st == QCborSimpleType(255)); + + if (v.isInteger()) { + QCOMPARE(v.toInteger(), expectedValue.toLongLong()); + QCOMPARE(v.toDouble(), 0. + expectedValue.toLongLong()); + } else { + QCOMPARE(v.toInteger(), qint64(expectedValue.toDouble())); + QCOMPARE(v.toDouble(), expectedValue.toDouble()); + } + QCOMPARE(v.toBool(true), st != QCborSimpleType::False); + QCOMPARE(v.toBool(), st == QCborSimpleType::True); + if (st == QCborSimpleType::Undefined) + QCOMPARE(v.toSimpleType(QCborSimpleType::Null), QCborSimpleType::Undefined); + else if (isSimpleType) + QCOMPARE(v.toSimpleType(), st); + else + QCOMPARE(v.toSimpleType(), QCborSimpleType::Undefined); + +#define CMP(expr, T, validexpr) \ + if (expectedValue.userType() == qMetaTypeId<T>()) \ + QCOMPARE(expr, expectedValue.value<T>()); \ + else \ + QVERIFY(validexpr) + CMP(v.toByteArray(), QByteArray, v.toByteArray().isNull()); + CMP(v.toString(), QString, v.toString().isNull()); + CMP(v.toDateTime(), QDateTime, !v.toDateTime().isValid()); + CMP(v.toUrl(), QUrl, !v.toUrl().isValid()); + CMP(v.toRegularExpression(), QRegularExpression, v.toRegularExpression().pattern().isNull()); + CMP(v.toUuid(), QUuid, v.toUuid().isNull()); +#undef CMP + + QVERIFY(v.toArray().isEmpty()); + QVERIFY(v.toMap().isEmpty()); + + QVERIFY(v["Hello"].isUndefined()); + QVERIFY(v[0].isUndefined()); +} + +void tst_QCborValue::basics() +{ + QFETCH(QCborValue::Type, type); + QFETCH(QCborValue, v); + QFETCH(QVariant, expectedValue); + + basicTypeCheck(type, v, expectedValue); +} + +void tst_QCborValue::tagged() +{ + QFETCH(QCborValue::Type, type); + QFETCH(QCborValue, v); + QFETCH(QVariant, expectedValue); + + // make it tagged + QCborValue tagged(QCborKnownTags::Signature, v); + QVERIFY(tagged.isTag()); + QCOMPARE(tagged.tag(), QCborTag(QCborKnownTags::Signature)); + + // shouldn't compare equal + QVERIFY(tagged != v); + QVERIFY(v != tagged); + + // ensure we can reach the original value + basicTypeCheck(type, tagged.taggedValue(), expectedValue); + QVERIFY(tagged.taggedValue() == v); + QVERIFY(v == tagged.taggedValue()); + + // nested tagging should work too + QCborValue tagged2(QCborKnownTags::EncodedCbor, tagged); + QVERIFY(tagged2.isTag()); + QCOMPARE(tagged2.tag(), QCborTag(QCborKnownTags::EncodedCbor)); + + QVERIFY(tagged2 != tagged); + QVERIFY(tagged != tagged2); + + QVERIFY(tagged2.taggedValue() == tagged); + QVERIFY(tagged == tagged2.taggedValue()); + QVERIFY(tagged2.taggedValue().taggedValue() == v); + QVERIFY(v == tagged2.taggedValue().taggedValue()); +} + +void tst_QCborValue::extendedTypes_data() +{ + QTest::addColumn<QCborValue>("extended"); + QTest::addColumn<QCborValue>("tagged"); + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + + QTest::newRow("DateTime") << QCborValue(dt) + << QCborValue(QCborKnownTags::DateTimeString, dt.toString(Qt::ISODateWithMs)); + QTest::newRow("Url:Empty") << QCborValue(QUrl()) + << QCborValue(QCborKnownTags::Url, QString()); + QTest::newRow("Url:Authority") << QCborValue(QUrl("https://example.com")) + << QCborValue(QCborKnownTags::Url, "https://example.com"); + QTest::newRow("Url:Path") << QCborValue(QUrl("file:///tmp/none")) + << QCborValue(QCborKnownTags::Url, "file:///tmp/none"); + QTest::newRow("Url:QueryFragment") << QCborValue(QUrl("whatever:?a=b&c=d#e")) + << QCborValue(QCborKnownTags::Url, "whatever:?a=b&c=d#e"); + QTest::newRow("Regex:Empty") << QCborValue(QRegularExpression()) + << QCborValue(QCborKnownTags::RegularExpression, QString()); + QTest::newRow("Regex") << QCborValue(QRegularExpression("^.*$")) + << QCborValue(QCborKnownTags::RegularExpression, QString("^.*$")); + QTest::newRow("Uuid") << QCborValue(uuid) + << QCborValue(QCborKnownTags::Uuid, uuid.toRfc4122()); +} + +void tst_QCborValue::extendedTypes() +{ + QFETCH(QCborValue, extended); + QFETCH(QCborValue, tagged); + QVERIFY(extended.isTag()); + QVERIFY(tagged.isTag()); + QVERIFY(extended == tagged); + QVERIFY(tagged == extended); + + QCOMPARE(extended.tag(), tagged.tag()); + QCOMPARE(extended.taggedValue(), tagged.taggedValue()); +} + +void tst_QCborValue::copyCompare() +{ + QFETCH(QCborValue, v); + QCborValue other = v; + other = v; + v = other; + + QCOMPARE(v.compare(other), 0); + QCOMPARE(v, other); + QVERIFY(!(v != other)); + QVERIFY(!(v < other)); +#if QT_HAS_INCLUDE(<compare>) + QVERIFY(v <= other); + QVERIFY(v >= other); + QVERIFY(!(v > other)); +#endif + + if (v.isUndefined()) + other = nullptr; + else + other = {}; + QVERIFY(v.type() != other.type()); + QVERIFY(!(v == other)); + QVERIFY(v != other); + + // they're different types, so they can't compare equal + QVERIFY(v.compare(other) != 0); + QVERIFY((v < other) || (other < v)); +} + +void tst_QCborValue::arrayDefaultInitialization() +{ + QCborArray a; + QVERIFY(a.isEmpty()); + QCOMPARE(a.size(), 0); + QVERIFY(!a.contains(0)); + QVERIFY(!a.contains(-1)); + QVERIFY(!a.contains(false)); + QVERIFY(!a.contains(true)); + QVERIFY(!a.contains(nullptr)); + QVERIFY(!a.contains({})); + QVERIFY(!a.contains(1.0)); + QVERIFY(!a.contains(QByteArray("Hello"))); + QVERIFY(!a.contains("Hello")); + QVERIFY(!a.contains(QCborArray())); + QVERIFY(!a.contains(QCborMap())); + QVERIFY(!a.contains(QCborValue(QDateTime::currentDateTimeUtc()))); + QVERIFY(!a.contains(QCborValue(QUrl("http://example.com")))); + QVERIFY(!a.contains(QCborValue(QUuid::createUuid()))); + + QVERIFY(a.at(0).isUndefined()); + QCOMPARE(a.constBegin(), a.constEnd()); + + QVERIFY(a == a); + QVERIFY(a == QCborArray()); + QVERIFY(QCborArray() == a); + + QCborValue v(a); + QVERIFY(v.isArray()); + QVERIFY(!v.isMap()); + QVERIFY(!v.isTag()); + QVERIFY(v[0].isUndefined()); + + QCborArray a2 = v.toArray(); + QVERIFY(a2.isEmpty()); + QCOMPARE(a2, a); +} + +void tst_QCborValue::mapDefaultInitialization() +{ + QCborMap m; + QVERIFY(m.isEmpty()); + QCOMPARE(m.size(), 0); + QVERIFY(m.keys().isEmpty()); + QVERIFY(!m.contains(0)); + QVERIFY(!m.contains(-1)); + QVERIFY(!m.contains(false)); + QVERIFY(!m.contains(true)); + QVERIFY(!m.contains(QCborValue::Null)); + QVERIFY(!m.contains({})); + QVERIFY(!m.contains(1.0)); + QVERIFY(!m.contains(QLatin1String("Hello"))); + QVERIFY(!m.contains(QStringLiteral("Hello"))); + QVERIFY(!m.contains(QCborValue(QByteArray("Hello")))); + QVERIFY(!m.contains(QCborArray())); + QVERIFY(!m.contains(QCborMap())); + QVERIFY(!m.contains(QCborValue(QDateTime::currentDateTimeUtc()))); + QVERIFY(!m.contains(QCborValue(QUrl("http://example.com")))); + QVERIFY(!m.contains(QCborValue(QUuid::createUuid()))); + + QVERIFY(m.value(0).isUndefined()); + QVERIFY(m.value(QLatin1String("Hello")).isUndefined()); + QVERIFY(m.value(QStringLiteral("Hello")).isUndefined()); + QVERIFY(m.value(QCborValue()).isUndefined()); +#if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) + QVERIFY(m.value("Hello").isUndefined()); +#endif + + QVERIFY(m == m); + QVERIFY(m == QCborMap{}); + QVERIFY(QCborMap{} == m); + + QCborValue v(m); + QVERIFY(v.isMap()); + QVERIFY(!v.isArray()); + QVERIFY(!v.isTag()); + QVERIFY(v[0].isUndefined()); + QVERIFY(v[QLatin1String("Hello")].isUndefined()); + QVERIFY(v["Hello"].isUndefined()); + + QCborMap m2 = v.toMap(); + QVERIFY(m2.isEmpty()); + QCOMPARE(m2.size(), 0); + QCOMPARE(m2, m); +} + +void tst_QCborValue::arrayEmptyInitializerList() +{ + QCborArray a{}; + QVERIFY(a.isEmpty()); + QCOMPARE(a.size(), 0); + QVERIFY(a == a); + QVERIFY(a == QCborArray()); + QVERIFY(QCborArray() == a); +} + +void tst_QCborValue::mapEmptyInitializerList() +{ + QCborMap m{}; + QVERIFY(m.isEmpty()); + QCOMPARE(m.size(), 0); + QVERIFY(m == m); + QVERIFY(m == QCborMap{}); + QVERIFY(QCborMap{} == m); +} + +void tst_QCborValue::arrayEmptyDetach() +{ + QCborArray a; + QCOMPARE(a.begin(), a.end()); + QVERIFY(a.isEmpty()); + QCOMPARE(a.size(), 0); + + QVERIFY(a == a); + QVERIFY(a == QCborArray()); + QVERIFY(QCborArray() == a); + + QCborValue v(a); + QVERIFY(v.isArray()); + QVERIFY(!v.isMap()); + QVERIFY(!v.isTag()); + + QCborArray a2 = v.toArray(); + QVERIFY(a2.isEmpty()); + QCOMPARE(a2, a); +} + +void tst_QCborValue::mapEmptyDetach() +{ + QCborMap m; + QCOMPARE(m.begin(), m.end()); + QVERIFY(m.isEmpty()); + QCOMPARE(m.size(), 0); + + QVERIFY(m == m); + QVERIFY(m == QCborMap{}); + QVERIFY(QCborMap{} == m); + + QCborValue v(m); + QVERIFY(v.isMap()); + QVERIFY(!v.isArray()); + QVERIFY(!v.isTag()); + + QCborMap m2 = v.toMap(); + QVERIFY(m2.isEmpty()); + QCOMPARE(m2, m); +} + +void tst_QCborValue::arrayInitializerList() +{ + QCborArray a{0, -1, false, true, nullptr, {}, 1.0}; + QVERIFY(!a.isEmpty()); + QCOMPARE(a.size(), 7); + QCOMPARE(a.at(0), QCborValue(0)); + QCOMPARE(a.at(1), QCborValue(-1)); + QCOMPARE(a.at(2), QCborValue(QCborValue::False)); + QCOMPARE(a.at(3), QCborValue(QCborValue::True)); + QCOMPARE(a.at(4), QCborValue(QCborValue::Null)); + QCOMPARE(a.at(5), QCborValue(QCborValue::Undefined)); + QCOMPARE(a.at(6), QCborValue(1.0)); + + QVERIFY(a == a); + QVERIFY(a != QCborArray{}); + QVERIFY(QCborArray{} != a); + QVERIFY(a == QCborArray({0, -1, false, true, nullptr, {}, 1.0})); + + QCborValue v = a; + QCOMPARE(v[0], QCborValue(0)); + QCOMPARE(v[1], QCborValue(-1)); + QCOMPARE(v[2], QCborValue(QCborValue::False)); + QCOMPARE(v[3], QCborValue(QCborValue::True)); + QCOMPARE(v[4], QCborValue(QCborValue::Null)); + QCOMPARE(v[5], QCborValue(QCborValue::Undefined)); + QCOMPARE(v[6], QCborValue(1.0)); + + QVERIFY(a.contains(0)); + QVERIFY(a.contains(-1)); + QVERIFY(a.contains(false)); + QVERIFY(a.contains(true)); + QVERIFY(a.contains(nullptr)); + QVERIFY(a.contains({})); + QVERIFY(a.contains(1.0)); + QVERIFY(!a.contains(QByteArray("Hello"))); + QVERIFY(!a.contains("Hello")); + QVERIFY(!a.contains(QCborArray())); + QVERIFY(!a.contains(QCborMap())); + QVERIFY(!a.contains(QCborValue(QDateTime::currentDateTimeUtc()))); + QVERIFY(!a.contains(QCborValue(QUrl("http://example.com")))); + QVERIFY(!a.contains(QCborValue(QUuid::createUuid()))); + + // iterators + auto it = a.constBegin(); + auto end = a.constEnd(); + QCOMPARE(end - it, 7); + QCOMPARE(it + 7, end); + QVERIFY(it->isInteger()); + QCOMPARE(*it, QCborValue(0)); + QCOMPARE(it[1], QCborValue(-1)); + QCOMPARE(*(it + 2), QCborValue(false)); + it += 3; + QCOMPARE(*it, QCborValue(true)); + ++it; + QCOMPARE(*it, QCborValue(nullptr)); + it++; + QCOMPARE(*it, QCborValue()); + --end; + QCOMPARE(*end, QCborValue(1.0)); + end--; + QCOMPARE(it, end); + + // range for + int i = 0; + for (const QCborValue &v : qAsConst(a)) { + QVERIFY(!v.isInvalid()); + QCOMPARE(v.isUndefined(), i == 5); // 6th element is Undefined + ++i; + } + QCOMPARE(i, a.size()); +} + +void tst_QCborValue::mapSimpleInitializerList() +{ + QCborMap m{{0, 0}, {1, 0}, {2, "Hello"}, {"Hello", 2}, {3, QLatin1String("World")}, {QLatin1String("World"), 3}}; + QCOMPARE(m.size(), 6); + QVERIFY(m == m); + QVERIFY(m != QCborMap{}); + QVERIFY(QCborMap{} != m); + QVERIFY(m == QCborMap({{0, 0}, {1, 0}, {2, "Hello"}, {"Hello", 2}, {3, QLatin1String("World")}, {QLatin1String("World"), 3}})); + + QCborValue vmap = m; + { + QVERIFY(m.contains(0)); + QCborValue v = m.value(0); + QVERIFY(v.isInteger()); + QCOMPARE(v.toInteger(), 0); + QCOMPARE(vmap[0], v); + } + { + QVERIFY(m.contains(1)); + QCborValue v = m.value(1); + QVERIFY(v.isInteger()); + QCOMPARE(v.toInteger(), 0); + QCOMPARE(vmap[1], v); + } + { + QVERIFY(m.contains(2)); + QCborValue v = m.value(2); + QVERIFY(v.isString()); + QCOMPARE(v.toString(), "Hello"); + QCOMPARE(vmap[2], v); + } + { + QVERIFY(m.contains(3)); + QCborValue v = m.value(3); + QVERIFY(v.isString()); + QCOMPARE(v.toString(), "World"); + QCOMPARE(vmap[3], v); + } + { + QVERIFY(m.contains(QStringLiteral("Hello"))); + QCborValue v = m.value(QLatin1String("Hello")); + QVERIFY(v.isInteger()); + QCOMPARE(v.toInteger(), 2); + QCOMPARE(vmap[QStringLiteral("Hello")], v); + } + { + QVERIFY(m.contains(QLatin1String("World"))); + QCborValue v = m.value(QStringLiteral("World")); + QVERIFY(v.isInteger()); + QCOMPARE(v.toInteger(), 3); + QCOMPARE(vmap[QLatin1String("World")], v); + } + + QVERIFY(!m.contains(QCborValue::Null)); + QVERIFY(!m.contains(QCborValue())); + QVERIFY(!m.contains(QCborValue(1.0))); // Important: 1.0 does not match 1 + QVERIFY(!m.contains(QCborValue(QByteArray("Hello")))); + QVERIFY(!m.contains(QCborArray())); + QVERIFY(!m.contains(QCborMap())); + QVERIFY(!m.contains(QCborValue(QDateTime::currentDateTimeUtc()))); + QVERIFY(!m.contains(QCborValue(QUrl("http://example.com")))); + QVERIFY(!m.contains(QCborValue(QUuid::createUuid()))); + + // iterators (QCborMap is not sorted) + auto it = m.constBegin(); + auto end = m.constEnd(); + QCOMPARE(end - it, 6); + QCOMPARE(it + 6, end); + QCOMPARE(it.key(), QCborValue(0)); + QCOMPARE(it.value(), QCborValue(0)); + QVERIFY(it->isInteger()); + ++it; + QCOMPARE(it.key(), QCborValue(1)); + QCOMPARE(it.value(), QCborValue(0)); + QCOMPARE((it + 1).key(), QCborValue(2)); + QVERIFY((it + 1)->isString()); + QCOMPARE((it + 1)->toString(), "Hello"); + it += 2; + QCOMPARE(it.key(), QCborValue("Hello")); + QVERIFY(it->isInteger()); + it++; + QCOMPARE(it.key(), QCborValue(3)); + QVERIFY(it->isString()); + QCOMPARE(it.value().toString(), "World"); + --end; + QCOMPARE(end.key(), QCborValue("World")); + QCOMPARE(end.value(), QCborValue(3)); + end--; + QCOMPARE(it, end); + + // range for + int i = 0; + for (auto pair : qAsConst(m)) { + QVERIFY(!pair.first.isUndefined()); + QVERIFY(!pair.second.isUndefined()); + ++i; + } + QCOMPARE(i, m.size()); +} + +void tst_QCborValue::arrayMutation() +{ + QCborArray a{42}; + { + QCborValueRef v = a[0]; + QVERIFY(!a.isEmpty()); + QVERIFY(v.isInteger()); + QCOMPARE(v.toInteger(), 42); + + // now mutate the list + v = true; + QVERIFY(v.isBool()); + QVERIFY(v.isTrue()); + QVERIFY(a.at(0).isTrue()); + QVERIFY(a.at(0) == v); + QVERIFY(v == a.at(0)); + } + + QVERIFY(a == a); + QVERIFY(a == QCborArray{true}); + + QCborArray a2 = a; + a.append(nullptr); + QCOMPARE(a.size(), 2); + QCOMPARE(a2.size(), 1); + + // self-insertion + a2.append(a2); + QCOMPARE(a2.size(), 2); + QCOMPARE(a2.last().toArray().size(), 1); + + QCborValueRef v = a[0]; + QVERIFY(v.isTrue()); + v = 2.5; + QVERIFY(v.isDouble()); + QVERIFY(a.first().isDouble()); + QVERIFY(a.last().isNull()); + QVERIFY(a2.first().isTrue()); + + a2 = a; + auto it = a.begin(); // detaches again + auto end = a.end(); + QCOMPARE(end - it, 2); + QCOMPARE(it + 2, end); + QCOMPARE(*it, QCborValue(2.5)); + QCOMPARE(*++it, QCborValue(nullptr)); + QVERIFY(a2 == a); + QVERIFY(a == a2); + + *it = -1; + QCOMPARE(*it, QCborValue(-1)); + QCOMPARE(a.at(1), QCborValue(-1)); + QCOMPARE(a2.at(1), QCborValue(nullptr)); + QCOMPARE(++it, end); +} + +void tst_QCborValue::mapMutation() +{ + QCborMap m; + QVERIFY(m.isEmpty()); + + { + QCborValueRef v = m[42]; + QCOMPARE(m.size(), 1); + QVERIFY(v.isUndefined()); + + // now mutate the list + v = true; + QVERIFY(v.isBool()); + QVERIFY(v.isTrue()); + QVERIFY(m.begin()->isTrue()); + QVERIFY(m.begin().value() == v); + QVERIFY(v == m.begin().value()); + } + + QVERIFY(m == QCborMap({{42, true}})); + QVERIFY(QCborMap({{42, true}}) == m); + + QCborMap m2 = m; + m.insert({nullptr, nullptr}); + QCOMPARE(m.size(), 2); + QCOMPARE(m2.size(), 1); + + QCborValueRef v = m[42]; + QVERIFY(v.isTrue()); + v = 2.5; + QVERIFY(v.isDouble()); + QVERIFY(m.begin()->isDouble()); + QVERIFY((m.end() - 1)->isNull()); + QVERIFY(m2.begin()->isTrue()); + + m2 = m; + auto it = m.begin(); // detaches again + auto end = m.end(); + QCOMPARE(end - it, 2); + QCOMPARE(it + 2, end); + QCOMPARE(it.key(), QCborValue(42)); + QCOMPARE(it.value(), QCborValue(2.5)); + QCOMPARE((++it).value(), QCborValue(nullptr)); + QCOMPARE(it.key(), QCborValue(nullptr)); + QVERIFY(m2 == m); + QVERIFY(m == m2); + + it.value() = -1; + QCOMPARE(it.key(), QCborValue(nullptr)); + QCOMPARE(it.value(), QCborValue(-1)); + QCOMPARE((m.end() - 1)->toInteger(), -1); + QVERIFY((m2.end() - 1)->isNull()); + QCOMPARE(++it, end); +} + +void tst_QCborValue::arrayPrepend() +{ + QCborArray a; + a.prepend(0); + a.prepend(nullptr); + QCOMPARE(a.at(1), QCborValue(0)); + QCOMPARE(a.at(0), QCborValue(nullptr)); + QCOMPARE(a.size(), 2); +} + +void tst_QCborValue::arrayInsertRemove() +{ + QFETCH(QCborValue, v); + QCborArray a; + a.append(42); + a.append(v); + a.insert(1, QCborValue(nullptr)); + QCOMPARE(a.at(0), QCborValue(42)); + QCOMPARE(a.at(1), QCborValue(nullptr)); + QCOMPARE(a.at(2), v); + + // remove 42 + a.removeAt(0); + QCOMPARE(a.size(), 2); + QCOMPARE(a.at(0), QCborValue(nullptr)); + QCOMPARE(a.at(1), v); + + auto it = a.begin(); + it = a.erase(it); // removes nullptr + QCOMPARE(a.size(), 1); + QCOMPARE(a.at(0), v); + + it = a.erase(it); + QVERIFY(a.isEmpty()); + QCOMPARE(it, a.end()); + + // reinsert the element so we can take it + a.append(v); + QCOMPARE(a.takeAt(0), v); + QVERIFY(a.isEmpty()); +} + +void tst_QCborValue::arrayStringElements() +{ + QCborArray a{"Hello"}; + a.append(QByteArray("Hello")); + a.append(QLatin1String("World")); + QVERIFY(a == a); + QVERIFY(a == QCborArray({QLatin1String("Hello"), + QByteArray("Hello"), QStringLiteral("World")})); + + QCborValueRef r1 = a[0]; + QCOMPARE(r1.toString(), "Hello"); + QCOMPARE(r1.operator QCborValue(), QCborValue("Hello")); + QVERIFY(r1 == QCborValue("Hello")); + + QCborValue v2 = a.at(1); + QCOMPARE(v2.toByteArray(), QByteArray("Hello")); + QCOMPARE(v2, QCborValue(QByteArray("Hello"))); + + // v2 must continue to be valid after the entry getting removed + a.removeAt(1); + QCOMPARE(v2.toByteArray(), QByteArray("Hello")); + QCOMPARE(v2, QCborValue(QByteArray("Hello"))); + + v2 = a.at(1); + QCOMPARE(v2.toString(), "World"); + QCOMPARE(v2, QCborValue("World")); + + QCOMPARE(a.takeAt(1).toString(), "World"); + QCOMPARE(a.takeAt(0).toString(), "Hello"); + QVERIFY(a.isEmpty()); +} + +void tst_QCborValue::mapStringValues() +{ + QCborMap m{{0, "Hello"}}; + m.insert({1, QByteArray("Hello")}); + m.insert({2, QLatin1String("World")}); + QVERIFY(m == m); + + QCborValueRef r1 = m[0]; + QCOMPARE(r1.toString(), "Hello"); + QCOMPARE(r1.operator QCborValue(), QCborValue("Hello")); + QVERIFY(r1 == QCborValue("Hello")); + + QCborValue v2 = m.value(1); + QCOMPARE(v2.toByteArray(), QByteArray("Hello")); + QCOMPARE(v2, QCborValue(QByteArray("Hello"))); + + // v2 must continue to be valid after the entry getting removed + m.erase(m.constFind(1)); + QCOMPARE(v2.toByteArray(), QByteArray("Hello")); + QCOMPARE(v2, QCborValue(QByteArray("Hello"))); + + v2 = (m.begin() + 1).value(); + QCOMPARE(v2.toString(), "World"); + QCOMPARE(v2, QCborValue("World")); + + QCOMPARE(m.extract(m.begin() + 1).toString(), "World"); + QCOMPARE(m.take(0).toString(), "Hello"); + QVERIFY(m.isEmpty()); +} + +void tst_QCborValue::mapStringKeys() +{ + QCborMap m{{QLatin1String("Hello"), 1}, {QStringLiteral("World"), 2}}; + QCOMPARE(m.value(QStringLiteral("Hello")), QCborValue(1)); + QCOMPARE(m.value(QLatin1String("World")), QCborValue(2)); + + QCborMap m2 = m; + QVERIFY(m2 == m); + QVERIFY(m == m2); + + m.insert({QByteArray("foo"), "bar"}); + QCOMPARE(m.size(), 3); + QCOMPARE(m2.size(), 2); + QVERIFY(m2 != m); + QVERIFY(m != m2); + + QVERIFY(m2.value(QCborValue(QByteArray("foo"))).isUndefined()); + QVERIFY(m.value(QCborValue(QLatin1String("foo"))).isUndefined()); + QCOMPARE(m.value(QCborValue(QByteArray("foo"))).toString(), "bar"); +} + +void tst_QCborValue::mapInsertRemove() +{ + QFETCH(QCborValue, v); + QCborMap m{{1, v}}; + + m.remove(1); + QVERIFY(m.isEmpty()); + QVERIFY(!m.contains(1)); + + m.insert(2, v); + QVERIFY(m.contains(2)); + QVERIFY(m[2] == v); + QVERIFY(v == m[2]); + + auto it = m.find(2); + it = m.erase(it); + QVERIFY(m.isEmpty()); + + // creates m[2] and m[42] just by referencing them + m[2]; + QCborValueRef r = m[42]; + QCOMPARE(m.size(), 2); + + r = v; + it = m.find(42); + QVERIFY(it.value() == v); + QVERIFY(v == it.value()); + QVERIFY(it.value() == r); + QVERIFY(r == it.value()); + + QCOMPARE(m.extract(it), v); + QVERIFY(!m.contains(42)); + + m[2] = v; + QCOMPARE(m.take(2), v); + QVERIFY(m.take(2).isUndefined()); + QVERIFY(m.isEmpty()); +} + +void tst_QCborValue::arrayInsertTagged() +{ + QFETCH(QCborValue, v); + + // make it tagged + QCborValue tagged(QCborKnownTags::Signature, v); + + QCborArray a{tagged}; + a.insert(1, tagged); + QCOMPARE(a.size(), 2); + QCOMPARE(a.at(0), tagged); + QCOMPARE(a.at(1), tagged); + QCOMPARE(a.at(0).taggedValue(), v); + QCOMPARE(a.at(1).taggedValue(), v); + QCOMPARE(a.takeAt(0).taggedValue(), v); + QCOMPARE(a.takeAt(0).taggedValue(), v); + QVERIFY(a.isEmpty()); +} + +void tst_QCborValue::mapInsertTagged() +{ + QFETCH(QCborValue, v); + + // make it tagged + QCborValue tagged(QCborKnownTags::Signature, v); + + QCborMap m{{11, tagged}}; + m.insert({-21, tagged}); + QCOMPARE(m.size(), 2); + QCOMPARE(m.constBegin().value(), tagged); + QCOMPARE(m.value(-21), tagged); + QCOMPARE(m.value(11).taggedValue(), v); + QCOMPARE((m.end() - 1).value().taggedValue(), v); + QCOMPARE(m.extract(m.end() - 1).taggedValue(), v); + QVERIFY(!m.contains(-21)); + QCOMPARE(m.take(11).taggedValue(), v); + QVERIFY(m.isEmpty()); +} + +void tst_QCborValue::arraySelfAssign() +{ + QFETCH(QCborValue, v); + QCborArray a; + + a = {v}; + + // Test 1: QCborValue created first, so + // QCborArray::insert() detaches + { + a.append(a); + QCOMPARE(a.size(), 2); + QCOMPARE(a.last().toArray().size(), 1); + } + + a = {v}; + + // Test 2: QCborValueRef created first + { + a.append(36); + auto it = a.end() - 1; + *it = a; + + QCOMPARE(a.size(), 2); + QCOMPARE(it->toArray().size(), 2); + QCOMPARE(it->toArray().last(), QCborValue(36)); + } +} + +void tst_QCborValue::mapSelfAssign() +{ + QFETCH(QCborValue, v); + QCborMap m; + + m = {{0, v}}; + QCOMPARE(m.size(), 1); + + // Test 1: create a QCborValue first + // in this case, QCborMap::operator[] detaches first + { + QCborValue vm = m; + m[1] = vm; // self-assign + QCOMPARE(m.size(), 2); + QCOMPARE(m.value(0), v); + + QCborMap m2 = m.value(1).toMap(); + // there mustn't be an element with key 1 + QCOMPARE(m2.size(), 1); + QCOMPARE(m2.value(0), v); + QVERIFY(!m2.contains(1)); + } + + m = {{0, v}}; + + // Test 2: create the QCborValueRef first + // in this case, there's no opportunity to detach + { + QCborValueRef rv = m[1]; + rv = m; // self-assign (implicit QCborValue creation) + QCOMPARE(m.size(), 2); + QCOMPARE(m.value(0), v); + + QCborMap m2 = m.value(1).toMap(); + // there must be an element with key 1 + QCOMPARE(m2.size(), 2); + QCOMPARE(m2.value(0), v); + QVERIFY(m2.contains(1)); + QCOMPARE(m2.value(1), QCborValue()); + } + + m = {{0, v}}; + + // Test 3: don't force creation of either before + // in this case, it's up to the compiler to choose + { + m[1] = m; // self-assign + QCOMPARE(m.size(), 2); + + QCborMap m2 = m.value(1).toMap(); + QVERIFY(m2.size() == 1 || m2.size() == 2); + } + + m = {{0, v}}; + + // Test 4: self-assign as key + // in this scase, QCborMap::operator[] must detach + { + m[m] = v; + QCOMPARE(m.size(), 2); + + auto it = m.constEnd() - 1; + QCOMPARE(it.value(), v); + QCOMPARE(it.key(), QCborMap({{0, v}})); + } +} + +void tst_QCborValue::mapComplexKeys() +{ + QFETCH(QCborValue, v); + QCborValue tagged(QCborKnownTags::Signature, v); + + QCborMap m{{42, true}, {v, 42}, {-3, nullptr}}; + QCOMPARE(m.size(), 3); + QVERIFY(m.contains(42)); + QVERIFY(m.contains(-3)); + QVERIFY(m.contains(v)); + QVERIFY(!m.contains(tagged)); + + auto it = m.constFind(v); + QVERIFY(it != m.constEnd()); + QVERIFY(it.key() == v); + QVERIFY(v == it.key()); + QCOMPARE(it.value().toInteger(), 42); + + QCborArray a{0, 1, 2, 3, v}; + m[a] = 1; + QCOMPARE(m.size(), 4); + QCOMPARE((m.constEnd() - 1).value(), QCborValue(1)); + if (v != QCborValue(QCborValue::Array)) + QVERIFY(!m.contains(QCborArray{})); + QVERIFY(!m.contains(QCborArray{0})); + QVERIFY(!m.contains(QCborArray{0, 1})); + QVERIFY(!m.contains(QCborArray{0, 1, 2})); + QVERIFY(!m.contains(QCborArray{0, 1, 2, 4})); + QVERIFY(!m.contains(QCborArray{0, 1, 2, 3, v, 4})); + + it = m.constFind(QCborArray{0, 1, 2, 3, v}); + QVERIFY(it != m.constEnd()); + QCOMPARE(it.key(), a); + QCOMPARE(it.value(), QCborValue(1)); + + m[m] = 1; // assign itself as a key -- this necessarily detaches before + QCOMPARE(m.size(), 5); + QCOMPARE((m.end() - 1).value(), 1); + QCOMPARE((m.end() - 1).key().toMap().size(), 4); + + QCborValue mv(m); + if (v.isInteger()) { + // we should be able to find using the overloads too + QCOMPARE(m[v.toInteger()].toInteger(), 42); + QCOMPARE(mv[v.toInteger()].toInteger(), 42); + } else if (v.isString()) { + // ditto + QCOMPARE(m[v.toString()].toInteger(), 42); + QCOMPARE(mv[v.toString()].toInteger(), 42); + + // basics_data() strings are Latin1 + QByteArray latin1 = v.toString().toLatin1(); + Q_ASSERT(v.toString() == QString::fromLatin1(latin1)); + QCOMPARE(m[QLatin1String(latin1)].toInteger(), 42); + } + + m.remove(v); + QVERIFY(!m.contains(v)); + QVERIFY(!m.contains(tagged)); + + QCborValueRef r = m[tagged]; + QVERIFY(!m.contains(v)); + QVERIFY(m.contains(tagged)); + r = 47; + QCOMPARE(m[tagged].toInteger(), 47); + QCOMPARE(m.take(tagged).toInteger(), 47); + QVERIFY(!m.contains(tagged)); +} + +void tst_QCborValue::sorting() +{ + QCborValue vundef, vnull(nullptr); + QCborValue vtrue(true), vfalse(false); + QCborValue vint1(1), vint2(2); + QCborValue vneg1(-1), vneg2(-2); + QCborValue vba2(QByteArray("Hello")), vba3(QByteArray("World")), vba1(QByteArray("foo")); + QCborValue vs2("Hello"), vs3("World"), vs1("foo"); + QCborValue va1(QCborValue::Array), va2(QCborArray{1}), va3(QCborArray{0, 0}); + QCborValue vm1(QCborValue::Map), vm2(QCborMap{{1, 0}}), vm3(QCborMap{{0, 0}, {1, 0}}); + QCborValue vdt1(QDateTime::fromMSecsSinceEpoch(0, Qt::UTC)), vdt2(QDateTime::currentDateTimeUtc()); + QCborValue vtagged1(QCborKnownTags::UnixTime_t, 0), vtagged2(QCborKnownTags::UnixTime_t, 0.0), + vtagged3(QCborKnownTags::Signature, 0), vtagged4(QCborTag(-2), 0), vtagged5(QCborTag(-1), 0); + QCborValue vurl1(QUrl("https://example.net")), vurl2(QUrl("https://example.com/")); + QCborValue vuuid1{QUuid()}, vuuid2(QUuid::createUuid()); + QCborValue vsimple1(QCborSimpleType(1)), vsimple32(QCborSimpleType(32)), vsimple255(QCborSimpleType(255)); + QCborValue vdouble1(1.5), vdouble2(qInf()); + QCborValue vndouble1(-1.5), vndouble2(-qInf()); + +#define CHECK_ORDER(v1, v2) \ + QVERIFY(v1 < v2); \ + QVERIFY(!(v2 < v2)) + + // intra-type comparisons + CHECK_ORDER(vfalse, vtrue); + CHECK_ORDER(vsimple1, vsimple32); + CHECK_ORDER(vsimple32, vsimple255); + CHECK_ORDER(vint1, vint2); + CHECK_ORDER(vdouble1, vdouble2); + CHECK_ORDER(vndouble1, vndouble2); + // note: shorter length sorts first + CHECK_ORDER(vba1, vba2); + CHECK_ORDER(vba2, vba3); + CHECK_ORDER(vs1, vs2); + CHECK_ORDER(vs2, vs3); + CHECK_ORDER(va1, va2); + CHECK_ORDER(va2, va3); + CHECK_ORDER(vm1, vm2); + CHECK_ORDER(vm2, vm3); + CHECK_ORDER(vdt1, vdt2); + CHECK_ORDER(vtagged1, vtagged2); + CHECK_ORDER(vtagged2, vtagged3); + CHECK_ORDER(vtagged3, vtagged4); + CHECK_ORDER(vtagged4, vtagged5); + CHECK_ORDER(vurl1, vurl2); + CHECK_ORDER(vuuid1, vuuid2); + + // surprise 1: CBOR sorts integrals by absolute value + CHECK_ORDER(vneg1, vneg2); + + // surprise 2: CBOR sorts negatives after positives (sign+magnitude) + CHECK_ORDER(vint2, vneg1); + QVERIFY(vint2.toInteger() > vneg1.toInteger()); + CHECK_ORDER(vdouble2, vndouble1); + QVERIFY(vdouble2.toDouble() > vndouble1.toDouble()); + + // inter-type comparisons + CHECK_ORDER(vneg2, vba1); + CHECK_ORDER(vba3, vs1); + CHECK_ORDER(vs3, va1); + CHECK_ORDER(va2, vm1); + CHECK_ORDER(vm2, vdt1); + CHECK_ORDER(vdt2, vtagged1); + CHECK_ORDER(vtagged2, vurl1); + CHECK_ORDER(vurl1, vuuid1); + CHECK_ORDER(vuuid2, vtagged3); + CHECK_ORDER(vtagged4, vsimple1); + CHECK_ORDER(vsimple1, vfalse); + CHECK_ORDER(vtrue, vnull); + CHECK_ORDER(vnull, vundef); + CHECK_ORDER(vundef, vsimple32); + CHECK_ORDER(vsimple255, vdouble1); + + // which shows all doubles sorted after integrals + CHECK_ORDER(vint2, vdouble1); + QVERIFY(vint2.toInteger() > vdouble1.toDouble()); +#undef CHECK_ORDER +} + +static void addCommonCborData() +{ + // valid for both decoding and encoding + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<QByteArray>("result"); + QTest::addColumn<QCborValue::EncodingOptions>("options"); + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + QCborValue::EncodingOptions noxfrm = QCborValue::NoTransformation; + + // integrals + QTest::newRow("Integer:0") << QCborValue(0) << raw("\x00") << noxfrm; + QTest::newRow("Integer:1") << QCborValue(1) << raw("\x01") << noxfrm; + QTest::newRow("Integer:-1") << QCborValue(-1) << raw("\x20") << noxfrm; + QTest::newRow("Integer:INT64_MAX") << QCborValue(std::numeric_limits<qint64>::max()) + << raw("\x1b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << noxfrm; + QTest::newRow("Integer:INT64_MIN") << QCborValue(std::numeric_limits<qint64>::min()) + << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << noxfrm; + + QTest::newRow("simple0") << QCborValue(QCborValue::SimpleType) << raw("\xe0") << noxfrm; + QTest::newRow("simple1") << QCborValue(QCborSimpleType(1)) << raw("\xe1") << noxfrm; + QTest::newRow("simple255") << QCborValue(QCborSimpleType(255)) << raw("\xf8\xff") << noxfrm; + QTest::newRow("Undefined") << QCborValue() << raw("\xf7") << noxfrm; + QTest::newRow("Null") << QCborValue(nullptr) << raw("\xf6") << noxfrm; + QTest::newRow("True") << QCborValue(true) << raw("\xf5") << noxfrm; + QTest::newRow("False") << QCborValue(false) << raw("\xf4") << noxfrm; + QTest::newRow("simple32") << QCborValue(QCborSimpleType(32)) << raw("\xf8\x20") << noxfrm; + QTest::newRow("simple255") << QCborValue(QCborSimpleType(255)) << raw("\xf8\xff") << noxfrm; + + QTest::newRow("Double:0") << QCborValue(0.) << raw("\xfb\0\0\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Double:1.5") << QCborValue(1.5) << raw("\xfb\x3f\xf8\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Double:-1.5") << QCborValue(-1.5) << raw("\xfb\xbf\xf8\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Double:INT64_MAX+1") << QCborValue(std::numeric_limits<qint64>::max() + 1.) + << raw("\xfb\x43\xe0\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Double:maxintegralfp") << QCborValue(18446744073709551616.0 - 2048) + << raw("\xfb\x43\xef\xff\xff""\xff\xff\xff\xff") + << noxfrm; + QTest::newRow("Double:minintegralfp") << QCborValue(-18446744073709551616.0 + 2048) + << raw("\xfb\xc3\xef\xff\xff""\xff\xff\xff\xff") + << noxfrm; + QTest::newRow("Double:inf") << QCborValue(qInf()) << raw("\xfb\x7f\xf0\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Double:-inf") << QCborValue(-qInf()) << raw("\xfb\xff\xf0\0""\0\0\0\0\0") << noxfrm; + QTest::newRow("Double:nan") << QCborValue(qQNaN()) << raw("\xfb\x7f\xf8\0\0""\0\0\0\0") << noxfrm; + + QTest::newRow("Float:0") << QCborValue(0.) << raw("\xfa\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float:1.5") << QCborValue(1.5) << raw("\xfa\x3f\xc0\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float:-1.5") << QCborValue(-1.5) << raw("\xfa\xbf\xc0\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float:inf") << QCborValue(qInf()) << raw("\xfa\x7f\x80\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float:-inf") << QCborValue(-qInf()) << raw("\xfa\xff\x80\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float:nan") << QCborValue(qQNaN()) << raw("\xfa\x7f\xc0\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + + QTest::newRow("Float16:0") << QCborValue(0.) << raw("\xf9\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + QTest::newRow("Float16:1.5") << QCborValue(1.5) << raw("\xf9\x3e\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + QTest::newRow("Float16:-1.5") << QCborValue(-1.5) << raw("\xf9\xbe\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + QTest::newRow("Float16:inf") << QCborValue(qInf()) << raw("\xf9\x7c\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + QTest::newRow("Float16:-inf") << QCborValue(-qInf()) << raw("\xf9\xfc\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + QTest::newRow("Float16:nan") << QCborValue(qQNaN()) << raw("\xf9\x7e\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + + // out of range of qint64, but in range for CBOR, so these do get converted + // to integrals on write and back to double on read + QTest::newRow("UseInteger:INT64_MAX+1") << QCborValue(std::numeric_limits<qint64>::max() + 1.) + << raw("\x1b\x80\0\0\0""\0\0\0\0") + << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:maxintegralfp") << QCborValue(18446744073709551616.0 - 2048) + << raw("\x1b\xff\xff\xff\xff""\xff\xff\xf8\0") + << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:minintegralfp") << QCborValue(-18446744073709551616.0 + 2048) + << raw("\x3b\xff\xff\xff\xff""\xff\xff\xf7\xff") + << QCborValue::EncodingOptions(QCborValue::UseIntegers); + + QTest::newRow("ByteArray:Empty") << QCborValue(QByteArray()) << raw("\x40") << noxfrm; + QTest::newRow("ByteArray") << QCborValue(QByteArray("Hello")) << raw("\x45Hello") << noxfrm; + QTest::newRow("ByteArray:WithNull") << QCborValue(raw("\0\1\2\xff")) << raw("\x44\0\1\2\xff") << noxfrm; + + QTest::newRow("String:Empty") << QCborValue(QString()) << raw("\x60") << noxfrm; + QTest::newRow("String:UsAscii") << QCborValue("Hello") << raw("\x65Hello") << noxfrm; + QTest::newRow("String:Latin1") << QCborValue(QLatin1String("R\xe9sum\xe9")) + << raw("\x68R\xc3\xa9sum\xc3\xa9") << noxfrm; + QTest::newRow("String:Unicode") << QCborValue(QStringLiteral(u"éś α €")) + << raw("\x6b\xc3\xa9\xc5\x9b \xce\xb1 \xe2\x82\xac") << noxfrm; + + QTest::newRow("DateTime") << QCborValue(dt) // this is UTC + << "\xc0\x78\x18" + dt.toString(Qt::ISODateWithMs).toLatin1() + << noxfrm; + QTest::newRow("DateTime-UTC") << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::UTC)) + << raw("\xc0\x78\x18" "2018-01-01T09:00:00.000Z") + << noxfrm; + QTest::newRow("DateTime-Local") << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::LocalTime)) + << raw("\xc0\x77" "2018-01-01T09:00:00.000") + << noxfrm; + QTest::newRow("DateTime+01:00") << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::OffsetFromUTC, 3600)) + << raw("\xc0\x78\x1d" "2018-01-01T09:00:00.000+01:00") + << noxfrm; + QTest::newRow("Url:Empty") << QCborValue(QUrl()) << raw("\xd8\x20\x60") << noxfrm; + QTest::newRow("Url") << QCborValue(QUrl("HTTPS://example.com/{%30%31}?q=%3Ca+b%20%C2%A9%3E&%26")) + << raw("\xd8\x20\x78\x27" "https://example.com/{01}?q=<a+b \xC2\xA9>&%26") + << noxfrm; + QTest::newRow("Regex:Empty") << QCborValue(QRegularExpression()) << raw("\xd8\x23\x60") << noxfrm; + QTest::newRow("Regex") << QCborValue(QRegularExpression("^.*$")) + << raw("\xd8\x23\x64" "^.*$") << noxfrm; + QTest::newRow("Uuid") << QCborValue(uuid) << raw("\xd8\x25\x50") + uuid.toRfc4122() << noxfrm; + + // empty arrays and maps + QTest::newRow("Array") << QCborValue(QCborArray()) << raw("\x80") << noxfrm; + QTest::newRow("Map") << QCborValue(QCborMap()) << raw("\xa0") << noxfrm; + + QTest::newRow("Tagged:ByteArray") << QCborValue(QCborKnownTags::PositiveBignum, raw("\1\0\0\0\0""\0\0\0\0")) + << raw("\xc2\x49\1\0\0\0\0""\0\0\0\0") << noxfrm; + QTest::newRow("Tagged:Array") << QCborValue(QCborKnownTags::Decimal, QCborArray{-2, 27315}) + << raw("\xc4\x82\x21\x19\x6a\xb3") << noxfrm; +} + +void tst_QCborValue::toCbor_data() +{ + addCommonCborData(); + + // The rest of these tests are conversions whose decoding does not yield + // back the same QCborValue. + + // Signalling NaN get normalized to quiet ones + QTest::newRow("Double:snan") << QCborValue(qSNaN()) << raw("\xfb\x7f\xf8\0""\0\0\0\0\0") << QCborValue::EncodingOptions(); + QTest::newRow("Float:snan") << QCborValue(qSNaN()) << raw("\xfa\x7f\xc0\0\0") << QCborValue::EncodingOptions(QCborValue::UseFloat); + QTest::newRow("Float16:snan") << QCborValue(qSNaN()) << raw("\xf9\x7e\0") << QCborValue::EncodingOptions(QCborValue::UseFloat16); + + // Floating point written as integers are read back as integers + QTest::newRow("UseInteger:0") << QCborValue(0.) << raw("\x00") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:1") << QCborValue(1.) << raw("\x01") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:-1") << QCborValue(-1.) << raw("\x20") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:INT64_MIN") << QCborValue(std::numeric_limits<qint64>::min() + 0.) + << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << QCborValue::EncodingOptions(QCborValue::UseIntegers); + + // but obviously non-integral or out of range floating point stay FP + QTest::newRow("UseInteger:1.5") << QCborValue(1.5) << raw("\xfb\x3f\xf8\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:-1.5") << QCborValue(-1.5) << raw("\xfb\xbf\xf8\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:inf") << QCborValue(qInf()) << raw("\xfb\x7f\xf0\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:-inf") << QCborValue(-qInf()) << raw("\xfb\xff\xf0\0""\0\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:nan") << QCborValue(qQNaN()) << raw("\xfb\x7f\xf8\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:2^64") << QCborValue(18446744073709551616.0) << raw("\xfb\x43\xf0\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); + QTest::newRow("UseInteger:-2^65") << QCborValue(-2 * 18446744073709551616.0) << raw("\xfb\xc4\0\0\0""\0\0\0\0") << QCborValue::EncodingOptions(QCborValue::UseIntegers); +} + +void tst_QCborValue::toCbor() +{ + QFETCH(QCborValue, v); + QFETCH(QByteArray, result); + QFETCH(QCborValue::EncodingOptions, options); + + QCOMPARE(v.toCbor(options), result); + + // in maps and arrays + QCOMPARE(QCborArray{v}.toCborValue().toCbor(options), "\x81" + result); + QCOMPARE(QCborArray({v, v}).toCborValue().toCbor(options), + "\x82" + result + result); + QCOMPARE(QCborMap({{1, v}}).toCborValue().toCbor(options), + "\xa1\x01" + result); + + // tagged + QCborValue t(QCborKnownTags::Signature, v); + QCOMPARE(t.toCbor(options), "\xd9\xd9\xf7" + result); + QCOMPARE(QCborArray({t, t}).toCborValue().toCbor(options), + "\x82\xd9\xd9\xf7" + result + "\xd9\xd9\xf7" + result); + QCOMPARE(QCborMap({{1, t}}).toCborValue().toCbor(options), + "\xa1\x01\xd9\xd9\xf7" + result); +} + +void tst_QCborValue::fromCbor_data() +{ + addCommonCborData(); + + // chunked strings + QTest::newRow("ByteArray:Chunked") << QCborValue(QByteArray("Hello")) + << raw("\x5f\x43Hel\x42lo\xff"); + QTest::newRow("ByteArray:Chunked:Empty") << QCborValue(QByteArray()) << raw("\x5f\xff"); + QTest::newRow("String:Chunked") << QCborValue("Hello") + << raw("\x7f\x63Hel\x62lo\xff"); + QTest::newRow("String:Chunked:Empty") << QCborValue(QString()) + << raw("\x7f\xff"); + + QTest::newRow("DateTime:NoMilli") << QCborValue(QDateTime::fromSecsSinceEpoch(1515565477, Qt::UTC)) + << raw("\xc0\x74" "2018-01-10T06:24:37Z"); + QTest::newRow("UnixTime_t:Integer") << QCborValue(QDateTime::fromSecsSinceEpoch(1515565477, Qt::UTC)) + << raw("\xc1\x1a\x5a\x55\xb1\xa5"); + QTest::newRow("UnixTime_t:Double") << QCborValue(QDateTime::fromMSecsSinceEpoch(1515565477125, Qt::UTC)) + << raw("\xc1\xfb\x41\xd6\x95\x6c""\x69\x48\x00\x00"); + + QTest::newRow("Url:NotNormalized") << QCborValue(QUrl("https://example.com/\xc2\xa9 ")) + << raw("\xd8\x20\x78\x1dHTTPS://EXAMPLE.COM/%c2%a9%20"); + + QTest::newRow("Uuid:Zero") << QCborValue(QUuid()) << raw("\xd8\x25\x40"); + QTest::newRow("Uuid:TooShort") << QCborValue(QUuid::fromRfc4122(raw("\1\2\3\4""\4\3\2\0""\0\0\0\0""\0\0\0\0"))) + << raw("\xd8\x25\x47" "\1\2\3\4\4\3\2"); + QTest::newRow("Uuid:TooLong") << QCborValue(QUuid::fromRfc4122(raw("\1\2\3\4""\4\3\2\0""\0\0\0\0""\0\0\0\1"))) + << raw("\xd8\x25\x51" "\1\2\3\4""\4\3\2\0""\0\0\0\0""\0\0\0\1""\2"); +} + +void tst_QCborValue::fromCbor() +{ + QFETCH(QCborValue, v); + QFETCH(QByteArray, result); + + auto doCheck = [](const QCborValue &v, const QByteArray &result) { + QCborParserError error; + QCborValue decoded = QCborValue::fromCbor(result, &error); + QVERIFY2(error.error == QCborError(), qPrintable(error.errorString())); + QCOMPARE(error.offset, result.size()); + QVERIFY(decoded == v); + QVERIFY(v == decoded); + }; + + doCheck(v, result); + if (QTest::currentTestFailed()) + return; + + // in an array + doCheck(QCborArray{v}, "\x81" + result); + if (QTest::currentTestFailed()) + return; + + doCheck(QCborArray{v, v}, "\x82" + result + result); + if (QTest::currentTestFailed()) + return; + + // in a map + doCheck(QCborMap{{1, v}}, "\xa1\1" + result); + if (QTest::currentTestFailed()) + return; + + // undefined-length arrays and maps + doCheck(QCborArray{v}, "\x9f" + result + "\xff"); + if (QTest::currentTestFailed()) + return; + doCheck(QCborArray{v, v}, "\x9f" + result + result + "\xff"); + if (QTest::currentTestFailed()) + return; + doCheck(QCborMap{{1, v}}, "\xbf\1" + result + "\xff"); + if (QTest::currentTestFailed()) + return; + + // tagged + QCborValue t(QCborKnownTags::Signature, v); + doCheck(t, "\xd9\xd9\xf7" + result); + if (QTest::currentTestFailed()) + return; + + // in an array + doCheck(QCborArray{t}, "\x81\xd9\xd9\xf7" + result); + if (QTest::currentTestFailed()) + return; + + doCheck(QCborArray{t, t}, "\x82\xd9\xd9\xf7" + result + "\xd9\xd9\xf7" + result); + if (QTest::currentTestFailed()) + return; + + // in a map + doCheck(QCborMap{{1, t}}, "\xa1\1\xd9\xd9\xf7" + result); + if (QTest::currentTestFailed()) + return; +} + +void tst_QCborValue::validation_data() +{ + addValidationColumns(); + addValidationData(); + + // These tests say we have arrays and maps with very large item counts. + // They are meant to ensure we don't pre-allocate a lot of memory + // unnecessarily and possibly crash the application. The actual number of + // elements in the stream is only 2, so we should get an unexpected EOF + // error. QCborValue internally uses 16 bytes per element, so we get to + // 2 GB at 2^27 elements. + QTest::addRow("very-large-array-no-overflow") << raw("\x9a\x07\xff\xff\xff" "\0\0"); + QTest::addRow("very-large-array-overflow1") << raw("\x9a\x40\0\0\0" "\0\0"); + + // this makes sure we don't accidentally clip to 32-bit: sending 2^32+2 elements + QTest::addRow("very-large-array-overflow2") << raw("\x9b\0\0\0\1""\0\0\0\2" "\0\0"); +} + +void tst_QCborValue::validation() +{ + QFETCH(QByteArray, data); + + QCborParserError error; + QCborValue decoded = QCborValue::fromCbor(data, &error); + QVERIFY(error.error != QCborError{}); + + if (data.startsWith('\x81')) { + // decode without the array prefix + decoded = QCborValue::fromCbor(data.mid(1), &error); + QVERIFY(error.error != QCborError{}); + } +} + +void tst_QCborValue::toDiagnosticNotation_data() +{ + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<int>("opts"); + QTest::addColumn<QString>("expected"); + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + + QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>(); + auto add = [me](const QCborValue &v, const QString &exp) { + auto addRow = [=](const char *prefix) -> QTestData & { + QCborValue::Type t = v.type(); + if (t == QCborValue::Integer) + return QTest::addRow("%sInteger:%lld", prefix, v.toInteger()); + if (t == QCborValue::Double) + return QTest::addRow("%sDouble:%g", prefix, v.toDouble()); + if (t == QCborValue::ByteArray) + return QTest::addRow("%sByteArray:%d", prefix, v.toByteArray().size()); + if (t == QCborValue::String) + return QTest::addRow("%sString:%d", prefix, v.toString().size()); + + QByteArray typeString = me.valueToKey(t); + Q_ASSERT(!typeString.isEmpty()); + return QTest::newRow(prefix + typeString); + }; + addRow("") << v << int(QCborValue::DiagnosticNotationOptions{}) << exp; + addRow("LW:") << v << int(QCborValue::LineWrapped) << exp; + addRow("Array:") << QCborValue(QCborArray{v}) << int(QCborValue::DiagnosticNotationOptions{}) << '[' + exp + ']'; + addRow("Mapped:") << QCborValue(QCborMap{{2, v}}) << int(QCborValue::DiagnosticNotationOptions{}) << "{2: " + exp + '}'; + addRow("Mapping:") << QCborValue(QCborMap{{v, 2}}) << int(QCborValue::DiagnosticNotationOptions{}) << '{' + exp + ": 2}"; + }; + + // empty arrays and maps + QTest::newRow("EmptyArray") + << QCborValue(QCborArray()) << int(QCborValue::DiagnosticNotationOptions{}) + << "[]"; + QTest::newRow("EmptyMap") + << QCborValue(QCborMap()) << int(QCborValue::DiagnosticNotationOptions{}) + << "{}"; + + add(QCborValue(), "undefined"); + add(QCborValue::Null, "null"); + add(false, "false"); + add(true, "true"); + add(QCborSimpleType(0), "simple(0)"); + QTest::newRow("SimpleType-255") + << QCborValue(QCborSimpleType(255)) << int(QCborValue::DiagnosticNotationOptions{}) + << "simple(255)"; + add(0, "0"); + add(1, "1"); + add(-1, "-1"); + add(std::numeric_limits<qint64>::min(), QString::number(std::numeric_limits<qint64>::min())); + add(std::numeric_limits<qint64>::max(), QString::number(std::numeric_limits<qint64>::max())); + add(0., "0.0"); + add(1.25, "1.25"); + add(-1.25, "-1.25"); + add(qInf(), "inf"); + add(-qInf(), "-inf"); + add(qQNaN(), "nan"); + add(QByteArray(), "h''"); + add(QByteArray("Hello"), "h'48656c6c6f'"); + add(QLatin1String(), QLatin1String("\"\"")); + add("Hello", "\"Hello\""); + add("\"Hello\\World\"", "\"\\\"Hello\\\\World\\\"\""); + add(QCborValue(dt), "0(\"" + dt.toString(Qt::ISODateWithMs) + "\")"); + add(QCborValue(QUrl("http://example.com")), "32(\"http://example.com\")"); + add(QCborValue(QRegularExpression("^.*$")), "35(\"^.*$\")"); + add(QCborValue(uuid), "37(h'" + uuid.toString(QUuid::Id128) + "')"); + + // arrays and maps with more than one element + QTest::newRow("2Array") + << QCborValue(QCborArray{0, 1}) << int(QCborValue::DiagnosticNotationOptions{}) + << "[0, 1]"; + QTest::newRow("2Map") + << QCborValue(QCborMap{{0, 1}, {"foo", "bar"}}) << int(QCborValue::DiagnosticNotationOptions{}) + << "{0: 1, \"foo\": \"bar\"}"; + + // line wrapping in arrays and maps + QTest::newRow("LW:EmptyArray") + << QCborValue(QCborArray()) << int(QCborValue::LineWrapped) + << "[\n]"; + QTest::newRow("LW:EmptyMap") + << QCborValue(QCborMap()) << int(QCborValue::LineWrapped) + << "{\n}"; + QTest::newRow("LW:Array:Integer:0") + << QCborValue(QCborArray{0}) << int(QCborValue::LineWrapped) + << "[\n 0\n]"; + QTest::newRow("LW:Array:String:5") + << QCborValue(QCborArray{"Hello"}) << int(QCborValue::LineWrapped) + << "[\n \"Hello\"\n]"; + QTest::newRow("LW:Map:0-0") + << QCborValue(QCborMap{{0, 0}}) << int(QCborValue::LineWrapped) + << "{\n 0: 0\n}"; + QTest::newRow("LW:Map:String:5") + << QCborValue(QCborMap{{0, "Hello"}}) << int(QCborValue::LineWrapped) + << "{\n 0: \"Hello\"\n}"; + QTest::newRow("LW:2Array") + << QCborValue(QCborArray{0, 1}) << int(QCborValue::LineWrapped) + << "[\n 0,\n 1\n]"; + QTest::newRow("LW:2Map") + << QCborValue(QCborMap{{0, 0}, {"foo", "bar"}}) << int(QCborValue::LineWrapped) + << "{\n 0: 0,\n \"foo\": \"bar\"\n}"; + + // nested arrays and maps + QTest::newRow("Array:EmptyArray") + << QCborValue(QCborArray() << QCborArray()) << int(QCborValue::DiagnosticNotationOptions{}) + << "[[]]"; + QTest::newRow("Array:EmptyMap") + << QCborValue(QCborArray() << QCborMap()) << int(QCborValue::DiagnosticNotationOptions{}) + << "[{}]"; + QTest::newRow("LW:Array:EmptyArray") + << QCborValue(QCborArray() << QCborArray()) << int(QCborValue::LineWrapped) + << "[\n [\n ]\n]"; + QTest::newRow("LW:Array:EmptyMap") + << QCborValue(QCborArray() << QCborMap()) << int(QCborValue::LineWrapped) + << "[\n {\n }\n]"; + QTest::newRow("LW:Array:2Array") + << QCborValue(QCborArray() << QCborArray{0, 1}) << int(QCborValue::LineWrapped) + << "[\n [\n 0,\n 1\n ]\n]"; + QTest::newRow("LW:Map:2Array") + << QCborValue(QCborMap{{0, QCborArray{0, 1}}}) << int(QCborValue::LineWrapped) + << "{\n 0: [\n 0,\n 1\n ]\n}"; + QTest::newRow("LW:Map:2Map") + << QCborValue(QCborMap{{-1, QCborMap{{0, 0}, {"foo", "bar"}}}}) << int(QCborValue::LineWrapped) + << "{\n -1: {\n 0: 0,\n \"foo\": \"bar\"\n }\n}"; + + // string escaping + QTest::newRow("String:escaping") + << QCborValue("\1\a\b\t\f\r\n\v\x1f\x7f \"\xc2\xa0\xe2\x82\xac\xf0\x90\x80\x80\\\"") + << int(QCborValue::DiagnosticNotationOptions{}) + << "\"\\u0001\\a\\b\\t\\f\\r\\n\\v\\u001F\\u007F \\\"\\u00A0\\u20AC\\U00010000\\\\\\\"\""; + + // extended formatting for byte arrays + QTest::newRow("Extended:ByteArray:0") + << QCborValue(QByteArray()) << int(QCborValue::ExtendedFormat) + << "h''"; + QTest::newRow("Extended:ByteArray:5") + << QCborValue(QByteArray("Hello")) << int(QCborValue::ExtendedFormat) + << "h'48 65 6c 6c 6f'"; + QTest::newRow("Extended:ByteArray:Base64url") + << QCborValue(QCborKnownTags::ExpectedBase64url, QByteArray("\xff\xef")) + << int(QCborValue::ExtendedFormat) << "21(b64'_-8')"; + QTest::newRow("Extended:ByteArray:Base64") + << QCborValue(QCborKnownTags::ExpectedBase64, QByteArray("\xff\xef")) + << int(QCborValue::ExtendedFormat) << "22(b64'/+8=')"; + + // formatting applies through arrays too + QTest::newRow("Extended:Array:ByteArray:Base64url") + << QCborValue(QCborKnownTags::ExpectedBase64url, QCborArray{QByteArray("\xff\xef")}) + << int(QCborValue::ExtendedFormat) << "21([b64'_-8'])"; + // and only the innermost applies + QTest::newRow("ByteArray:multiple-tags") + << QCborValue(QCborKnownTags::ExpectedBase64url, + QCborArray{QCborValue(QCborKnownTags::ExpectedBase16, QByteArray("Hello")), + QByteArray("\xff\xef")}) + << int(QCborValue::ExtendedFormat) << "21([23(h'48 65 6c 6c 6f'), b64'_-8'])"; +} + +void tst_QCborValue::toDiagnosticNotation() +{ + QFETCH(QCborValue, v); + QFETCH(QString, expected); + QFETCH(int, opts); + + QString result = v.toDiagnosticNotation(QCborValue::DiagnosticNotationOptions(opts)); + QCOMPARE(result, expected); +} + +QTEST_MAIN(tst_QCborValue) + +#include "tst_qcborvalue.moc" diff --git a/tests/auto/corelib/serialization/qcborvalue_json/qcborvalue_json.pro b/tests/auto/corelib/serialization/qcborvalue_json/qcborvalue_json.pro new file mode 100644 index 0000000000..c11000b7c2 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborvalue_json/qcborvalue_json.pro @@ -0,0 +1,7 @@ +QT = core testlib +TARGET = tst_qcborvalue_json +CONFIG += testcase +SOURCES += \ + tst_qcborvalue_json.cpp + +DEFINES += SRCDIR=\\\"$$PWD/\\\" diff --git a/tests/auto/corelib/serialization/qcborvalue_json/tst_qcborvalue_json.cpp b/tests/auto/corelib/serialization/qcborvalue_json/tst_qcborvalue_json.cpp new file mode 100644 index 0000000000..56245a7173 --- /dev/null +++ b/tests/auto/corelib/serialization/qcborvalue_json/tst_qcborvalue_json.cpp @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qcborvalue.h> +#include <QtTest> + +Q_DECLARE_METATYPE(QCborValue) + +class tst_QCborValue_Json : public QObject +{ + Q_OBJECT + +private slots: + void toVariant_data(); + void toVariant(); + void toJson_data() { toVariant_data(); } + void toJson(); + void taggedByteArrayToJson_data(); + void taggedByteArrayToJson(); + + void fromVariant_data() { toVariant_data(); } + void fromVariant(); + void fromJson_data(); + void fromJson(); + + void nonStringKeysInMaps_data(); + void nonStringKeysInMaps(); +}; + +void tst_QCborValue_Json::toVariant_data() +{ + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<QVariant>("variant"); + QTest::addColumn<QJsonValue>("json"); + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUuid uuid = QUuid::createUuid(); + + QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>(); + auto add = [me](const QCborValue &v, const QVariant &exp, const QJsonValue &json) { + auto addRow = [=]() -> QTestData & { + const char *typeString = me.valueToKey(v.type()); + if (v.type() == QCborValue::Integer) + return QTest::addRow("Integer:%lld", exp.toLongLong()); + if (v.type() == QCborValue::Double) + return QTest::addRow("Double:%g", exp.toDouble()); + if (v.type() == QCborValue::ByteArray || v.type() == QCborValue::String) + return QTest::addRow("%s:%d", typeString, exp.toString().size()); + if (v.type() >= 0x10000) + return QTest::newRow(exp.typeName()); + return QTest::newRow(typeString); + }; + addRow() << v << exp << json; + }; + + // good JSON matching: + add(QCborValue(), QVariant(), QJsonValue::Undefined); + add(nullptr, QVariant::fromValue(nullptr), QJsonValue::Null); + add(false, false, false); + add(true, true, true); + add(0, 0, 0); + add(1, 1, 1); + add(-1, -1, -1); + add(0., 0., 0.); + add(1.25, 1.25, 1.25); + add(-1.25, -1.25, -1.25); + add("Hello", "Hello", "Hello"); + + // converts to string in JSON: + add(QByteArray("Hello"), QByteArray("Hello"), "SGVsbG8"); + add(QCborValue(dt), dt, dt.toString(Qt::ISODateWithMs)); + add(QCborValue(QUrl("http://example.com/{q}")), QUrl("http://example.com/{q}"), + "http://example.com/%7Bq%7D"); // note the encoded form in JSON + add(QCborValue(QRegularExpression(".")), QRegularExpression("."), "."); + add(QCborValue(uuid), uuid, uuid.toString(QUuid::WithoutBraces)); + + // not valid in JSON + QTest::newRow("simpletype") << QCborValue(QCborSimpleType(255)) + << QVariant::fromValue(QCborSimpleType(255)) + << QJsonValue("simple(255)"); + QTest::newRow("Double:inf") << QCborValue(qInf()) + << QVariant(qInf()) + << QJsonValue(); + QTest::newRow("Double:-inf") << QCborValue(-qInf()) + << QVariant(-qInf()) + << QJsonValue(); + QTest::newRow("Double:nan") << QCborValue(qQNaN()) + << QVariant(qQNaN()) + << QJsonValue(); + + // large integral values lose precision in JSON + QTest::newRow("Integer:max") << QCborValue(std::numeric_limits<qint64>::max()) + << QVariant(std::numeric_limits<qint64>::max()) + << QJsonValue(std::numeric_limits<qint64>::max()); + QTest::newRow("Integer:min") << QCborValue(std::numeric_limits<qint64>::min()) + << QVariant(std::numeric_limits<qint64>::min()) + << QJsonValue(std::numeric_limits<qint64>::min()); + + // empty arrays and maps + add(QCborArray(), QVariantList(), QJsonArray()); + add(QCborMap(), QVariantMap(), QJsonObject()); +} + +void tst_QCborValue_Json::toVariant() +{ + QFETCH(QCborValue, v); + QFETCH(QVariant, variant); + + if (qIsNaN(variant.toDouble())) { + // because NaN != NaN, QVariant(NaN) != QVariant(NaN), so we + // only need to compare the classification + QVERIFY(qIsNaN(v.toVariant().toDouble())); + + // the rest of this function depends on the variant comparison + return; + } + + QCOMPARE(v.toVariant(), variant); + if (variant.isValid()) { + QVariant variant2 = QVariant::fromValue(v); + QVERIFY(variant2.canConvert(variant.userType())); + QVERIFY(variant2.convert(variant.userType())); + QCOMPARE(variant2, variant); + } + + // tags get ignored: + QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toVariant(), variant); + + // make arrays with this item + QCOMPARE(QCborArray({v}).toVariantList(), QVariantList({variant})); + QCOMPARE(QCborArray({v, v}).toVariantList(), QVariantList({variant, variant})); + + // and maps + QCOMPARE(QCborMap({{"foo", v}}).toVariantMap(), QVariantMap({{"foo", variant}})); + QCOMPARE(QCborMap({{"foo", v}}).toVariantHash(), QVariantHash({{"foo", variant}})); + + // finally, mixed + QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toVariantList(), + QVariantList{QVariantMap({{"foo", variant}})}); +} + +void tst_QCborValue_Json::toJson() +{ + QFETCH(QCborValue, v); + QFETCH(QJsonValue, json); + + QCOMPARE(v.toJsonValue(), json); + QCOMPARE(QVariant::fromValue(v).toJsonValue(), json); + + // most tags get ignored: + QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toJsonValue(), json); + + // make arrays with this item + QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json})); + QCOMPARE(QCborArray({v, v}).toJsonArray(), QJsonArray({json, json})); + + // and maps + QCOMPARE(QCborMap({{"foo", v}}).toJsonObject(), QJsonObject({{"foo", json}})); + + // finally, mixed + QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toJsonArray(), + QJsonArray{QJsonObject({{"foo", json}})}); +} + +void tst_QCborValue_Json::taggedByteArrayToJson_data() +{ + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<QJsonValue>("json"); + + QByteArray data("\xff\x01"); + QTest::newRow("base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << QJsonValue("_wE"); + QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << QJsonValue("/wE="); + QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << QJsonValue("ff01"); +} + +void tst_QCborValue_Json::taggedByteArrayToJson() +{ + QFETCH(QCborValue, v); + QFETCH(QJsonValue, json); + + QCOMPARE(v.toJsonValue(), json); + QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json})); +} + +void tst_QCborValue_Json::fromVariant() +{ + QFETCH(QCborValue, v); + QFETCH(QVariant, variant); + + QCOMPARE(QCborValue::fromVariant(variant), v); + QCOMPARE(variant.value<QCborValue>(), v); + + // try arrays + QCOMPARE(QCborArray::fromVariantList({variant}), QCborArray{v}); + QCOMPARE(QCborArray::fromVariantList({variant, variant}), QCborArray({v, v})); + + if (variant.type() == QVariant::String) { + QString s = variant.toString(); + QCOMPARE(QCborArray::fromStringList({s}), QCborArray{v}); + QCOMPARE(QCborArray::fromStringList({s, s}), QCborArray({v, v})); + } + + // maps... + QVariantMap map{{"foo", variant}}; + QCOMPARE(QCborMap::fromVariantMap(map), QCborMap({{"foo", v}})); + QCOMPARE(QCborMap::fromVariantHash({{"foo", variant}}), QCborMap({{"foo", v}})); + + // nested + QVariantMap outer{{"bar", QVariantList{0, map, true}}}; + QCOMPARE(QCborMap::fromVariantMap(outer), + QCborMap({{"bar", QCborArray{0, QCborMap{{"foo", v}}, true}}})); +} + +void tst_QCborValue_Json::fromJson_data() +{ + QTest::addColumn<QCborValue>("v"); + QTest::addColumn<QJsonValue>("json"); + + QTest::newRow("null") << QCborValue(QCborValue::Null) << QJsonValue(QJsonValue::Null); + QTest::newRow("false") << QCborValue(false) << QJsonValue(false); + QTest::newRow("true") << QCborValue(true) << QJsonValue(true); + QTest::newRow("0") << QCborValue(0) << QJsonValue(0.); + QTest::newRow("1") << QCborValue(1) << QJsonValue(1); + QTest::newRow("1.5") << QCborValue(1.5) << QJsonValue(1.5); + QTest::newRow("string") << QCborValue("Hello") << QJsonValue("Hello"); + QTest::newRow("array") << QCborValue(QCborValue::Array) << QJsonValue(QJsonValue::Array); + QTest::newRow("map") << QCborValue(QCborValue::Map) << QJsonValue(QJsonValue::Object); +} + +void tst_QCborValue_Json::fromJson() +{ + QFETCH(QCborValue, v); + QFETCH(QJsonValue, json); + + QCOMPARE(QCborValue::fromJsonValue(json), v); + QCOMPARE(QVariant(json).value<QCborValue>(), v); + QCOMPARE(QCborArray::fromJsonArray({json}), QCborArray({v})); + QCOMPARE(QCborArray::fromJsonArray({json, json}), QCborArray({v, v})); + QCOMPARE(QCborMap::fromJsonObject({{"foo", json}}), QCborMap({{"foo", v}})); + + // confirm we can roundtrip back to JSON + QCOMPARE(QCborValue::fromJsonValue(json).toJsonValue(), json); +} + +void tst_QCborValue_Json::nonStringKeysInMaps_data() +{ + QTest::addColumn<QCborValue>("key"); + QTest::addColumn<QString>("converted"); + + auto add = [](const char *str, const QCborValue &v) { + QTest::newRow(str) << v << str; + }; + add("0", 0); + add("-1", -1); + add("false", false); + add("true", true); + add("null", nullptr); + add("undefined", {}); // should this be ""? + add("simple(255)", QCborSimpleType(255)); + add("2.5", 2.5); + + QByteArray data("\xff\x01"); + QTest::newRow("bytearray") << QCborValue(data) << "_wE"; + QTest::newRow("base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << "_wE"; + QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE="; + QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01"; + + QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[]"; + QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{}"; + QTest::newRow("array") << QCborValue(QCborArray{1, true, 2.5, "Hello"}) + << "[1, true, 2.5, \"Hello\"]"; + QTest::newRow("map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}}) + << "{\"Hello\": 0, 0: \"Hello\"}"; + + QDateTime dt = QDateTime::currentDateTimeUtc(); + QUrl url("https://example.com"); + QUuid uuid = QUuid::createUuid(); + QTest::newRow("QDateTime") << QCborValue(dt) << dt.toString(Qt::ISODateWithMs); + QTest::newRow("QUrl") << QCborValue(url) << url.toString(QUrl::FullyEncoded); + QTest::newRow("QRegularExpression") << QCborValue(QRegularExpression(".*")) << ".*"; + QTest::newRow("QUuid") << QCborValue(uuid) << uuid.toString(QUuid::WithoutBraces); +} + +void tst_QCborValue_Json::nonStringKeysInMaps() +{ + QFETCH(QCborValue, key); + QFETCH(QString, converted); + + QCborMap m; + m.insert(key, 0); + + { + QVariantMap vm = m.toVariantMap(); + auto it = vm.begin(); + QVERIFY(it != vm.end()); + QCOMPARE(it.key(), converted); + QCOMPARE(it.value(), 0); + QCOMPARE(++it, vm.end()); + } + + { + QJsonObject o = m.toJsonObject(); + auto it = o.begin(); + QVERIFY(it != o.end()); + QCOMPARE(it.key(), converted); + QCOMPARE(it.value(), 0); + QCOMPARE(++it, o.end()); + } +} + +QTEST_MAIN(tst_QCborValue_Json) + +#include "tst_qcborvalue_json.moc" diff --git a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp index 14a2528cc6..011a0e1a85 100644 --- a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp +++ b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp @@ -260,16 +260,16 @@ static int NColorRoles[] = { QPalette::HighlightedText + 1, // Qt_4_0, Qt_4_1 QPalette::HighlightedText + 1, // Qt_4_2 QPalette::AlternateBase + 1, // Qt_4_3 - QPalette::ToolTipText + 1, // Qt_4_4 - QPalette::ToolTipText + 1, // Qt_4_5 - QPalette::ToolTipText + 1, // Qt_4_6 - QPalette::ToolTipText + 1, // Qt_5_0 - QPalette::ToolTipText + 1, // Qt_5_1 - QPalette::ToolTipText + 1, // Qt_5_2 - QPalette::ToolTipText + 1, // Qt_5_3 - QPalette::ToolTipText + 1, // Qt_5_4 - QPalette::ToolTipText + 1, // Qt_5_5 - QPalette::ToolTipText + 1, // Qt_5_6 + QPalette::PlaceholderText + 1, // Qt_4_4 + QPalette::PlaceholderText + 1, // Qt_4_5 + QPalette::PlaceholderText + 1, // Qt_4_6 + QPalette::PlaceholderText + 1, // Qt_5_0 + QPalette::PlaceholderText + 1, // Qt_5_1 + QPalette::PlaceholderText + 1, // Qt_5_2 + QPalette::PlaceholderText + 1, // Qt_5_3 + QPalette::PlaceholderText + 1, // Qt_5_4 + QPalette::PlaceholderText + 1, // Qt_5_5 + QPalette::PlaceholderText + 1, // Qt_5_6 0 // add the correct value for Qt_5_7 here later }; @@ -2139,7 +2139,7 @@ void tst_QDataStream::setVersion() */ // revise the test if new color roles or color groups are added - QVERIFY(QPalette::NColorRoles == QPalette::ToolTipText + 1); + QVERIFY(QPalette::NColorRoles == QPalette::PlaceholderText + 1); QCOMPARE(int(QPalette::NColorGroups), 3); QByteArray ba2; @@ -2211,25 +2211,22 @@ void tst_QDataStream::setVersion() } } -class SequentialBuffer : public QBuffer +class SequentialBuffer : public QIODevice { public: - SequentialBuffer(QByteArray *data) : QBuffer(data) { offset = 0; } + SequentialBuffer(QByteArray *data) : QIODevice() { buf.setBuffer(data); } - bool isSequential() const { return true; } - bool seek(qint64 pos) { offset = pos; return QBuffer::seek(pos); } - qint64 pos() const { return qint64(offset); } + bool isSequential() const override { return true; } + bool open(OpenMode mode) override { return buf.open(mode) && QIODevice::open(mode | QIODevice::Unbuffered); } + void close() override { buf.close(); QIODevice::close(); } + qint64 bytesAvailable() const override { return QIODevice::bytesAvailable() + buf.bytesAvailable(); } protected: - qint64 readData(char *data, qint64 maxSize) - { - qint64 ret = QBuffer::readData(data, maxSize); - offset += ret; - return ret; - } + qint64 readData(char *data, qint64 maxSize) override { return buf.read(data, maxSize); } + qint64 writeData(const char *data, qint64 maxSize) override { return buf.write(data, maxSize); } private: - int offset; + QBuffer buf; }; void tst_QDataStream::skipRawData_data() @@ -3329,15 +3326,21 @@ void tst_QDataStream::transaction_data() QTest::addColumn<bool>("bData"); QTest::addColumn<float>("fData"); QTest::addColumn<double>("dData"); + QTest::addColumn<QImage>("imgData"); QTest::addColumn<QByteArray>("strData"); QTest::addColumn<QByteArray>("rawData"); + QImage img1(open_xpm); + QImage img2; + QImage img3(50, 50, QImage::Format_ARGB32); + img3.fill(qRgba(12, 34, 56, 78)); + QTest::newRow("1") << qint8(1) << qint16(2) << qint32(3) << qint64(4) << true << 5.0f - << double(6.0) << QByteArray("Hello world!") << QByteArray("Qt rocks!"); + << double(6.0) << img1 << QByteArray("Hello world!") << QByteArray("Qt rocks!"); QTest::newRow("2") << qint8(1 << 6) << qint16(1 << 14) << qint32(1 << 30) << qint64Data(3) << false << 123.0f - << double(234.0) << stringData(5).toUtf8() << stringData(6).toUtf8(); + << double(234.0) << img2 << stringData(5).toUtf8() << stringData(6).toUtf8(); QTest::newRow("3") << qint8(-1) << qint16(-2) << qint32(-3) << qint64(-4) << true << -123.0f - << double(-234.0) << stringData(3).toUtf8() << stringData(4).toUtf8(); + << double(-234.0) << img3 << stringData(3).toUtf8() << stringData(4).toUtf8(); } void tst_QDataStream::transaction() @@ -3351,6 +3354,7 @@ void tst_QDataStream::transaction() QFETCH(bool, bData); QFETCH(float, fData); QFETCH(double, dData); + QFETCH(QImage, imgData); QFETCH(QByteArray, strData); QFETCH(QByteArray, rawData); @@ -3358,12 +3362,13 @@ void tst_QDataStream::transaction() QDataStream stream(&testBuffer, QIODevice::WriteOnly); stream << i8Data << i16Data << i32Data << i64Data - << bData << fData << dData << strData.constData(); + << bData << fData << dData << imgData << strData.constData(); stream.writeRawData(rawData.constData(), rawData.size()); } for (int splitPos = 0; splitPos <= testBuffer.size(); ++splitPos) { QByteArray readBuffer(testBuffer.left(splitPos)); + SequentialBuffer dev(&readBuffer); dev.open(QIODevice::ReadOnly); QDataStream stream(&dev); @@ -3375,12 +3380,13 @@ void tst_QDataStream::transaction() bool b; float f; double d; + QImage img; char *str; QByteArray raw(rawData.size(), 0); forever { stream.startTransaction(); - stream >> i8 >> i16 >> i32 >> i64 >> b >> f >> d >> str; + stream >> i8 >> i16 >> i32 >> i64 >> b >> f >> d >> img >> str; stream.readRawData(raw.data(), raw.size()); if (stream.commitTransaction()) @@ -3402,6 +3408,7 @@ void tst_QDataStream::transaction() QCOMPARE(b, bData); QCOMPARE(f, fData); QCOMPARE(d, dData); + QCOMPARE(img, imgData); QVERIFY(strData == str); delete [] str; QCOMPARE(raw, rawData); diff --git a/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp b/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp index df8746e518..edea4713a1 100644 --- a/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp +++ b/tests/auto/corelib/serialization/qtextstream/tst_qtextstream.cpp @@ -161,6 +161,7 @@ private slots: void string_write_operator_ToDevice(); void latin1String_write_operator_ToDevice(); void stringref_write_operator_ToDevice(); + void stringview_write_operator_ToDevice(); // other void skipWhiteSpace_data(); @@ -2573,6 +2574,17 @@ void tst_QTextStream::stringref_write_operator_ToDevice() QCOMPARE(buf.buffer().constData(), "No explicit lengthExplicit length"); } +void tst_QTextStream::stringview_write_operator_ToDevice() +{ + QBuffer buf; + buf.open(QBuffer::WriteOnly); + QTextStream stream(&buf); + const QStringView expected = QStringViewLiteral("expectedStringView"); + stream << expected; + stream.flush(); + QCOMPARE(buf.buffer().constData(), "expectedStringView"); +} + // ------------------------------------------------------------------------------ void tst_QTextStream::useCase1() { diff --git a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp index 16a4200b5d..8fdf91b090 100644 --- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp @@ -577,6 +577,8 @@ private slots: void invalidStringCharacters() const; void hasError() const; void readBack() const; + void roundTrip() const; + void roundTrip_data() const; private: static QByteArray readFile(const QString &filename); @@ -1741,5 +1743,35 @@ void tst_QXmlStream::readBack() const } } +void tst_QXmlStream::roundTrip_data() const +{ + QTest::addColumn<QString>("in"); + + QTest::newRow("QTBUG-63434") << + "<?xml version=\"1.0\"?>" + "<root>" + "<father>" + "<child xmlns:unknown=\"http://mydomain\">Text</child>" + "</father>" + "</root>\n"; +} + +void tst_QXmlStream::roundTrip() const +{ + QFETCH(QString, in); + QString out; + + QXmlStreamReader reader(in); + QXmlStreamWriter writer(&out); + + while (!reader.atEnd()) { + reader.readNext(); + QVERIFY(!reader.hasError()); + writer.writeCurrentToken(reader); + QVERIFY(!writer.hasError()); + } + QCOMPARE(out, in); +} + #include "tst_qxmlstream.moc" // vim: et:ts=4:sw=4:sts=4 diff --git a/tests/auto/corelib/serialization/serialization.pro b/tests/auto/corelib/serialization/serialization.pro index afb9c5b61c..9187de1bc5 100644 --- a/tests/auto/corelib/serialization/serialization.pro +++ b/tests/auto/corelib/serialization/serialization.pro @@ -1,6 +1,10 @@ TEMPLATE = subdirs SUBDIRS = \ json \ + qcborstreamreader \ + qcborstreamwriter \ + qcborvalue \ + qcborvalue_json \ qdatastream \ qtextstream \ qxmlstream |