File indexing completed on 2024-12-15 04:59:10

0001 /*
0002     SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #define QT_FORCE_ASSERTS 1
0008 
0009 #include <QProcess>
0010 #include <QQmlExpression>
0011 #include <QQuickItem>
0012 #include <QQuickView>
0013 #include <QSignalSpy>
0014 #include <QTest>
0015 
0016 namespace
0017 {
0018 template<typename T>
0019 [[nodiscard]] T evaluate(QObject *scope, const QString &expression)
0020 {
0021     QQmlExpression expr(qmlContext(scope), scope, expression);
0022     QVariant result = expr.evaluate();
0023     Q_ASSERT_X(!expr.hasError(), "MediaMonitorTest", qUtf8Printable(expr.error().toString()));
0024     return result.value<T>();
0025 }
0026 
0027 template<>
0028 void evaluate<void>(QObject *scope, const QString &expression)
0029 {
0030     QQmlExpression expr(qmlContext(scope), scope, expression);
0031     expr.evaluate();
0032     Q_ASSERT_X(!expr.hasError(), "MediaMonitorTest", qUtf8Printable(expr.error().toString()));
0033 }
0034 
0035 bool initView(QQuickView *view, const QString &urlStr)
0036 {
0037     view->setSource(QUrl(urlStr));
0038 
0039     QSignalSpy statusSpy(view, &QQuickView::statusChanged);
0040     if (view->status() == QQuickView::Loading) {
0041         statusSpy.wait();
0042     } else if (view->status() != QQuickView::Ready) {
0043         qCritical() << "Not loading" << view->errors();
0044         return false;
0045     }
0046 
0047     if (view->status() != QQuickView::Ready) {
0048         qCritical() << view->errors();
0049         return false;
0050     }
0051 
0052     return true;
0053 }
0054 }
0055 
0056 class MediaMonitorTest : public QObject
0057 {
0058     Q_OBJECT
0059 
0060 private Q_SLOTS:
0061     /**
0062      * Loads MediaMonitor in QML, and tests it can monitor music streams
0063      */
0064     void test_MediaMonitor();
0065 };
0066 
0067 void MediaMonitorTest::test_MediaMonitor()
0068 {
0069     QProcess pidof;
0070     pidof.setProgram(QStringLiteral("pidof"));
0071     pidof.setArguments({QStringLiteral("pipewire")});
0072     pidof.start();
0073     pidof.waitForFinished();
0074 
0075     QProcess pipewire;
0076     if (QString::fromUtf8(pidof.readAllStandardOutput()).toInt() == 0) {
0077         pipewire.setProgram(QStringLiteral("pipewire"));
0078         pipewire.start();
0079         QVERIFY2(pipewire.waitForStarted(), pipewire.readAllStandardError().constData());
0080     }
0081 
0082     auto view = std::make_unique<QQuickView>();
0083     QByteArray errorMessage;
0084     QVERIFY(initView(view.get(), QFINDTESTDATA(QStringLiteral("mediamonitortest.qml"))));
0085 
0086     QQuickItem *rootObject = view->rootObject();
0087     QSignalSpy countSpy(rootObject, SIGNAL(countChanged()));
0088     // To make sure modelData is not null after count changes
0089     QSignalSpy modelDataChangedSpy(rootObject, SIGNAL(modelDataChanged()));
0090 
0091     QProcess player;
0092     player.setProgram(QStringLiteral("pw-play"));
0093     player.setArguments({QStringLiteral("-v"), QFINDTESTDATA(QStringLiteral("alarm-clock-elapsed.oga"))});
0094     player.start();
0095     QVERIFY2(player.waitForStarted(), player.readAllStandardError().constData());
0096 
0097     if (evaluate<int>(rootObject, QStringLiteral("root.count")) == 0) {
0098         countSpy.wait();
0099     }
0100     if (evaluate<QObject *>(rootObject, QStringLiteral("root.modelData")) == nullptr) {
0101         modelDataChangedSpy.wait();
0102     }
0103 
0104     QVERIFY(evaluate<bool>(rootObject, QStringLiteral("monitor.detectionAvailable")));
0105     QCOMPARE(evaluate<int>(rootObject, QStringLiteral("root.count")), 1);
0106     if (qEnvironmentVariableIsSet("KDECI_BUILD")) {
0107         // There is no output in CI
0108         const int suspendedInt = evaluate<int>(rootObject, QStringLiteral("Monitor.NodeState.Suspended"));
0109         QTRY_COMPARE(evaluate<int>(rootObject, QStringLiteral("root.modelData.state")), suspendedInt);
0110     } else {
0111         const int runningInt = evaluate<int>(rootObject, QStringLiteral("Monitor.NodeState.Running"));
0112         QTRY_COMPARE(evaluate<int>(rootObject, QStringLiteral("root.modelData.state")), runningInt);
0113     }
0114 
0115     // Changing role will trigger reconnecting
0116     QObject *monitor = evaluate<QObject *>(rootObject, QStringLiteral("monitor"));
0117     QVERIFY(monitor);
0118     QSignalSpy detectionAvailableChangedSpy(monitor, SIGNAL(detectionAvailableChanged()));
0119     QSignalSpy roleChangedSpy(monitor, SIGNAL(roleChanged()));
0120     evaluate<void>(rootObject, QStringLiteral("monitor.role = Monitor.MediaMonitor.Camera"));
0121     if (roleChangedSpy.empty()) {
0122         QVERIFY(roleChangedSpy.wait());
0123     }
0124     QVERIFY(evaluate<bool>(rootObject, QStringLiteral("monitor.detectionAvailable")));
0125     QCOMPARE(detectionAvailableChangedSpy.size(), 2); // True -> False -> True
0126     QCOMPARE(evaluate<int>(rootObject, QStringLiteral("root.count")), 0);
0127 
0128     // Change back to Music role
0129     roleChangedSpy.clear();
0130     detectionAvailableChangedSpy.clear();
0131     evaluate<void>(rootObject, QStringLiteral("monitor.role = Monitor.MediaMonitor.Music"));
0132     if (roleChangedSpy.empty()) {
0133         QVERIFY(roleChangedSpy.wait());
0134     }
0135     QVERIFY(evaluate<bool>(rootObject, QStringLiteral("monitor.detectionAvailable")));
0136     QCOMPARE(detectionAvailableChangedSpy.size(), 2); // True -> False -> True
0137     if (evaluate<int>(rootObject, QStringLiteral("root.count")) == 0) {
0138         QVERIFY(countSpy.wait());
0139     }
0140     QCOMPARE(evaluate<int>(rootObject, QStringLiteral("root.count")), 1);
0141 
0142     // Now close the player, and check if the count changes
0143     player.terminate();
0144     if (evaluate<int>(rootObject, QStringLiteral("root.count")) > 0) {
0145         QVERIFY(countSpy.wait());
0146     }
0147     QCOMPARE(evaluate<int>(rootObject, QStringLiteral("root.count")), 0);
0148 
0149     if (pipewire.state() == QProcess::Running) {
0150         pipewire.terminate();
0151         QTRY_VERIFY(!evaluate<bool>(rootObject, QStringLiteral("monitor.detectionAvailable")));
0152     }
0153 }
0154 
0155 QTEST_MAIN(MediaMonitorTest)
0156 
0157 #include "mediamonitortest.moc"