File indexing completed on 2024-11-10 05:11:08

0001 /*
0002  * Copyright 2018 by Marco Martin <mart@kde.org>
0003  *
0004  * Licensed under the Apache License, Version 2.0 (the "License");
0005  * you may not use this file except in compliance with the License.
0006  * You may obtain a copy of the License at
0007  *
0008  *    http://www.apache.org/licenses/LICENSE-2.0
0009  *
0010  * Unless required by applicable law or agreed to in writing, software
0011  * distributed under the License is distributed on an "AS IS" BASIS,
0012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013  * See the License for the specific language governing permissions and
0014  * limitations under the License.
0015  *
0016  */
0017 
0018 #include <QtTest>
0019 #include <QWebSocket>
0020 #include <QWebSocketServer>
0021 #include <QAbstractItemModel>
0022 #include <QQuickView>
0023 #include <QQmlEngine>
0024 #include "../import/mycroftcontroller.h"
0025 #include "../import/abstractdelegate.h"
0026 #include "../import/filereader.h"
0027 #include "../import/globalsettings.h"
0028 #include "../import/activeskillsmodel.h"
0029 #include "../import/delegatesmodel.h"
0030 #include "../import/abstractskillview.h"
0031 #include "../import/sessiondatamap.h"
0032 #include "../import/sessiondatamodel.h"
0033 
0034 class ServerTest : public QObject
0035 {
0036     Q_OBJECT
0037 
0038 public Q_SLOTS:
0039     void initTestCase();
0040 
0041 private Q_SLOTS:
0042     void testGuiConnection();
0043     void create100Skills();
0044     void move100Skills();
0045     void delete100Skills();
0046     void repeat100();
0047 
0048 
0049 private:
0050     AbstractDelegate *delegateForSkill(const QString &skill, const QUrl &url);
0051     QList <AbstractDelegate *>delegatesForSkill(const QString &skill);
0052 
0053     //Client
0054     MycroftController *m_controller;
0055     AbstractSkillView *m_view;
0056 
0057     QQuickView *m_window;
0058 
0059     //Server
0060     QWebSocketServer *m_mainServerSocket;
0061     QWebSocketServer *m_guiServerSocket;
0062 
0063     QWebSocket *m_mainWebSocket;
0064     QWebSocket *m_guiWebSocket;
0065 };
0066 
0067 
0068 static QObject *fileReaderSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
0069 {
0070     Q_UNUSED(engine)
0071     Q_UNUSED(scriptEngine)
0072 
0073     return new FileReader;
0074 }
0075 
0076 static QObject *globalSettingsSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
0077 {
0078     Q_UNUSED(engine)
0079     Q_UNUSED(scriptEngine)
0080 
0081     return new GlobalSettings;
0082 }
0083 
0084 static QObject *mycroftControllerSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
0085 {
0086     Q_UNUSED(engine);
0087     Q_UNUSED(scriptEngine);
0088 
0089     return MycroftController::instance();
0090 }
0091 
0092 
0093 
0094 AbstractDelegate *ServerTest::delegateForSkill(const QString &skill, const QUrl &url)
0095 {
0096     DelegatesModel *delegatesModel = m_view->activeSkills()->delegatesModelForSkill(skill);
0097     if (!delegatesModel) {
0098         return nullptr;
0099     }
0100 
0101     for (auto d : delegatesModel->delegates()) {
0102         if (d->qmlUrl() == url) {
0103             return d;
0104         }
0105     }
0106     return nullptr;
0107 }
0108 
0109 QList <AbstractDelegate *>ServerTest::delegatesForSkill(const QString &skill)
0110 {
0111     DelegatesModel *delegatesModel = m_view->activeSkills()->delegatesModelForSkill(skill);
0112 
0113     if (!delegatesModel) {
0114         return {};
0115     }
0116     return delegatesModel->delegates();
0117 }
0118 
0119 void ServerTest::initTestCase()
0120 {
0121     m_mainServerSocket = new QWebSocketServer(QStringLiteral("core"),
0122                                             QWebSocketServer::NonSecureMode, this);
0123     m_mainServerSocket->listen(QHostAddress::Any, 8181);
0124     m_guiServerSocket = new QWebSocketServer(QStringLiteral("gui"),
0125                                             QWebSocketServer::NonSecureMode, this);
0126     m_guiServerSocket->listen(QHostAddress::Any, 1818);
0127     m_controller = MycroftController::instance();
0128     //TODO: delete
0129     //m_view = new AbstractSkillView;
0130     m_window = new QQuickView;
0131 
0132     bool pluginFound = false;
0133     for (const auto &path : m_window->engine()->importPathList()) {
0134         QDir importDir(path);
0135         if (importDir.entryList().contains(QStringLiteral("Mycroft"))) {
0136             pluginFound = true;
0137             break;
0138         }
0139     }
0140 
0141     if (!pluginFound) {
0142         qmlRegisterSingletonType<MycroftController>("Mycroft", 1, 0, "MycroftController", mycroftControllerSingletonProvider);
0143         qmlRegisterSingletonType<GlobalSettings>("Mycroft", 1, 0, "GlobalSettings", globalSettingsSingletonProvider);
0144         qmlRegisterSingletonType<FileReader>("Mycroft", 1, 0, "FileReader", fileReaderSingletonProvider);
0145         qmlRegisterType<AbstractSkillView>("Mycroft", 1, 0, "AbstractSkillView");
0146         qmlRegisterType<AbstractDelegate>("Mycroft", 1, 0, "AbstractDelegate");
0147 
0148         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/AudioPlayer.qml")), "Mycroft", 1, 0, "AudioPlayer");
0149         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/AutoFitLabel.qml")), "Mycroft", 1, 0, "AutoFitLabel");
0150         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/Delegate.qml")), "Mycroft", 1, 0, "Delegate");
0151         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/PaginatedText.qml")), "Mycroft", 1, 0, "PaginatedText");
0152         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/ProportionalDelegate.qml")), "Mycroft", 1, 0, "ProportionalDelegate");
0153         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/ScrollableDelegate.qml")), "Mycroft", 1, 0, "ScrollableDelegate");
0154         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/SkillView.qml")), "Mycroft", 1, 0, "SkillView");
0155         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/SlideShow.qml")), "Mycroft", 1, 0, "SlideShow");
0156         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/SlidingImage.qml")), "Mycroft", 1, 0, "SlidingImage");
0157         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/StatusIndicator.qml")), "Mycroft", 1, 0, "StatusIndicator");
0158         qmlRegisterType(QUrl(QStringLiteral("qrc:/qml/VideoPlayer.qml")), "Mycroft", 1, 0, "VideoPlayer");
0159 
0160         qmlRegisterUncreatableType<ActiveSkillsModel>("Mycroft", 1, 0, "ActiveSkillsModel", QStringLiteral("You cannot instantiate items of type ActiveSkillsModel"));
0161         qmlRegisterUncreatableType<DelegatesModel>("Mycroft", 1, 0, "DelegatesModel", QStringLiteral("You cannot instantiate items of type DelegatesModel"));
0162         qmlRegisterUncreatableType<SessionDataMap>("Mycroft", 1, 0, "SessionDataMap", QStringLiteral("You cannot instantiate items of type SessionDataMap"));
0163 
0164         qmlRegisterType(QUrl::fromLocalFile(QFINDTESTDATA(QStringLiteral("../import/qml/Delegate.qml"))), "Mycroft", 1, 0, "Delegate");
0165 
0166         qmlProtectModule("Mycroft", 1);
0167     }
0168 
0169     m_window->setResizeMode(QQuickView::SizeRootObjectToView);
0170     m_window->resize(400, 800);
0171 
0172     //Load the AbstractSkillview from our specialization in QML
0173     m_window->setSource(QUrl::fromLocalFile(QFINDTESTDATA("../import/qml/SkillView.qml")));
0174     if (m_window->errors().length() > 0) {
0175         qWarning() << m_window->errors();
0176     }
0177     m_window->show();
0178     m_view = qobject_cast<AbstractSkillView *>(m_window->rootObject());
0179     QVERIFY(m_view);
0180 
0181     new QAbstractItemModelTester(m_view->activeSkills(), QAbstractItemModelTester::FailureReportingMode::QtTest, this);
0182 }
0183 
0184 //TODO: test a spotty connection
0185 void ServerTest::testGuiConnection()
0186 {
0187     QSignalSpy newConnectionSpy(m_mainServerSocket, &QWebSocketServer::newConnection);
0188     QSignalSpy controllerSocketStatusChangedSpy(m_controller, &MycroftController::socketStatusChanged);
0189     m_controller->start();
0190 
0191     //wait the server received a connection and the client got connected state
0192     newConnectionSpy.wait();
0193 
0194     m_mainWebSocket = m_mainServerSocket->nextPendingConnection();
0195     QSignalSpy textFromMainSpy(m_mainWebSocket, &QWebSocket::textMessageReceived);
0196     QVERIFY(m_mainWebSocket);
0197 
0198     controllerSocketStatusChangedSpy.wait();
0199     QCOMPARE(m_controller->status(), MycroftController::Open);
0200 
0201     textFromMainSpy.wait();
0202     auto doc = QJsonDocument::fromJson(textFromMainSpy.first().first().toString().toLatin1());
0203     auto type = doc[QStringLiteral("type")].toString();
0204     auto guiId = doc[QStringLiteral("data")][QStringLiteral("gui_id")].toString();
0205 
0206     QCOMPARE(type, QStringLiteral("mycroft.gui.connected"));
0207     QVERIFY(guiId.length() > 0);
0208 
0209     //Now, connect the gui
0210     QSignalSpy newGuiConnectionSpy(m_guiServerSocket, &QWebSocketServer::newConnection);
0211     m_mainWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.gui.port\", \"data\": {\"gui_id\": \"%1\", \"port\": 1818}}").arg(guiId));
0212     newGuiConnectionSpy.wait();
0213     m_guiWebSocket = m_guiServerSocket->nextPendingConnection();
0214     QVERIFY(m_guiWebSocket);
0215 }
0216 
0217 void ServerTest::create100Skills()
0218 {
0219     QSignalSpy skillInsertedSpy(m_view->activeSkills(), &ActiveSkillsModel::rowsInserted);
0220 
0221     QCOMPARE(m_view->activeSkills()->rowCount(), 0);
0222 
0223     const QUrl url(QStringLiteral("file://") + QFINDTESTDATA("delegatewithloader.qml"));
0224 
0225     for (int i = 0; i < 10; ++i) {
0226         const QString id = QString::number(i);
0227 
0228         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.list.insert\", \"namespace\": \"mycroft.system.active_skills\", \"position\": 0, \"data\": [{\"skill_id\": \"") + id + QStringLiteral("\"}]}"));
0229 
0230         skillInsertedSpy.wait();
0231         SessionDataMap *map = m_view->sessionDataForSkill(id);
0232         QVERIFY(map);
0233 
0234         //set data
0235         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.set\", \"namespace\": \"") + id + QStringLiteral("\", \"data\": {\"temperature\": \"24°C\", \"otherproperty\": \"value\"}}"));
0236 
0237         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.gui.list.insert\", \"namespace\": \"") + id + QStringLiteral("\", \"position\": 0, \"data\": [{\"url\": \"") + url.toString() + QStringLiteral("\"}]}"));
0238 
0239         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.set\", \"namespace\": \"") + id + QStringLiteral("\", \"data\": {\"temperature\": \"24°C\", \"otherproperty\": \"value\", \"state\": \"subdelegate1\"}}"));
0240     }
0241 
0242     for (int i = 0; i < 10; ++i) {
0243         const QString id = QString::number(i);
0244         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.set\", \"namespace\": \"") + id + QStringLiteral("\", \"data\": {\"temperature\": \"24°C\", \"otherproperty\": \"value\", \"state\": \"subdelegate2\"}}"));
0245     }
0246 
0247     QCOMPARE(m_view->activeSkills()->rowCount(), 10);
0248     for (int i = 0; i < 10; ++i) {
0249         QCOMPARE(m_view->activeSkills()->data(m_view->activeSkills()->index(9-i,0), ActiveSkillsModel::SkillId).toString(), QString::number(i));
0250     }
0251 }
0252 
0253 void ServerTest::move100Skills()
0254 {
0255     QSignalSpy skillMovedSpy(m_view->activeSkills(), &ActiveSkillsModel::rowsMoved);
0256 
0257    // QTest::qWait(3000);
0258     const QUrl url(QStringLiteral("file://") + QFINDTESTDATA("delegatewithloader.qml"));
0259 
0260     for (int i = 0; i < 10; ++i) {
0261 
0262         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.list.move\", \"namespace\": \"mycroft.system.active_skills\", \"from\": 9, \"to\": 0, \"items_number\": 1}"));
0263 
0264         skillMovedSpy.wait();
0265     }
0266     
0267 }
0268 
0269 void ServerTest::delete100Skills()
0270 {
0271     QSignalSpy skillRemovedSpy(m_view->activeSkills(), &ActiveSkillsModel::rowsRemoved);
0272 
0273    // QTest::qWait(3000);
0274     const QUrl url(QStringLiteral("file://") + QFINDTESTDATA("delegatewithloader.qml"));
0275 
0276     for (int i = 0; i < 10; ++i) {
0277         const QString id = QString::number(i);
0278         // Try to do a race condition between setting value and deleting
0279         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.set\", \"namespace\": \"") + id + QStringLiteral("\", \"data\": {\"temperature\": \"25°C\", \"otherproperty\": \"value\", \"state\": \"subdelegate2\"}}"));
0280         
0281         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.list.remove\", \"namespace\": \"mycroft.system.active_skills\", \"position\": 0, \"items_number\": 1}"));
0282 
0283         m_guiWebSocket->sendTextMessage(QStringLiteral("{\"type\": \"mycroft.session.set\", \"namespace\": \"") + id + QStringLiteral("\", \"data\": {\"temperature\": \"11°C\", \"otherproperty\": \"value\"}}"));
0284 
0285         skillRemovedSpy.wait();
0286     }
0287     
0288 }
0289 
0290 void ServerTest::repeat100()
0291 {
0292     for (int i = 0; i < 10; ++i) {
0293         create100Skills();
0294         delete100Skills();
0295     }
0296 }
0297 
0298 QTEST_MAIN(ServerTest);
0299 
0300 #include "stresstest.moc"