1 | // Copyright (C) 2021 The Qt Company Ltd. |
---|---|
2 | // Copyright (C) 2024 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #ifndef QTESTTOSTRING_H |
6 | #define QTESTTOSTRING_H |
7 | |
8 | #include <QtTest/qttestglobal.h> |
9 | |
10 | #include <QtCore/qttypetraits.h> |
11 | |
12 | #if QT_CONFIG(itemmodel) |
13 | # include <QtCore/qabstractitemmodel.h> |
14 | #endif |
15 | #include <QtCore/qbitarray.h> |
16 | #include <QtCore/qbytearray.h> |
17 | #include <QtCore/qcborarray.h> |
18 | #include <QtCore/qcborcommon.h> |
19 | #include <QtCore/qcbormap.h> |
20 | #include <QtCore/qcborvalue.h> |
21 | #include <QtCore/qdatetime.h> |
22 | #include <QtCore/qmetaobject.h> |
23 | #include <QtCore/qmetatype.h> |
24 | #include <QtCore/qobject.h> |
25 | #include <QtCore/qpoint.h> |
26 | #include <QtCore/qrect.h> |
27 | #include <QtCore/qsize.h> |
28 | #include <QtCore/qstring.h> |
29 | #include <QtCore/qstringlist.h> |
30 | #include <QtCore/qurl.h> |
31 | #include <QtCore/quuid.h> |
32 | #include <QtCore/qvariant.h> |
33 | |
34 | #include <cstdio> |
35 | |
36 | QT_BEGIN_NAMESPACE |
37 | |
38 | namespace QTest { |
39 | namespace Internal { |
40 | |
41 | template<typename T> // Output registered enums |
42 | inline typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, char*>::type toString(T e) |
43 | { |
44 | QMetaEnum me = QMetaEnum::fromType<T>(); |
45 | return qstrdup(me.valueToKey(value: int(e))); // int cast is necessary to support enum classes |
46 | } |
47 | |
48 | template <typename T> |
49 | inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && std::is_enum_v<T>, char*>::type toString(const T &e) |
50 | { |
51 | return qstrdup(QByteArray::number(static_cast<std::underlying_type_t<T>>(e)).constData()); |
52 | } |
53 | |
54 | template <typename T> // Fallback; for built-in types debug streaming must be possible |
55 | inline typename std::enable_if<!QtPrivate::IsQEnumHelper<T>::Value && !std::is_enum_v<T>, char *>::type toString(const T &t) |
56 | { |
57 | char *result = nullptr; |
58 | #ifndef QT_NO_DEBUG_STREAM |
59 | if constexpr (QTypeTraits::has_ostream_operator_v<QDebug, T>) { |
60 | result = qstrdup(QDebug::toString(t).toUtf8().constData()); |
61 | } else { |
62 | static_assert(!QMetaTypeId2<T>::IsBuiltIn, |
63 | "Built-in type must implement debug streaming operator " |
64 | "or provide QTest::toString specialization"); |
65 | } |
66 | #endif |
67 | return result; |
68 | } |
69 | |
70 | template<typename F> // Output QFlags of registered enumerations |
71 | inline typename std::enable_if<QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f) |
72 | { |
73 | const QMetaEnum me = QMetaEnum::fromType<F>(); |
74 | return qstrdup(me.valueToKeys(value: int(f.toInt())).constData()); |
75 | } |
76 | |
77 | template <typename F> // Fallback: Output hex value |
78 | inline typename std::enable_if<!QtPrivate::IsQEnumHelper<F>::Value, char*>::type toString(QFlags<F> f) |
79 | { |
80 | const size_t space = 3 + 2 * sizeof(unsigned); // 2 for 0x, two hex digits per byte, 1 for '\0' |
81 | char *msg = new char[space]; |
82 | std::snprintf(s: msg, maxlen: space, format: "0x%x", unsigned(f.toInt())); |
83 | return msg; |
84 | } |
85 | |
86 | } // namespace Internal |
87 | |
88 | Q_TESTLIB_EXPORT bool compare_string_helper(const char *t1, const char *t2, const char *actual, |
89 | const char *expected, const char *file, int line); |
90 | Q_TESTLIB_EXPORT char *formatString(const char *prefix, const char *suffix, size_t numArguments, ...); |
91 | Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, qsizetype length); |
92 | Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, qsizetype length); |
93 | Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string); |
94 | |
95 | template <typename T> |
96 | inline char *toString(const T &t) |
97 | { |
98 | return Internal::toString(t); |
99 | } |
100 | |
101 | template <typename T1, typename T2> |
102 | inline char *toString(const std::pair<T1, T2> &pair); |
103 | |
104 | template <class... Types> |
105 | inline char *toString(const std::tuple<Types...> &tuple); |
106 | |
107 | template <typename Rep, typename Period> |
108 | inline char *toString(std::chrono::duration<Rep, Period> duration); |
109 | |
110 | Q_TESTLIB_EXPORT char *toString(const char *); |
111 | Q_TESTLIB_EXPORT char *toString(const volatile void *); |
112 | Q_TESTLIB_EXPORT char *toString(const QObject *); |
113 | Q_TESTLIB_EXPORT char *toString(const volatile QObject *); |
114 | |
115 | #define QTEST_COMPARE_DECL(KLASS)\ |
116 | template<> Q_TESTLIB_EXPORT char *toString<KLASS >(const KLASS &); |
117 | #ifndef Q_QDOC |
118 | QTEST_COMPARE_DECL(short) |
119 | QTEST_COMPARE_DECL(ushort) |
120 | QTEST_COMPARE_DECL(int) |
121 | QTEST_COMPARE_DECL(uint) |
122 | QTEST_COMPARE_DECL(long) |
123 | QTEST_COMPARE_DECL(ulong) |
124 | QTEST_COMPARE_DECL(qint64) |
125 | QTEST_COMPARE_DECL(quint64) |
126 | |
127 | QTEST_COMPARE_DECL(float) |
128 | QTEST_COMPARE_DECL(double) |
129 | QTEST_COMPARE_DECL(qfloat16) |
130 | QTEST_COMPARE_DECL(char) |
131 | QTEST_COMPARE_DECL(signed char) |
132 | QTEST_COMPARE_DECL(unsigned char) |
133 | QTEST_COMPARE_DECL(bool) |
134 | #endif |
135 | #undef QTEST_COMPARE_DECL |
136 | |
137 | template <> inline char *toString(const QStringView &str) |
138 | { |
139 | return QTest::toPrettyUnicode(string: str); |
140 | } |
141 | |
142 | template<> inline char *toString(const QString &str) |
143 | { |
144 | return toString(str: QStringView(str)); |
145 | } |
146 | |
147 | template<> inline char *toString(const QLatin1StringView &str) |
148 | { |
149 | return toString(str: QString(str)); |
150 | } |
151 | |
152 | template<> inline char *toString(const QByteArray &ba) |
153 | { |
154 | return QTest::toPrettyCString(unicode: ba.constData(), length: ba.size()); |
155 | } |
156 | |
157 | template<> inline char *toString(const QBitArray &ba) |
158 | { |
159 | qsizetype size = ba.size(); |
160 | char *str = new char[size + 1]; |
161 | for (qsizetype i = 0; i < size; ++i) |
162 | str[i] = "01"[ba.testBit(i)]; |
163 | str[size] = '\0'; |
164 | return str; |
165 | } |
166 | |
167 | #if QT_CONFIG(datestring) |
168 | template<> inline char *toString(const QTime &time) |
169 | { |
170 | return time.isValid() |
171 | ? qstrdup(qPrintable(time.toString(u"hh:mm:ss.zzz"))) |
172 | : qstrdup("Invalid QTime"); |
173 | } |
174 | |
175 | template<> inline char *toString(const QDate &date) |
176 | { |
177 | return date.isValid() |
178 | ? qstrdup(qPrintable(date.toString(u"yyyy/MM/dd"))) |
179 | : qstrdup("Invalid QDate"); |
180 | } |
181 | |
182 | template<> inline char *toString(const QDateTime &dateTime) |
183 | { |
184 | return dateTime.isValid() |
185 | ? qstrdup(qPrintable(dateTime.toString(u"yyyy/MM/dd hh:mm:ss.zzz[t]"))) |
186 | : qstrdup("Invalid QDateTime"); |
187 | } |
188 | #endif // datestring |
189 | |
190 | template<> inline char *toString(const QCborError &c) |
191 | { |
192 | // use the Q_ENUM formatting |
193 | return toString(t: c.c); |
194 | } |
195 | |
196 | template<> inline char *toString(const QChar &c) |
197 | { |
198 | const ushort uc = c.unicode(); |
199 | if (uc < 128) { |
200 | char msg[32]; |
201 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QChar: '%c' (0x%x)", char(uc), unsigned(uc)); |
202 | return qstrdup(msg); |
203 | } |
204 | return qstrdup(qPrintable(QString::fromLatin1("QChar: '%1' (0x%2)").arg(c).arg(QString::number(static_cast<int>(c.unicode()), 16)))); |
205 | } |
206 | |
207 | #if QT_CONFIG(itemmodel) |
208 | template<> inline char *toString(const QModelIndex &idx) |
209 | { |
210 | char msg[128]; |
211 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QModelIndex(%d,%d,%p,%p)", |
212 | idx.row(), idx.column(), idx.internalPointer(), |
213 | static_cast<const void*>(idx.model())); |
214 | return qstrdup(msg); |
215 | } |
216 | #endif |
217 | |
218 | template<> inline char *toString(const QPoint &p) |
219 | { |
220 | char msg[128]; |
221 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QPoint(%d,%d)", p.x(), p.y()); |
222 | return qstrdup(msg); |
223 | } |
224 | |
225 | template<> inline char *toString(const QSize &s) |
226 | { |
227 | char msg[128]; |
228 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QSize(%dx%d)", s.width(), s.height()); |
229 | return qstrdup(msg); |
230 | } |
231 | |
232 | template<> inline char *toString(const QRect &s) |
233 | { |
234 | char msg[256]; |
235 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QRect(%d,%d %dx%d) (bottomright %d,%d)", |
236 | s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom()); |
237 | return qstrdup(msg); |
238 | } |
239 | |
240 | template<> inline char *toString(const QPointF &p) |
241 | { |
242 | char msg[64]; |
243 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QPointF(%g,%g)", p.x(), p.y()); |
244 | return qstrdup(msg); |
245 | } |
246 | |
247 | template<> inline char *toString(const QSizeF &s) |
248 | { |
249 | char msg[64]; |
250 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QSizeF(%gx%g)", s.width(), s.height()); |
251 | return qstrdup(msg); |
252 | } |
253 | |
254 | template<> inline char *toString(const QRectF &s) |
255 | { |
256 | char msg[256]; |
257 | std::snprintf(s: msg, maxlen: sizeof(msg), format: "QRectF(%g,%g %gx%g) (bottomright %g,%g)", |
258 | s.left(), s.top(), s.width(), s.height(), s.right(), s.bottom()); |
259 | return qstrdup(msg); |
260 | } |
261 | |
262 | template<> inline char *toString(const QUrl &uri) |
263 | { |
264 | if (!uri.isValid()) |
265 | return qstrdup(qPrintable(QLatin1StringView("Invalid URL: ") + uri.errorString())); |
266 | return qstrdup(uri.toEncoded().constData()); |
267 | } |
268 | |
269 | template <> inline char *toString(const QUuid &uuid) |
270 | { |
271 | return qstrdup(uuid.toByteArray().constData()); |
272 | } |
273 | |
274 | template<> inline char *toString(const QVariant &v) |
275 | { |
276 | QByteArray vstring("QVariant("); |
277 | if (v.isValid()) { |
278 | QByteArray type(v.typeName()); |
279 | if (type.isEmpty()) { |
280 | type = QByteArray::number(v.userType()); |
281 | } |
282 | vstring.append(a: type); |
283 | if (!v.isNull()) { |
284 | vstring.append(c: ','); |
285 | if (v.canConvert<QString>()) { |
286 | vstring.append(a: v.toString().toLocal8Bit()); |
287 | } |
288 | else { |
289 | vstring.append(s: "<value not representable as string>"); |
290 | } |
291 | } |
292 | } |
293 | vstring.append(c: ')'); |
294 | |
295 | return qstrdup(vstring.constData()); |
296 | } |
297 | |
298 | template<> inline char *toString(const QPartialOrdering &o) |
299 | { |
300 | if (o == QPartialOrdering::Less) |
301 | return qstrdup("Less"); |
302 | if (o == QPartialOrdering::Equivalent) |
303 | return qstrdup("Equivalent"); |
304 | if (o == QPartialOrdering::Greater) |
305 | return qstrdup("Greater"); |
306 | if (o == QPartialOrdering::Unordered) |
307 | return qstrdup("Unordered"); |
308 | return qstrdup("<invalid>"); |
309 | } |
310 | |
311 | namespace Internal { |
312 | struct QCborValueFormatter |
313 | { |
314 | enum { BufferLen = 256 }; |
315 | static char *formatSimpleType(QCborSimpleType st) |
316 | { |
317 | char *buf = new char[BufferLen]; |
318 | std::snprintf(s: buf, maxlen: BufferLen, format: "QCborValue(QCborSimpleType(%d))", int(st)); |
319 | return buf; |
320 | } |
321 | |
322 | static char *formatTag(QCborTag tag, const QCborValue &taggedValue) |
323 | { |
324 | QScopedArrayPointer<char> hold(format(v: taggedValue)); |
325 | char *buf = new char[BufferLen]; |
326 | std::snprintf(s: buf, maxlen: BufferLen, format: "QCborValue(QCborTag(%llu), %s)", |
327 | qToUnderlying(e: tag), hold.get()); |
328 | return buf; |
329 | } |
330 | |
331 | static char *innerFormat(QCborValue::Type t, const char *str) |
332 | { |
333 | static const QMetaEnum typeEnum = []() { |
334 | int idx = QCborValue::staticMetaObject.indexOfEnumerator(name: "Type"); |
335 | return QCborValue::staticMetaObject.enumerator(index: idx); |
336 | }(); |
337 | |
338 | char *buf = new char[BufferLen]; |
339 | const char *typeName = typeEnum.valueToKey(value: t); |
340 | if (typeName) |
341 | std::snprintf(s: buf, maxlen: BufferLen, format: "QCborValue(%s, %s)", typeName, str); |
342 | else |
343 | std::snprintf(s: buf, maxlen: BufferLen, format: "QCborValue(<unknown type 0x%02x>)", t); |
344 | return buf; |
345 | } |
346 | |
347 | template<typename T> static char *format(QCborValue::Type type, const T &t) |
348 | { |
349 | QScopedArrayPointer<char> hold(QTest::toString(t)); |
350 | return innerFormat(t: type, str: hold.get()); |
351 | } |
352 | |
353 | static char *format(const QCborValue &v) |
354 | { |
355 | switch (v.type()) { |
356 | case QCborValue::Integer: |
357 | return format(type: v.type(), t: v.toInteger()); |
358 | case QCborValue::ByteArray: |
359 | return format(type: v.type(), t: v.toByteArray()); |
360 | case QCborValue::String: |
361 | return format(type: v.type(), t: v.toString()); |
362 | case QCborValue::Array: |
363 | return innerFormat(t: v.type(), str: QScopedArrayPointer<char>(format(a: v.toArray())).get()); |
364 | case QCborValue::Map: |
365 | return innerFormat(t: v.type(), str: QScopedArrayPointer<char>(format(m: v.toMap())).get()); |
366 | case QCborValue::Tag: |
367 | return formatTag(tag: v.tag(), taggedValue: v.taggedValue()); |
368 | case QCborValue::SimpleType: |
369 | break; |
370 | case QCborValue::True: |
371 | return qstrdup("QCborValue(true)"); |
372 | case QCborValue::False: |
373 | return qstrdup("QCborValue(false)"); |
374 | case QCborValue::Null: |
375 | return qstrdup("QCborValue(nullptr)"); |
376 | case QCborValue::Undefined: |
377 | return qstrdup("QCborValue()"); |
378 | case QCborValue::Double: |
379 | return format(type: v.type(), t: v.toDouble()); |
380 | case QCborValue::DateTime: |
381 | case QCborValue::Url: |
382 | case QCborValue::RegularExpression: |
383 | return format(type: v.type(), t: v.taggedValue().toString()); |
384 | case QCborValue::Uuid: |
385 | return format(type: v.type(), t: v.toUuid()); |
386 | case QCborValue::Invalid: |
387 | return qstrdup("QCborValue(<invalid>)"); |
388 | } |
389 | |
390 | if (v.isSimpleType()) |
391 | return formatSimpleType(st: v.toSimpleType()); |
392 | return innerFormat(t: v.type(), str: ""); |
393 | } |
394 | |
395 | static char *format(const QCborArray &a) |
396 | { |
397 | QByteArray out(1, '['); |
398 | const char *comma = ""; |
399 | for (QCborValueConstRef v : a) { |
400 | QScopedArrayPointer<char> s(format(v)); |
401 | out += comma; |
402 | out += s.get(); |
403 | comma = ", "; |
404 | } |
405 | out += ']'; |
406 | return qstrdup(out.constData()); |
407 | } |
408 | |
409 | static char *format(const QCborMap &m) |
410 | { |
411 | QByteArray out(1, '{'); |
412 | const char *comma = ""; |
413 | for (auto pair : m) { |
414 | QScopedArrayPointer<char> key(format(v: pair.first)); |
415 | QScopedArrayPointer<char> value(format(v: pair.second)); |
416 | out += comma; |
417 | out += key.get(); |
418 | out += ": "; |
419 | out += value.get(); |
420 | comma = ", "; |
421 | } |
422 | out += '}'; |
423 | return qstrdup(out.constData()); |
424 | } |
425 | }; |
426 | } |
427 | |
428 | template<> inline char *toString(const QCborValue &v) |
429 | { |
430 | return Internal::QCborValueFormatter::format(v); |
431 | } |
432 | |
433 | template<> inline char *toString(const QCborValueRef &v) |
434 | { |
435 | return toString(v: QCborValue(v)); |
436 | } |
437 | |
438 | template<> inline char *toString(const QCborArray &a) |
439 | { |
440 | return Internal::QCborValueFormatter::format(a); |
441 | } |
442 | |
443 | template<> inline char *toString(const QCborMap &m) |
444 | { |
445 | return Internal::QCborValueFormatter::format(m); |
446 | } |
447 | |
448 | template <typename Rep, typename Period> char *toString(std::chrono::duration<Rep, Period> dur) |
449 | { |
450 | QString r; |
451 | QDebug d(&r); |
452 | d.nospace() << qSetRealNumberPrecision(precision: 9) << dur; |
453 | if constexpr (Period::num != 1 || Period::den != 1) { |
454 | // include the equivalent value in seconds, in parentheses |
455 | using namespace std::chrono; |
456 | d << " ("<< duration_cast<duration<qreal>>(dur).count() << "s)"; |
457 | } |
458 | return qstrdup(std::move(r).toUtf8().constData()); |
459 | } |
460 | |
461 | template <typename T1, typename T2> |
462 | inline char *toString(const std::pair<T1, T2> &pair) |
463 | { |
464 | const QScopedArrayPointer<char> first(toString(pair.first)); |
465 | const QScopedArrayPointer<char> second(toString(pair.second)); |
466 | return formatString(prefix: "std::pair(", suffix: ")", numArguments: 2, first.data(), second.data()); |
467 | } |
468 | |
469 | template <typename Tuple, std::size_t... I> |
470 | inline char *tupleToString(const Tuple &tuple, std::index_sequence<I...>) { |
471 | using UP = std::unique_ptr<char[]>; |
472 | // Generate a table of N + 1 elements where N is the number of |
473 | // elements in the tuple. |
474 | // The last element is needed to support the empty tuple use case. |
475 | const UP data[] = { |
476 | UP(toString(std::get<I>(tuple)))..., UP{} |
477 | }; |
478 | return formatString("std::tuple(", ")", sizeof...(I), data[I].get()...); |
479 | } |
480 | |
481 | template <class... Types> |
482 | inline char *toString(const std::tuple<Types...> &tuple) |
483 | { |
484 | return tupleToString(tuple, std::make_index_sequence<sizeof...(Types)>{}); |
485 | } |
486 | |
487 | inline char *toString(std::nullptr_t) |
488 | { |
489 | return toString(str: QStringView(u"nullptr")); |
490 | } |
491 | } // namespace QTest |
492 | |
493 | QT_END_NAMESPACE |
494 | |
495 | #endif // QTESTTOSTRING_H |
496 |
Definitions
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- toString
- QCborValueFormatter
- formatSimpleType
- formatTag
- innerFormat
- format
- format
- format
- format
- toString
- toString
- toString
- toString
- toString
- toString
- tupleToString
- toString
Learn Advanced QML with KDAB
Find out more