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 | |