/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "debugutil_p.h" #include "qqmldebugprocess_p.h" #include "../../../shared/util.h" #include #include #include #include #include #include #include class QQmlProfilerTestClient : public QQmlProfilerEventReceiver { Q_OBJECT public: QQmlProfilerTestClient(QQmlDebugConnection *connection) : client(new QQmlProfilerClient(connection, this)) { connect(client.data(), &QQmlProfilerClient::traceStarted, this, &QQmlProfilerTestClient::startTrace); connect(client.data(), &QQmlProfilerClient::traceFinished, this, &QQmlProfilerTestClient::endTrace); } void startTrace(qint64 timestamp, const QList &engineIds); void endTrace(qint64 timestamp, const QList &engineIds); QPointer client; // Owned by QQmlDebugTest QVector types; QVector qmlMessages; QVector javascriptMessages; QVector jsHeapMessages; QVector asynchronousMessages; QVector pixmapMessages; int numLoadedEventTypes() const override; void addEventType(const QQmlProfilerEventType &type) override; void addEvent(const QQmlProfilerEvent &event) override; private: qint64 lastTimestamp = -1; }; void QQmlProfilerTestClient::startTrace(qint64 timestamp, const QList &engineIds) { types.append(QQmlProfilerEventType(Event, MaximumRangeType, StartTrace)); asynchronousMessages.append(QQmlProfilerEvent(timestamp, types.length() - 1, engineIds.toVector())); } void QQmlProfilerTestClient::endTrace(qint64 timestamp, const QList &engineIds) { types.append(QQmlProfilerEventType(Event, MaximumRangeType, EndTrace)); asynchronousMessages.append(QQmlProfilerEvent(timestamp, types.length() - 1, engineIds.toVector())); } int QQmlProfilerTestClient::numLoadedEventTypes() const { return types.length(); } void QQmlProfilerTestClient::addEventType(const QQmlProfilerEventType &type) { types.append(type); } void QQmlProfilerTestClient::addEvent(const QQmlProfilerEvent &event) { const int typeIndex = event.typeIndex(); QVERIFY(typeIndex < types.length()); const QQmlProfilerEventType &type = types[typeIndex]; QVERIFY(event.timestamp() >= lastTimestamp); lastTimestamp = event.timestamp(); switch (type.message()) { case Event: { switch (type.detailType()) { case StartTrace: QFAIL("StartTrace should not be passed on as event"); break; case EndTrace: QFAIL("EndTrace should not be passed on as event"); break; case AnimationFrame: asynchronousMessages.append(event); break; case Mouse: case Key: qmlMessages.append(event); break; default: QFAIL(qPrintable(QString::fromLatin1("Event with unknown detailType %1 received at %2.") .arg(type.detailType()).arg(event.timestamp()))); break; } break; } case RangeStart: case RangeData: case RangeLocation: case RangeEnd: QFAIL("Range stages are transmitted as part of events"); break; case Complete: QFAIL("Complete should not be passed on as event"); break; case PixmapCacheEvent: pixmapMessages.append(event); break; case SceneGraphFrame: asynchronousMessages.append(event); break; case MemoryAllocation: jsHeapMessages.append(event); break; case DebugMessage: // Unhandled break; case MaximumMessage: switch (type.rangeType()) { case Painting: QFAIL("QtQuick1 paint message received."); break; case Compiling: case Creating: case Binding: case HandlingSignal: qmlMessages.append(event); break; case Javascript: javascriptMessages.append(event); break; default: QFAIL(qPrintable( QString::fromLatin1("Unknown range event %1 received at %2.") .arg(type.rangeType()).arg(event.timestamp()))); break; } break; } } class tst_QQmlProfilerService : public QQmlDebugTest { Q_OBJECT private: enum MessageListType { MessageListQML, MessageListJavaScript, MessageListJsHeap, MessageListAsynchronous, MessageListPixmap }; enum CheckType { CheckMessageType = 1 << 0, CheckDetailType = 1 << 1, CheckLine = 1 << 2, CheckColumn = 1 << 3, CheckDataEndsWith = 1 << 4, CheckFileEndsWith = 1 << 5, CheckNumbers = 1 << 6, CheckType = CheckMessageType | CheckDetailType | CheckLine | CheckColumn | CheckFileEndsWith }; ConnectResult connect(bool block, const QString &file, bool recordFromStart = true, uint flushInterval = 0, bool restrictServices = true, const QString &executable = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlscene"); void checkProcessTerminated(); void checkTraceReceived(); void checkJsHeap(); bool verify(MessageListType type, int expectedPosition, const QQmlProfilerEventType &expected, quint32 checks, const QVector &expectedNumbers); QList createClients() override; QScopedPointer m_client; private slots: void cleanup() override; void connect_data(); void connect(); void pixmapCacheData(); void scenegraphData(); void profileOnExit(); void controlFromJS(); void signalSourceLocation(); void javascript(); void flushInterval(); void translationBinding(); void memory(); void compile(); void multiEngine(); void batchOverflow(); private: bool m_recordFromStart = true; bool m_flushInterval = false; bool m_isComplete = false; // Don't use ({...}) here as MSVC will interpret that as the "QVector(int size)" ctor. const QVector m_rangeStart = (QVector() << RangeStart); const QVector m_rangeEnd = (QVector() << RangeEnd); }; #define VERIFY(type, position, expected, checks, numbers) \ QVERIFY(verify(type, position, expected, checks, numbers)) QQmlDebugTest::ConnectResult tst_QQmlProfilerService::connect( bool block, const QString &file, bool recordFromStart, uint flushInterval, bool restrictServices, const QString &executable) { m_recordFromStart = recordFromStart; m_flushInterval = flushInterval; m_isComplete = false; // ### Still using qmlscene due to QTBUG-33377 return QQmlDebugTest::connect( executable, restrictServices ? "CanvasFrameRate,EngineControl,DebugMessages" : QString(), testFile(file), block); } void tst_QQmlProfilerService::checkProcessTerminated() { // If the process ends before connect(), we get a non-success value from connect() // That's not a problem as we will still receive the trace. We check that process has terminated // cleanly here. // Wait for the process to finish by itself, if that hasn't happened already QVERIFY(m_client); QVERIFY(m_client->client); QTRY_COMPARE(m_client->client->state(), QQmlDebugClient::NotConnected); QVERIFY(m_process); QVERIFY(m_process->exitStatus() != QProcess::CrashExit); QTRY_COMPARE(m_process->exitStatus(), QProcess::NormalExit); } void tst_QQmlProfilerService::checkTraceReceived() { QVERIFY(m_process->exitStatus() != QProcess::CrashExit); QTRY_VERIFY2(m_isComplete, "No trace received in time."); QVector numbers; // must start with "StartTrace" QQmlProfilerEventType expected(Event, MaximumRangeType, StartTrace); VERIFY(MessageListAsynchronous, 0, expected, CheckMessageType | CheckDetailType, numbers); // must end with "EndTrace" expected = QQmlProfilerEventType(Event, MaximumRangeType, EndTrace); VERIFY(MessageListAsynchronous, m_client->asynchronousMessages.length() - 1, expected, CheckMessageType | CheckDetailType, numbers); } void tst_QQmlProfilerService::checkJsHeap() { QVERIFY(m_client); QVERIFY2(m_client->jsHeapMessages.count() > 0, "no JavaScript heap messages received"); bool seen_alloc = false; bool seen_small = false; bool seen_large = false; qint64 allocated = 0; qint64 used = 0; qint64 lastTimestamp = -1; foreach (const QQmlProfilerEvent &message, m_client->jsHeapMessages) { const auto amount = message.number(0); const QQmlProfilerEventType &type = m_client->types.at(message.typeIndex()); switch (type.detailType()) { case HeapPage: allocated += amount; seen_alloc = true; break; case SmallItem: used += amount; seen_small = true; break; case LargeItem: allocated += amount; used += amount; seen_large = true; break; } QVERIFY(message.timestamp() >= lastTimestamp); // The heap will only be consistent after all events of the same timestamp are processed. if (lastTimestamp == -1) { lastTimestamp = message.timestamp(); continue; } if (message.timestamp() == lastTimestamp) continue; lastTimestamp = message.timestamp(); QVERIFY2(used >= 0, QString::fromLatin1("Negative memory usage seen: %1") .arg(used).toUtf8().constData()); QVERIFY2(allocated >= 0, QString::fromLatin1("Negative memory allocation seen: %1") .arg(allocated).toUtf8().constData()); QVERIFY2(used <= allocated, QString::fromLatin1("More memory usage than allocation seen: %1 > %2") .arg(used).arg(allocated).toUtf8().constData()); } QVERIFY2(seen_alloc, "No heap allocation seen"); QVERIFY2(seen_small, "No small item seen"); QVERIFY2(seen_large, "No large item seen"); } bool tst_QQmlProfilerService::verify(tst_QQmlProfilerService::MessageListType type, int expectedPosition, const QQmlProfilerEventType &expected, quint32 checks, const QVector &expectedNumbers) { if (!m_client) { qWarning() << "No debug client available"; return false; } const QVector *target = nullptr; switch (type) { case MessageListQML: target = &(m_client->qmlMessages); break; case MessageListJavaScript: target = &(m_client->javascriptMessages); break; case MessageListJsHeap: target = &(m_client->jsHeapMessages); break; case MessageListAsynchronous: target = &(m_client->asynchronousMessages); break; case MessageListPixmap: target = &(m_client->pixmapMessages); break; } if (!target) { qWarning() << "Invalid MessageListType" << type; return false; } if (target->length() <= expectedPosition) { qWarning() << "Not enough events. expected position:" << expectedPosition << "length:" << target->length(); return false; } int position = expectedPosition; qint64 timestamp = target->at(expectedPosition).timestamp(); while (position > 0 && target->at(position - 1).timestamp() == timestamp) --position; QStringList warnings; do { const QQmlProfilerEvent &event = target->at(position); const QQmlProfilerEventType &actual = m_client->types.at(event.typeIndex()); if ((checks & CheckMessageType) && (actual.message() != expected.message() || actual.rangeType() != expected.rangeType())) { warnings << QString::fromLatin1("%1: unexpected messageType or rangeType. " "actual: %2, %3 - expected: %4, %5") .arg(position).arg(actual.message()).arg(actual.rangeType()) .arg(expected.message()).arg(expected.rangeType()); continue; } if ((checks & CheckDetailType) && actual.detailType() != expected.detailType()) { warnings << QString::fromLatin1("%1: unexpected detailType. actual: %2 - expected: %3") .arg(position).arg(actual.detailType()).arg(expected.detailType()); continue; } const QQmlProfilerEventLocation expectedLocation = expected.location(); const QQmlProfilerEventLocation actualLocation = actual.location(); if ((checks & CheckLine) && actualLocation.line() != expectedLocation.line()) { warnings << QString::fromLatin1("%1: unexpected line. actual: %2 - expected: %3") .arg(position).arg(actualLocation.line()) .arg(expectedLocation.line()); continue; } if ((checks & CheckColumn) && actualLocation.column() != expectedLocation.column()) { warnings << QString::fromLatin1("%1: unexpected column. actual: %2 - expected: %3") .arg(position).arg(actualLocation.column()) .arg(expectedLocation.column()); continue; } if ((checks & CheckFileEndsWith) && !actualLocation.filename().endsWith(expectedLocation.filename())) { warnings << QString::fromLatin1("%1: unexpected fileName. actual: %2 - expected: %3") .arg(position).arg(actualLocation.filename()) .arg(expectedLocation.filename()); continue; } if ((checks & CheckDataEndsWith) && !actual.data().endsWith(expected.data())) { warnings << QString::fromLatin1("%1: unexpected detailData. actual: %2 - expected: %3") .arg(position).arg(actual.data()).arg(expected.data()); continue; } if (checks & CheckNumbers) { const QVector actualNumbers = event.numbers>(); if (actualNumbers != expectedNumbers) { QStringList expectedList; for (qint64 number : expectedNumbers) expectedList.append(QString::number(number)); QStringList actualList; for (qint64 number : actualNumbers) actualList.append(QString::number(number)); warnings << QString::fromLatin1( "%1: unexpected numbers. actual [%2] - expected: [%3]") .arg(position) .arg(actualList.join(QLatin1String(", "))) .arg(expectedList.join(QLatin1String(", "))); continue; } } return true; } while (++position < target->length() && target->at(position).timestamp() == timestamp); foreach (const QString &message, warnings) qWarning() << message.toLocal8Bit().constData(); return false; } QList tst_QQmlProfilerService::createClients() { m_client.reset(new QQmlProfilerTestClient(m_connection)); m_client->client->setRecording(m_recordFromStart); m_client->client->setFlushInterval(m_flushInterval); QObject::connect(m_client->client.data(), &QQmlProfilerClient::complete, this, [this](){ m_isComplete = true; }); return QList({m_client->client}); } void tst_QQmlProfilerService::cleanup() { auto log = [this](const QQmlProfilerEvent &data, int i) { const QQmlProfilerEventType &type = m_client->types.at(data.typeIndex()); const QQmlProfilerEventLocation location = type.location(); qDebug() << i << data.timestamp() << type.message() << type.rangeType() << type.detailType() << location.filename() << location.line() << location.column() << data.numbers>(); }; if (m_client && QTest::currentTestFailed()) { qDebug() << "QML Messages:" << m_client->qmlMessages.count(); int i = 0; for (const QQmlProfilerEvent &data : qAsConst(m_client->qmlMessages)) log(data, i++); qDebug() << " "; qDebug() << "JavaScript Messages:" << m_client->javascriptMessages.count(); i = 0; for (const QQmlProfilerEvent &data : qAsConst(m_client->javascriptMessages)) log(data, i++); qDebug() << " "; qDebug() << "Asynchronous Messages:" << m_client->asynchronousMessages.count(); i = 0; for (const QQmlProfilerEvent &data : qAsConst(m_client->asynchronousMessages)) log(data, i++); qDebug() << " "; qDebug() << "Pixmap Cache Messages:" << m_client->pixmapMessages.count(); i = 0; for (const QQmlProfilerEvent &data : qAsConst(m_client->pixmapMessages)) log(data, i++); qDebug() << " "; qDebug() << "Javascript Heap Messages:" << m_client->jsHeapMessages.count(); i = 0; for (const QQmlProfilerEvent &data : qAsConst(m_client->jsHeapMessages)) log(data, i++); qDebug() << " "; } m_client.reset(); QQmlDebugTest::cleanup(); } void tst_QQmlProfilerService::connect_data() { QTest::addColumn("blockMode"); QTest::addColumn("restrictMode"); QTest::addColumn("traceEnabled"); QTest::newRow("normal/unrestricted/disabled") << false << false << false; QTest::newRow("block/unrestricted/disabled") << true << false << false; QTest::newRow("normal/restricted/disabled") << false << true << false; QTest::newRow("block/restricted/disabled") << true << true << false; QTest::newRow("normal/unrestricted/enabled") << false << false << true; QTest::newRow("block/unrestricted/enabled") << true << false << true; QTest::newRow("normal/restricted/enabled") << false << true << true; QTest::newRow("block/restricted/enabled") << true << true << true; } void tst_QQmlProfilerService::connect() { QFETCH(bool, blockMode); QFETCH(bool, restrictMode); QFETCH(bool, traceEnabled); QCOMPARE(connect(blockMode, "test.qml", traceEnabled, 0, restrictMode), ConnectSuccess); if (!traceEnabled) m_client->client->setRecording(true); QTRY_VERIFY(m_client->numLoadedEventTypes() > 0); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); } void tst_QQmlProfilerService::pixmapCacheData() { QCOMPARE(connect(true, "pixmapCacheTest.qml"), ConnectSuccess); // Don't wait for readyReadStandardOutput before the loop. It may have already arrived. while (m_process->output().indexOf(QLatin1String("image loaded")) == -1 && m_process->output().indexOf(QLatin1String("image error")) == -1) QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); auto createType = [](PixmapEventType type) { return QQmlProfilerEventType(PixmapCacheEvent, MaximumRangeType, type); }; QVector numbers; // image starting to load VERIFY(MessageListPixmap, 0, createType(PixmapLoadingStarted), CheckMessageType | CheckDetailType, numbers); // image size numbers = QVector({2, 2, 1}); VERIFY(MessageListPixmap, 1, createType(PixmapSizeKnown), CheckMessageType | CheckDetailType | CheckNumbers, numbers); // image loaded VERIFY(MessageListPixmap, 2, createType(PixmapLoadingFinished), CheckMessageType | CheckDetailType, numbers); // cache size VERIFY(MessageListPixmap, 3, createType(PixmapCacheCountChanged), CheckMessageType | CheckDetailType, numbers); } void tst_QQmlProfilerService::scenegraphData() { QCOMPARE(connect(true, "scenegraphTest.qml"), ConnectSuccess); while (!m_process->output().contains(QLatin1String("tick"))) QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); // Check that at least one frame was rendered. // There should be a SGContextFrame + SGRendererFrame + SGRenderLoopFrame sequence, // but we can't be sure to get the SGRenderLoopFrame in the threaded renderer. // // Since the rendering happens in a different thread, there could be other unrelated events // interleaved. Also, events could carry the same time stamps and be sorted in an unexpected way // if the clocks are acting up. qint64 contextFrameTime = -1; qint64 renderFrameTime = -1; #if QT_CONFIG(opengl) //Software renderer doesn't have context frames if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) { foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { const QQmlProfilerEventType &type = m_client->types.at(msg.typeIndex()); if (type.message() == SceneGraphFrame) { if (type.detailType() == SceneGraphContextFrame) { contextFrameTime = msg.timestamp(); break; } } } QVERIFY(contextFrameTime != -1); } #endif foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { const QQmlProfilerEventType &type = m_client->types.at(msg.typeIndex()); if (type.detailType() == SceneGraphRendererFrame) { QVERIFY(msg.timestamp() >= contextFrameTime); renderFrameTime = msg.timestamp(); break; } } QVERIFY(renderFrameTime != -1); foreach (const QQmlProfilerEvent &msg, m_client->asynchronousMessages) { const QQmlProfilerEventType &type = m_client->types.at(msg.typeIndex()); if (type.detailType() == SceneGraphRenderLoopFrame) { if (msg.timestamp() >= contextFrameTime) { // Make sure SceneGraphRenderLoopFrame is not between SceneGraphContextFrame and // SceneGraphRendererFrame. A SceneGraphRenderLoopFrame before everything else is // OK as the scene graph might decide to do an initial rendering. QVERIFY(msg.timestamp() >= renderFrameTime); break; } } } } void tst_QQmlProfilerService::profileOnExit() { QCOMPARE(connect(true, "exit.qml"), ConnectSuccess); checkProcessTerminated(); checkTraceReceived(); checkJsHeap(); } void tst_QQmlProfilerService::controlFromJS() { QCOMPARE(connect(true, "controlFromJS.qml", false), ConnectSuccess); QTRY_VERIFY(m_client->numLoadedEventTypes() > 0); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); } void tst_QQmlProfilerService::signalSourceLocation() { QCOMPARE(connect(true, "signalSourceLocation.qml"), ConnectSuccess); while (!(m_process->output().contains(QLatin1String("500")))) QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); auto createType = [](int line, int column) { return QQmlProfilerEventType( MaximumMessage, HandlingSignal, -1, QQmlProfilerEventLocation(QLatin1String("signalSourceLocation.qml"), line, column)); }; VERIFY(MessageListQML, 4, createType(8, 28), CheckType | CheckNumbers, m_rangeStart); VERIFY(MessageListQML, 6, createType(7, 21), CheckType | CheckNumbers, m_rangeEnd); } void tst_QQmlProfilerService::javascript() { QCOMPARE(connect(true, "javascript.qml"), ConnectSuccess); while (!(m_process->output().contains(QLatin1String("done")))) QVERIFY(QQmlDebugTest::waitForSignal(m_process, SIGNAL(readyReadStandardOutput()))); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); VERIFY(MessageListJavaScript, 2, QQmlProfilerEventType(MaximumMessage, Javascript), CheckMessageType | CheckDetailType | CheckNumbers, m_rangeStart); VERIFY(MessageListJavaScript, 3, QQmlProfilerEventType( MaximumMessage, Javascript, -1, QQmlProfilerEventLocation(QLatin1String("javascript.qml"), 4, 5)), CheckType | CheckNumbers, m_rangeStart); VERIFY(MessageListJavaScript, 4, QQmlProfilerEventType( MaximumMessage, Javascript, -1, QQmlProfilerEventLocation(), QLatin1String("something")), CheckMessageType | CheckDetailType | CheckDataEndsWith | CheckNumbers, m_rangeStart); VERIFY(MessageListJavaScript, 10, QQmlProfilerEventType(MaximumMessage, Javascript), CheckMessageType | CheckDetailType | CheckNumbers, m_rangeEnd); } void tst_QQmlProfilerService::flushInterval() { QCOMPARE(connect(true, "timer.qml", true, 1), ConnectSuccess); // Make sure we get multiple messages QTRY_VERIFY(m_client->qmlMessages.length() > 0); QVERIFY(m_client->qmlMessages.length() < 100); QTRY_VERIFY(m_client->qmlMessages.length() > 100); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); } void tst_QQmlProfilerService::translationBinding() { QCOMPARE(connect(true, "qstr.qml"), ConnectSuccess); checkProcessTerminated(); checkTraceReceived(); checkJsHeap(); const QQmlProfilerEventType type(MaximumMessage, Binding); VERIFY(MessageListQML, 4, type, CheckDetailType | CheckMessageType | CheckNumbers, m_rangeStart); VERIFY(MessageListQML, 5, type, CheckDetailType | CheckMessageType | CheckNumbers, m_rangeEnd); } void tst_QQmlProfilerService::memory() { QCOMPARE(connect(true, "memory.qml"), ConnectSuccess); checkProcessTerminated(); checkTraceReceived(); checkJsHeap(); QVERIFY(m_client); int smallItems = 0; for (const auto& message : m_client->jsHeapMessages) { const QQmlProfilerEventType &type = m_client->types[message.typeIndex()]; if (type.detailType() == SmallItem) ++smallItems; } QVERIFY(smallItems > 5); } static bool hasCompileEvents(const QVector &types) { for (const QQmlProfilerEventType &type : types) { if (type.message() == MaximumMessage && type.rangeType() == Compiling) return true; } return false; } void tst_QQmlProfilerService::compile() { // Flush interval so that we actually get the events before we stop recording. connect(true, "test.qml", true, 100); QVERIFY(m_client); // We need to check specifically for compile events as we can otherwise stop recording after the // StartTrace has arrived, but before it compiles anything. QTRY_VERIFY(hasCompileEvents(m_client->types)); m_client->client->setRecording(false); checkTraceReceived(); checkJsHeap(); Message rangeStage = MaximumMessage; for (const auto& message : m_client->qmlMessages) { const QQmlProfilerEventType &type = m_client->types[message.typeIndex()]; if (type.rangeType() == Compiling) { switch (rangeStage) { case MaximumMessage: QCOMPARE(message.rangeStage(), RangeStart); break; case RangeStart: QCOMPARE(message.rangeStage(), RangeEnd); break; default: QFAIL("Wrong range stage"); } rangeStage = message.rangeStage(); QCOMPARE(type.message(), MaximumMessage); QCOMPARE(type.location().filename(), testFileUrl("test.qml").toString()); QCOMPARE(type.location().line(), 0); QCOMPARE(type.location().column(), 0); } } QCOMPARE(rangeStage, RangeEnd); } void tst_QQmlProfilerService::multiEngine() { QCOMPARE(connect(true, "quit.qml", true, 0, false, debugJsServerPath("qqmlprofilerservice")), ConnectSuccess); QSignalSpy spy(m_client->client, SIGNAL(complete(qint64))); checkTraceReceived(); checkJsHeap(); QTRY_COMPARE(m_process->state(), QProcess::NotRunning); QCOMPARE(m_process->exitStatus(), QProcess::NormalExit); QCOMPARE(spy.count(), 1); } void tst_QQmlProfilerService::batchOverflow() { // The trace client checks that the events are received in order. QCOMPARE(connect(true, "batchOverflow.qml"), ConnectSuccess); checkProcessTerminated(); checkTraceReceived(); checkJsHeap(); } QTEST_MAIN(tst_QQmlProfilerService) #include "tst_qqmlprofilerservice.moc"