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

source code of qtbase/src/testlib/qtesttostring.h