diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 8910f3fece2..003e504c92b 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -5,7 +5,7 @@ ##################################################################### qt_internal_add_module(Network - PLUGIN_TYPES networkaccessbackends + PLUGIN_TYPES networkaccessbackends networkinformationbackends SOURCES access/qabstractnetworkcache.cpp access/qabstractnetworkcache.h access/qabstractnetworkcache_p.h access/qhsts.cpp access/qhsts_p.h @@ -30,6 +30,7 @@ qt_internal_add_module(Network kernel/qhostinfo.cpp kernel/qhostinfo.h kernel/qhostinfo_p.h kernel/qnetconmonitor_p.h kernel/qnetworkdatagram.cpp kernel/qnetworkdatagram.h kernel/qnetworkdatagram_p.h + kernel/qnetworkinformation.cpp kernel/qnetworkinformation_p.h kernel/qnetworkinformation.h kernel/qnetworkinterface.cpp kernel/qnetworkinterface.h kernel/qnetworkinterface_p.h kernel/qnetworkinterface_unix_p.h kernel/qnetworkproxy.cpp kernel/qnetworkproxy.h diff --git a/src/network/kernel/qnetworkinformation.cpp b/src/network/kernel/qnetworkinformation.cpp new file mode 100644 index 00000000000..2b0493ee269 --- /dev/null +++ b/src/network/kernel/qnetworkinformation.cpp @@ -0,0 +1,556 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// #define DEBUG_LOADING + +#include "qnetworkinformation.h" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfo) +Q_LOGGING_CATEGORY(lcNetInfo, "qt.network.info"); + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QNetworkInformationBackendFactory_iid, + QStringLiteral("/networkinformationbackends"))) + +struct QStaticNetworkInformationDataHolder +{ + QMutex instanceMutex; + std::unique_ptr instanceHolder; + QList factories; +}; +Q_GLOBAL_STATIC(QStaticNetworkInformationDataHolder, dataHolder); + +class QNetworkInformationPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QNetworkInformation) +public: + QNetworkInformationPrivate(QNetworkInformationBackend *backend) : backend(backend) { } + + static QNetworkInformation *create(QNetworkInformation::Features features); + static QNetworkInformation *create(QStringView name); + static QNetworkInformation *instance() + { + if (!dataHolder()) + return nullptr; + QMutexLocker locker(&dataHolder->instanceMutex); + return dataHolder->instanceHolder.get(); + } + static QStringList backendNames(); + static void addToList(QNetworkInformationBackendFactory *factory); + static void removeFromList(QNetworkInformationBackendFactory *factory); + +private: + static bool initializeList(); + + std::unique_ptr backend; + bool m_isOnline = true; // cached +}; + +bool QNetworkInformationPrivate::initializeList() +{ + if (!loader()) + return false; + if (!dataHolder()) + return false; + static QBasicMutex mutex; + QMutexLocker initLocker(&mutex); + +#if QT_CONFIG(library) + loader->update(); +#endif + // Instantiates the plugins (and registers the factories) + int index = 0; + while (loader->instance(index)) + ++index; + initLocker.unlock(); + + // Now sort the list on number of features available (then name) + const auto featuresNameOrder = [](QNetworkInformationBackendFactory *a, + QNetworkInformationBackendFactory *b) { + if (!a || !b) + return a && !b; + auto aFeaturesSupported = qPopulationCount(unsigned(a->featuresSupported())); + auto bFeaturesSupported = qPopulationCount(unsigned(b->featuresSupported())); + return aFeaturesSupported > bFeaturesSupported + || (aFeaturesSupported == bFeaturesSupported + && a->name().compare(b->name(), Qt::CaseInsensitive) < 0); + }; + QMutexLocker instanceLocker(&dataHolder->instanceMutex); + std::sort(dataHolder->factories.begin(), dataHolder->factories.end(), featuresNameOrder); + + return !dataHolder->factories.isEmpty(); +} + +void QNetworkInformationPrivate::addToList(QNetworkInformationBackendFactory *factory) +{ + // @note: factory is in the base class ctor + if (!dataHolder()) + return; + QMutexLocker locker(&dataHolder->instanceMutex); + dataHolder->factories.append(factory); +} + +void QNetworkInformationPrivate::removeFromList(QNetworkInformationBackendFactory *factory) +{ + // @note: factory is in the base class dtor + if (!dataHolder.exists()) + return; + QMutexLocker locker(&dataHolder->instanceMutex); + dataHolder->factories.removeAll(factory); +} + +QStringList QNetworkInformationPrivate::backendNames() +{ + if (!dataHolder()) + return {}; + if (!initializeList()) + return {}; + + QMutexLocker locker(&dataHolder->instanceMutex); + const QList copy = dataHolder->factories; + locker.unlock(); + + QStringList result; + result.reserve(copy.size()); + for (const auto *factory : copy) + result << factory->name(); + return result; +} + +QNetworkInformation *QNetworkInformationPrivate::create(QStringView name) +{ + if (!dataHolder()) + return nullptr; + QMutexLocker locker(&dataHolder->instanceMutex); +#ifdef DEBUG_LOADING + qDebug().nospace() << "create() called with name=\"" << name + << "\". instanceHolder initialized? " << !!dataHolder->instanceHolder; +#endif + if (dataHolder->instanceHolder) + return dataHolder->instanceHolder.get(); + + locker.unlock(); + if (!initializeList()) { +#ifdef DEBUG_LOADING + qDebug("Failed to initialize list, returning."); +#endif + return nullptr; + } + locker.relock(); + + QNetworkInformationBackend *backend = nullptr; + if (!name.isEmpty()) { + const auto nameMatches = [name](QNetworkInformationBackendFactory *factory) { + return factory->name().compare(name, Qt::CaseInsensitive) == 0; + }; + auto it = std::find_if(dataHolder->factories.cbegin(), dataHolder->factories.cend(), + nameMatches); + if (it == dataHolder->factories.cend()) { +#ifdef DEBUG_LOADING + if (dataHolder->factories.isEmpty()) { + qDebug("No plugins available"); + } else { + QString listNames; + listNames.reserve(8 * dataHolder->factories.count()); + for (const auto *factory : qAsConst(dataHolder->factories)) + listNames += factory->name() + QStringLiteral(", "); + listNames.chop(2); + qDebug().nospace() << "Couldn't find " << name << " in list with names: { " + << listNames << " }"; + } +#endif + return nullptr; + } +#ifdef DEBUG_LOADING + qDebug() << "Creating instance using loader named " << (*it)->name(); +#endif + backend = (*it)->create({}); + } else { +#ifdef DEBUG_LOADING + qDebug() << "Creating instance using loader named" << dataHolder->factories.front()->name(); +#endif + if (!dataHolder->factories.isEmpty()) + backend = dataHolder->factories.front()->create({}); + } + if (!backend) + return nullptr; + dataHolder->instanceHolder.reset(new QNetworkInformation(backend)); + Q_ASSERT(name.isEmpty() + || dataHolder->instanceHolder->backendName().compare(name, Qt::CaseInsensitive) == 0); + return dataHolder->instanceHolder.get(); +} + +QNetworkInformation *QNetworkInformationPrivate::create(QNetworkInformation::Features features) +{ + if (!dataHolder()) + return nullptr; + QMutexLocker locker(&dataHolder->instanceMutex); +#ifdef DEBUG_LOADING + qDebug().nospace() << "create() called with features=\"" << features + << "\". instanceHolder initialized? " << !!dataHolder->instanceHolder; +#endif + if (dataHolder->instanceHolder) + return dataHolder->instanceHolder.get(); + if (features == 0) + return nullptr; + + locker.unlock(); + if (!initializeList()) { +#ifdef DEBUG_LOADING + qDebug("Failed to initialize list, returning."); +#endif + return nullptr; + } + locker.relock(); + + const auto supportsRequestedFeatures = [features](QNetworkInformationBackendFactory *factory) { + return factory && (factory->featuresSupported() & features) == features; + }; + + for (auto it = dataHolder->factories.cbegin(), end = dataHolder->factories.cend(); it != end; + ++it) { + it = std::find_if(it, end, supportsRequestedFeatures); + if (it == end) { +#ifdef DEBUG_LOADING + if (dataHolder->factories.isEmpty()) { + qDebug("No plugins available"); + } else { + QStringList names; + names.reserve(dataHolder->factories.count()); + for (const auto *factory : qAsConst(dataHolder->factories)) + names += factory->name(); + qDebug() << "None of the following backends has all the requested features:" + << names << features; + } +#endif + break; + } +#ifdef DEBUG_LOADING + qDebug() << "Creating instance using loader named" << (*it)->name(); +#endif + if (QNetworkInformationBackend *backend = (*it)->create(features)) { + dataHolder->instanceHolder.reset(new QNetworkInformation(backend)); + Q_ASSERT(dataHolder->instanceHolder->supports(features)); + return dataHolder->instanceHolder.get(); + } +#ifdef DEBUG_LOADING + else { + qDebug() << "The factory returned a nullptr"; + } +#endif + } +#ifdef DEBUG_LOADING + qDebug() << "Couldn't find/create an appropriate backend."; +#endif + return nullptr; +} + +/*! + \class QNetworkInformationBackend + \internal (Semi-private) + \brief QNetworkInformationBackend provides the interface with + which QNetworkInformation does all of its actual work. + + Deriving from and implementing this class makes it a candidate + for use with QNetworkInformation. The derived class must, on + updates, call setters in the QNetworkInformationBackend which + will update the values and emit signals if the value has changed. + + \sa QNetworkInformationBackendFactory +*/ + +/*! + \internal + Destroys base backend class. +*/ +QNetworkInformationBackend::~QNetworkInformationBackend() = default; + +/*! + \fn QNetworkInformationBackend::name() + + Backend name, return the same in + QNetworkInformationBackendFactory::name(). +*/ + +/*! + \fn QNetworkInformation::Features QNetworkInformationBackend::featuresSupported() + + Features supported, return the same in + QNetworkInformationBackendFactory::featuresSupported(). +*/ + +/*! + \fn void QNetworkInformationBackend::reachabilityChanged() + + You should not emit this signal manually, call setReachability() + instead which will emit this signal when the value changes. + + \sa setReachability +*/ + +/*! + \fn void QNetworkInformationBackend::setReachability(QNetworkInformation::Reachability reachability) + + Call this when reachability has changed. It will automatically + emit reachabilityChanged(). + + \sa setReachability +*/ + +/*! + \class QNetworkInformationBackendFactory + \internal (Semi-private) + \brief QNetworkInformationBackendFactory provides the interface + for creating instances of QNetworkInformationBackend. + + Deriving from and implementing this class will let you register + your plugin with QNetworkInformation. It must provide some basic + information for querying information about the backend, and must + also create the backend if requested. If some pre-conditions for + the backend is not met it must return \nullptr. +*/ + +/*! + \internal + Adds the factory to an internal list. +*/ +QNetworkInformationBackendFactory::QNetworkInformationBackendFactory() +{ + QNetworkInformationPrivate::addToList(this); +} + +/*! + \internal + Removes the factory from an internal list. +*/ +QNetworkInformationBackendFactory::~QNetworkInformationBackendFactory() +{ + QNetworkInformationPrivate::removeFromList(this); +} + +/*! + \fn QString QNetworkInformationBackendFactory::name() + + Backend name, return the same in + QNetworkInformationBackend::name(). +*/ + +/*! + \fn QNetworkInformation::Features QNetworkInformationBackendFactory::featuresSupported() + + Features supported, return the same in + QNetworkInformationBackend::featuresSupported(). + The factory should not promise support for features that wouldn't + be available after creating the backend. +*/ + +/*! + \fn QNetworkInformationBackend *QNetworkInformationBackendFactory::create() + + Create and return an instance of QNetworkInformationBackend. It + will be deallocated by QNetworkInformation on shutdown. If some + precondition is not met, meaning the backend would not function + correctly, then you must return \nullptr. +*/ + +/*! + \class QNetworkInformation + \since 6.1 + \brief QNetworkInformation exposes various network information + through native backends. + + QNetworkInformation provides a cross-platform interface to + network-related information through plugins. + + Various plugins can have various functionality supported, and so + you can load() plugins based on which features are needed. + + QNetworkInformation is a singleton and stays alive from the first + successful load() until application shutdown. + + \sa QNetworkInformation::Feature +*/ + +/*! + \enum QNetworkInformation::Feature + + Lists all of the features that a plugin may currently support. + This can be used in QNetworkInformation::load(). + + \value Reachability + If the plugin supports this feature then + the \c reachability property will be available. + See also QNetworkInformation::Reachability. +*/ + +/*! + \enum QNetworkInformation::Reachability + + \value Unknown + If this value is returned then we may be connected but the OS + has still not confirmed full connectivity, or this features + is not supported. + \value Disconnected + Indicates that the system may have no connectivity at all. + \value Local + Indicates that the system is connected to a network, but it + might only be able to access devices on the local network. + \value Site + Indicates that the system is connected to a network, but it + might only be able to access devices on the local subnet or an + intranet. + \value Online + Indicates that the system is connected to a network and + able to access the Internet. + + \sa QNetworkInformation::reachability +*/ + +/*! + \internal ctor +*/ +QNetworkInformation::QNetworkInformation(QNetworkInformationBackend *backend) + : QObject(*(new QNetworkInformationPrivate(backend))) +{ + connect(backend, &QNetworkInformationBackend::reachabilityChanged, this, + [this]() { emit reachabilityChanged(d_func()->backend->m_reachability); }); +} + +/*! + \internal dtor +*/ +QNetworkInformation::~QNetworkInformation() = default; + +/*! + \property QNetworkInformation::reachability + \brief The current state of the system's network connectivity. + + Indicates the level of connectivity that can be expected. Do note + that this is only based on what the plugin/operating system + reports. In certain scenarios this is known to be wrong. For + example, on Windows the 'Online' check, by default, is performed + by Windows connecting to a Microsoft-owned server. If this server + is for any reason blocked then it will assume it does not have + Online reachability. Because of this you should not use this as a + pre-check before attempting to make a connection. +*/ + +QNetworkInformation::Reachability QNetworkInformation::reachability() const +{ + return d_func()->backend->m_reachability; +} + +/*! + Returns the name of the currently loaded backend. +*/ +QString QNetworkInformation::backendName() const +{ + return d_func()->backend->name(); +} + +/*! + Returns \c true if the currently loaded backend supports + \a features. +*/ +bool QNetworkInformation::supports(Features features) const +{ + return (d_func()->backend->featuresSupported() & features) == features; +} + +/*! + Attempts to load a backend whose name matches \a backend + (case insensitively). + + Returns \c true if it managed to load the requested backend or + if it was already loaded. Returns \c false otherwise + + \sa instance +*/ +bool QNetworkInformation::load(QStringView backend) +{ + auto loadedBackend = QNetworkInformationPrivate::create(backend); + return loadedBackend && loadedBackend->backendName() == backend; +} + +/*! + Load a backend which supports \a features. + + Returns \c true if it managed to load the requested backend or + if it was already loaded. Returns \c false otherwise + + \sa instance +*/ +bool QNetworkInformation::load(Features features) +{ + auto loadedBackend = QNetworkInformationPrivate::create(features); + return loadedBackend && loadedBackend->supports(features); +} + +/*! + Returns a list of the names of all currently available backends. +*/ +QStringList QNetworkInformation::availableBackends() +{ + return QNetworkInformationPrivate::backendNames(); +} + +/*! + Returns a pointer to the instance of the QNetworkInformation, + if any. + + \sa load() +*/ +QNetworkInformation *QNetworkInformation::instance() +{ + return QNetworkInformationPrivate::instance(); +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qnetworkinformation.h b/src/network/kernel/qnetworkinformation.h new file mode 100644 index 00000000000..6321300f406 --- /dev/null +++ b/src/network/kernel/qnetworkinformation.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKINFORMATION_H +#define QNETWORKINFORMATION_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QNetworkInformationBackend; +class QNetworkInformationPrivate; +class Q_NETWORK_EXPORT QNetworkInformation : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QNetworkInformation) + Q_PROPERTY(Reachability reachability READ reachability) +public: + enum class Reachability { + Unknown, + Disconnected, + Local, + Site, + Online, + }; + Q_ENUM(Reachability) + + enum class Feature { + Reachability = 0x1, + }; + Q_ENUM(Feature) + Q_DECLARE_FLAGS(Features, Feature) + + ~QNetworkInformation(); + + Reachability reachability() const; + + QString backendName() const; + + virtual bool supports(Features features) const; + + static bool load(QStringView backend = {}); + static bool load(Features features); + static QStringList availableBackends(); + static QNetworkInformation *instance(); + +Q_SIGNALS: + void reachabilityChanged(Reachability newReachability); + +private: + friend class QNetworkInformationPrivate; + QNetworkInformation(QNetworkInformationBackend *backend); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qnetworkinformation_p.h b/src/network/kernel/qnetworkinformation_p.h new file mode 100644 index 00000000000..fc783fa0799 --- /dev/null +++ b/src/network/kernel/qnetworkinformation_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKINFORMATION_P_H +#define QNETWORKINFORMATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Information API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class Q_NETWORK_EXPORT QNetworkInformationBackend : public QObject +{ + Q_OBJECT +public: + virtual ~QNetworkInformationBackend(); + + virtual QString name() const = 0; + virtual QNetworkInformation::Features featuresSupported() const = 0; + +Q_SIGNALS: + void reachabilityChanged(); + +protected: + void setReachability(QNetworkInformation::Reachability reachability) + { + if (m_reachability != reachability) { + m_reachability = reachability; + emit reachabilityChanged(); + } + } + +private: + QNetworkInformation::Reachability m_reachability = QNetworkInformation::Reachability::Unknown; + + friend class QNetworkInformation; + friend class QNetworkInformationPrivate; +}; + +class Q_NETWORK_EXPORT QNetworkInformationBackendFactory : public QObject +{ + Q_OBJECT +public: + QNetworkInformationBackendFactory(); + virtual ~QNetworkInformationBackendFactory(); + virtual QString name() const = 0; + virtual QNetworkInformationBackend *create(QNetworkInformation::Features requiredFeatures) const = 0; + virtual QNetworkInformation::Features featuresSupported() const = 0; +}; +#define QNetworkInformationBackendFactory_iid "org.qt-project.Qt.NetworkInformationBackendFactory" +Q_DECLARE_INTERFACE(QNetworkInformationBackendFactory, QNetworkInformationBackendFactory_iid); + +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/network/kernel/CMakeLists.txt b/tests/auto/network/kernel/CMakeLists.txt index 8b8960d0ae5..89526fa14c3 100644 --- a/tests/auto/network/kernel/CMakeLists.txt +++ b/tests/auto/network/kernel/CMakeLists.txt @@ -13,4 +13,5 @@ if(QT_FEATURE_private_tests AND NOT MACOS) endif() if(QT_FEATURE_private_tests) add_subdirectory(qauthenticator) + add_subdirectory(qnetworkinformation) endif() diff --git a/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt b/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt new file mode 100644 index 00000000000..f0c37913a8d --- /dev/null +++ b/tests/auto/network/kernel/qnetworkinformation/CMakeLists.txt @@ -0,0 +1,6 @@ +qt_internal_add_test(tst_qnetworkinformation + SOURCES + tst_qnetworkinformation.cpp + PUBLIC_LIBRARIES + Qt::NetworkPrivate +) diff --git a/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp b/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp new file mode 100644 index 00000000000..09fa65273d0 --- /dev/null +++ b/tests/auto/network/kernel/qnetworkinformation/tst_qnetworkinformation.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include +#include + +class MockFactory; +class tst_QNetworkInformation : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void reachability(); + void cleanupTestCase(); + +private: + std::unique_ptr mockFactory; +}; + +static const QString mockName = QStringLiteral("mock"); +class MockBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + MockBackend() + { + Q_ASSERT(!instance); + instance = this; + setReachability(QNetworkInformation::Reachability::Online); + } + ~MockBackend() { instance = nullptr; } + + QString name() const override { return mockName; } + + QNetworkInformation::Features featuresSupported() const override + { + return featuresSupportedStatic(); + } + + static void setNewReachability(QNetworkInformation::Reachability value) + { + Q_ASSERT(instance); + instance->setReachability(value); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + return { QNetworkInformation::Feature::Reachability }; + } + +private: + static inline MockBackend *instance = nullptr; +}; + +class MockFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT +public: + QString name() const override { return mockName; } + QNetworkInformationBackend * + create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + return new MockBackend(); + } + QNetworkInformation::Features featuresSupported() const override + { + return MockBackend::featuresSupportedStatic(); + } +}; + +void tst_QNetworkInformation::initTestCase() +{ + auto prevBackends = QNetworkInformation::availableBackends(); + qDebug() << "available backends:" << prevBackends; + // Creating the factory registers it as a backend + mockFactory = std::make_unique(); + auto backends = QNetworkInformation::availableBackends(); + QVERIFY(backends.size() > prevBackends.size()); + QVERIFY(backends.contains(u"mock")); + QVERIFY(QNetworkInformation::load(u"mock")); + QVERIFY(QNetworkInformation::load(u"mock")); + QVERIFY(!QNetworkInformation::load(u"mocks")); +} + +void tst_QNetworkInformation::cleanupTestCase() +{ + // Make sure the factory gets unregistered on destruction: + mockFactory.reset(); + auto backends = QNetworkInformation::availableBackends(); + QVERIFY(!backends.contains(u"mock")); +} + +void tst_QNetworkInformation::reachability() +{ + auto info = QNetworkInformation::instance(); + QNetworkInformation::Reachability boundIsOnline = QNetworkInformation::Reachability::Unknown; + bool signalEmitted = false; + + connect(info, &QNetworkInformation::reachabilityChanged, this, [&, info]() { + signalEmitted = true; + boundIsOnline = info->reachability(); + }); + QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Online); + MockBackend::setNewReachability(QNetworkInformation::Reachability::Disconnected); + QCoreApplication::processEvents(); + QVERIFY(signalEmitted); + QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Disconnected); + QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Disconnected); + + // Set the same value again, signal should not be emitted again + signalEmitted = false; + MockBackend::setNewReachability(QNetworkInformation::Reachability::Disconnected); + QCoreApplication::processEvents(); + QVERIFY(!signalEmitted); + + MockBackend::setNewReachability(QNetworkInformation::Reachability::Local); + QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Local); + QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Local); + MockBackend::setNewReachability(QNetworkInformation::Reachability::Site); + QCOMPARE(info->reachability(), QNetworkInformation::Reachability::Site); + QCOMPARE(boundIsOnline, QNetworkInformation::Reachability::Site); +} + +QTEST_MAIN(tst_QNetworkInformation); +#include "tst_qnetworkinformation.moc"