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"