diff --git a/src/plugins/qmlprofiler/qmlprofiler.qbs b/src/plugins/qmlprofiler/qmlprofiler.qbs index c93a12d0dee..d923983c36f 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.qbs +++ b/src/plugins/qmlprofiler/qmlprofiler.qbs @@ -92,6 +92,7 @@ QtcPlugin { "qmlprofilerattachdialog_test.cpp", "qmlprofilerattachdialog_test.h", "qmlprofilerbindingloopsrenderpass_test.cpp", "qmlprofilerbindingloopsrenderpass_test.h", + "qmlprofilerclientmanager_test.cpp", "qmlprofilerclientmanager_test.h", ] } } diff --git a/src/plugins/qmlprofiler/qmlprofilerplugin.cpp b/src/plugins/qmlprofiler/qmlprofilerplugin.cpp index 68596d128ea..ae489ce0b9e 100644 --- a/src/plugins/qmlprofiler/qmlprofilerplugin.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerplugin.cpp @@ -30,6 +30,7 @@ #include "qmlprofilertimelinemodel.h" #ifdef WITH_TESTS + #include "tests/debugmessagesmodel_test.h" #include "tests/flamegraph_test.h" #include "tests/flamegraphmodel_test.h" @@ -45,7 +46,15 @@ #include "tests/qmlprofileranimationsmodel_test.h" #include "tests/qmlprofilerattachdialog_test.h" #include "tests/qmlprofilerbindingloopsrenderpass_test.h" -#endif +#include "tests/qmlprofilerclientmanager_test.h" + +// Force QML Debugging to be enabled, so that we can selftest the profiler +#define QT_QML_DEBUG_NO_WARNING +#include +#include +#undef QT_QML_DEBUG_NO_WARNING + +#endif // WITH_TESTS #include #include @@ -107,6 +116,9 @@ QList QmlProfiler::Internal::QmlProfilerPlugin::createTestObjects() c tests << new QmlProfilerAnimationsModelTest; tests << new QmlProfilerAttachDialogTest; tests << new QmlProfilerBindingLoopsRenderPassTest; + tests << new QmlProfilerClientManagerTest; + + tests << new QQmlEngine; // Trigger debug connector to be started #endif return tests; } diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp new file mode 100644 index 00000000000..46a37bfc5e2 --- /dev/null +++ b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp @@ -0,0 +1,407 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "qmlprofilerclientmanager_test.h" +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace QmlProfiler { +namespace Internal { + +struct MessageHandler { + MessageHandler(QtMessageHandler handler) + { + defaultHandler = qInstallMessageHandler(handler); + } + + ~MessageHandler() + { + qInstallMessageHandler(defaultHandler); + } + + static QtMessageHandler defaultHandler; +}; + +QtMessageHandler MessageHandler::defaultHandler; + +QmlProfilerClientManagerTest::QmlProfilerClientManagerTest(QObject *parent) : + QObject(parent), modelManager(nullptr) +{ + clientManager.setRetryParams(10, 10); +} + +void QmlProfilerClientManagerTest::testConnectionFailure_data() +{ + QTest::addColumn("modelManager"); + QVarLengthArray modelManagers({nullptr, &modelManager}); + + QTest::addColumn("stateManager"); + QVarLengthArray stateManagers({nullptr, &stateManager}); + + QString hostName; + Utils::Port port = LocalQmlProfilerRunner::findFreePort(hostName); + + QTest::addColumn("host"); + QVarLengthArray hosts({"", "/-/|\\-\\|/-", hostName}); + + QTest::addColumn("port"); + QVarLengthArray ports({Utils::Port(), Utils::Port(5), port}); + + QTest::addColumn("socket"); + QVarLengthArray sockets({"", "/-/|\\-\\|/-", + LocalQmlProfilerRunner::findFreeSocket()}); + + foreach (QmlProfilerModelManager *modelManager, modelManagers) { + foreach (QmlProfilerStateManager *stateManager, stateManagers) { + foreach (QString host, hosts) { + foreach (Utils::Port port, ports) { + foreach (QString socket, sockets) { + QString tag = QString::fromLatin1("%1, %2, %3, %4, %5") + .arg(QLatin1String(modelManager ? "modelManager" : "")) + .arg(QLatin1String(stateManager ? "stateManager" : "")) + .arg(host.isEmpty() ? "" : host) + .arg(port.isValid() ? port.number() : 0) + .arg(socket.isEmpty() ? "" : socket); + QTest::newRow(tag.toLatin1().constData()) + << modelManager << stateManager << host << port << socket; + } + } + } + } + } +} + +void softAssertMessageHandler(QtMsgType type, const QMessageLogContext &context, + const QString &message) +{ + if (type != QtDebugMsg || !message.startsWith("SOFT ASSERT: ")) + MessageHandler::defaultHandler(type, context, message); +} + +void QmlProfilerClientManagerTest::testConnectionFailure() +{ + // This triggers a lot of soft asserts. We test that it still doesn't crash and stays in a + // consistent state. + QByteArray fatalAsserts = qgetenv("QTC_FATAL_ASSERTS"); + qunsetenv("QTC_FATAL_ASSERTS"); + MessageHandler handler(&softAssertMessageHandler); + Q_UNUSED(handler); + + QFETCH(QmlProfilerModelManager *, modelManager); + QFETCH(QmlProfilerStateManager *, stateManager); + QFETCH(QString, host); + QFETCH(Utils::Port, port); + QFETCH(QString, socket); + + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setModelManager(modelManager); + clientManager.setProfilerStateManager(stateManager); + if (socket.isEmpty()) { + clientManager.setTcpConnection(host, port); + } else { + clientManager.setLocalSocket(socket); + } + + QVERIFY(!clientManager.isConnected()); + + clientManager.connectToTcpServer(); + QTRY_COMPARE(failedSpy.count(), 1); + QCOMPARE(closedSpy.count(), 0); + QCOMPARE(openedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.startLocalServer(); + QTRY_COMPARE(failedSpy.count(), 2); + QCOMPARE(closedSpy.count(), 0); + QCOMPARE(openedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.retryConnect(); + QTRY_COMPARE(failedSpy.count(), 3); + QCOMPARE(closedSpy.count(), 0); + QCOMPARE(openedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.clearConnection(); + + qputenv("QTC_FATAL_ASSERTS", fatalAsserts); +} + +void QmlProfilerClientManagerTest::testUnresponsiveTcp() +{ + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setProfilerStateManager(&stateManager); + clientManager.setModelManager(&modelManager); + + QString hostName; + Utils::Port port = LocalQmlProfilerRunner::findFreePort(hostName); + + QTcpServer server; + server.listen(QHostAddress(hostName), port.number()); + QSignalSpy connectionSpy(&server, SIGNAL(newConnection())); + + clientManager.setTcpConnection(hostName, port); + clientManager.connectToTcpServer(); + + QTRY_COMPARE(connectionSpy.count(), 1); + QTRY_COMPARE(failedSpy.count(), 1); + QCOMPARE(openedSpy.count(), 0); + QCOMPARE(closedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.clearConnection(); +} + +void QmlProfilerClientManagerTest::testUnresponsiveLocal() +{ + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setProfilerStateManager(&stateManager); + clientManager.setModelManager(&modelManager); + + QString socketFile = LocalQmlProfilerRunner::findFreeSocket(); + QLocalSocket socket; + QSignalSpy connectionSpy(&socket, SIGNAL(connected())); + + clientManager.setLocalSocket(socketFile); + clientManager.startLocalServer(); + + socket.connectToServer(socketFile); + QTRY_COMPARE(connectionSpy.count(), 1); + QTRY_COMPARE(failedSpy.count(), 1); + QCOMPARE(openedSpy.count(), 0); + QCOMPARE(closedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.clearConnection(); +} + +void responsiveTestData() +{ + QTest::addColumn("flushInterval"); + QTest::addColumn("aggregateTraces"); + + QTest::newRow("no flush, no aggregate") << 0u << false; + QTest::newRow("no flush, aggregate") << 0u << true; + QTest::newRow("flush, no aggregate") << 1u << false; + QTest::newRow("flush, aggregate") << 1u << true; +} + +void QmlProfilerClientManagerTest::testResponsiveTcp_data() +{ + responsiveTestData(); +} + +void QmlProfilerClientManagerTest::testResponsiveTcp() +{ + QFETCH(quint32, flushInterval); + QFETCH(bool, aggregateTraces); + + QString hostName; + Utils::Port port = LocalQmlProfilerRunner::findFreePort(hostName); + + ProjectExplorer::StandardRunnable runnable; + runnable.environment = Utils::Environment::systemEnvironment(); + runnable.executable = qApp->applicationFilePath(); + runnable.commandLineArguments = "-test QmlProfiler,QQmlEngine " + + QmlDebug::qmlDebugTcpArguments(QmlDebug::QmlProfilerServices, port); + runnable.runMode = ProjectExplorer::ApplicationLauncher::Gui; + + ProjectExplorer::ApplicationLauncher launcher; + launcher.start(runnable); + + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setProfilerStateManager(&stateManager); + clientManager.setModelManager(&modelManager); + clientManager.setFlushInterval(flushInterval); + clientManager.setAggregateTraces(aggregateTraces); + QCOMPARE(clientManager.aggregateTraces(), aggregateTraces); + + connect(&clientManager, &QmlProfilerClientManager::connectionFailed, + &clientManager, &QmlProfilerClientManager::retryConnect); + + clientManager.setTcpConnection(hostName, port); + clientManager.connectToTcpServer(); + + QTRY_COMPARE(openedSpy.count(), 1); + QCOMPARE(closedSpy.count(), 0); + QVERIFY(clientManager.isConnected()); + + // Do some nasty things and make sure it doesn't crash + stateManager.setCurrentState(QmlProfilerStateManager::AppRunning); + stateManager.setClientRecording(false); + stateManager.setClientRecording(true); + clientManager.clearBufferedData(); + stateManager.setCurrentState(QmlProfilerStateManager::AppStopRequested); + + QTRY_VERIFY_WITH_TIMEOUT(!launcher.isRunning(), 10000); + QTRY_COMPARE(closedSpy.count(), 1); + QVERIFY(!clientManager.isConnected()); + + disconnect(&clientManager, &QmlProfilerClientManager::connectionFailed, + &clientManager, &QmlProfilerClientManager::retryConnect); + + stateManager.setCurrentState(QmlProfilerStateManager::Idle); +} + +void QmlProfilerClientManagerTest::testResponsiveLocal_data() +{ + responsiveTestData(); +} + +void QmlProfilerClientManagerTest::testResponsiveLocal() +{ + QFETCH(quint32, flushInterval); + QFETCH(bool, aggregateTraces); + + QString socket = LocalQmlProfilerRunner::findFreeSocket(); + + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setProfilerStateManager(&stateManager); + clientManager.setModelManager(&modelManager); + clientManager.setFlushInterval(flushInterval); + clientManager.setAggregateTraces(aggregateTraces); + QCOMPARE(clientManager.aggregateTraces(), aggregateTraces); + + connect(&clientManager, &QmlProfilerClientManager::connectionFailed, + &clientManager, &QmlProfilerClientManager::retryConnect); + + clientManager.setLocalSocket(socket); + clientManager.startLocalServer(); + + ProjectExplorer::StandardRunnable runnable; + runnable.environment = Utils::Environment::systemEnvironment(); + runnable.executable = qApp->applicationFilePath(); + runnable.commandLineArguments = "-test QmlProfiler,QQmlEngine " + + QmlDebug::qmlDebugLocalArguments(QmlDebug::QmlProfilerServices, socket); + runnable.runMode = ProjectExplorer::ApplicationLauncher::Gui; + + ProjectExplorer::ApplicationLauncher launcher; + launcher.start(runnable); + + QTRY_COMPARE(openedSpy.count(), 1); + QCOMPARE(closedSpy.count(), 0); + QVERIFY(clientManager.isConnected()); + + // Do some nasty things and make sure it doesn't crash + stateManager.setCurrentState(QmlProfilerStateManager::AppRunning); + stateManager.setClientRecording(false); + stateManager.setClientRecording(true); + clientManager.clearBufferedData(); + stateManager.setCurrentState(QmlProfilerStateManager::AppStopRequested); + + QTRY_VERIFY_WITH_TIMEOUT(!launcher.isRunning(), 10000); + QTRY_COMPARE(closedSpy.count(), 1); + QVERIFY(!clientManager.isConnected()); + + disconnect(&clientManager, &QmlProfilerClientManager::connectionFailed, + &clientManager, &QmlProfilerClientManager::retryConnect); + + stateManager.setCurrentState(QmlProfilerStateManager::Idle); +} + +void invalidHelloMessageHandler(QtMsgType type, const QMessageLogContext &context, + const QString &message) +{ + if (type != QtWarningMsg || message != "QML Debug Client: Invalid hello message") + MessageHandler::defaultHandler(type, context, message); +} + +void QmlProfilerClientManagerTest::testInvalidData() +{ + MessageHandler handler(&invalidHelloMessageHandler); + Q_UNUSED(handler); + + QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); + QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + + QVERIFY(!clientManager.isConnected()); + + clientManager.setProfilerStateManager(&stateManager); + clientManager.setModelManager(&modelManager); + + QString hostName; + Utils::Port port = LocalQmlProfilerRunner::findFreePort(hostName); + + bool dataSent = false; + QTcpServer server; + connect(&server, &QTcpServer::newConnection, [&server, &dataSent](){ + QTcpSocket *socket = server.nextPendingConnection(); + + // emulate packet protocol + qint32 sendSize32 = 10; + socket->write((char *)&sendSize32, sizeof(qint32)); + socket->write("----------------------- x -----------------------"); + + dataSent = true; + }); + + server.listen(QHostAddress(hostName), port.number()); + + clientManager.setTcpConnection(hostName, port); + clientManager.connectToTcpServer(); + + QTRY_VERIFY(dataSent); + QTRY_COMPARE(failedSpy.count(), 1); + QCOMPARE(openedSpy.count(), 0); + QCOMPARE(closedSpy.count(), 0); + QVERIFY(!clientManager.isConnected()); + + clientManager.clearConnection(); +} + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.h b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.h new file mode 100644 index 00000000000..2c105f29edf --- /dev/null +++ b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ +#pragma once + +#include +#include +#include + +#include + +namespace QmlProfiler { +namespace Internal { + +class QmlProfilerClientManagerTest : public QObject +{ + Q_OBJECT +public: + explicit QmlProfilerClientManagerTest(QObject *parent = 0); + +private slots: + void testConnectionFailure_data(); + void testConnectionFailure(); + + void testUnresponsiveTcp(); + void testUnresponsiveLocal(); + + void testResponsiveTcp_data(); + void testResponsiveTcp(); + + void testResponsiveLocal_data(); + void testResponsiveLocal(); + + void testInvalidData(); + +private: + QmlProfilerClientManager clientManager; + QmlProfilerModelManager modelManager; + QmlProfilerStateManager stateManager; + +}; + +} // namespace Internal +} // namespace QmlProfiler diff --git a/src/plugins/qmlprofiler/tests/tests.pri b/src/plugins/qmlprofiler/tests/tests.pri index 1e4944bb2bb..db4ed15007d 100644 --- a/src/plugins/qmlprofiler/tests/tests.pri +++ b/src/plugins/qmlprofiler/tests/tests.pri @@ -13,7 +13,8 @@ SOURCES += \ $$PWD/qmlnote_test.cpp \ $$PWD/qmlprofileranimationsmodel_test.cpp \ $$PWD/qmlprofilerattachdialog_test.cpp \ - $$PWD/qmlprofilerbindingloopsrenderpass_test.cpp + $$PWD/qmlprofilerbindingloopsrenderpass_test.cpp \ + $$PWD/qmlprofilerclientmanager_test.cpp HEADERS += \ $$PWD/debugmessagesmodel_test.h \ @@ -30,4 +31,5 @@ HEADERS += \ $$PWD/qmlnote_test.h \ $$PWD/qmlprofileranimationsmodel_test.h \ $$PWD/qmlprofilerattachdialog_test.h \ - $$PWD/qmlprofilerbindingloopsrenderpass_test.h + $$PWD/qmlprofilerbindingloopsrenderpass_test.h \ + $$PWD/qmlprofilerclientmanager_test.h