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

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