| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2020 Intel Corporation. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtCore module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include <QtCore/qcborvalue.h> |
| 41 | #include <QtTest> |
| 42 | |
| 43 | #include <QtCore/private/qbytearray_p.h> |
| 44 | |
| 45 | Q_DECLARE_METATYPE(QCborKnownTags) |
| 46 | Q_DECLARE_METATYPE(QCborValue) |
| 47 | Q_DECLARE_METATYPE(QCborValue::EncodingOptions) |
| 48 | |
| 49 | class tst_QCborValue : public QObject |
| 50 | { |
| 51 | Q_OBJECT |
| 52 | |
| 53 | private slots: |
| 54 | void basics_data(); |
| 55 | void basics(); |
| 56 | void tagged_data() { basics_data(); } |
| 57 | void tagged(); |
| 58 | void extendedTypes_data(); |
| 59 | void extendedTypes(); |
| 60 | void copyCompare_data() { basics_data(); } |
| 61 | void copyCompare(); |
| 62 | |
| 63 | void arrayDefaultInitialization(); |
| 64 | void arrayEmptyInitializerList(); |
| 65 | void arrayEmptyDetach(); |
| 66 | void arrayNonEmptyDetach(); |
| 67 | void arrayInitializerList(); |
| 68 | void arrayMutation(); |
| 69 | void arrayMutateWithCopies(); |
| 70 | void arrayPrepend(); |
| 71 | void arrayInsertRemove_data() { basics_data(); } |
| 72 | void arrayInsertRemove(); |
| 73 | void arrayInsertTagged_data() { basics_data(); } |
| 74 | void arrayInsertTagged(); |
| 75 | void arrayStringElements(); |
| 76 | void arraySelfAssign_data() { basics_data(); } |
| 77 | void arraySelfAssign(); |
| 78 | |
| 79 | void mapDefaultInitialization(); |
| 80 | void mapEmptyInitializerList(); |
| 81 | void mapEmptyDetach(); |
| 82 | void mapNonEmptyDetach(); |
| 83 | void mapSimpleInitializerList(); |
| 84 | void mapMutation(); |
| 85 | void mapMutateWithCopies(); |
| 86 | void mapStringValues(); |
| 87 | void mapStringKeys(); |
| 88 | void mapInsertRemove_data() { basics_data(); } |
| 89 | void mapInsertRemove(); |
| 90 | void mapInsertTagged_data() { basics_data(); } |
| 91 | void mapInsertTagged(); |
| 92 | void mapSelfAssign_data() { basics_data(); } |
| 93 | void mapSelfAssign(); |
| 94 | void mapComplexKeys_data() { basics_data(); } |
| 95 | void mapComplexKeys(); |
| 96 | |
| 97 | void sorting(); |
| 98 | |
| 99 | void toCbor_data(); |
| 100 | void toCbor(); |
| 101 | void toCborStreamWriter_data() { toCbor_data(); } |
| 102 | void toCborStreamWriter(); |
| 103 | void fromCbor_data(); |
| 104 | void fromCbor(); |
| 105 | void fromCborStreamReaderByteArray_data() { fromCbor_data(); } |
| 106 | void fromCborStreamReaderByteArray(); |
| 107 | void fromCborStreamReaderIODevice_data() { fromCbor_data(); } |
| 108 | void fromCborStreamReaderIODevice(); |
| 109 | void validation_data(); |
| 110 | void validation(); |
| 111 | void extendedTypeValidation_data(); |
| 112 | void extendedTypeValidation(); |
| 113 | void hugeDeviceValidation_data(); |
| 114 | void hugeDeviceValidation(); |
| 115 | void recursionLimit_data(); |
| 116 | void recursionLimit(); |
| 117 | void toDiagnosticNotation_data(); |
| 118 | void toDiagnosticNotation(); |
| 119 | |
| 120 | void datastreamSerialization_data(); |
| 121 | void datastreamSerialization(); |
| 122 | void streamVariantSerialization(); |
| 123 | }; |
| 124 | |
| 125 | namespace SimpleEncodeToCbor { |
| 126 | inline size_t lengthOf(int) |
| 127 | { |
| 128 | return 1; // encode as byte |
| 129 | } |
| 130 | |
| 131 | template <unsigned N> inline size_t lengthOf(const char (&)[N]) |
| 132 | { |
| 133 | return N - 1; |
| 134 | } |
| 135 | |
| 136 | |
| 137 | inline size_t lengthOf(const char *str) |
| 138 | { |
| 139 | return strlen(s: str); |
| 140 | } |
| 141 | |
| 142 | template <typename T> inline size_t lengthOf(T) |
| 143 | { |
| 144 | return sizeof(T); |
| 145 | } |
| 146 | |
| 147 | static void encodeOneAt(char *ptr, int v, size_t) |
| 148 | { |
| 149 | // encode as byte |
| 150 | *ptr = char(v); |
| 151 | } |
| 152 | |
| 153 | static void encodeOneAt(char *ptr, const char *v, size_t size) |
| 154 | { |
| 155 | memcpy(dest: ptr, src: v, n: size); |
| 156 | } |
| 157 | |
| 158 | template <typename T> |
| 159 | static typename std::enable_if<std::is_unsigned<T>::value>::type |
| 160 | encodeOneAt(char *ptr, T v, size_t) |
| 161 | { |
| 162 | qToBigEndian(v, ptr); |
| 163 | } |
| 164 | |
| 165 | template <typename T> |
| 166 | static typename std::enable_if<std::is_floating_point<T>::value || |
| 167 | std::is_same<T, qfloat16>::value>::type |
| 168 | encodeOneAt(char *ptr, T v, size_t) |
| 169 | { |
| 170 | typename QIntegerForSizeof<T>::Unsigned u; |
| 171 | memcpy(&u, &v, sizeof(u)); |
| 172 | qToBigEndian(u, ptr); |
| 173 | } |
| 174 | |
| 175 | static char *encodeAt(char *ptr) |
| 176 | { |
| 177 | return ptr; |
| 178 | } |
| 179 | |
| 180 | template <typename Arg0, typename... Args> |
| 181 | static char *encodeAt(char *ptr, Arg0 a0, Args... a) |
| 182 | { |
| 183 | encodeOneAt(ptr, a0, lengthOf(a0)); |
| 184 | return encodeAt(ptr + lengthOf(a0), a...); |
| 185 | } |
| 186 | |
| 187 | } // namespace SimpleEncodetoCbor |
| 188 | |
| 189 | template <typename... Args> |
| 190 | static QByteArray encode(Args... a) |
| 191 | { |
| 192 | // this would be much easier with C++17 fold expressions... |
| 193 | using namespace SimpleEncodeToCbor; |
| 194 | using namespace std; |
| 195 | size_t lengths[] = { lengthOf(a)... }; |
| 196 | size_t total = accumulate(begin(lengths), end(lengths), size_t(0), plus<size_t>{}); |
| 197 | QByteArray result(QByteArray::size_type(total), Qt::Uninitialized); |
| 198 | char *ptr = result.data(); |
| 199 | encodeAt(ptr, a...); |
| 200 | return result; |
| 201 | } |
| 202 | |
| 203 | // Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) |
| 204 | #include "data.cpp" |
| 205 | |
| 206 | struct SimpleTypeWrapper |
| 207 | { |
| 208 | // QCborSimpleType is an enum, so QVariant knows how to convert it to |
| 209 | // integer and we don't want it to do that. |
| 210 | SimpleTypeWrapper(QCborSimpleType type = {}) : st(type) {} |
| 211 | QCborSimpleType st; |
| 212 | }; |
| 213 | Q_DECLARE_METATYPE(SimpleTypeWrapper) |
| 214 | |
| 215 | void tst_QCborValue::basics_data() |
| 216 | { |
| 217 | QTest::addColumn<QCborValue::Type>(name: "type" ); |
| 218 | QTest::addColumn<QCborValue>(name: "v" ); |
| 219 | QTest::addColumn<QVariant>(name: "expectedValue" ); |
| 220 | QDateTime dt = QDateTime::currentDateTimeUtc(); |
| 221 | QUuid uuid = QUuid::createUuid(); |
| 222 | |
| 223 | QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>(); |
| 224 | auto add = [me](QCborValue::Type t, const QCborValue &v, const QVariant &exp) { |
| 225 | auto addRow = [=]() -> QTestData & { |
| 226 | const char *typeString = me.valueToKey(value: t); |
| 227 | if (t == QCborValue::Integer) |
| 228 | return QTest::addRow(format: "Integer:%lld" , exp.toLongLong()); |
| 229 | if (t == QCborValue::Double) |
| 230 | return QTest::addRow(format: "Double:%g" , exp.toDouble()); |
| 231 | if (t == QCborValue::ByteArray || t == QCborValue::String) |
| 232 | return QTest::addRow(format: "%s:%d" , typeString, exp.toString().size()); |
| 233 | return QTest::newRow(dataTag: typeString); |
| 234 | }; |
| 235 | addRow() << t << v << exp; |
| 236 | }; |
| 237 | auto st = [](QCborSimpleType t) { return QVariant::fromValue<SimpleTypeWrapper>(value: t); }; |
| 238 | |
| 239 | add(QCborValue::Undefined, QCborValue(), st(QCborSimpleType::Undefined)); |
| 240 | add(QCborValue::Null, QCborValue::Null, st(QCborSimpleType::Null)); |
| 241 | QTest::newRow(dataTag: "nullptr" ) << QCborValue::Null << QCborValue(nullptr) |
| 242 | << st(QCborSimpleType::Null); |
| 243 | add(QCborValue::False, false, st(QCborSimpleType::False)); |
| 244 | QTest::newRow(dataTag: "false" ) << QCborValue::False << QCborValue(QCborValue::False) |
| 245 | << st(QCborSimpleType::False); |
| 246 | add(QCborValue::True, true, st(QCborSimpleType::True)); |
| 247 | QTest::newRow(dataTag: "true" ) << QCborValue::True << QCborValue(QCborValue::True) |
| 248 | << st(QCborSimpleType::True); |
| 249 | QTest::newRow(dataTag: "simpletype" ) << QCborValue::Type(QCborValue::SimpleType + 255) |
| 250 | << QCborValue(QCborSimpleType(255)) |
| 251 | << st(QCborSimpleType(255)); |
| 252 | add(QCborValue::Integer, 0, 0); |
| 253 | add(QCborValue::Integer, 1, 1); |
| 254 | add(QCborValue::Integer, -1, -1); |
| 255 | add(QCborValue::Integer, std::numeric_limits<qint64>::min(), std::numeric_limits<qint64>::min()); |
| 256 | add(QCborValue::Integer, std::numeric_limits<qint64>::max(), std::numeric_limits<qint64>::max()); |
| 257 | add(QCborValue::Double, 0., 0.); |
| 258 | add(QCborValue::Double, 1.25, 1.25); |
| 259 | add(QCborValue::Double, -1.25, -1.25); |
| 260 | add(QCborValue::Double, qInf(), qInf()); |
| 261 | add(QCborValue::Double, -qInf(), -qInf()); |
| 262 | add(QCborValue::Double, qQNaN(), qQNaN()); |
| 263 | add(QCborValue::ByteArray, QByteArray("Hello" ), QByteArray("Hello" )); |
| 264 | add(QCborValue::ByteArray, QByteArray(), QByteArray()); |
| 265 | add(QCborValue::String, "Hello" , "Hello" ); |
| 266 | add(QCborValue::String, QLatin1String(), QString()); |
| 267 | add(QCborValue::DateTime, QCborValue(dt), dt); |
| 268 | add(QCborValue::Url, QCborValue(QUrl("http://example.com" )), QUrl("http://example.com" )); |
| 269 | add(QCborValue::RegularExpression, QCborValue(QRegularExpression("^.*$" )), QRegularExpression("^.*$" )); |
| 270 | add(QCborValue::Uuid, QCborValue(uuid), uuid); |
| 271 | |
| 272 | // empty arrays and maps |
| 273 | add(QCborValue::Array, QCborArray(), QVariantList()); |
| 274 | add(QCborValue::Map, QCborMap(), QVariantMap()); |
| 275 | } |
| 276 | |
| 277 | static void basicTypeCheck(QCborValue::Type type, const QCborValue &v, const QVariant &expectedValue) |
| 278 | { |
| 279 | bool isSimpleType = (expectedValue.userType() == qMetaTypeId<SimpleTypeWrapper>()); |
| 280 | QCborSimpleType st = expectedValue.value<SimpleTypeWrapper>().st; |
| 281 | |
| 282 | QCOMPARE(v.type(), type); |
| 283 | QCOMPARE(v.isInteger(), type == QCborValue::Integer); |
| 284 | QCOMPARE(v.isByteArray(), type == QCborValue::ByteArray); |
| 285 | QCOMPARE(v.isString(), type == QCborValue::String); |
| 286 | QCOMPARE(v.isArray(), type == QCborValue::Array); |
| 287 | QCOMPARE(v.isMap(), type == QCborValue::Map); |
| 288 | QCOMPARE(v.isFalse(), type == QCborValue::False); |
| 289 | QCOMPARE(v.isTrue(), type == QCborValue::True); |
| 290 | QCOMPARE(v.isBool(), type == QCborValue::False || type == QCborValue::True); |
| 291 | QCOMPARE(v.isNull(), type == QCborValue::Null); |
| 292 | QCOMPARE(v.isUndefined(), type == QCborValue::Undefined); |
| 293 | QCOMPARE(v.isDouble(), type == QCborValue::Double); |
| 294 | QCOMPARE(v.isDateTime(), type == QCborValue::DateTime); |
| 295 | QCOMPARE(v.isUrl(), type == QCborValue::Url); |
| 296 | QCOMPARE(v.isUuid(), type == QCborValue::Uuid); |
| 297 | QCOMPARE(v.isInvalid(), type == QCborValue::Invalid); |
| 298 | QCOMPARE(v.isContainer(), type == QCborValue::Array || type == QCborValue::Map); |
| 299 | QCOMPARE(v.isSimpleType(), isSimpleType); |
| 300 | QCOMPARE(v.isSimpleType(QCborSimpleType::False), st == QCborSimpleType::False); |
| 301 | QCOMPARE(v.isSimpleType(QCborSimpleType::True), st == QCborSimpleType::True); |
| 302 | QCOMPARE(v.isSimpleType(QCborSimpleType::Null), st == QCborSimpleType::Null); |
| 303 | QCOMPARE(v.isSimpleType(QCborSimpleType::Undefined), st == QCborSimpleType::Undefined); |
| 304 | QCOMPARE(v.isSimpleType(QCborSimpleType(255)), st == QCborSimpleType(255)); |
| 305 | |
| 306 | if (v.isInteger()) { |
| 307 | QCOMPARE(v.toInteger(), expectedValue.toLongLong()); |
| 308 | QCOMPARE(v.toDouble(), 0. + expectedValue.toLongLong()); |
| 309 | } else { |
| 310 | QCOMPARE(v.toInteger(), qint64(expectedValue.toDouble())); |
| 311 | QCOMPARE(v.toDouble(), expectedValue.toDouble()); |
| 312 | } |
| 313 | QCOMPARE(v.toBool(true), st != QCborSimpleType::False); |
| 314 | QCOMPARE(v.toBool(), st == QCborSimpleType::True); |
| 315 | if (st == QCborSimpleType::Undefined) |
| 316 | QCOMPARE(v.toSimpleType(QCborSimpleType::Null), QCborSimpleType::Undefined); |
| 317 | else if (isSimpleType) |
| 318 | QCOMPARE(v.toSimpleType(), st); |
| 319 | else |
| 320 | QCOMPARE(v.toSimpleType(), QCborSimpleType::Undefined); |
| 321 | |
| 322 | #define CMP(expr, T, validexpr) \ |
| 323 | if (expectedValue.userType() == qMetaTypeId<T>()) \ |
| 324 | QCOMPARE(expr, expectedValue.value<T>()); \ |
| 325 | else \ |
| 326 | QVERIFY(validexpr) |
| 327 | CMP(v.toByteArray(), QByteArray, v.toByteArray().isNull()); |
| 328 | CMP(v.toString(), QString, v.toString().isNull()); |
| 329 | CMP(v.toDateTime(), QDateTime, !v.toDateTime().isValid()); |
| 330 | CMP(v.toUrl(), QUrl, !v.toUrl().isValid()); |
| 331 | CMP(v.toRegularExpression(), QRegularExpression, v.toRegularExpression().pattern().isNull()); |
| 332 | CMP(v.toUuid(), QUuid, v.toUuid().isNull()); |
| 333 | #undef CMP |
| 334 | |
| 335 | QVERIFY(v.toArray().isEmpty()); |
| 336 | QVERIFY(v.toMap().isEmpty()); |
| 337 | |
| 338 | QVERIFY(v["Hello" ].isUndefined()); |
| 339 | QVERIFY(v[0].isUndefined()); |
| 340 | } |
| 341 | |
| 342 | void tst_QCborValue::basics() |
| 343 | { |
| 344 | QFETCH(QCborValue::Type, type); |
| 345 | QFETCH(QCborValue, v); |
| 346 | QFETCH(QVariant, expectedValue); |
| 347 | |
| 348 | basicTypeCheck(type, v, expectedValue); |
| 349 | } |
| 350 | |
| 351 | void tst_QCborValue::tagged() |
| 352 | { |
| 353 | QFETCH(QCborValue::Type, type); |
| 354 | QFETCH(QCborValue, v); |
| 355 | QFETCH(QVariant, expectedValue); |
| 356 | |
| 357 | // make it tagged |
| 358 | QCborValue tagged(QCborKnownTags::Signature, v); |
| 359 | QVERIFY(tagged.isTag()); |
| 360 | QCOMPARE(tagged.tag(), QCborTag(QCborKnownTags::Signature)); |
| 361 | |
| 362 | // shouldn't compare equal |
| 363 | QVERIFY(tagged != v); |
| 364 | QVERIFY(v != tagged); |
| 365 | |
| 366 | // ensure we can reach the original value |
| 367 | basicTypeCheck(type, v: tagged.taggedValue(), expectedValue); |
| 368 | QVERIFY(tagged.taggedValue() == v); |
| 369 | QVERIFY(v == tagged.taggedValue()); |
| 370 | |
| 371 | // nested tagging should work too |
| 372 | QCborValue tagged2(QCborKnownTags::EncodedCbor, tagged); |
| 373 | QVERIFY(tagged2.isTag()); |
| 374 | QCOMPARE(tagged2.tag(), QCborTag(QCborKnownTags::EncodedCbor)); |
| 375 | |
| 376 | QVERIFY(tagged2 != tagged); |
| 377 | QVERIFY(tagged != tagged2); |
| 378 | |
| 379 | QVERIFY(tagged2.taggedValue() == tagged); |
| 380 | QVERIFY(tagged == tagged2.taggedValue()); |
| 381 | QVERIFY(tagged2.taggedValue().taggedValue() == v); |
| 382 | QVERIFY(v == tagged2.taggedValue().taggedValue()); |
| 383 | } |
| 384 | |
| 385 | void tst_QCborValue::extendedTypes_data() |
| 386 | { |
| 387 | QTest::addColumn<QCborValue>(name: "extended" ); |
| 388 | QTest::addColumn<QCborKnownTags>(name: "tag" ); |
| 389 | QTest::addColumn<QCborValue>(name: "taggedValue" ); |
| 390 | QTest::addColumn<QCborValue>(name: "correctedTaggedValue" ); |
| 391 | QCborValue v(QCborValue::Invalid); |
| 392 | QDateTime dt = QDateTime::currentDateTimeUtc(); |
| 393 | QDateTime dtTzOffset(dt.date(), dt.time(), Qt::OffsetFromUTC, dt.offsetFromUtc()); |
| 394 | QUuid uuid = QUuid::createUuid(); |
| 395 | |
| 396 | // non-correcting extended types (tagged value remains unchanged) |
| 397 | QTest::newRow(dataTag: "DateTime" ) << QCborValue(dt) |
| 398 | << QCborKnownTags::DateTimeString << QCborValue(dt.toString(format: Qt::ISODateWithMs)) << v; |
| 399 | QTest::newRow(dataTag: "DateTime:TzOffset" ) << QCborValue(dtTzOffset) |
| 400 | << QCborKnownTags::DateTimeString << QCborValue(dtTzOffset.toString(format: Qt::ISODateWithMs)) << v; |
| 401 | QTest::newRow(dataTag: "Url:Empty" ) << QCborValue(QUrl()) |
| 402 | << QCborKnownTags::Url << QCborValue(QString()) << v; |
| 403 | QTest::newRow(dataTag: "Url:Authority" ) << QCborValue(QUrl("https://example.com" )) |
| 404 | << QCborKnownTags::Url << QCborValue("https://example.com" ) << v; |
| 405 | QTest::newRow(dataTag: "Url:Path" ) << QCborValue(QUrl("file:///tmp/none" )) |
| 406 | << QCborKnownTags::Url << QCborValue("file:///tmp/none" ) << v; |
| 407 | QTest::newRow(dataTag: "Url:QueryFragment" ) << QCborValue(QUrl("whatever:?a=b&c=d#e" )) |
| 408 | << QCborKnownTags::Url << QCborValue("whatever:?a=b&c=d#e" ) << v; |
| 409 | QTest::newRow(dataTag: "Regex:Empty" ) << QCborValue(QRegularExpression()) |
| 410 | << QCborKnownTags::RegularExpression << QCborValue(QString()) << v; |
| 411 | QTest::newRow(dataTag: "Regex" ) << QCborValue(QRegularExpression("^.*$" )) |
| 412 | << QCborKnownTags::RegularExpression << QCborValue(QString("^.*$" )) << v; |
| 413 | QTest::newRow(dataTag: "Uuid" ) << QCborValue(uuid) |
| 414 | << QCborKnownTags::Uuid << QCborValue(uuid.toRfc4122()) << v; |
| 415 | |
| 416 | // correcting extended types |
| 417 | QDateTime dtNoMsecs = dt.fromSecsSinceEpoch(secs: dt.toSecsSinceEpoch(), spe: Qt::UTC); |
| 418 | QUrl url("https://example.com/\xc2\xa9 " ); |
| 419 | QTest::newRow(dataTag: "UnixTime_t:Integer" ) << QCborValue(dtNoMsecs) << QCborKnownTags::UnixTime_t |
| 420 | << QCborValue(dtNoMsecs.toSecsSinceEpoch()) |
| 421 | << QCborValue(dtNoMsecs.toString(format: Qt::ISODateWithMs)); |
| 422 | QTest::newRow(dataTag: "UnixTime_t:Double" ) << QCborValue(dt) << QCborKnownTags::UnixTime_t |
| 423 | << QCborValue(dt.toMSecsSinceEpoch() / 1000.) |
| 424 | << QCborValue(dt.toString(format: Qt::ISODateWithMs)); |
| 425 | QTest::newRow(dataTag: "DateTime::JustDate" ) << QCborValue(QDateTime({2018, 1, 1}, {})) |
| 426 | << QCborKnownTags::DateTimeString |
| 427 | << QCborValue("2018-01-01" ) << QCborValue("2018-01-01T00:00:00.000" ); |
| 428 | QTest::newRow(dataTag: "DateTime::TzOffset" ) << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::UTC)) |
| 429 | << QCborKnownTags::DateTimeString |
| 430 | << QCborValue("2018-01-01T09:00:00.000+00:00" ) |
| 431 | << QCborValue("2018-01-01T09:00:00.000Z" ); |
| 432 | QTest::newRow(dataTag: "Url:NotNormalized" ) << QCborValue(url) << QCborKnownTags::Url |
| 433 | << QCborValue("HTTPS://EXAMPLE.COM/%c2%a9%20" ) |
| 434 | << QCborValue(url.toString()); |
| 435 | QTest::newRow(dataTag: "Uuid:Zero" ) << QCborValue(QUuid()) << QCborKnownTags::Uuid |
| 436 | << QCborValue(QByteArray()) |
| 437 | << QCborValue(QByteArray(sizeof(QUuid), 0)); |
| 438 | QTest::newRow(dataTag: "Uuid:TooShort" ) << QCborValue(QUuid(0x12345678, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) |
| 439 | << QCborKnownTags::Uuid |
| 440 | << QCborValue(raw(data: "\x12\x34\x56\x78" )) |
| 441 | << QCborValue(raw(data: "\x12\x34\x56\x78" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" )); |
| 442 | QTest::newRow(dataTag: "Uuid:TooLong" ) << QCborValue(uuid) << QCborKnownTags::Uuid |
| 443 | << QCborValue(uuid.toRfc4122() + "\1\2\3\4" ) << QCborValue(uuid.toRfc4122()); |
| 444 | } |
| 445 | |
| 446 | void tst_QCborValue::extendedTypes() |
| 447 | { |
| 448 | QFETCH(QCborValue, extended); |
| 449 | QFETCH(QCborKnownTags, tag); |
| 450 | QFETCH(QCborValue, taggedValue); |
| 451 | QFETCH(QCborValue, correctedTaggedValue); |
| 452 | if (correctedTaggedValue.isInvalid()) |
| 453 | correctedTaggedValue = taggedValue; |
| 454 | |
| 455 | QCborValue tagged(tag, taggedValue); |
| 456 | QVERIFY(extended.isTag()); |
| 457 | QVERIFY(tagged.isTag()); |
| 458 | QCOMPARE(tagged.taggedValue(), correctedTaggedValue); |
| 459 | QVERIFY(extended == tagged); |
| 460 | QVERIFY(tagged == extended); |
| 461 | |
| 462 | QCOMPARE(extended.tag(), tagged.tag()); |
| 463 | QCOMPARE(extended.taggedValue(), tagged.taggedValue()); |
| 464 | } |
| 465 | |
| 466 | void tst_QCborValue::copyCompare() |
| 467 | { |
| 468 | QFETCH(QCborValue, v); |
| 469 | QCborValue other = v; |
| 470 | |
| 471 | // self-moving |
| 472 | v = std::move(v); |
| 473 | QCOMPARE(v, other); // make sure it's still valid |
| 474 | |
| 475 | // moving |
| 476 | v = std::move(other); |
| 477 | other = std::move(v); |
| 478 | |
| 479 | // normal copying |
| 480 | other = v; |
| 481 | other = v; |
| 482 | v = other; |
| 483 | |
| 484 | |
| 485 | QCOMPARE(v.compare(other), 0); |
| 486 | QCOMPARE(v, other); |
| 487 | QVERIFY(!(v != other)); |
| 488 | QVERIFY(!(v < other)); |
| 489 | #if 0 && __has_include(<compare>) |
| 490 | QVERIFY(v <= other); |
| 491 | QVERIFY(v >= other); |
| 492 | QVERIFY(!(v > other)); |
| 493 | #endif |
| 494 | |
| 495 | if (v.isUndefined()) |
| 496 | other = nullptr; |
| 497 | else |
| 498 | other = {}; |
| 499 | QVERIFY(v.type() != other.type()); |
| 500 | QVERIFY(!(v == other)); |
| 501 | QVERIFY(v != other); |
| 502 | |
| 503 | // they're different types, so they can't compare equal |
| 504 | QVERIFY(v.compare(other) != 0); |
| 505 | QVERIFY((v < other) || (other < v)); |
| 506 | } |
| 507 | |
| 508 | void tst_QCborValue::arrayDefaultInitialization() |
| 509 | { |
| 510 | QCborArray a; |
| 511 | QVERIFY(a.isEmpty()); |
| 512 | QCOMPARE(a.size(), 0); |
| 513 | QVERIFY(!a.contains(0)); |
| 514 | QVERIFY(!a.contains(-1)); |
| 515 | QVERIFY(!a.contains(false)); |
| 516 | QVERIFY(!a.contains(true)); |
| 517 | QVERIFY(!a.contains(nullptr)); |
| 518 | QVERIFY(!a.contains({})); |
| 519 | QVERIFY(!a.contains(1.0)); |
| 520 | QVERIFY(!a.contains(QByteArray("Hello" ))); |
| 521 | QVERIFY(!a.contains("Hello" )); |
| 522 | QVERIFY(!a.contains(QCborArray())); |
| 523 | QVERIFY(!a.contains(QCborMap())); |
| 524 | QVERIFY(!a.contains(QCborValue(QDateTime::currentDateTimeUtc()))); |
| 525 | QVERIFY(!a.contains(QCborValue(QUrl("http://example.com" )))); |
| 526 | QVERIFY(!a.contains(QCborValue(QUuid::createUuid()))); |
| 527 | |
| 528 | QVERIFY(a.at(0).isUndefined()); |
| 529 | QCOMPARE(a.constBegin(), a.constEnd()); |
| 530 | |
| 531 | QVERIFY(a == a); |
| 532 | QVERIFY(a == QCborArray()); |
| 533 | QVERIFY(QCborArray() == a); |
| 534 | |
| 535 | QCborValue v(a); |
| 536 | QVERIFY(v.isArray()); |
| 537 | QVERIFY(!v.isMap()); |
| 538 | QVERIFY(!v.isTag()); |
| 539 | |
| 540 | QCborArray a2 = v.toArray(); |
| 541 | QVERIFY(a2.isEmpty()); |
| 542 | QCOMPARE(a2, a); |
| 543 | auto front = v[0]; |
| 544 | QVERIFY(front.isUndefined()); |
| 545 | front = 1; |
| 546 | QCOMPARE(v[0], 1); |
| 547 | QVERIFY(a2.isEmpty()); |
| 548 | a2 = v.toArray(); |
| 549 | QCOMPARE(a2.size(), 1); |
| 550 | } |
| 551 | |
| 552 | void tst_QCborValue::mapDefaultInitialization() |
| 553 | { |
| 554 | QCborMap m; |
| 555 | QVERIFY(m.isEmpty()); |
| 556 | QCOMPARE(m.size(), 0); |
| 557 | QVERIFY(m.keys().isEmpty()); |
| 558 | QVERIFY(!m.contains(0)); |
| 559 | QVERIFY(!m.contains(-1)); |
| 560 | QVERIFY(!m.contains(false)); |
| 561 | QVERIFY(!m.contains(true)); |
| 562 | QVERIFY(!m.contains(QCborValue::Null)); |
| 563 | QVERIFY(!m.contains({})); |
| 564 | QVERIFY(!m.contains(1.0)); |
| 565 | QVERIFY(!m.contains(QLatin1String("Hello" ))); |
| 566 | QVERIFY(!m.contains(QStringLiteral("Hello" ))); |
| 567 | QVERIFY(!m.contains(QCborValue(QByteArray("Hello" )))); |
| 568 | QVERIFY(!m.contains(QCborArray())); |
| 569 | QVERIFY(!m.contains(QCborMap())); |
| 570 | QVERIFY(!m.contains(QCborValue(QDateTime::currentDateTimeUtc()))); |
| 571 | QVERIFY(!m.contains(QCborValue(QUrl("http://example.com" )))); |
| 572 | QVERIFY(!m.contains(QCborValue(QUuid::createUuid()))); |
| 573 | |
| 574 | QVERIFY(m.value(0).isUndefined()); |
| 575 | QVERIFY(m.value(QLatin1String("Hello" )).isUndefined()); |
| 576 | QVERIFY(m.value(QStringLiteral("Hello" )).isUndefined()); |
| 577 | QVERIFY(m.value(QCborValue()).isUndefined()); |
| 578 | #if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) |
| 579 | QVERIFY(m.value("Hello" ).isUndefined()); |
| 580 | #endif |
| 581 | |
| 582 | QVERIFY(m == m); |
| 583 | QVERIFY(m == QCborMap{}); |
| 584 | QVERIFY(QCborMap{} == m); |
| 585 | |
| 586 | const QCborValue v(m); |
| 587 | QVERIFY(v.isMap()); |
| 588 | QVERIFY(!v.isArray()); |
| 589 | QVERIFY(!v.isTag()); |
| 590 | QVERIFY(v[0].isUndefined()); |
| 591 | QVERIFY(v[QLatin1String("Hello" )].isUndefined()); |
| 592 | QVERIFY(v["Hello" ].isUndefined()); |
| 593 | |
| 594 | QCborMap m2 = v.toMap(); |
| 595 | QVERIFY(m2.isEmpty()); |
| 596 | QCOMPARE(m2.size(), 0); |
| 597 | QCOMPARE(m2, m); |
| 598 | } |
| 599 | |
| 600 | void tst_QCborValue::arrayEmptyInitializerList() |
| 601 | { |
| 602 | QCborArray a{}; |
| 603 | QVERIFY(a.isEmpty()); |
| 604 | QCOMPARE(a.size(), 0); |
| 605 | QVERIFY(a == a); |
| 606 | QVERIFY(a == QCborArray()); |
| 607 | QVERIFY(QCborArray() == a); |
| 608 | } |
| 609 | |
| 610 | void tst_QCborValue::mapEmptyInitializerList() |
| 611 | { |
| 612 | QCborMap m{}; |
| 613 | QVERIFY(m.isEmpty()); |
| 614 | QCOMPARE(m.size(), 0); |
| 615 | QVERIFY(m == m); |
| 616 | QVERIFY(m == QCborMap{}); |
| 617 | QVERIFY(QCborMap{} == m); |
| 618 | } |
| 619 | |
| 620 | void tst_QCborValue::arrayEmptyDetach() |
| 621 | { |
| 622 | QCborArray a; |
| 623 | QCOMPARE(a.begin(), a.end()); |
| 624 | QVERIFY(a.isEmpty()); |
| 625 | QCOMPARE(a.size(), 0); |
| 626 | |
| 627 | QVERIFY(a == a); |
| 628 | QVERIFY(a == QCborArray()); |
| 629 | QVERIFY(QCborArray() == a); |
| 630 | |
| 631 | QCborValue v(a); |
| 632 | QVERIFY(v.isArray()); |
| 633 | QVERIFY(!v.isMap()); |
| 634 | QVERIFY(!v.isTag()); |
| 635 | |
| 636 | QCborArray a2 = v.toArray(); |
| 637 | QVERIFY(a2.isEmpty()); |
| 638 | QCOMPARE(a2, a); |
| 639 | } |
| 640 | |
| 641 | void tst_QCborValue::mapEmptyDetach() |
| 642 | { |
| 643 | QCborMap m; |
| 644 | QCOMPARE(m.begin(), m.end()); |
| 645 | QVERIFY(m.isEmpty()); |
| 646 | QCOMPARE(m.size(), 0); |
| 647 | |
| 648 | QVERIFY(m == m); |
| 649 | QVERIFY(m == QCborMap{}); |
| 650 | QVERIFY(QCborMap{} == m); |
| 651 | |
| 652 | QCborValue v(m); |
| 653 | QVERIFY(v.isMap()); |
| 654 | QVERIFY(!v.isArray()); |
| 655 | QVERIFY(!v.isTag()); |
| 656 | |
| 657 | QCborMap m2 = v.toMap(); |
| 658 | QVERIFY(m2.isEmpty()); |
| 659 | QCOMPARE(m2, m); |
| 660 | } |
| 661 | |
| 662 | void tst_QCborValue::arrayNonEmptyDetach() |
| 663 | { |
| 664 | QCborArray a; |
| 665 | a.append(value: 1); |
| 666 | a.append(value: 2); |
| 667 | |
| 668 | QCOMPARE(a.first(), 1); |
| 669 | QCOMPARE(a.last(), 2); |
| 670 | QVERIFY(!a.contains(3)); |
| 671 | QVERIFY(a.constBegin() != a.constEnd()); |
| 672 | QVERIFY(a.begin() != a.end()); |
| 673 | |
| 674 | // now the same, with an active copy |
| 675 | { QCborArray copy(a); QCOMPARE(a.first(), 1); } |
| 676 | { QCborArray copy(a); QCOMPARE(a.last(), 2); } |
| 677 | { QCborArray copy(a); QVERIFY(!a.contains(3)); } |
| 678 | { QCborArray copy(a); QVERIFY(a.constBegin() != a.constEnd()); } |
| 679 | { QCborArray copy(a); QVERIFY(a.begin() != a.end()); } |
| 680 | } |
| 681 | |
| 682 | void tst_QCborValue::mapNonEmptyDetach() |
| 683 | { |
| 684 | QCborMap m; |
| 685 | m.insert(key: 1, value_: {}); |
| 686 | m.insert(key: 2, value_: nullptr); |
| 687 | QVERIFY(!m.contains(3)); |
| 688 | QVERIFY(m.constBegin() != m.constEnd()); |
| 689 | QVERIFY(m.begin() != m.end()); |
| 690 | // test all 4 overloads of find() |
| 691 | QVERIFY(m.constFind(3) == m.constEnd()); |
| 692 | QVERIFY(m.constFind(QLatin1String("3" )) == m.constEnd()); |
| 693 | QVERIFY(m.constFind(QString("3" )) == m.constEnd()); |
| 694 | QVERIFY(m.constFind(QCborValue(3)) == m.constEnd()); |
| 695 | QVERIFY(m.find(3) == m.end()); |
| 696 | QVERIFY(m.find(QLatin1String("3" )) == m.end()); |
| 697 | QVERIFY(m.find(QString("3" )) == m.end()); |
| 698 | QVERIFY(m.find(QCborValue(3)) == m.end()); |
| 699 | { auto it = m.find(key: 3); QVERIFY(it == m.end()); } |
| 700 | { auto it = m.find(key: QLatin1String("3" )); QVERIFY(it == m.end()); } |
| 701 | { auto it = m.find(key: QString("3" )); QVERIFY(it == m.end()); } |
| 702 | { auto it = m.find(key: QCborValue(3)); QVERIFY(it == m.end()); } |
| 703 | |
| 704 | // now the same, with an active copy |
| 705 | { QCborMap copy(m); QVERIFY(!m.contains(3)); } |
| 706 | { QCborMap copy(m); QVERIFY(m.constBegin() != m.constEnd()); } |
| 707 | { QCborMap copy(m); QVERIFY(m.begin() != m.end()); } |
| 708 | { QCborMap copy(m); QVERIFY(m.constFind(3) == m.constEnd()); } |
| 709 | { QCborMap copy(m); QVERIFY(m.constFind(QLatin1String("3" )) == m.constEnd()); } |
| 710 | { QCborMap copy(m); QVERIFY(m.constFind(QString("3" )) == m.constEnd()); } |
| 711 | { QCborMap copy(m); QVERIFY(m.constFind(QCborValue(3)) == m.constEnd()); } |
| 712 | { QCborMap copy(m); QVERIFY(m.find(3) == m.end()); } |
| 713 | { QCborMap copy(m); QVERIFY(m.find(QLatin1String("3" )) == m.end()); } |
| 714 | { QCborMap copy(m); QVERIFY(m.find(QString("3" )) == m.end()); } |
| 715 | { QCborMap copy(m); QVERIFY(m.find(QCborValue(3)) == m.end()); }\ |
| 716 | { QCborMap copy(m); auto it = m.find(key: 3); QVERIFY(it == m.end()); } |
| 717 | { QCborMap copy(m); auto it = m.find(key: QLatin1String("3" )); QVERIFY(it == m.end()); } |
| 718 | { QCborMap copy(m); auto it = m.find(key: QString("3" )); QVERIFY(it == m.end()); } |
| 719 | { QCborMap copy(m); auto it = m.find(key: QCborValue(3)); QVERIFY(it == m.end()); } |
| 720 | } |
| 721 | |
| 722 | void tst_QCborValue::arrayInitializerList() |
| 723 | { |
| 724 | QCborArray a{0, -1, false, true, nullptr, {}, 1.0}; |
| 725 | QVERIFY(!a.isEmpty()); |
| 726 | QCOMPARE(a.size(), 7); |
| 727 | QCOMPARE(a.at(0), QCborValue(0)); |
| 728 | QCOMPARE(a.at(1), QCborValue(-1)); |
| 729 | QCOMPARE(a.at(2), QCborValue(QCborValue::False)); |
| 730 | QCOMPARE(a.at(3), QCborValue(QCborValue::True)); |
| 731 | QCOMPARE(a.at(4), QCborValue(QCborValue::Null)); |
| 732 | QCOMPARE(a.at(5), QCborValue(QCborValue::Undefined)); |
| 733 | QCOMPARE(a.at(6), QCborValue(1.0)); |
| 734 | |
| 735 | QVERIFY(a == a); |
| 736 | QVERIFY(a != QCborArray{}); |
| 737 | QVERIFY(QCborArray{} != a); |
| 738 | QVERIFY(a == QCborArray({0, -1, false, true, nullptr, {}, 1.0})); |
| 739 | |
| 740 | QCborValue v = a; |
| 741 | QCOMPARE(v[0], QCborValue(0)); |
| 742 | QCOMPARE(v[1], QCborValue(-1)); |
| 743 | QCOMPARE(v[2], QCborValue(QCborValue::False)); |
| 744 | QCOMPARE(v[3], QCborValue(QCborValue::True)); |
| 745 | QCOMPARE(v[4], QCborValue(QCborValue::Null)); |
| 746 | QCOMPARE(v[5], QCborValue(QCborValue::Undefined)); |
| 747 | QCOMPARE(v[6], QCborValue(1.0)); |
| 748 | |
| 749 | QVERIFY(a.contains(0)); |
| 750 | QVERIFY(a.contains(-1)); |
| 751 | QVERIFY(a.contains(false)); |
| 752 | QVERIFY(a.contains(true)); |
| 753 | QVERIFY(a.contains(nullptr)); |
| 754 | QVERIFY(a.contains({})); |
| 755 | QVERIFY(a.contains(1.0)); |
| 756 | QVERIFY(!a.contains(QByteArray("Hello" ))); |
| 757 | QVERIFY(!a.contains("Hello" )); |
| 758 | QVERIFY(!a.contains(QCborArray())); |
| 759 | QVERIFY(!a.contains(QCborMap())); |
| 760 | QVERIFY(!a.contains(QCborValue(QDateTime::currentDateTimeUtc()))); |
| 761 | QVERIFY(!a.contains(QCborValue(QUrl("http://example.com" )))); |
| 762 | QVERIFY(!a.contains(QCborValue(QUuid::createUuid()))); |
| 763 | |
| 764 | // iterators |
| 765 | auto it = a.constBegin(); |
| 766 | auto end = a.constEnd(); |
| 767 | QCOMPARE(end - it, 7); |
| 768 | QCOMPARE(it + 7, end); |
| 769 | QVERIFY(it->isInteger()); |
| 770 | QCOMPARE(*it, QCborValue(0)); |
| 771 | QCOMPARE(it[1], QCborValue(-1)); |
| 772 | QCOMPARE(*(it + 2), QCborValue(false)); |
| 773 | it += 3; |
| 774 | QCOMPARE(*it, QCborValue(true)); |
| 775 | ++it; |
| 776 | QCOMPARE(*it, QCborValue(nullptr)); |
| 777 | it++; |
| 778 | QCOMPARE(*it, QCborValue()); |
| 779 | --end; |
| 780 | QCOMPARE(*end, QCborValue(1.0)); |
| 781 | end--; |
| 782 | QCOMPARE(it, end); |
| 783 | |
| 784 | // range for |
| 785 | int i = 0; |
| 786 | for (const QCborValue &v : qAsConst(t&: a)) { |
| 787 | QVERIFY(!v.isInvalid()); |
| 788 | QCOMPARE(v.isUndefined(), i == 5); // 6th element is Undefined |
| 789 | ++i; |
| 790 | } |
| 791 | QCOMPARE(i, a.size()); |
| 792 | } |
| 793 | |
| 794 | void tst_QCborValue::mapSimpleInitializerList() |
| 795 | { |
| 796 | QCborMap m{{0, 0}, {1, 0}, {2, "Hello" }, {"Hello" , 2}, {3, QLatin1String("World" )}, {QLatin1String("World" ), 3}}; |
| 797 | QCOMPARE(m.size(), 6); |
| 798 | QVERIFY(m == m); |
| 799 | QVERIFY(m != QCborMap{}); |
| 800 | QVERIFY(QCborMap{} != m); |
| 801 | QVERIFY(m == QCborMap({{0, 0}, {1, 0}, {2, "Hello" }, {"Hello" , 2}, {3, QLatin1String("World" )}, {QLatin1String("World" ), 3}})); |
| 802 | |
| 803 | QCborValue vmap = m; |
| 804 | { |
| 805 | QVERIFY(m.contains(0)); |
| 806 | QCborValue v = m.value(key: 0); |
| 807 | QVERIFY(v.isInteger()); |
| 808 | QCOMPARE(v.toInteger(), 0); |
| 809 | QCOMPARE(vmap[0], v); |
| 810 | } |
| 811 | { |
| 812 | QVERIFY(m.contains(1)); |
| 813 | QCborValue v = m.value(key: 1); |
| 814 | QVERIFY(v.isInteger()); |
| 815 | QCOMPARE(v.toInteger(), 0); |
| 816 | QCOMPARE(vmap[1], v); |
| 817 | } |
| 818 | { |
| 819 | QVERIFY(m.contains(2)); |
| 820 | QCborValue v = m.value(key: 2); |
| 821 | QVERIFY(v.isString()); |
| 822 | QCOMPARE(v.toString(), "Hello" ); |
| 823 | QCOMPARE(vmap[2], v); |
| 824 | } |
| 825 | { |
| 826 | QVERIFY(m.contains(3)); |
| 827 | QCborValue v = m.value(key: 3); |
| 828 | QVERIFY(v.isString()); |
| 829 | QCOMPARE(v.toString(), "World" ); |
| 830 | QCOMPARE(vmap[3], v); |
| 831 | } |
| 832 | { |
| 833 | QVERIFY(m.contains(QStringLiteral("Hello" ))); |
| 834 | QCborValue v = m.value(key: QLatin1String("Hello" )); |
| 835 | QVERIFY(v.isInteger()); |
| 836 | QCOMPARE(v.toInteger(), 2); |
| 837 | QCOMPARE(vmap[QStringLiteral("Hello" )], v); |
| 838 | } |
| 839 | { |
| 840 | QVERIFY(m.contains(QLatin1String("World" ))); |
| 841 | QCborValue v = m.value(QStringLiteral("World" )); |
| 842 | QVERIFY(v.isInteger()); |
| 843 | QCOMPARE(v.toInteger(), 3); |
| 844 | QCOMPARE(vmap[QLatin1String("World" )], v); |
| 845 | } |
| 846 | |
| 847 | QVERIFY(!m.contains(QCborValue::Null)); |
| 848 | QVERIFY(!m.contains(QCborValue())); |
| 849 | QVERIFY(!m.contains(QCborValue(1.0))); // Important: 1.0 does not match 1 |
| 850 | QVERIFY(!m.contains(QCborValue(QByteArray("Hello" )))); |
| 851 | QVERIFY(!m.contains(QCborArray())); |
| 852 | QVERIFY(!m.contains(QCborMap())); |
| 853 | QVERIFY(!m.contains(QCborValue(QDateTime::currentDateTimeUtc()))); |
| 854 | QVERIFY(!m.contains(QCborValue(QUrl("http://example.com" )))); |
| 855 | QVERIFY(!m.contains(QCborValue(QUuid::createUuid()))); |
| 856 | |
| 857 | // iterators (QCborMap is not sorted) |
| 858 | auto it = m.constBegin(); |
| 859 | auto end = m.constEnd(); |
| 860 | QCOMPARE(end - it, 6); |
| 861 | QCOMPARE(it + 6, end); |
| 862 | QCOMPARE(it.key(), QCborValue(0)); |
| 863 | QCOMPARE(it.value(), QCborValue(0)); |
| 864 | QVERIFY(it->isInteger()); |
| 865 | ++it; |
| 866 | QCOMPARE(it.key(), QCborValue(1)); |
| 867 | QCOMPARE(it.value(), QCborValue(0)); |
| 868 | QCOMPARE((it + 1).key(), QCborValue(2)); |
| 869 | QVERIFY((it + 1)->isString()); |
| 870 | QCOMPARE((it + 1)->toString(), "Hello" ); |
| 871 | it += 2; |
| 872 | QCOMPARE(it.key(), QCborValue("Hello" )); |
| 873 | QVERIFY(it->isInteger()); |
| 874 | it++; |
| 875 | QCOMPARE(it.key(), QCborValue(3)); |
| 876 | QVERIFY(it->isString()); |
| 877 | QCOMPARE(it.value().toString(), "World" ); |
| 878 | --end; |
| 879 | QCOMPARE(end.key(), QCborValue("World" )); |
| 880 | QCOMPARE(end.value(), QCborValue(3)); |
| 881 | end--; |
| 882 | QCOMPARE(it, end); |
| 883 | |
| 884 | // range for |
| 885 | int i = 0; |
| 886 | for (auto pair : qAsConst(t&: m)) { |
| 887 | QVERIFY(!pair.first.isUndefined()); |
| 888 | QVERIFY(!pair.second.isUndefined()); |
| 889 | ++i; |
| 890 | } |
| 891 | QCOMPARE(i, m.size()); |
| 892 | } |
| 893 | |
| 894 | void tst_QCborValue::arrayMutation() |
| 895 | { |
| 896 | QCborArray a{42}; |
| 897 | { |
| 898 | QCborValueRef v = a[0]; |
| 899 | QVERIFY(!a.isEmpty()); |
| 900 | QVERIFY(v.isInteger()); |
| 901 | QCOMPARE(v.toInteger(), 42); |
| 902 | |
| 903 | // now mutate the list |
| 904 | v = true; |
| 905 | QVERIFY(v.isBool()); |
| 906 | QVERIFY(v.isTrue()); |
| 907 | QVERIFY(a.at(0).isTrue()); |
| 908 | QVERIFY(a.at(0) == v); |
| 909 | QVERIFY(v == a.at(0)); |
| 910 | } |
| 911 | |
| 912 | QVERIFY(a == a); |
| 913 | QVERIFY(a == QCborArray{true}); |
| 914 | |
| 915 | QCborArray a2 = a; |
| 916 | a.append(value: nullptr); |
| 917 | QCOMPARE(a.size(), 2); |
| 918 | QCOMPARE(a2.size(), 1); |
| 919 | |
| 920 | // self-insertion |
| 921 | a2.append(value: a2); |
| 922 | QCOMPARE(a2.size(), 2); |
| 923 | QCOMPARE(a2.last().toArray().size(), 1); |
| 924 | |
| 925 | QCborValueRef v = a[0]; |
| 926 | QVERIFY(v.isTrue()); |
| 927 | v = 2.5; |
| 928 | QVERIFY(v.isDouble()); |
| 929 | QVERIFY(a.first().isDouble()); |
| 930 | QVERIFY(a.last().isNull()); |
| 931 | QVERIFY(a2.first().isTrue()); |
| 932 | |
| 933 | a2 = a; |
| 934 | auto it = a.begin(); // detaches again |
| 935 | auto end = a.end(); |
| 936 | QCOMPARE(end - it, 2); |
| 937 | QCOMPARE(it + 2, end); |
| 938 | QCOMPARE(*it, QCborValue(2.5)); |
| 939 | QCOMPARE(*++it, QCborValue(nullptr)); |
| 940 | QVERIFY(a2 == a); |
| 941 | QVERIFY(a == a2); |
| 942 | |
| 943 | *it = -1; |
| 944 | QCOMPARE(*it, QCborValue(-1)); |
| 945 | QCOMPARE(a.at(1), QCborValue(-1)); |
| 946 | QCOMPARE(a2.at(1), QCborValue(nullptr)); |
| 947 | QCOMPARE(++it, end); |
| 948 | |
| 949 | // Array accessed via value: |
| 950 | QCborValue val(a); |
| 951 | val[2] = QCborArray{2, 3, 5, 7}; |
| 952 | QCOMPARE(a.size(), 2); // Unchanged |
| 953 | QVERIFY(val.isArray()); |
| 954 | QCOMPARE(val.toArray().size(), 3); |
| 955 | val[2][4] = 17; |
| 956 | QVERIFY(val.isArray()); |
| 957 | QVERIFY(val[2].isArray()); |
| 958 | QCOMPARE(val[2].toArray().size(), 5); |
| 959 | QCOMPARE(val[2][4], 17); |
| 960 | QCOMPARE(val.toArray().size(), 3); |
| 961 | val[3] = 42; |
| 962 | QVERIFY(val.isArray()); |
| 963 | QCOMPARE(val.toArray().size(), 4); |
| 964 | QCOMPARE(val[3], 42); |
| 965 | |
| 966 | // Coerce to map on string key: |
| 967 | const QLatin1String any("any" ); |
| 968 | val[any] = any; |
| 969 | QVERIFY(val.isMap()); |
| 970 | QCOMPARE(val.toMap().size(), 5); |
| 971 | QVERIFY(val[2].isArray()); |
| 972 | QCOMPARE(val[2].toArray().size(), 5); |
| 973 | } |
| 974 | |
| 975 | void tst_QCborValue::arrayMutateWithCopies() |
| 976 | { |
| 977 | { |
| 978 | QCborArray array; |
| 979 | array.append(value: "TEST" ); |
| 980 | QCOMPARE(array.size(), 1); |
| 981 | QCOMPARE(array.at(0), "TEST" ); |
| 982 | |
| 983 | array.append(value: array.at(i: 0)); |
| 984 | QCOMPARE(array.size(), 2); |
| 985 | QCOMPARE(array.at(0), "TEST" ); |
| 986 | QCOMPARE(array.at(1), "TEST" ); |
| 987 | } |
| 988 | { |
| 989 | QCborArray array; |
| 990 | array.append(value: "TEST" ); |
| 991 | QCOMPARE(array.size(), 1); |
| 992 | QCOMPARE(array.at(0), "TEST" ); |
| 993 | |
| 994 | // same as previous, but with prepend() not append() |
| 995 | array.prepend(value: array.at(i: 0)); |
| 996 | QCOMPARE(array.size(), 2); |
| 997 | QCOMPARE(array.at(0), "TEST" ); |
| 998 | QCOMPARE(array.at(1), "TEST" ); |
| 999 | } |
| 1000 | { |
| 1001 | QCborArray array; |
| 1002 | array.append(value: "TEST" ); |
| 1003 | QCOMPARE(array.size(), 1); |
| 1004 | QCOMPARE(array.at(0), "TEST" ); |
| 1005 | |
| 1006 | // same as previous, but using a QCborValueRef |
| 1007 | QCborValueRef rv = array[0]; |
| 1008 | array.prepend(value: rv); |
| 1009 | QCOMPARE(array.size(), 2); |
| 1010 | QCOMPARE(array.at(0), "TEST" ); |
| 1011 | QCOMPARE(array.at(1), "TEST" ); |
| 1012 | } |
| 1013 | { |
| 1014 | QCborArray array; |
| 1015 | array.append(value: "TEST" ); |
| 1016 | QCOMPARE(array.size(), 1); |
| 1017 | QCOMPARE(array.at(0), "TEST" ); |
| 1018 | |
| 1019 | // same as previous, but now extending the array |
| 1020 | QCborValueRef rv = array[0]; |
| 1021 | array[2] = rv; |
| 1022 | QCOMPARE(array.size(), 3); |
| 1023 | QCOMPARE(array.at(0), "TEST" ); |
| 1024 | QCOMPARE(array.at(2), "TEST" ); |
| 1025 | } |
| 1026 | } |
| 1027 | |
| 1028 | void tst_QCborValue::mapMutation() |
| 1029 | { |
| 1030 | QCborMap m; |
| 1031 | QVERIFY(m.isEmpty()); |
| 1032 | |
| 1033 | { |
| 1034 | QCborValueRef v = m[42]; |
| 1035 | QCOMPARE(m.size(), 1); |
| 1036 | QVERIFY(v.isUndefined()); |
| 1037 | |
| 1038 | // now mutate the list |
| 1039 | // simple -> HasByteData |
| 1040 | const QString strValue = QStringLiteral("value" ); |
| 1041 | v = strValue; |
| 1042 | QVERIFY(v.isString()); |
| 1043 | QCOMPARE(v, QCborValue(strValue)); |
| 1044 | QCOMPARE(m, QCborMap({{42, strValue}})); |
| 1045 | |
| 1046 | // HasByteData -> HasByteData |
| 1047 | const QLatin1String otherStrValue("othervalue" ); |
| 1048 | v = otherStrValue; |
| 1049 | QVERIFY(v.isString()); |
| 1050 | QCOMPARE(v, QCborValue(otherStrValue)); |
| 1051 | QCOMPARE(m, QCborMap({{42, otherStrValue}})); |
| 1052 | |
| 1053 | // HasByteData -> simple |
| 1054 | v = 42; |
| 1055 | QVERIFY(v.isInteger()); |
| 1056 | QCOMPARE(v, QCborValue(42)); |
| 1057 | QCOMPARE(m, QCborMap({{42, 42}})); |
| 1058 | |
| 1059 | // simple -> container |
| 1060 | v = QCborArray{1, 2, 3}; |
| 1061 | QVERIFY(v.isArray()); |
| 1062 | QCOMPARE(v, QCborArray({1, 2, 3})); |
| 1063 | QCOMPARE(m, QCborMap({{42, QCborArray{1, 2, 3}}})); |
| 1064 | |
| 1065 | // container -> simple |
| 1066 | v = true; |
| 1067 | QVERIFY(v.isBool()); |
| 1068 | QVERIFY(v.isTrue()); |
| 1069 | QCOMPARE(m, QCborMap({{42, true}})); |
| 1070 | QVERIFY(m.begin()->isTrue()); |
| 1071 | QVERIFY(m.begin().value() == v); |
| 1072 | QVERIFY(v == m.begin().value()); |
| 1073 | } |
| 1074 | |
| 1075 | QVERIFY(m == QCborMap({{42, true}})); |
| 1076 | QVERIFY(QCborMap({{42, true}}) == m); |
| 1077 | |
| 1078 | QCborMap m2 = m; |
| 1079 | m.insert(v: {nullptr, nullptr}); |
| 1080 | QCOMPARE(m.size(), 2); |
| 1081 | QCOMPARE(m2.size(), 1); |
| 1082 | |
| 1083 | QCborValueRef v = m[42]; |
| 1084 | QVERIFY(v.isTrue()); |
| 1085 | v = 2.5; |
| 1086 | QVERIFY(v.isDouble()); |
| 1087 | QVERIFY(m.begin()->isDouble()); |
| 1088 | QVERIFY((m.end() - 1)->isNull()); |
| 1089 | QVERIFY(m2.begin()->isTrue()); |
| 1090 | |
| 1091 | m2 = m; |
| 1092 | auto it = m.begin(); // detaches again |
| 1093 | auto end = m.end(); |
| 1094 | QCOMPARE(end - it, 2); |
| 1095 | QCOMPARE(it + 2, end); |
| 1096 | QCOMPARE(it.key(), QCborValue(42)); |
| 1097 | QCOMPARE(it.value(), QCborValue(2.5)); |
| 1098 | QCOMPARE((++it).value(), QCborValue(nullptr)); |
| 1099 | QCOMPARE(it.key(), QCborValue(nullptr)); |
| 1100 | QVERIFY(m2 == m); |
| 1101 | QVERIFY(m == m2); |
| 1102 | |
| 1103 | it.value() = -1; |
| 1104 | QCOMPARE(it.key(), QCborValue(nullptr)); |
| 1105 | QCOMPARE(it.value(), QCborValue(-1)); |
| 1106 | QCOMPARE((m.end() - 1)->toInteger(), -1); |
| 1107 | QVERIFY((m2.end() - 1)->isNull()); |
| 1108 | QCOMPARE(++it, end); |
| 1109 | |
| 1110 | // Map accessed via value: |
| 1111 | QCborValue val(m); |
| 1112 | val[7] = QCborMap({{0, 2}, {1, 3}, {2, 5}}); |
| 1113 | QCOMPARE(m.size(), 2); // Unchanged |
| 1114 | QVERIFY(val.isMap()); |
| 1115 | QCOMPARE(val.toMap().size(), 3); |
| 1116 | val[7][3] = 11; |
| 1117 | QVERIFY(val.isMap()); |
| 1118 | QVERIFY(val[7].isMap()); |
| 1119 | QCOMPARE(val[7].toMap().size(), 4); |
| 1120 | val[14] = 42; |
| 1121 | QVERIFY(val.isMap()); |
| 1122 | QCOMPARE(val.toMap().size(), 4); |
| 1123 | |
| 1124 | const QLatin1String any("any" ); |
| 1125 | const QString hello(QStringLiteral("Hello World" )); |
| 1126 | val[any][3][hello] = any; |
| 1127 | QVERIFY(val.isMap()); |
| 1128 | QCOMPARE(val.toMap().size(), 5); |
| 1129 | QVERIFY(val[any].isMap()); |
| 1130 | QCOMPARE(val[any].toMap().size(), 1); |
| 1131 | QVERIFY(val[any][3].isMap()); |
| 1132 | QCOMPARE(val[any][3].toMap().size(), 1); |
| 1133 | } |
| 1134 | |
| 1135 | void tst_QCborValue::mapMutateWithCopies() |
| 1136 | { |
| 1137 | { |
| 1138 | QCborMap map; |
| 1139 | map[QLatin1String("prop1" )] = "TEST" ; |
| 1140 | QCOMPARE(map.size(), 1); |
| 1141 | QCOMPARE(map.value("prop1" ), "TEST" ); |
| 1142 | |
| 1143 | map[QLatin1String("prop2" )] = map.value(key: "prop1" ); |
| 1144 | QCOMPARE(map.size(), 2); |
| 1145 | QCOMPARE(map.value("prop1" ), "TEST" ); |
| 1146 | QCOMPARE(map.value("prop2" ), "TEST" ); |
| 1147 | } |
| 1148 | { |
| 1149 | // see QTBUG-83366 |
| 1150 | QCborMap map; |
| 1151 | map[QLatin1String("value" )] = "TEST" ; |
| 1152 | QCOMPARE(map.size(), 1); |
| 1153 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1154 | |
| 1155 | QCborValue v = map.value(key: "value" ); |
| 1156 | map[QLatin1String("prop2" )] = v; |
| 1157 | QCOMPARE(map.size(), 2); |
| 1158 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1159 | QCOMPARE(map.value("prop2" ), "TEST" ); |
| 1160 | } |
| 1161 | { |
| 1162 | QCborMap map; |
| 1163 | map[QLatin1String("value" )] = "TEST" ; |
| 1164 | QCOMPARE(map.size(), 1); |
| 1165 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1166 | |
| 1167 | // same as previous, but this is a QJsonValueRef |
| 1168 | QCborValueRef rv = map[QLatin1String("prop2" )]; |
| 1169 | rv = map[QLatin1String("value" )]; |
| 1170 | QCOMPARE(map.size(), 2); |
| 1171 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1172 | QCOMPARE(map.value("prop2" ), "TEST" ); |
| 1173 | } |
| 1174 | { |
| 1175 | QCborMap map; |
| 1176 | map[QLatin1String("value" )] = "TEST" ; |
| 1177 | QCOMPARE(map.size(), 1); |
| 1178 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1179 | |
| 1180 | // same as previous, but now we call the operator[] that reallocates |
| 1181 | // after we create the source QCborValueRef |
| 1182 | QCborValueRef rv = map[QLatin1String("value" )]; |
| 1183 | map[QLatin1String("prop2" )] = rv; |
| 1184 | QCOMPARE(map.size(), 2); |
| 1185 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1186 | QCOMPARE(map.value("prop2" ), "TEST" ); |
| 1187 | } |
| 1188 | { |
| 1189 | QCborMap map; |
| 1190 | map[QLatin1String("value" )] = "TEST" ; |
| 1191 | QCOMPARE(map.size(), 1); |
| 1192 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1193 | |
| 1194 | QCborValueRef v = map[QLatin1String("value" )]; |
| 1195 | QCborMap map2 = map; |
| 1196 | map.insert(key: QLatin1String("prop2" ), value_: v); |
| 1197 | QCOMPARE(map.size(), 2); |
| 1198 | QCOMPARE(map.value("value" ), "TEST" ); |
| 1199 | QCOMPARE(map.value("prop2" ), "TEST" ); |
| 1200 | QCOMPARE(map2.size(), 1); |
| 1201 | QCOMPARE(map2.value("value" ), "TEST" ); |
| 1202 | } |
| 1203 | } |
| 1204 | |
| 1205 | void tst_QCborValue::arrayPrepend() |
| 1206 | { |
| 1207 | QCborArray a; |
| 1208 | a.prepend(value: 0); |
| 1209 | a.prepend(value: nullptr); |
| 1210 | QCOMPARE(a.at(1), QCborValue(0)); |
| 1211 | QCOMPARE(a.at(0), QCborValue(nullptr)); |
| 1212 | QCOMPARE(a.size(), 2); |
| 1213 | } |
| 1214 | |
| 1215 | void tst_QCborValue::arrayInsertRemove() |
| 1216 | { |
| 1217 | QFETCH(QCborValue, v); |
| 1218 | QCborArray a; |
| 1219 | a.append(value: 42); |
| 1220 | a.append(value: v); |
| 1221 | a.insert(i: 1, value: QCborValue(nullptr)); |
| 1222 | QCOMPARE(a.at(0), QCborValue(42)); |
| 1223 | QCOMPARE(a.at(1), QCborValue(nullptr)); |
| 1224 | QCOMPARE(a.at(2), v); |
| 1225 | |
| 1226 | // remove 42 |
| 1227 | a.removeAt(i: 0); |
| 1228 | QCOMPARE(a.size(), 2); |
| 1229 | QCOMPARE(a.at(0), QCborValue(nullptr)); |
| 1230 | QCOMPARE(a.at(1), v); |
| 1231 | |
| 1232 | auto it = a.begin(); |
| 1233 | it = a.erase(it); // removes nullptr |
| 1234 | QCOMPARE(a.size(), 1); |
| 1235 | QCOMPARE(a.at(0), v); |
| 1236 | |
| 1237 | it = a.erase(it); |
| 1238 | QVERIFY(a.isEmpty()); |
| 1239 | QCOMPARE(it, a.end()); |
| 1240 | |
| 1241 | // reinsert the element so we can take it |
| 1242 | a.append(value: v); |
| 1243 | QCOMPARE(a.takeAt(0), v); |
| 1244 | QVERIFY(a.isEmpty()); |
| 1245 | } |
| 1246 | |
| 1247 | void tst_QCborValue::arrayStringElements() |
| 1248 | { |
| 1249 | QCborArray a{"Hello" }; |
| 1250 | a.append(value: QByteArray("Hello" )); |
| 1251 | a.append(value: QLatin1String("World" )); |
| 1252 | QVERIFY(a == a); |
| 1253 | QVERIFY(a == QCborArray({QLatin1String("Hello" ), |
| 1254 | QByteArray("Hello" ), QStringLiteral("World" )})); |
| 1255 | |
| 1256 | QCborValueRef r1 = a[0]; |
| 1257 | QCOMPARE(r1.toString(), "Hello" ); |
| 1258 | QCOMPARE(r1.operator QCborValue(), QCborValue("Hello" )); |
| 1259 | QVERIFY(r1 == QCborValue("Hello" )); |
| 1260 | |
| 1261 | QCborValue v2 = a.at(i: 1); |
| 1262 | QCOMPARE(v2.toByteArray(), QByteArray("Hello" )); |
| 1263 | QCOMPARE(v2, QCborValue(QByteArray("Hello" ))); |
| 1264 | |
| 1265 | // v2 must continue to be valid after the entry getting removed |
| 1266 | a.removeAt(i: 1); |
| 1267 | QCOMPARE(v2.toByteArray(), QByteArray("Hello" )); |
| 1268 | QCOMPARE(v2, QCborValue(QByteArray("Hello" ))); |
| 1269 | |
| 1270 | v2 = a.at(i: 1); |
| 1271 | QCOMPARE(v2.toString(), "World" ); |
| 1272 | QCOMPARE(v2, QCborValue("World" )); |
| 1273 | |
| 1274 | QCOMPARE(a.takeAt(1).toString(), "World" ); |
| 1275 | QCOMPARE(a.takeAt(0).toString(), "Hello" ); |
| 1276 | QVERIFY(a.isEmpty()); |
| 1277 | } |
| 1278 | |
| 1279 | void tst_QCborValue::mapStringValues() |
| 1280 | { |
| 1281 | QCborMap m{{0, "Hello" }}; |
| 1282 | m.insert(v: {1, QByteArray("Hello" )}); |
| 1283 | m.insert(v: {2, QLatin1String("World" )}); |
| 1284 | QVERIFY(m == m); |
| 1285 | |
| 1286 | QCborValueRef r1 = m[0]; |
| 1287 | QCOMPARE(r1.toString(), "Hello" ); |
| 1288 | QCOMPARE(r1.operator QCborValue(), QCborValue("Hello" )); |
| 1289 | QVERIFY(r1 == QCborValue("Hello" )); |
| 1290 | |
| 1291 | QCborValue v2 = m.value(key: 1); |
| 1292 | QCOMPARE(v2.toByteArray(), QByteArray("Hello" )); |
| 1293 | QCOMPARE(v2, QCborValue(QByteArray("Hello" ))); |
| 1294 | |
| 1295 | // v2 must continue to be valid after the entry getting removed |
| 1296 | m.erase(it: m.constFind(key: 1)); |
| 1297 | QCOMPARE(v2.toByteArray(), QByteArray("Hello" )); |
| 1298 | QCOMPARE(v2, QCborValue(QByteArray("Hello" ))); |
| 1299 | |
| 1300 | v2 = (m.begin() + 1).value(); |
| 1301 | QCOMPARE(v2.toString(), "World" ); |
| 1302 | QCOMPARE(v2, QCborValue("World" )); |
| 1303 | |
| 1304 | QCOMPARE(m.extract(m.begin() + 1).toString(), "World" ); |
| 1305 | QCOMPARE(m.take(0).toString(), "Hello" ); |
| 1306 | QVERIFY(m.isEmpty()); |
| 1307 | } |
| 1308 | |
| 1309 | void tst_QCborValue::mapStringKeys() |
| 1310 | { |
| 1311 | QCborMap m{{QLatin1String("Hello" ), 1}, {QStringLiteral("World" ), 2}}; |
| 1312 | QCOMPARE(m.value(QStringLiteral("Hello" )), QCborValue(1)); |
| 1313 | QCOMPARE(m.value(QLatin1String("World" )), QCborValue(2)); |
| 1314 | |
| 1315 | QCborMap m2 = m; |
| 1316 | QVERIFY(m2 == m); |
| 1317 | QVERIFY(m == m2); |
| 1318 | |
| 1319 | m.insert(v: {QByteArray("foo" ), "bar" }); |
| 1320 | QCOMPARE(m.size(), 3); |
| 1321 | QCOMPARE(m2.size(), 2); |
| 1322 | QVERIFY(m2 != m); |
| 1323 | QVERIFY(m != m2); |
| 1324 | |
| 1325 | QVERIFY(m2.value(QCborValue(QByteArray("foo" ))).isUndefined()); |
| 1326 | QVERIFY(m.value(QCborValue(QLatin1String("foo" ))).isUndefined()); |
| 1327 | QCOMPARE(m.value(QCborValue(QByteArray("foo" ))).toString(), "bar" ); |
| 1328 | } |
| 1329 | |
| 1330 | void tst_QCborValue::mapInsertRemove() |
| 1331 | { |
| 1332 | QFETCH(QCborValue, v); |
| 1333 | QCborMap m{{1, v}}; |
| 1334 | |
| 1335 | m.remove(key: 1); |
| 1336 | QVERIFY(m.isEmpty()); |
| 1337 | QVERIFY(!m.contains(1)); |
| 1338 | |
| 1339 | m.insert(key: 2, value_: v); |
| 1340 | QVERIFY(m.contains(2)); |
| 1341 | QVERIFY(m[2] == v); |
| 1342 | QVERIFY(v == m[2]); |
| 1343 | |
| 1344 | auto it = m.find(key: 2); |
| 1345 | it = m.erase(it); |
| 1346 | QVERIFY(m.isEmpty()); |
| 1347 | |
| 1348 | // creates m[2] and m[42] just by referencing them |
| 1349 | m[2]; |
| 1350 | QCborValueRef r = m[42]; |
| 1351 | QCOMPARE(m.size(), 2); |
| 1352 | |
| 1353 | r = v; |
| 1354 | it = m.find(key: 42); |
| 1355 | QVERIFY(it.value() == v); |
| 1356 | QVERIFY(v == it.value()); |
| 1357 | QVERIFY(it.value() == r); |
| 1358 | QVERIFY(r == it.value()); |
| 1359 | |
| 1360 | QCOMPARE(m.extract(it), v); |
| 1361 | QVERIFY(!m.contains(42)); |
| 1362 | |
| 1363 | m[2] = v; |
| 1364 | QCOMPARE(m.take(2), v); |
| 1365 | QVERIFY(m.take(2).isUndefined()); |
| 1366 | QVERIFY(m.isEmpty()); |
| 1367 | } |
| 1368 | |
| 1369 | void tst_QCborValue::arrayInsertTagged() |
| 1370 | { |
| 1371 | QFETCH(QCborValue, v); |
| 1372 | |
| 1373 | // make it tagged |
| 1374 | QCborValue tagged(QCborKnownTags::Signature, v); |
| 1375 | |
| 1376 | QCborArray a{tagged}; |
| 1377 | a.insert(i: 1, value: tagged); |
| 1378 | QCOMPARE(a.size(), 2); |
| 1379 | QCOMPARE(a.at(0), tagged); |
| 1380 | QCOMPARE(a.at(1), tagged); |
| 1381 | QCOMPARE(a.at(0).taggedValue(), v); |
| 1382 | QCOMPARE(a.at(1).taggedValue(), v); |
| 1383 | QCOMPARE(a.takeAt(0).taggedValue(), v); |
| 1384 | QCOMPARE(a.takeAt(0).taggedValue(), v); |
| 1385 | QVERIFY(a.isEmpty()); |
| 1386 | } |
| 1387 | |
| 1388 | void tst_QCborValue::mapInsertTagged() |
| 1389 | { |
| 1390 | QFETCH(QCborValue, v); |
| 1391 | |
| 1392 | // make it tagged |
| 1393 | QCborValue tagged(QCborKnownTags::Signature, v); |
| 1394 | |
| 1395 | QCborMap m{{11, tagged}}; |
| 1396 | m.insert(v: {-21, tagged}); |
| 1397 | QCOMPARE(m.size(), 2); |
| 1398 | QCOMPARE(m.constBegin().value(), tagged); |
| 1399 | QCOMPARE(m.value(-21), tagged); |
| 1400 | QCOMPARE(m.value(11).taggedValue(), v); |
| 1401 | QCOMPARE((m.end() - 1).value().taggedValue(), v); |
| 1402 | QCOMPARE(m.extract(m.end() - 1).taggedValue(), v); |
| 1403 | QVERIFY(!m.contains(-21)); |
| 1404 | QCOMPARE(m.take(11).taggedValue(), v); |
| 1405 | QVERIFY(m.isEmpty()); |
| 1406 | } |
| 1407 | |
| 1408 | void tst_QCborValue::arraySelfAssign() |
| 1409 | { |
| 1410 | QFETCH(QCborValue, v); |
| 1411 | QCborArray a; |
| 1412 | |
| 1413 | a = {v}; |
| 1414 | |
| 1415 | // Test 1: QCborValue created first, so |
| 1416 | // QCborArray::insert() detaches |
| 1417 | { |
| 1418 | a.append(value: a); |
| 1419 | QCOMPARE(a.size(), 2); |
| 1420 | QCOMPARE(a.last().toArray().size(), 1); |
| 1421 | } |
| 1422 | |
| 1423 | a = {v}; |
| 1424 | |
| 1425 | // Test 2: QCborValueRef created first |
| 1426 | { |
| 1427 | a.append(value: 36); |
| 1428 | auto it = a.end() - 1; |
| 1429 | *it = a; |
| 1430 | |
| 1431 | QCOMPARE(a.size(), 2); |
| 1432 | QCOMPARE(it->toArray().size(), 2); |
| 1433 | QCOMPARE(it->toArray().last(), QCborValue(36)); |
| 1434 | } |
| 1435 | } |
| 1436 | |
| 1437 | void tst_QCborValue::mapSelfAssign() |
| 1438 | { |
| 1439 | QFETCH(QCborValue, v); |
| 1440 | QCborMap m; |
| 1441 | |
| 1442 | m = {{0, v}}; |
| 1443 | QCOMPARE(m.size(), 1); |
| 1444 | |
| 1445 | // Test 1: create a QCborValue first |
| 1446 | // in this case, QCborMap::operator[] detaches first |
| 1447 | { |
| 1448 | QCborValue vm = m; |
| 1449 | m[1] = vm; // self-assign |
| 1450 | QCOMPARE(m.size(), 2); |
| 1451 | QCOMPARE(m.value(0), v); |
| 1452 | |
| 1453 | QCborMap m2 = m.value(key: 1).toMap(); |
| 1454 | // there mustn't be an element with key 1 |
| 1455 | QCOMPARE(m2.size(), 1); |
| 1456 | QCOMPARE(m2.value(0), v); |
| 1457 | QVERIFY(!m2.contains(1)); |
| 1458 | } |
| 1459 | |
| 1460 | m = {{0, v}}; |
| 1461 | |
| 1462 | // Test 2: create the QCborValueRef first |
| 1463 | // in this case, there's no opportunity to detach |
| 1464 | { |
| 1465 | QCborValueRef rv = m[1]; |
| 1466 | rv = m; // self-assign (implicit QCborValue creation) |
| 1467 | QCOMPARE(m.size(), 2); |
| 1468 | QCOMPARE(m.value(0), v); |
| 1469 | |
| 1470 | QCborMap m2 = m.value(key: 1).toMap(); |
| 1471 | // there must be an element with key 1 |
| 1472 | QCOMPARE(m2.size(), 2); |
| 1473 | QCOMPARE(m2.value(0), v); |
| 1474 | QVERIFY(m2.contains(1)); |
| 1475 | QCOMPARE(m2.value(1), QCborValue()); |
| 1476 | } |
| 1477 | |
| 1478 | m = {{0, v}}; |
| 1479 | |
| 1480 | // Test 3: don't force creation of either before |
| 1481 | // in this case, it's up to the compiler to choose |
| 1482 | { |
| 1483 | m[1] = m; // self-assign |
| 1484 | QCOMPARE(m.size(), 2); |
| 1485 | |
| 1486 | QCborMap m2 = m.value(key: 1).toMap(); |
| 1487 | QVERIFY(m2.size() == 1 || m2.size() == 2); |
| 1488 | } |
| 1489 | |
| 1490 | m = {{0, v}}; |
| 1491 | |
| 1492 | // Test 4: self-assign as key |
| 1493 | // in this scase, QCborMap::operator[] must detach |
| 1494 | { |
| 1495 | m[m] = v; |
| 1496 | QCOMPARE(m.size(), 2); |
| 1497 | |
| 1498 | auto it = m.constEnd() - 1; |
| 1499 | QCOMPARE(it.value(), v); |
| 1500 | QCOMPARE(it.key(), QCborMap({{0, v}})); |
| 1501 | } |
| 1502 | } |
| 1503 | |
| 1504 | void tst_QCborValue::mapComplexKeys() |
| 1505 | { |
| 1506 | QFETCH(QCborValue, v); |
| 1507 | QCborValue tagged(QCborKnownTags::Signature, v); |
| 1508 | |
| 1509 | QCborMap m{{42, true}, {v, 42}, {-3, nullptr}}; |
| 1510 | QCOMPARE(m.size(), 3); |
| 1511 | QVERIFY(m.contains(42)); |
| 1512 | QVERIFY(m.contains(-3)); |
| 1513 | QVERIFY(m.contains(v)); |
| 1514 | QVERIFY(!m.contains(tagged)); |
| 1515 | |
| 1516 | auto it = m.constFind(key: v); |
| 1517 | QVERIFY(it != m.constEnd()); |
| 1518 | QVERIFY(it.key() == v); |
| 1519 | QVERIFY(v == it.key()); |
| 1520 | QCOMPARE(it.value().toInteger(), 42); |
| 1521 | |
| 1522 | QCborArray a{0, 1, 2, 3, v}; |
| 1523 | m[a] = 1; |
| 1524 | QCOMPARE(m.size(), 4); |
| 1525 | QCOMPARE((m.constEnd() - 1).value(), QCborValue(1)); |
| 1526 | if (v != QCborValue(QCborValue::Array)) |
| 1527 | QVERIFY(!m.contains(QCborArray{})); |
| 1528 | QVERIFY(!m.contains(QCborArray{0})); |
| 1529 | QVERIFY(!m.contains(QCborArray{0, 1})); |
| 1530 | QVERIFY(!m.contains(QCborArray{0, 1, 2})); |
| 1531 | QVERIFY(!m.contains(QCborArray{0, 1, 2, 4})); |
| 1532 | QVERIFY(!m.contains(QCborArray{0, 1, 2, 3, v, 4})); |
| 1533 | |
| 1534 | it = m.constFind(key: QCborArray{0, 1, 2, 3, v}); |
| 1535 | QVERIFY(it != m.constEnd()); |
| 1536 | QCOMPARE(it.key(), a); |
| 1537 | QCOMPARE(it.value(), QCborValue(1)); |
| 1538 | |
| 1539 | m[m] = 1; // assign itself as a key -- this necessarily detaches before |
| 1540 | QCOMPARE(m.size(), 5); |
| 1541 | QCOMPARE((m.end() - 1).value(), 1); |
| 1542 | QCOMPARE((m.end() - 1).key().toMap().size(), 4); |
| 1543 | |
| 1544 | QCborValue mv(m); |
| 1545 | if (v.isInteger()) { |
| 1546 | // we should be able to find using the overloads too |
| 1547 | QCOMPARE(m[v.toInteger()].toInteger(), 42); |
| 1548 | QCOMPARE(mv[v.toInteger()].toInteger(), 42); |
| 1549 | } else if (v.isString()) { |
| 1550 | // ditto |
| 1551 | QCOMPARE(m[v.toString()].toInteger(), 42); |
| 1552 | QCOMPARE(mv[v.toString()].toInteger(), 42); |
| 1553 | |
| 1554 | // basics_data() strings are Latin1 |
| 1555 | QByteArray latin1 = v.toString().toLatin1(); |
| 1556 | Q_ASSERT(v.toString() == QString::fromLatin1(latin1)); |
| 1557 | QCOMPARE(m[QLatin1String(latin1)].toInteger(), 42); |
| 1558 | } |
| 1559 | |
| 1560 | m.remove(key: v); |
| 1561 | QVERIFY(!m.contains(v)); |
| 1562 | QVERIFY(!m.contains(tagged)); |
| 1563 | |
| 1564 | QCborValueRef r = m[tagged]; |
| 1565 | QVERIFY(!m.contains(v)); |
| 1566 | QVERIFY(m.contains(tagged)); |
| 1567 | r = 47; |
| 1568 | QCOMPARE(m[tagged].toInteger(), 47); |
| 1569 | QCOMPARE(m.take(tagged).toInteger(), 47); |
| 1570 | QVERIFY(!m.contains(tagged)); |
| 1571 | } |
| 1572 | |
| 1573 | void tst_QCborValue::sorting() |
| 1574 | { |
| 1575 | QCborValue vundef, vnull(nullptr); |
| 1576 | QCborValue vtrue(true), vfalse(false); |
| 1577 | QCborValue vint1(1), vint2(2); |
| 1578 | QCborValue vneg1(-1), vneg2(-2); |
| 1579 | QCborValue vba2(QByteArray("Hello" )), vba3(QByteArray("World" )), vba1(QByteArray("foo" )); |
| 1580 | QCborValue vs2("Hello" ), vs3("World" ), vs1("foo" ); |
| 1581 | QCborValue va1(QCborValue::Array), va2(QCborArray{1}), va3(QCborArray{0, 0}); |
| 1582 | QCborValue vm1(QCborValue::Map), vm2(QCborMap{{1, 0}}), vm3(QCborMap{{0, 0}, {1, 0}}); |
| 1583 | QCborValue vdt1(QDateTime::fromMSecsSinceEpoch(msecs: 0, spec: Qt::UTC)), vdt2(QDateTime::currentDateTimeUtc()); |
| 1584 | QCborValue vtagged1(QCborKnownTags::PositiveBignum, QByteArray()), |
| 1585 | vtagged2(QCborKnownTags::PositiveBignum, 0.0), // bignums are supposed to have byte arrays... |
| 1586 | vtagged3(QCborKnownTags::Signature, 0), |
| 1587 | vtagged4(QCborTag(-2), 0), |
| 1588 | vtagged5(QCborTag(-1), 0); |
| 1589 | QCborValue vurl1(QUrl("https://example.net" )), vurl2(QUrl("https://example.com/" )); |
| 1590 | QCborValue vuuid1{QUuid()}, vuuid2(QUuid::createUuid()); |
| 1591 | QCborValue vsimple1(QCborSimpleType(1)), vsimple32(QCborSimpleType(32)), vsimple255(QCborSimpleType(255)); |
| 1592 | QCborValue vdouble1(1.5), vdouble2(qInf()); |
| 1593 | QCborValue vndouble1(-1.5), vndouble2(-qInf()); |
| 1594 | |
| 1595 | #define CHECK_ORDER(v1, v2) \ |
| 1596 | QVERIFY(v1 < v2); \ |
| 1597 | QVERIFY(!(v2 < v2)) |
| 1598 | |
| 1599 | // intra-type comparisons |
| 1600 | CHECK_ORDER(vfalse, vtrue); |
| 1601 | CHECK_ORDER(vsimple1, vsimple32); |
| 1602 | CHECK_ORDER(vsimple32, vsimple255); |
| 1603 | CHECK_ORDER(vint1, vint2); |
| 1604 | CHECK_ORDER(vdouble1, vdouble2); |
| 1605 | CHECK_ORDER(vndouble1, vndouble2); |
| 1606 | // note: shorter length sorts first |
| 1607 | CHECK_ORDER(vba1, vba2); |
| 1608 | CHECK_ORDER(vba2, vba3); |
| 1609 | CHECK_ORDER(vs1, vs2); |
| 1610 | CHECK_ORDER(vs2, vs3); |
| 1611 | CHECK_ORDER(va1, va2); |
| 1612 | CHECK_ORDER(va2, va3); |
| 1613 | CHECK_ORDER(vm1, vm2); |
| 1614 | CHECK_ORDER(vm2, vm3); |
| 1615 | CHECK_ORDER(vdt1, vdt2); |
| 1616 | CHECK_ORDER(vtagged1, vtagged2); |
| 1617 | CHECK_ORDER(vtagged2, vtagged3); |
| 1618 | CHECK_ORDER(vtagged3, vtagged4); |
| 1619 | CHECK_ORDER(vtagged4, vtagged5); |
| 1620 | CHECK_ORDER(vurl1, vurl2); |
| 1621 | CHECK_ORDER(vuuid1, vuuid2); |
| 1622 | |
| 1623 | // surprise 1: CBOR sorts integrals by absolute value |
| 1624 | CHECK_ORDER(vneg1, vneg2); |
| 1625 | |
| 1626 | // surprise 2: CBOR sorts negatives after positives (sign+magnitude) |
| 1627 | CHECK_ORDER(vint2, vneg1); |
| 1628 | QVERIFY(vint2.toInteger() > vneg1.toInteger()); |
| 1629 | CHECK_ORDER(vdouble2, vndouble1); |
| 1630 | QVERIFY(vdouble2.toDouble() > vndouble1.toDouble()); |
| 1631 | |
| 1632 | // inter-type comparisons |
| 1633 | CHECK_ORDER(vneg2, vba1); |
| 1634 | CHECK_ORDER(vba3, vs1); |
| 1635 | CHECK_ORDER(vs3, va1); |
| 1636 | CHECK_ORDER(va2, vm1); |
| 1637 | CHECK_ORDER(vm2, vdt1); |
| 1638 | CHECK_ORDER(vdt2, vtagged1); |
| 1639 | CHECK_ORDER(vtagged2, vurl1); |
| 1640 | CHECK_ORDER(vurl1, vuuid1); |
| 1641 | CHECK_ORDER(vuuid2, vtagged3); |
| 1642 | CHECK_ORDER(vtagged4, vsimple1); |
| 1643 | CHECK_ORDER(vsimple1, vfalse); |
| 1644 | CHECK_ORDER(vtrue, vnull); |
| 1645 | CHECK_ORDER(vnull, vundef); |
| 1646 | CHECK_ORDER(vundef, vsimple32); |
| 1647 | CHECK_ORDER(vsimple255, vdouble1); |
| 1648 | |
| 1649 | // which shows all doubles sorted after integrals |
| 1650 | CHECK_ORDER(vint2, vdouble1); |
| 1651 | QVERIFY(vint2.toInteger() > vdouble1.toDouble()); |
| 1652 | #undef CHECK_ORDER |
| 1653 | } |
| 1654 | |
| 1655 | static void addCommonCborData() |
| 1656 | { |
| 1657 | // valid for both decoding and encoding |
| 1658 | QTest::addColumn<QCborValue>(name: "v" ); |
| 1659 | QTest::addColumn<QByteArray>(name: "result" ); |
| 1660 | QTest::addColumn<QCborValue::EncodingOptions>(name: "options" ); |
| 1661 | QDateTime dt = QDateTime::currentDateTimeUtc(); |
| 1662 | QUuid uuid = QUuid::createUuid(); |
| 1663 | QCborValue::EncodingOptions noxfrm = QCborValue::NoTransformation; |
| 1664 | |
| 1665 | // integrals |
| 1666 | QTest::newRow(dataTag: "Integer:0" ) << QCborValue(0) << raw(data: "\x00" ) << noxfrm; |
| 1667 | QTest::newRow(dataTag: "Integer:1" ) << QCborValue(1) << raw(data: "\x01" ) << noxfrm; |
| 1668 | QTest::newRow(dataTag: "Integer:-1" ) << QCborValue(-1) << raw(data: "\x20" ) << noxfrm; |
| 1669 | QTest::newRow(dataTag: "Integer:INT64_MAX" ) << QCborValue(std::numeric_limits<qint64>::max()) |
| 1670 | << raw(data: "\x1b\x7f\xff\xff\xff" "\xff\xff\xff\xff" ) |
| 1671 | << noxfrm; |
| 1672 | QTest::newRow(dataTag: "Integer:INT64_MIN" ) << QCborValue(std::numeric_limits<qint64>::min()) |
| 1673 | << raw(data: "\x3b\x7f\xff\xff\xff" "\xff\xff\xff\xff" ) |
| 1674 | << noxfrm; |
| 1675 | |
| 1676 | QTest::newRow(dataTag: "simple0" ) << QCborValue(QCborValue::SimpleType) << raw(data: "\xe0" ) << noxfrm; |
| 1677 | QTest::newRow(dataTag: "simple1" ) << QCborValue(QCborSimpleType(1)) << raw(data: "\xe1" ) << noxfrm; |
| 1678 | QTest::newRow(dataTag: "simple255" ) << QCborValue(QCborSimpleType(255)) << raw(data: "\xf8\xff" ) << noxfrm; |
| 1679 | QTest::newRow(dataTag: "Undefined" ) << QCborValue() << raw(data: "\xf7" ) << noxfrm; |
| 1680 | QTest::newRow(dataTag: "Null" ) << QCborValue(nullptr) << raw(data: "\xf6" ) << noxfrm; |
| 1681 | QTest::newRow(dataTag: "True" ) << QCborValue(true) << raw(data: "\xf5" ) << noxfrm; |
| 1682 | QTest::newRow(dataTag: "False" ) << QCborValue(false) << raw(data: "\xf4" ) << noxfrm; |
| 1683 | QTest::newRow(dataTag: "simple32" ) << QCborValue(QCborSimpleType(32)) << raw(data: "\xf8\x20" ) << noxfrm; |
| 1684 | QTest::newRow(dataTag: "simple255" ) << QCborValue(QCborSimpleType(255)) << raw(data: "\xf8\xff" ) << noxfrm; |
| 1685 | |
| 1686 | QTest::newRow(dataTag: "Double:0" ) << QCborValue(0.) << raw(data: "\xfb\0\0\0\0" "\0\0\0\0" ) << noxfrm; |
| 1687 | QTest::newRow(dataTag: "Double:1.5" ) << QCborValue(1.5) << raw(data: "\xfb\x3f\xf8\0\0" "\0\0\0\0" ) << noxfrm; |
| 1688 | QTest::newRow(dataTag: "Double:-1.5" ) << QCborValue(-1.5) << raw(data: "\xfb\xbf\xf8\0\0" "\0\0\0\0" ) << noxfrm; |
| 1689 | QTest::newRow(dataTag: "Double:INT64_MAX+1" ) << QCborValue(std::numeric_limits<qint64>::max() + 1.) |
| 1690 | << raw(data: "\xfb\x43\xe0\0\0" "\0\0\0\0" ) << noxfrm; |
| 1691 | QTest::newRow(dataTag: "Double:maxintegralfp" ) << QCborValue(18446744073709551616.0 - 2048) |
| 1692 | << raw(data: "\xfb\x43\xef\xff\xff" "\xff\xff\xff\xff" ) |
| 1693 | << noxfrm; |
| 1694 | QTest::newRow(dataTag: "Double:minintegralfp" ) << QCborValue(-18446744073709551616.0 + 2048) |
| 1695 | << raw(data: "\xfb\xc3\xef\xff\xff" "\xff\xff\xff\xff" ) |
| 1696 | << noxfrm; |
| 1697 | QTest::newRow(dataTag: "Double:inf" ) << QCborValue(qInf()) << raw(data: "\xfb\x7f\xf0\0\0" "\0\0\0\0" ) << noxfrm; |
| 1698 | QTest::newRow(dataTag: "Double:-inf" ) << QCborValue(-qInf()) << raw(data: "\xfb\xff\xf0\0" "\0\0\0\0\0" ) << noxfrm; |
| 1699 | QTest::newRow(dataTag: "Double:nan" ) << QCborValue(qQNaN()) << raw(data: "\xfb\x7f\xf8\0\0" "\0\0\0\0" ) << noxfrm; |
| 1700 | |
| 1701 | QTest::newRow(dataTag: "Float:0" ) << QCborValue(0.) << raw(data: "\xfa\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1702 | QTest::newRow(dataTag: "Float:1.5" ) << QCborValue(1.5) << raw(data: "\xfa\x3f\xc0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1703 | QTest::newRow(dataTag: "Float:-1.5" ) << QCborValue(-1.5) << raw(data: "\xfa\xbf\xc0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1704 | QTest::newRow(dataTag: "Float:inf" ) << QCborValue(qInf()) << raw(data: "\xfa\x7f\x80\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1705 | QTest::newRow(dataTag: "Float:-inf" ) << QCborValue(-qInf()) << raw(data: "\xfa\xff\x80\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1706 | QTest::newRow(dataTag: "Float:nan" ) << QCborValue(qQNaN()) << raw(data: "\xfa\x7f\xc0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1707 | |
| 1708 | QTest::newRow(dataTag: "Float16:0" ) << QCborValue(0.) << raw(data: "\xf9\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1709 | QTest::newRow(dataTag: "Float16:1.5" ) << QCborValue(1.5) << raw(data: "\xf9\x3e\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1710 | QTest::newRow(dataTag: "Float16:-1.5" ) << QCborValue(-1.5) << raw(data: "\xf9\xbe\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1711 | QTest::newRow(dataTag: "Float16:inf" ) << QCborValue(qInf()) << raw(data: "\xf9\x7c\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1712 | QTest::newRow(dataTag: "Float16:-inf" ) << QCborValue(-qInf()) << raw(data: "\xf9\xfc\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1713 | QTest::newRow(dataTag: "Float16:nan" ) << QCborValue(qQNaN()) << raw(data: "\xf9\x7e\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1714 | |
| 1715 | // out of range of qint64, but in range for CBOR, so these do get converted |
| 1716 | // to integrals on write and back to double on read |
| 1717 | QTest::newRow(dataTag: "UseInteger:INT64_MAX+1" ) << QCborValue(std::numeric_limits<qint64>::max() + 1.) |
| 1718 | << raw(data: "\x1b\x80\0\0\0" "\0\0\0\0" ) |
| 1719 | << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1720 | QTest::newRow(dataTag: "UseInteger:maxintegralfp" ) << QCborValue(18446744073709551616.0 - 2048) |
| 1721 | << raw(data: "\x1b\xff\xff\xff\xff" "\xff\xff\xf8\0" ) |
| 1722 | << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1723 | QTest::newRow(dataTag: "UseInteger:minintegralfp" ) << QCborValue(-18446744073709551616.0 + 2048) |
| 1724 | << raw(data: "\x3b\xff\xff\xff\xff" "\xff\xff\xf7\xff" ) |
| 1725 | << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1726 | |
| 1727 | QTest::newRow(dataTag: "ByteArray:Empty" ) << QCborValue(QByteArray()) << raw(data: "\x40" ) << noxfrm; |
| 1728 | QTest::newRow(dataTag: "ByteArray" ) << QCborValue(QByteArray("Hello" )) << raw(data: "\x45Hello" ) << noxfrm; |
| 1729 | QTest::newRow(dataTag: "ByteArray:WithNull" ) << QCborValue(raw(data: "\0\1\2\xff" )) << raw(data: "\x44\0\1\2\xff" ) << noxfrm; |
| 1730 | |
| 1731 | QTest::newRow(dataTag: "String:Empty" ) << QCborValue(QString()) << raw(data: "\x60" ) << noxfrm; |
| 1732 | QTest::newRow(dataTag: "String:UsAscii" ) << QCborValue("Hello" ) << raw(data: "\x65Hello" ) << noxfrm; |
| 1733 | QTest::newRow(dataTag: "String:Latin1" ) << QCborValue(QLatin1String("R\xe9sum\xe9" )) |
| 1734 | << raw(data: "\x68R\xc3\xa9sum\xc3\xa9" ) << noxfrm; |
| 1735 | QTest::newRow(dataTag: "String:Unicode" ) << QCborValue(QStringLiteral(u"éś α €" )) |
| 1736 | << raw(data: "\x6b\xc3\xa9\xc5\x9b \xce\xb1 \xe2\x82\xac" ) << noxfrm; |
| 1737 | |
| 1738 | QTest::newRow(dataTag: "DateTime" ) << QCborValue(dt) // this is UTC |
| 1739 | << "\xc0\x78\x18" + dt.toString(format: Qt::ISODateWithMs).toLatin1() |
| 1740 | << noxfrm; |
| 1741 | QTest::newRow(dataTag: "DateTime-UTC" ) << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::UTC)) |
| 1742 | << raw(data: "\xc0\x78\x18" "2018-01-01T09:00:00.000Z" ) |
| 1743 | << noxfrm; |
| 1744 | QTest::newRow(dataTag: "DateTime-Local" ) << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::LocalTime)) |
| 1745 | << raw(data: "\xc0\x77" "2018-01-01T09:00:00.000" ) |
| 1746 | << noxfrm; |
| 1747 | QTest::newRow(dataTag: "DateTime+01:00" ) << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::OffsetFromUTC, 3600)) |
| 1748 | << raw(data: "\xc0\x78\x1d" "2018-01-01T09:00:00.000+01:00" ) |
| 1749 | << noxfrm; |
| 1750 | QTest::newRow(dataTag: "Url:Empty" ) << QCborValue(QUrl()) << raw(data: "\xd8\x20\x60" ) << noxfrm; |
| 1751 | QTest::newRow(dataTag: "Url" ) << QCborValue(QUrl("HTTPS://example.com/{%30%31}?q=%3Ca+b%20%C2%A9%3E&%26" )) |
| 1752 | << raw(data: "\xd8\x20\x78\x27" "https://example.com/{01}?q=<a+b \xC2\xA9>&%26" ) |
| 1753 | << noxfrm; |
| 1754 | QTest::newRow(dataTag: "Url:NonAscii" ) << QCborValue(QUrl("https://example.com/\xc2\xa0" )) |
| 1755 | << raw(data: "\xd8\x20\x76" "https://example.com/\xc2\xa0" ) |
| 1756 | << noxfrm; |
| 1757 | QTest::newRow(dataTag: "Regex:Empty" ) << QCborValue(QRegularExpression()) << raw(data: "\xd8\x23\x60" ) << noxfrm; |
| 1758 | QTest::newRow(dataTag: "Regex" ) << QCborValue(QRegularExpression("^.*$" )) |
| 1759 | << raw(data: "\xd8\x23\x64" "^.*$" ) << noxfrm; |
| 1760 | QTest::newRow(dataTag: "Uuid" ) << QCborValue(uuid) << raw(data: "\xd8\x25\x50" ) + uuid.toRfc4122() << noxfrm; |
| 1761 | |
| 1762 | // empty arrays and maps |
| 1763 | QTest::newRow(dataTag: "Array" ) << QCborValue(QCborArray()) << raw(data: "\x80" ) << noxfrm; |
| 1764 | QTest::newRow(dataTag: "Map" ) << QCborValue(QCborMap()) << raw(data: "\xa0" ) << noxfrm; |
| 1765 | |
| 1766 | QTest::newRow(dataTag: "Tagged:ByteArray" ) << QCborValue(QCborKnownTags::PositiveBignum, raw(data: "\1\0\0\0\0" "\0\0\0\0" )) |
| 1767 | << raw(data: "\xc2\x49\1\0\0\0\0" "\0\0\0\0" ) << noxfrm; |
| 1768 | QTest::newRow(dataTag: "Tagged:Array" ) << QCborValue(QCborKnownTags::Decimal, QCborArray{-2, 27315}) |
| 1769 | << raw(data: "\xc4\x82\x21\x19\x6a\xb3" ) << noxfrm; |
| 1770 | } |
| 1771 | |
| 1772 | void tst_QCborValue::toCbor_data() |
| 1773 | { |
| 1774 | addCommonCborData(); |
| 1775 | |
| 1776 | // The rest of these tests are conversions whose decoding does not yield |
| 1777 | // back the same QCborValue. |
| 1778 | |
| 1779 | #if QT_CONFIG(signaling_nan) |
| 1780 | // Signalling NaN get normalized to quiet ones |
| 1781 | QTest::newRow(dataTag: "Double:snan" ) << QCborValue(qSNaN()) << raw(data: "\xfb\x7f\xf8\0" "\0\0\0\0\0" ) << QCborValue::EncodingOptions(); |
| 1782 | QTest::newRow(dataTag: "Float:snan" ) << QCborValue(qSNaN()) << raw(data: "\xfa\x7f\xc0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat); |
| 1783 | QTest::newRow(dataTag: "Float16:snan" ) << QCborValue(qSNaN()) << raw(data: "\xf9\x7e\0" ) << QCborValue::EncodingOptions(QCborValue::UseFloat16); |
| 1784 | #endif |
| 1785 | |
| 1786 | // Floating point written as integers are read back as integers |
| 1787 | QTest::newRow(dataTag: "UseInteger:0" ) << QCborValue(0.) << raw(data: "\x00" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1788 | QTest::newRow(dataTag: "UseInteger:1" ) << QCborValue(1.) << raw(data: "\x01" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1789 | QTest::newRow(dataTag: "UseInteger:-1" ) << QCborValue(-1.) << raw(data: "\x20" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1790 | QTest::newRow(dataTag: "UseInteger:INT64_MIN" ) << QCborValue(std::numeric_limits<qint64>::min() + 0.) |
| 1791 | << raw(data: "\x3b\x7f\xff\xff\xff" "\xff\xff\xff\xff" ) |
| 1792 | << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1793 | |
| 1794 | // but obviously non-integral or out of range floating point stay FP |
| 1795 | QTest::newRow(dataTag: "UseInteger:1.5" ) << QCborValue(1.5) << raw(data: "\xfb\x3f\xf8\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1796 | QTest::newRow(dataTag: "UseInteger:-1.5" ) << QCborValue(-1.5) << raw(data: "\xfb\xbf\xf8\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1797 | QTest::newRow(dataTag: "UseInteger:inf" ) << QCborValue(qInf()) << raw(data: "\xfb\x7f\xf0\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1798 | QTest::newRow(dataTag: "UseInteger:-inf" ) << QCborValue(-qInf()) << raw(data: "\xfb\xff\xf0\0" "\0\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1799 | QTest::newRow(dataTag: "UseInteger:nan" ) << QCborValue(qQNaN()) << raw(data: "\xfb\x7f\xf8\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1800 | QTest::newRow(dataTag: "UseInteger:2^64" ) << QCborValue(18446744073709551616.0) << raw(data: "\xfb\x43\xf0\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1801 | QTest::newRow(dataTag: "UseInteger:-2^65" ) << QCborValue(-2 * 18446744073709551616.0) << raw(data: "\xfb\xc4\0\0\0" "\0\0\0\0" ) << QCborValue::EncodingOptions(QCborValue::UseIntegers); |
| 1802 | } |
| 1803 | |
| 1804 | void tst_QCborValue::toCbor() |
| 1805 | { |
| 1806 | QFETCH(QCborValue, v); |
| 1807 | QFETCH(QByteArray, result); |
| 1808 | QFETCH(QCborValue::EncodingOptions, options); |
| 1809 | |
| 1810 | QCOMPARE(v.toCbor(options), result); |
| 1811 | |
| 1812 | // in maps and arrays |
| 1813 | QCOMPARE(QCborArray{v}.toCborValue().toCbor(options), "\x81" + result); |
| 1814 | QCOMPARE(QCborArray({v, v}).toCborValue().toCbor(options), |
| 1815 | "\x82" + result + result); |
| 1816 | QCOMPARE(QCborMap({{1, v}}).toCborValue().toCbor(options), |
| 1817 | "\xa1\x01" + result); |
| 1818 | |
| 1819 | // tagged |
| 1820 | QCborValue t(QCborKnownTags::Signature, v); |
| 1821 | QCOMPARE(t.toCbor(options), "\xd9\xd9\xf7" + result); |
| 1822 | QCOMPARE(QCborArray({t, t}).toCborValue().toCbor(options), |
| 1823 | "\x82\xd9\xd9\xf7" + result + "\xd9\xd9\xf7" + result); |
| 1824 | QCOMPARE(QCborMap({{1, t}}).toCborValue().toCbor(options), |
| 1825 | "\xa1\x01\xd9\xd9\xf7" + result); |
| 1826 | } |
| 1827 | |
| 1828 | void tst_QCborValue::toCborStreamWriter() |
| 1829 | { |
| 1830 | QFETCH(QCborValue, v); |
| 1831 | QFETCH(QByteArray, result); |
| 1832 | QFETCH(QCborValue::EncodingOptions, options); |
| 1833 | |
| 1834 | QByteArray output; |
| 1835 | QBuffer buffer(&output); |
| 1836 | buffer.open(openMode: QIODevice::WriteOnly); |
| 1837 | QCborStreamWriter writer(&buffer); |
| 1838 | |
| 1839 | v.toCbor(writer, opt: options); |
| 1840 | QCOMPARE(buffer.pos(), result.size()); |
| 1841 | QCOMPARE(output, result); |
| 1842 | } |
| 1843 | |
| 1844 | void tst_QCborValue::fromCbor_data() |
| 1845 | { |
| 1846 | addCommonCborData(); |
| 1847 | |
| 1848 | // chunked strings |
| 1849 | QTest::newRow(dataTag: "ByteArray:Chunked" ) << QCborValue(QByteArray("Hello" )) |
| 1850 | << raw(data: "\x5f\x43Hel\x42lo\xff" ); |
| 1851 | QTest::newRow(dataTag: "ByteArray:Chunked:Empty" ) << QCborValue(QByteArray()) << raw(data: "\x5f\xff" ); |
| 1852 | QTest::newRow(dataTag: "String:Chunked" ) << QCborValue("Hello" ) |
| 1853 | << raw(data: "\x7f\x63Hel\x62lo\xff" ); |
| 1854 | QTest::newRow(dataTag: "String:Chunked:Empty" ) << QCborValue(QString()) |
| 1855 | << raw(data: "\x7f\xff" ); |
| 1856 | |
| 1857 | QTest::newRow(dataTag: "DateTime:NoMilli" ) << QCborValue(QDateTime::fromSecsSinceEpoch(secs: 1515565477, spe: Qt::UTC)) |
| 1858 | << raw(data: "\xc0\x74" "2018-01-10T06:24:37Z" ); |
| 1859 | // date-only is only permitted local time |
| 1860 | QTest::newRow(dataTag: "DateTime:NoTime:Local" ) << QCborValue(QDateTime(QDate(2020, 4, 15), QTime(0, 0), Qt::LocalTime)) |
| 1861 | << raw(data: "\xc0\x6a" "2020-04-15" ); |
| 1862 | QTest::newRow(dataTag: "DateTime:24:00:00" ) << QCborValue(QDateTime(QDate(2020, 4, 16), QTime(0, 0), Qt::UTC)) |
| 1863 | << raw(data: "\xc0\x74" "2020-04-15T24:00:00Z" ); |
| 1864 | QTest::newRow(dataTag: "DateTime:+00:00" ) << QCborValue(QDateTime::fromMSecsSinceEpoch(msecs: 1515565477125, spec: Qt::UTC)) |
| 1865 | << raw(data: "\xc0\x78\x1d" "2018-01-10T06:24:37.125+00:00" ); |
| 1866 | QTest::newRow(dataTag: "DateTime:+01:00" ) << QCborValue(QDateTime::fromMSecsSinceEpoch(msecs: 1515565477125, spec: Qt::OffsetFromUTC, offsetFromUtc: 60*60)) |
| 1867 | << raw(data: "\xc0\x78\x1d" "2018-01-10T07:24:37.125+01:00" ); |
| 1868 | QTest::newRow(dataTag: "UnixTime_t:Integer" ) << QCborValue(QDateTime::fromSecsSinceEpoch(secs: 1515565477, spe: Qt::UTC)) |
| 1869 | << raw(data: "\xc1\x1a\x5a\x55\xb1\xa5" ); |
| 1870 | QTest::newRow(dataTag: "UnixTime_t:Double" ) << QCborValue(QDateTime::fromMSecsSinceEpoch(msecs: 1515565477125, spec: Qt::UTC)) |
| 1871 | << raw(data: "\xc1\xfb\x41\xd6\x95\x6c" "\x69\x48\x00\x00" ); |
| 1872 | |
| 1873 | QTest::newRow(dataTag: "Url:NotNormalized" ) << QCborValue(QUrl("https://example.com/\xc2\xa9 " )) |
| 1874 | << raw(data: "\xd8\x20\x78\x1dHTTPS://EXAMPLE.COM/%c2%a9%20" ); |
| 1875 | |
| 1876 | QTest::newRow(dataTag: "Uuid:Zero" ) << QCborValue(QUuid()) << raw(data: "\xd8\x25\x40" ); |
| 1877 | QTest::newRow(dataTag: "Uuid:TooShort" ) << QCborValue(QUuid::fromRfc4122(raw(data: "\1\2\3\4" "\4\3\2\0" "\0\0\0\0" "\0\0\0\0" ))) |
| 1878 | << raw(data: "\xd8\x25\x47" "\1\2\3\4\4\3\2" ); |
| 1879 | QTest::newRow(dataTag: "Uuid:TooLong" ) << QCborValue(QUuid::fromRfc4122(raw(data: "\1\2\3\4" "\4\3\2\0" "\0\0\0\0" "\0\0\0\1" ))) |
| 1880 | << raw(data: "\xd8\x25\x51" "\1\2\3\4" "\4\3\2\0" "\0\0\0\0" "\0\0\0\1" "\2" ); |
| 1881 | } |
| 1882 | |
| 1883 | void fromCbor_common(void (*doCheck)(const QCborValue &, const QByteArray &)) |
| 1884 | { |
| 1885 | QFETCH(QCborValue, v); |
| 1886 | QFETCH(QByteArray, result); |
| 1887 | |
| 1888 | doCheck(v, result); |
| 1889 | if (QTest::currentTestFailed()) |
| 1890 | return; |
| 1891 | |
| 1892 | // in an array |
| 1893 | doCheck(QCborArray{v}, "\x81" + result); |
| 1894 | if (QTest::currentTestFailed()) |
| 1895 | return; |
| 1896 | |
| 1897 | doCheck(QCborArray{v, v}, "\x82" + result + result); |
| 1898 | if (QTest::currentTestFailed()) |
| 1899 | return; |
| 1900 | |
| 1901 | // in a map |
| 1902 | doCheck(QCborMap{{1, v}}, "\xa1\1" + result); |
| 1903 | if (QTest::currentTestFailed()) |
| 1904 | return; |
| 1905 | |
| 1906 | // undefined-length arrays and maps |
| 1907 | doCheck(QCborArray{v}, "\x9f" + result + "\xff" ); |
| 1908 | if (QTest::currentTestFailed()) |
| 1909 | return; |
| 1910 | doCheck(QCborArray{v, v}, "\x9f" + result + result + "\xff" ); |
| 1911 | if (QTest::currentTestFailed()) |
| 1912 | return; |
| 1913 | doCheck(QCborMap{{1, v}}, "\xbf\1" + result + "\xff" ); |
| 1914 | if (QTest::currentTestFailed()) |
| 1915 | return; |
| 1916 | |
| 1917 | // tagged |
| 1918 | QCborValue t(QCborKnownTags::Signature, v); |
| 1919 | doCheck(t, "\xd9\xd9\xf7" + result); |
| 1920 | if (QTest::currentTestFailed()) |
| 1921 | return; |
| 1922 | |
| 1923 | // in an array |
| 1924 | doCheck(QCborArray{t}, "\x81\xd9\xd9\xf7" + result); |
| 1925 | if (QTest::currentTestFailed()) |
| 1926 | return; |
| 1927 | |
| 1928 | doCheck(QCborArray{t, t}, "\x82\xd9\xd9\xf7" + result + "\xd9\xd9\xf7" + result); |
| 1929 | if (QTest::currentTestFailed()) |
| 1930 | return; |
| 1931 | |
| 1932 | // in a map |
| 1933 | doCheck(QCborMap{{1, t}}, "\xa1\1\xd9\xd9\xf7" + result); |
| 1934 | if (QTest::currentTestFailed()) |
| 1935 | return; |
| 1936 | } |
| 1937 | |
| 1938 | void tst_QCborValue::fromCbor() |
| 1939 | { |
| 1940 | auto doCheck = [](const QCborValue &v, const QByteArray &result) { |
| 1941 | QCborParserError error; |
| 1942 | QCborValue decoded = QCborValue::fromCbor(ba: result, error: &error); |
| 1943 | QVERIFY2(error.error == QCborError(), qPrintable(error.errorString())); |
| 1944 | QCOMPARE(error.offset, result.size()); |
| 1945 | QVERIFY(decoded == v); |
| 1946 | QVERIFY(v == decoded); |
| 1947 | }; |
| 1948 | |
| 1949 | fromCbor_common(doCheck); |
| 1950 | } |
| 1951 | |
| 1952 | void tst_QCborValue::fromCborStreamReaderByteArray() |
| 1953 | { |
| 1954 | auto doCheck = [](const QCborValue &expected, const QByteArray &data) { |
| 1955 | QCborStreamReader reader(data); |
| 1956 | QCborValue decoded = QCborValue::fromCbor(reader); |
| 1957 | QCOMPARE(reader.lastError(), QCborError()); |
| 1958 | QCOMPARE(reader.currentOffset(), data.size()); |
| 1959 | QVERIFY(decoded == expected); |
| 1960 | QVERIFY(expected == decoded); |
| 1961 | }; |
| 1962 | |
| 1963 | fromCbor_common(doCheck); |
| 1964 | } |
| 1965 | |
| 1966 | void tst_QCborValue::fromCborStreamReaderIODevice() |
| 1967 | { |
| 1968 | auto doCheck = [](const QCborValue &expected, const QByteArray &data) { |
| 1969 | QBuffer buffer; |
| 1970 | buffer.setData(data); |
| 1971 | buffer.open(openMode: QIODevice::ReadOnly); |
| 1972 | QCborStreamReader reader(&buffer); |
| 1973 | QCborValue decoded = QCborValue::fromCbor(reader); |
| 1974 | QCOMPARE(reader.lastError(), QCborError()); |
| 1975 | QCOMPARE(reader.currentOffset(), data.size()); |
| 1976 | QVERIFY(decoded == expected); |
| 1977 | QVERIFY(expected == decoded); |
| 1978 | QCOMPARE(buffer.pos(), reader.currentOffset()); |
| 1979 | }; |
| 1980 | |
| 1981 | fromCbor_common(doCheck); |
| 1982 | } |
| 1983 | |
| 1984 | #include "../cborlargedatavalidation.cpp" |
| 1985 | |
| 1986 | void tst_QCborValue::validation_data() |
| 1987 | { |
| 1988 | // Add QCborStreamReader-specific limitations due to use of QByteArray and |
| 1989 | // QString, which are allocated by QArrayData::allocate(). |
| 1990 | const qsizetype MaxInvalid = std::numeric_limits<QByteArray::size_type>::max(); |
| 1991 | const qsizetype MinInvalid = MaxByteArraySize + 1 - sizeof(QByteArray::size_type); |
| 1992 | addValidationColumns(); |
| 1993 | addValidationData(minInvalid: MinInvalid); |
| 1994 | addValidationLargeData(minInvalid: MinInvalid, maxInvalid: MaxInvalid); |
| 1995 | |
| 1996 | // Chunked strings whose total overflows the limit, but each individual |
| 1997 | // chunk doesn't. 0x5a for 32-bit, 0x5b for 64-bit. |
| 1998 | char toolong[1 + sizeof(qsizetype)]; |
| 1999 | toolong[0] = sizeof(MinInvalid) > 4 ? 0x5b : 0x5a; |
| 2000 | qToBigEndian(src: MinInvalid - 1, dest: toolong + 1); |
| 2001 | QTest::addRow(format: "bytearray-2chunked+1-too-big-for-qbytearray-%llx" , MinInvalid) |
| 2002 | << ("\x5f\x41z" + QByteArray(toolong, sizeof(toolong)) + '\xff') |
| 2003 | << 0 << CborErrorDataTooLarge; |
| 2004 | toolong[0] |= 0x20; |
| 2005 | QTest::addRow(format: "string-2chunked+1-too-big-for-qbytearray-%llx" , MinInvalid) |
| 2006 | << ("\x7f\x61z" + QByteArray(toolong, sizeof(toolong)) + '\xff') |
| 2007 | << 0 << CborErrorDataTooLarge; |
| 2008 | |
| 2009 | // These tests say we have arrays and maps with very large item counts. |
| 2010 | // They are meant to ensure we don't pre-allocate a lot of memory |
| 2011 | // unnecessarily and possibly crash the application. The actual number of |
| 2012 | // elements in the stream is only 2, so we should get an unexpected EOF |
| 2013 | // error. QCborValue internally uses 16 bytes per element, so we get to 2 |
| 2014 | // GB at 2^27 elements (32-bit) or, theoretically, 2^63 bytes at 2^59 |
| 2015 | // elements (64-bit). |
| 2016 | if (sizeof(QVector<int>::size_type) == sizeof(int)) { |
| 2017 | // 32-bit sizes (Qt 5 and 32-bit platforms) |
| 2018 | QTest::addRow(format: "very-large-array-no-overflow" ) << raw(data: "\x9a\x07\xff\xff\xff" "\0\0" ) << 0 << CborErrorUnexpectedEOF; |
| 2019 | QTest::addRow(format: "very-large-array-overflow1" ) << raw(data: "\x9a\x40\0\0\0" "\0\0" ) << 0 << CborErrorUnexpectedEOF; |
| 2020 | |
| 2021 | // this makes sure we don't accidentally clip to 32-bit: sending 2^32+2 elements |
| 2022 | QTest::addRow(format: "very-large-array-overflow2" ) << raw(data: "\x9b\0\0\0\1" "\0\0\0\2" "\0\0" ) << 0 << CborErrorDataTooLarge; |
| 2023 | } else { |
| 2024 | // 64-bit Qt 6 |
| 2025 | QTest::addRow(format: "very-large-array-no-overflow" ) << raw(data: "\x9b\x07\xff\xff\xff" "\xff\xff\xff\xff" "\0\0" ); |
| 2026 | QTest::addRow(format: "very-large-array-overflow" ) << raw(data: "\x9b\x40\0\0\0" "\0\0\0\0" "\0\0" ); |
| 2027 | } |
| 2028 | } |
| 2029 | |
| 2030 | void tst_QCborValue::validation() |
| 2031 | { |
| 2032 | QFETCH(QByteArray, data); |
| 2033 | QFETCH(CborError, expectedError); |
| 2034 | QCborError error = { .c: QCborError::Code(expectedError) }; |
| 2035 | |
| 2036 | QCborParserError parserError; |
| 2037 | QCborValue decoded = QCborValue::fromCbor(ba: data, error: &parserError); |
| 2038 | QCOMPARE(parserError.error, error); |
| 2039 | |
| 2040 | if (data.startsWith(c: '\x81')) { |
| 2041 | // decode without the array prefix |
| 2042 | char *ptr = const_cast<char *>(data.constData()); |
| 2043 | QByteArray mid = QByteArray::fromRawData(ptr + 1, size: data.size() - 1); |
| 2044 | decoded = QCborValue::fromCbor(ba: mid, error: &parserError); |
| 2045 | QCOMPARE(parserError.error, error); |
| 2046 | } |
| 2047 | } |
| 2048 | |
| 2049 | void tst_QCborValue::extendedTypeValidation_data() |
| 2050 | { |
| 2051 | QTest::addColumn<QByteArray>(name: "data" ); |
| 2052 | QTest::addColumn<QCborValue>(name: "expected" ); |
| 2053 | |
| 2054 | // QDateTime currently stores time in milliseconds, so make sure |
| 2055 | // we don't overflow |
| 2056 | { |
| 2057 | quint64 limit = std::numeric_limits<quint64>::max() / 1000; |
| 2058 | QTest::newRow(dataTag: "UnixTime_t:integer-overflow-positive" ) |
| 2059 | << encode(a: 0xc1, a: 0x1b, a: limit + 1) |
| 2060 | << QCborValue(QCborKnownTags::UnixTime_t, qint64(limit) + 1); |
| 2061 | QTest::newRow(dataTag: "UnixTime_t:integer-overflow-negative" ) |
| 2062 | << encode(a: 0xc1, a: 0x3b, a: limit) |
| 2063 | << QCborValue(QCborKnownTags::UnixTime_t, -qint64(limit) - 1); |
| 2064 | |
| 2065 | double fplimit = std::numeric_limits<qint64>::min() / (-1000.); // 2^63 ms |
| 2066 | QTest::newRow(dataTag: "UnixTime_t:fp-overflow-positive" ) |
| 2067 | << encode(a: 0xc1, a: 0xfb, a: fplimit) |
| 2068 | << QCborValue(QCborKnownTags::UnixTime_t, fplimit); |
| 2069 | QTest::newRow(dataTag: "UnixTime_t:fp-overflow-negative" ) |
| 2070 | << encode(a: 0xc1, a: 0xfb, a: -fplimit) |
| 2071 | << QCborValue(QCborKnownTags::UnixTime_t, -fplimit); |
| 2072 | } |
| 2073 | |
| 2074 | // But in fact, QCborValue stores date/times as their ISO textual |
| 2075 | // representation, which means it can't represent dates before year 1 or |
| 2076 | // after year 9999. |
| 2077 | { |
| 2078 | QDateTime dt(QDate(-1, 1, 1), QTime(0, 0), Qt::UTC); |
| 2079 | QTest::newRow(dataTag: "UnixTime_t:negative-year" ) |
| 2080 | << encode(a: 0xc1, a: 0x3b, a: quint64(-dt.toSecsSinceEpoch()) - 1) |
| 2081 | << QCborValue(QCborKnownTags::UnixTime_t, dt.toSecsSinceEpoch()); |
| 2082 | |
| 2083 | dt.setDate(QDate(10000, 1, 1)); |
| 2084 | QTest::newRow(dataTag: "UnixTime_t:year10k" ) |
| 2085 | << encode(a: 0xc1, a: 0x1b, a: quint64(dt.toSecsSinceEpoch())) |
| 2086 | << QCborValue(QCborKnownTags::UnixTime_t, dt.toSecsSinceEpoch()); |
| 2087 | } |
| 2088 | |
| 2089 | // Invalid ISO date/time strings |
| 2090 | { |
| 2091 | auto add = [](const char *tag, const char *str) { |
| 2092 | QByteArray raw; |
| 2093 | if (strlen(s: str) < 0x18) |
| 2094 | raw = encode(a: 0xc0, a: 0x60 + int(strlen(s: str)), a: str); |
| 2095 | else |
| 2096 | raw = encode(a: 0xc0, a: 0x78, a: quint8(strlen(s: str)), a: str); |
| 2097 | QTest::addRow(format: "DateTime:%s" , tag) |
| 2098 | << raw << QCborValue(QCborKnownTags::DateTimeString, QString(str)); |
| 2099 | }; |
| 2100 | // tst_QDateTime::fromStringDateFormat has more tests |
| 2101 | add("junk" , "jjj" ); |
| 2102 | add("zoned-date-only" , "2020-04-15Z" ); |
| 2103 | add("month-13" , "2020-13-01T00:00:00Z" ); |
| 2104 | add("negative-month" , "2020--1-01T00:00:00Z" ); |
| 2105 | add("jan-32" , "2020-01-32T00:00:00Z" ); |
| 2106 | add("apr-31" , "2020-04-31T00:00:00Z" ); |
| 2107 | add("feb-30" , "2020-02-30T00:00:00Z" ); |
| 2108 | add("feb-29-nonleap" , "2021-02-29T00:00:00Z" ); |
| 2109 | add("negative-day" , "2020-01--1T00:00:00Z" ); |
| 2110 | add("bad-separator" , "2020-04-15j13:30:59Z" ); |
| 2111 | add("hour-25" , "2020-04-15T25:00:00Z" ); |
| 2112 | add("negative-hour" , "2020-04-15T-1:00:00Z" ); |
| 2113 | add("minute-60" , "2020-04-15T23:60:00Z" ); |
| 2114 | add("negative-minute" , "2020-04-15T23:-1:00Z" ); |
| 2115 | add("second-60" , "2020-04-15T23:59:60Z" ); // not a leap second |
| 2116 | add("negative-second" , "2020-04-15T23:59:-1Z" ); |
| 2117 | add("negative-milli" , "2020-04-15T23.59:59.-1Z" ); |
| 2118 | |
| 2119 | // walking null |
| 2120 | char dt[] = "2020-04-15T17:33:32.125Z" ; |
| 2121 | quint8 len = strlen(s: dt); |
| 2122 | for (int i = 0; i < int(len); ++i) { |
| 2123 | char c = '\0'; |
| 2124 | qSwap(value1&: c, value2&: dt[i]); |
| 2125 | QTest::addRow(format: "DateTime:Null-at-%d" , i) |
| 2126 | << encode(a: 0xc0, a: 0x78, a: len) + QByteArray(dt, len) |
| 2127 | << QCborValue(QCborKnownTags::DateTimeString, QLatin1String(dt, len)); |
| 2128 | qSwap(value1&: c, value2&: dt[i]); |
| 2129 | } |
| 2130 | } |
| 2131 | |
| 2132 | // Improperly-encoded URLs |
| 2133 | { |
| 2134 | const char badurl[] = "%zz" ; |
| 2135 | QTest::newRow(dataTag: "Url:Invalid" ) |
| 2136 | << encode(a: 0xd8, a: int(QCborKnownTags::Url), a: 0x60 + int(strlen(s: badurl)), a: badurl) |
| 2137 | << QCborValue(QCborKnownTags::Url, QLatin1String(badurl)); |
| 2138 | } |
| 2139 | } |
| 2140 | |
| 2141 | void tst_QCborValue::extendedTypeValidation() |
| 2142 | { |
| 2143 | QFETCH(QByteArray, data); |
| 2144 | QFETCH(QCborValue, expected); |
| 2145 | |
| 2146 | QCborParserError error; |
| 2147 | QCborValue decoded = QCborValue::fromCbor(ba: data, error: &error); |
| 2148 | QVERIFY2(error.error == QCborError(), qPrintable(error.errorString())); |
| 2149 | QCOMPARE(error.offset, data.size()); |
| 2150 | QCOMPARE(decoded, expected); |
| 2151 | |
| 2152 | QByteArray encoded = decoded.toCbor(); |
| 2153 | #if QT_VERSION < QT_VERSION_CHECK(6,0,0) |
| 2154 | // behavior change, see qdatetime.cpp:fromIsoTimeString |
| 2155 | QEXPECT_FAIL("DateTime:Null-at-19" , "QDateTime parsing fixed, but only in 6.0" , Abort); |
| 2156 | #endif |
| 2157 | QCOMPARE(encoded, data); |
| 2158 | } |
| 2159 | |
| 2160 | void tst_QCborValue::hugeDeviceValidation_data() |
| 2161 | { |
| 2162 | // because QCborValue will attempt to retain the original string in UTF-8, |
| 2163 | // the size which it can't store is actually the byte array size |
| 2164 | addValidationHugeDevice(byteArrayInvalid: MaxByteArraySize + 1, stringInvalid: MaxByteArraySize + 1); |
| 2165 | } |
| 2166 | |
| 2167 | void tst_QCborValue::hugeDeviceValidation() |
| 2168 | { |
| 2169 | QFETCH(QSharedPointer<QIODevice>, device); |
| 2170 | QFETCH(CborError, expectedError); |
| 2171 | QCborError error = { .c: QCborError::Code(expectedError) }; |
| 2172 | |
| 2173 | device->open(mode: QIODevice::ReadOnly | QIODevice::Unbuffered); |
| 2174 | QCborStreamReader reader(device.data()); |
| 2175 | QCborValue decoded = QCborValue::fromCbor(reader); |
| 2176 | QCOMPARE(reader.lastError(), error); |
| 2177 | } |
| 2178 | |
| 2179 | void tst_QCborValue::recursionLimit_data() |
| 2180 | { |
| 2181 | constexpr int RecursionAttempts = 4096; |
| 2182 | QTest::addColumn<QByteArray>(name: "data" ); |
| 2183 | QByteArray arrays(RecursionAttempts, char(0x81)); |
| 2184 | QByteArray _arrays(RecursionAttempts, char(0x9f)); |
| 2185 | QByteArray maps(RecursionAttempts, char(0xa1)); |
| 2186 | QByteArray _maps(RecursionAttempts, char(0xbf)); |
| 2187 | QByteArray tags(RecursionAttempts, char(0xc0)); |
| 2188 | |
| 2189 | QTest::newRow(dataTag: "array-nesting-too-deep" ) << arrays; |
| 2190 | QTest::newRow(dataTag: "_array-nesting-too-deep" ) << _arrays; |
| 2191 | QTest::newRow(dataTag: "map-nesting-too-deep" ) << maps; |
| 2192 | QTest::newRow(dataTag: "_map-nesting-too-deep" ) << _maps; |
| 2193 | QTest::newRow(dataTag: "tag-nesting-too-deep" ) << tags; |
| 2194 | |
| 2195 | QByteArray mixed(5 * RecursionAttempts, Qt::Uninitialized); |
| 2196 | char *ptr = mixed.data(); |
| 2197 | for (int i = 0; i < RecursionAttempts; ++i) { |
| 2198 | quint8 type = qBound(min: quint8(QCborStreamReader::Array), val: quint8(i & 0x80), max: quint8(QCborStreamReader::Tag)); |
| 2199 | quint8 additional_info = i & 0x1f; |
| 2200 | if (additional_info == 0x1f) |
| 2201 | (void)additional_info; // leave it |
| 2202 | else if (additional_info > 0x1a) |
| 2203 | additional_info = 0x1a; |
| 2204 | else if (additional_info < 1) |
| 2205 | additional_info = 1; |
| 2206 | |
| 2207 | *ptr++ = type | additional_info; |
| 2208 | if (additional_info == 0x18) { |
| 2209 | *ptr++ = uchar(i); |
| 2210 | } else if (additional_info == 0x19) { |
| 2211 | qToBigEndian(src: ushort(i), dest: ptr); |
| 2212 | ptr += 2; |
| 2213 | } else if (additional_info == 0x1a) { |
| 2214 | qToBigEndian(src: uint(i), dest: ptr); |
| 2215 | ptr += 4; |
| 2216 | } |
| 2217 | } |
| 2218 | |
| 2219 | QTest::newRow(dataTag: "mixed-nesting-too-deep" ) << mixed; |
| 2220 | } |
| 2221 | |
| 2222 | void tst_QCborValue::recursionLimit() |
| 2223 | { |
| 2224 | QFETCH(QByteArray, data); |
| 2225 | |
| 2226 | QCborParserError error; |
| 2227 | QCborValue decoded = QCborValue::fromCbor(ba: data, error: &error); |
| 2228 | QCOMPARE(error.error, QCborError::NestingTooDeep); |
| 2229 | } |
| 2230 | |
| 2231 | void tst_QCborValue::toDiagnosticNotation_data() |
| 2232 | { |
| 2233 | QTest::addColumn<QCborValue>(name: "v" ); |
| 2234 | QTest::addColumn<int>(name: "opts" ); |
| 2235 | QTest::addColumn<QString>(name: "expected" ); |
| 2236 | QDateTime dt = QDateTime::currentDateTimeUtc(); |
| 2237 | QUuid uuid = QUuid::createUuid(); |
| 2238 | |
| 2239 | QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>(); |
| 2240 | auto add = [me](const QCborValue &v, const QString &exp) { |
| 2241 | auto addRow = [=](const char *prefix) -> QTestData & { |
| 2242 | QCborValue::Type t = v.type(); |
| 2243 | if (t == QCborValue::Integer) |
| 2244 | return QTest::addRow(format: "%sInteger:%lld" , prefix, v.toInteger()); |
| 2245 | if (t == QCborValue::Double) |
| 2246 | return QTest::addRow(format: "%sDouble:%g" , prefix, v.toDouble()); |
| 2247 | if (t == QCborValue::ByteArray) |
| 2248 | return QTest::addRow(format: "%sByteArray:%d" , prefix, v.toByteArray().size()); |
| 2249 | if (t == QCborValue::String) |
| 2250 | return QTest::addRow(format: "%sString:%d" , prefix, v.toString().size()); |
| 2251 | |
| 2252 | QByteArray typeString = me.valueToKey(value: t); |
| 2253 | Q_ASSERT(!typeString.isEmpty()); |
| 2254 | return QTest::newRow(dataTag: prefix + typeString); |
| 2255 | }; |
| 2256 | addRow("" ) << v << int(QCborValue::DiagnosticNotationOptions{}) << exp; |
| 2257 | addRow("LW:" ) << v << int(QCborValue::LineWrapped) << exp; |
| 2258 | addRow("Array:" ) << QCborValue(QCborArray{v}) << int(QCborValue::DiagnosticNotationOptions{}) << '[' + exp + ']'; |
| 2259 | addRow("Mapped:" ) << QCborValue(QCborMap{{2, v}}) << int(QCborValue::DiagnosticNotationOptions{}) << "{2: " + exp + '}'; |
| 2260 | addRow("Mapping:" ) << QCborValue(QCborMap{{v, 2}}) << int(QCborValue::DiagnosticNotationOptions{}) << '{' + exp + ": 2}" ; |
| 2261 | }; |
| 2262 | |
| 2263 | // empty arrays and maps |
| 2264 | QTest::newRow(dataTag: "EmptyArray" ) |
| 2265 | << QCborValue(QCborArray()) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2266 | << "[]" ; |
| 2267 | QTest::newRow(dataTag: "EmptyMap" ) |
| 2268 | << QCborValue(QCborMap()) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2269 | << "{}" ; |
| 2270 | |
| 2271 | add(QCborValue(), "undefined" ); |
| 2272 | add(QCborValue::Null, "null" ); |
| 2273 | add(false, "false" ); |
| 2274 | add(true, "true" ); |
| 2275 | add(QCborSimpleType(0), "simple(0)" ); |
| 2276 | QTest::newRow(dataTag: "SimpleType-255" ) |
| 2277 | << QCborValue(QCborSimpleType(255)) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2278 | << "simple(255)" ; |
| 2279 | add(0, "0" ); |
| 2280 | add(1, "1" ); |
| 2281 | add(-1, "-1" ); |
| 2282 | add(std::numeric_limits<qint64>::min(), QString::number(std::numeric_limits<qint64>::min())); |
| 2283 | add(std::numeric_limits<qint64>::max(), QString::number(std::numeric_limits<qint64>::max())); |
| 2284 | add(0., "0.0" ); |
| 2285 | add(1.25, "1.25" ); |
| 2286 | add(-1.25, "-1.25" ); |
| 2287 | add(qInf(), "inf" ); |
| 2288 | add(-qInf(), "-inf" ); |
| 2289 | add(qQNaN(), "nan" ); |
| 2290 | add(QByteArray(), "h''" ); |
| 2291 | add(QByteArray("Hello" ), "h'48656c6c6f'" ); |
| 2292 | add(QLatin1String(), QLatin1String("\"\"" )); |
| 2293 | add("Hello" , "\"Hello\"" ); |
| 2294 | add("\"Hello\\World\"" , "\"\\\"Hello\\\\World\\\"\"" ); |
| 2295 | add(QCborValue(dt), "0(\"" + dt.toString(format: Qt::ISODateWithMs) + "\")" ); |
| 2296 | add(QCborValue(QUrl("http://example.com" )), "32(\"http://example.com\")" ); |
| 2297 | add(QCborValue(QRegularExpression("^.*$" )), "35(\"^.*$\")" ); |
| 2298 | add(QCborValue(uuid), "37(h'" + uuid.toString(mode: QUuid::Id128) + "')" ); |
| 2299 | |
| 2300 | // arrays and maps with more than one element |
| 2301 | QTest::newRow(dataTag: "2Array" ) |
| 2302 | << QCborValue(QCborArray{0, 1}) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2303 | << "[0, 1]" ; |
| 2304 | QTest::newRow(dataTag: "2Map" ) |
| 2305 | << QCborValue(QCborMap{{0, 1}, {"foo" , "bar" }}) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2306 | << "{0: 1, \"foo\": \"bar\"}" ; |
| 2307 | |
| 2308 | // line wrapping in arrays and maps |
| 2309 | QTest::newRow(dataTag: "LW:EmptyArray" ) |
| 2310 | << QCborValue(QCborArray()) << int(QCborValue::LineWrapped) |
| 2311 | << "[\n]" ; |
| 2312 | QTest::newRow(dataTag: "LW:EmptyMap" ) |
| 2313 | << QCborValue(QCborMap()) << int(QCborValue::LineWrapped) |
| 2314 | << "{\n}" ; |
| 2315 | QTest::newRow(dataTag: "LW:Array:Integer:0" ) |
| 2316 | << QCborValue(QCborArray{0}) << int(QCborValue::LineWrapped) |
| 2317 | << "[\n 0\n]" ; |
| 2318 | QTest::newRow(dataTag: "LW:Array:String:5" ) |
| 2319 | << QCborValue(QCborArray{"Hello" }) << int(QCborValue::LineWrapped) |
| 2320 | << "[\n \"Hello\"\n]" ; |
| 2321 | QTest::newRow(dataTag: "LW:Map:0-0" ) |
| 2322 | << QCborValue(QCborMap{{0, 0}}) << int(QCborValue::LineWrapped) |
| 2323 | << "{\n 0: 0\n}" ; |
| 2324 | QTest::newRow(dataTag: "LW:Map:String:5" ) |
| 2325 | << QCborValue(QCborMap{{0, "Hello" }}) << int(QCborValue::LineWrapped) |
| 2326 | << "{\n 0: \"Hello\"\n}" ; |
| 2327 | QTest::newRow(dataTag: "LW:2Array" ) |
| 2328 | << QCborValue(QCborArray{0, 1}) << int(QCborValue::LineWrapped) |
| 2329 | << "[\n 0,\n 1\n]" ; |
| 2330 | QTest::newRow(dataTag: "LW:2Map" ) |
| 2331 | << QCborValue(QCborMap{{0, 0}, {"foo" , "bar" }}) << int(QCborValue::LineWrapped) |
| 2332 | << "{\n 0: 0,\n \"foo\": \"bar\"\n}" ; |
| 2333 | |
| 2334 | // nested arrays and maps |
| 2335 | QTest::newRow(dataTag: "Array:EmptyArray" ) |
| 2336 | << QCborValue(QCborArray() << QCborArray()) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2337 | << "[[]]" ; |
| 2338 | QTest::newRow(dataTag: "Array:EmptyMap" ) |
| 2339 | << QCborValue(QCborArray() << QCborMap()) << int(QCborValue::DiagnosticNotationOptions{}) |
| 2340 | << "[{}]" ; |
| 2341 | QTest::newRow(dataTag: "LW:Array:EmptyArray" ) |
| 2342 | << QCborValue(QCborArray() << QCborArray()) << int(QCborValue::LineWrapped) |
| 2343 | << "[\n [\n ]\n]" ; |
| 2344 | QTest::newRow(dataTag: "LW:Array:EmptyMap" ) |
| 2345 | << QCborValue(QCborArray() << QCborMap()) << int(QCborValue::LineWrapped) |
| 2346 | << "[\n {\n }\n]" ; |
| 2347 | QTest::newRow(dataTag: "LW:Array:2Array" ) |
| 2348 | << QCborValue(QCborArray() << QCborArray{0, 1}) << int(QCborValue::LineWrapped) |
| 2349 | << "[\n [\n 0,\n 1\n ]\n]" ; |
| 2350 | QTest::newRow(dataTag: "LW:Map:2Array" ) |
| 2351 | << QCborValue(QCborMap{{0, QCborArray{0, 1}}}) << int(QCborValue::LineWrapped) |
| 2352 | << "{\n 0: [\n 0,\n 1\n ]\n}" ; |
| 2353 | QTest::newRow(dataTag: "LW:Map:2Map" ) |
| 2354 | << QCborValue(QCborMap{{-1, QCborMap{{0, 0}, {"foo" , "bar" }}}}) << int(QCborValue::LineWrapped) |
| 2355 | << "{\n -1: {\n 0: 0,\n \"foo\": \"bar\"\n }\n}" ; |
| 2356 | |
| 2357 | // string escaping |
| 2358 | QTest::newRow(dataTag: "String:escaping" ) |
| 2359 | << QCborValue("\1\a\b\t\f\r\n\v\x1f\x7f \"\xc2\xa0\xe2\x82\xac\xf0\x90\x80\x80\\\"" ) |
| 2360 | << int(QCborValue::DiagnosticNotationOptions{}) |
| 2361 | << "\"\\u0001\\a\\b\\t\\f\\r\\n\\v\\u001F\\u007F \\\"\\u00A0\\u20AC\\U00010000\\\\\\\"\"" ; |
| 2362 | |
| 2363 | // extended formatting for byte arrays |
| 2364 | QTest::newRow(dataTag: "Extended:ByteArray:0" ) |
| 2365 | << QCborValue(QByteArray()) << int(QCborValue::ExtendedFormat) |
| 2366 | << "h''" ; |
| 2367 | QTest::newRow(dataTag: "Extended:ByteArray:5" ) |
| 2368 | << QCborValue(QByteArray("Hello" )) << int(QCborValue::ExtendedFormat) |
| 2369 | << "h'48 65 6c 6c 6f'" ; |
| 2370 | QTest::newRow(dataTag: "Extended:ByteArray:Base64url" ) |
| 2371 | << QCborValue(QCborKnownTags::ExpectedBase64url, QByteArray("\xff\xef" )) |
| 2372 | << int(QCborValue::ExtendedFormat) << "21(b64'_-8')" ; |
| 2373 | QTest::newRow(dataTag: "Extended:ByteArray:Base64" ) |
| 2374 | << QCborValue(QCborKnownTags::ExpectedBase64, QByteArray("\xff\xef" )) |
| 2375 | << int(QCborValue::ExtendedFormat) << "22(b64'/+8=')" ; |
| 2376 | |
| 2377 | // formatting applies through arrays too |
| 2378 | QTest::newRow(dataTag: "Extended:Array:ByteArray:Base64url" ) |
| 2379 | << QCborValue(QCborKnownTags::ExpectedBase64url, QCborArray{QByteArray("\xff\xef" )}) |
| 2380 | << int(QCborValue::ExtendedFormat) << "21([b64'_-8'])" ; |
| 2381 | // and only the innermost applies |
| 2382 | QTest::newRow(dataTag: "ByteArray:multiple-tags" ) |
| 2383 | << QCborValue(QCborKnownTags::ExpectedBase64url, |
| 2384 | QCborArray{QCborValue(QCborKnownTags::ExpectedBase16, QByteArray("Hello" )), |
| 2385 | QByteArray("\xff\xef" )}) |
| 2386 | << int(QCborValue::ExtendedFormat) << "21([23(h'48 65 6c 6c 6f'), b64'_-8'])" ; |
| 2387 | } |
| 2388 | |
| 2389 | void tst_QCborValue::toDiagnosticNotation() |
| 2390 | { |
| 2391 | QFETCH(QCborValue, v); |
| 2392 | QFETCH(QString, expected); |
| 2393 | QFETCH(int, opts); |
| 2394 | |
| 2395 | QString result = v.toDiagnosticNotation(opts: QCborValue::DiagnosticNotationOptions(opts)); |
| 2396 | QCOMPARE(result, expected); |
| 2397 | } |
| 2398 | |
| 2399 | |
| 2400 | void tst_QCborValue::datastreamSerialization_data() |
| 2401 | { |
| 2402 | addCommonCborData(); |
| 2403 | } |
| 2404 | |
| 2405 | void tst_QCborValue::datastreamSerialization() |
| 2406 | { |
| 2407 | QFETCH(QCborValue, v); |
| 2408 | QByteArray buffer; |
| 2409 | { |
| 2410 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2411 | save << v; |
| 2412 | QDataStream load(buffer); |
| 2413 | QCborValue output; |
| 2414 | load >> output; |
| 2415 | QCOMPARE(output, v); |
| 2416 | } |
| 2417 | if (v.isArray()) { |
| 2418 | QCborArray array = v.toArray(); |
| 2419 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2420 | save << array; |
| 2421 | QDataStream load(buffer); |
| 2422 | QCborValue output; |
| 2423 | load >> output; |
| 2424 | QCOMPARE(output, array); |
| 2425 | } else if (v.isMap()) { |
| 2426 | QCborMap map = v.toMap(); |
| 2427 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2428 | save << map; |
| 2429 | QDataStream load(buffer); |
| 2430 | QCborValue output; |
| 2431 | load >> output; |
| 2432 | QCOMPARE(output, map); |
| 2433 | } |
| 2434 | } |
| 2435 | |
| 2436 | void tst_QCborValue::streamVariantSerialization() |
| 2437 | { |
| 2438 | // Check interface only, implementation is tested through to and from |
| 2439 | // cbor functions. |
| 2440 | QByteArray buffer; |
| 2441 | { |
| 2442 | QCborArray array{665, 666, 667}; |
| 2443 | QVariant output; |
| 2444 | QVariant variant = QVariant::fromValue(value: array); |
| 2445 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2446 | save << variant; |
| 2447 | QDataStream load(buffer); |
| 2448 | load >> output; |
| 2449 | QCOMPARE(output.userType(), QMetaType::QCborArray); |
| 2450 | QCOMPARE(qvariant_cast<QCborArray>(output), array); |
| 2451 | } |
| 2452 | { |
| 2453 | QCborMap obj{{"foo" , 42}}; |
| 2454 | QVariant output; |
| 2455 | QVariant variant = QVariant::fromValue(value: obj); |
| 2456 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2457 | save << variant; |
| 2458 | QDataStream load(buffer); |
| 2459 | load >> output; |
| 2460 | QCOMPARE(output.userType(), QMetaType::QCborMap); |
| 2461 | QCOMPARE(qvariant_cast<QCborMap>(output), obj); |
| 2462 | } |
| 2463 | { |
| 2464 | QCborValue value{42}; |
| 2465 | QVariant output; |
| 2466 | QVariant variant = QVariant::fromValue(value); |
| 2467 | QDataStream save(&buffer, QIODevice::WriteOnly); |
| 2468 | save << variant; |
| 2469 | QDataStream load(buffer); |
| 2470 | load >> output; |
| 2471 | QCOMPARE(output.userType(), QMetaType::QCborValue); |
| 2472 | QCOMPARE(qvariant_cast<QCborValue>(output), value); |
| 2473 | } |
| 2474 | } |
| 2475 | |
| 2476 | QTEST_MAIN(tst_QCborValue) |
| 2477 | |
| 2478 | #include "tst_qcborvalue.moc" |
| 2479 | |