// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include struct DummyPermission // a minimal QPermission-compatible type { using QtPermissionHelper = void; int state = 0; }; Q_DECLARE_METATYPE(DummyPermission) class tst_QPermission : public QObject { Q_OBJECT private Q_SLOTS: void converting_Dummy() const { return converting_impl(); } void converting_Location() const { return converting_impl(); } void converting_Calendar() const { return converting_impl(); } void converting_Contacts() const { return converting_impl(); } void converting_Camera() const { return converting_impl(); } void converting_Microphone() const { return converting_impl(); } void converting_Bluetooth() const { return converting_impl(); } void conversionMaintainsState() const; void functorWithoutContext(); void functorWithContextInThread(); void receiverInThread(); void destroyedContextObject(); private: template void converting_impl() const; }; template void tst_QPermission::converting_impl() const { T concrete; const T cconcrete = concrete; const auto metaType = QMetaType::fromType(); // construction is implicit: // from rvalue: { QPermission p = T(); QCOMPARE_EQ(p.type(), metaType); } // from mutable lvalue: { QPermission p = concrete; QCOMPARE_EQ(p.type(), metaType); } // from const lvalue: { QPermission p = cconcrete; QCOMPARE_EQ(p.type(), metaType); } // value<>() compiles: { const QPermission p = concrete; auto v = p.value(); static_assert(std::is_same_v>); QCOMPARE_NE(v, std::nullopt); } } void tst_QPermission::conversionMaintainsState() const { DummyPermission dummy{42}, dummy_default; QCOMPARE_NE(dummy.state, dummy_default.state); QLocationPermission loc, loc_default; QCOMPARE_EQ(loc_default.accuracy(), QLocationPermission::Accuracy::Approximate); QCOMPARE_EQ(loc_default.availability(), QLocationPermission::Availability::WhenInUse); loc.setAccuracy(QLocationPermission::Accuracy::Precise); loc.setAvailability(QLocationPermission::Availability::Always); QCOMPARE_EQ(loc.accuracy(), QLocationPermission::Accuracy::Precise); QCOMPARE_EQ(loc.availability(), QLocationPermission::Availability::Always); QCalendarPermission cal, cal_default; QCOMPARE_EQ(cal_default.accessMode(), QCalendarPermission::AccessMode::ReadOnly); cal.setAccessMode(QCalendarPermission::AccessMode::ReadWrite); QCOMPARE_EQ(cal.accessMode(), QCalendarPermission::AccessMode::ReadWrite); QContactsPermission con, con_default; QCOMPARE_EQ(con_default.accessMode(), QContactsPermission::AccessMode::ReadOnly); con.setAccessMode(QContactsPermission::AccessMode::ReadWrite); QCOMPARE_EQ(con.accessMode(), QContactsPermission::AccessMode::ReadWrite); // // QCameraPermission, QMicrophonePermission, QBluetoothPermission don't have // state at the time of writing // QPermission p; // maintain state between the blocks below to test reset behavior { p = dummy; auto v = p.value(); QCOMPARE_NE(v, std::nullopt); auto &r = *v; QCOMPARE_EQ(r.state, dummy.state); // check mismatched returns nullopt: QCOMPARE_EQ(p.value(), std::nullopt); } { p = loc; auto v = p.value(); QCOMPARE_NE(v, std::nullopt); auto &r = *v; QCOMPARE_EQ(r.accuracy(), loc.accuracy()); QCOMPARE_EQ(r.availability(), loc.availability()); // check mismatched returns nullopt: QCOMPARE_EQ(p.value(), std::nullopt); } { p = con; auto v = p.value(); QCOMPARE_NE(v, std::nullopt); auto &r = *v; QCOMPARE_EQ(r.accessMode(), con.accessMode()); // check mismatched returns nullopt: QCOMPARE_EQ(p.value(), std::nullopt); } { p = cal; auto v = p.value(); QCOMPARE_NE(v, std::nullopt); auto &r = *v; QCOMPARE_EQ(r.accessMode(), cal.accessMode()); // check mismatched returns nullopt: QCOMPARE_EQ(p.value(), std::nullopt); } } template requestPermission(std::declval(), std::declval()))> > void wrapRequestPermission(const QPermission &p, Func &&f) { qApp->requestPermission(p, std::forward(f)); } template using CompatibleTest = decltype(wrapRequestPermission(std::declval(), std::declval())); // Compile test for context-less functor overloads void tst_QPermission::functorWithoutContext() { int argc = 0; char *argv = nullptr; QCoreApplication app(argc, &argv); DummyPermission dummy; #ifdef Q_OS_DARWIN QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); #endif qApp->requestPermission(dummy, [](const QPermission &permission){ QVERIFY(permission.value()); }); wrapRequestPermission(dummy, [](const QPermission &permission){ QVERIFY(permission.value()); }); auto compatible = [](const QPermission &) {}; using Compatible = decltype(compatible); auto incompatible = [](const QString &) {}; using Incompatible = decltype(incompatible); static_assert(qxp::is_detected_v); static_assert(!qxp::is_detected_v); } void tst_QPermission::functorWithContextInThread() { int argc = 0; char *argv = nullptr; QCoreApplication app(argc, &argv); QThread::currentThread()->setObjectName("main thread"); QThread receiverThread; receiverThread.setObjectName("receiverThread"); QObject receiver; receiver.moveToThread(&receiverThread); receiverThread.start(); auto guard = qScopeGuard([&receiverThread]{ receiverThread.quit(); QVERIFY(receiverThread.wait(1000)); }); DummyPermission dummy; #ifdef Q_OS_DARWIN QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); #endif QThread *permissionReceiverThread = nullptr; qApp->requestPermission(dummy, &receiver, [&](const QPermission &permission){ auto dummy = permission.value(); QVERIFY(dummy); permissionReceiverThread = QThread::currentThread(); }); QTRY_COMPARE(permissionReceiverThread, &receiverThread); } void tst_QPermission::receiverInThread() { int argc = 0; char *argv = nullptr; QCoreApplication app(argc, &argv); QThread::currentThread()->setObjectName("main thread"); QThread receiverThread; receiverThread.setObjectName("receiverThread"); class Receiver : public QObject { public: using QObject::QObject; void handlePermission(const QPermission &permission) { auto dummy = permission.value(); QVERIFY(dummy); permissionReceiverThread = QThread::currentThread(); } QThread *permissionReceiverThread = nullptr; } receiver; receiver.moveToThread(&receiverThread); receiverThread.start(); auto guard = qScopeGuard([&receiverThread]{ receiverThread.quit(); QVERIFY(receiverThread.wait(1000)); }); DummyPermission dummy; #ifdef Q_OS_DARWIN QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); #endif qApp->requestPermission(dummy, &receiver, &Receiver::handlePermission); QTRY_COMPARE(receiver.permissionReceiverThread, &receiverThread); // compile tests: none of these work and the error output isn't horrible // qApp->requestPermission(dummy, &receiver, "&tst_QPermission::receiverInThread"); // qApp->requestPermission(dummy, &receiver, &tst_QPermission::receiverInThread); // qApp->requestPermission(dummy, &receiver, &QObject::destroyed); } void tst_QPermission::destroyedContextObject() { int argc = 0; char *argv = nullptr; QCoreApplication app(argc, &argv); QObject *context = new QObject; DummyPermission dummy; #ifdef Q_OS_DARWIN QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); #endif bool permissionReceived = false; qApp->requestPermission(dummy, context, [&]{ permissionReceived = true; }); QVERIFY2(!permissionReceived, "Permission received synchronously"); delete context; QTest::qWait(100); QVERIFY(!permissionReceived); } QTEST_APPLESS_MAIN(tst_QPermission) #include "tst_qpermission.moc"