summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Samir <a.samirh78@gmail.com>2024-02-22 16:01:54 +0200
committerAhmad Samir <a.samirh78@gmail.com>2024-03-03 19:56:55 +0200
commit4fa9034d0c592e5f07531d41463c8c462f5e8895 (patch)
tree2457778849a1e49a85569ccac8a2dd72ad58c04c
parent7ff1285e7a93d51e1f2a079ae33349906d9e9fea (diff)
Copy QTimer source files to QChronoTimer
Ultimately this is the best way to keep the log history of the code. Change-Id: I3413deffdb093a3239d65b6ca939e744224e722a Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/kernel/qchronotimer.cpp646
-rw-r--r--src/corelib/kernel/qchronotimer.h187
-rw-r--r--tests/auto/corelib/kernel/qchronotimer/.gitignore1
-rw-r--r--tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt34
-rw-r--r--tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp1539
-rw-r--r--tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt38
6 files changed, 2445 insertions, 0 deletions
diff --git a/src/corelib/kernel/qchronotimer.cpp b/src/corelib/kernel/qchronotimer.cpp
new file mode 100644
index 0000000000..cbeeb97934
--- /dev/null
+++ b/src/corelib/kernel/qchronotimer.cpp
@@ -0,0 +1,646 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qtimer.h"
+#include "qtimer_p.h"
+#include "qsingleshottimer_p.h"
+
+#include "qabstracteventdispatcher.h"
+#include "qcoreapplication.h"
+#include "qcoreapplication_p.h"
+#include "qdeadlinetimer.h"
+#include "qmetaobject_p.h"
+#include "qobject_p.h"
+#include "qproperty_p.h"
+#include "qthread.h"
+
+using namespace std::chrono_literals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QTimer
+ \inmodule QtCore
+ \brief The QTimer class provides repetitive and single-shot timers.
+
+ \ingroup events
+
+
+ The QTimer class provides a high-level programming interface for
+ timers. To use it, create a QTimer, connect its timeout() signal
+ to the appropriate slots, and call start(). From then on, it will
+ emit the timeout() signal at constant intervals.
+
+ Example for a one second (1000 millisecond) timer (from the
+ \l{widgets/analogclock}{Analog Clock} example):
+
+ \snippet ../widgets/widgets/analogclock/analogclock.cpp 4
+ \snippet ../widgets/widgets/analogclock/analogclock.cpp 5
+ \snippet ../widgets/widgets/analogclock/analogclock.cpp 6
+
+ From then on, the \c update() slot is called every second.
+
+ You can set a timer to time out only once by calling
+ setSingleShot(true). You can also use the static
+ QTimer::singleShot() function to call a slot after a specified
+ interval:
+
+ \snippet timers/timers.cpp 3
+
+ In multithreaded applications, you can use QTimer in any thread
+ that has an event loop. To start an event loop from a non-GUI
+ thread, use QThread::exec(). Qt uses the timer's
+ \l{QObject::thread()}{thread affinity} to determine which thread
+ will emit the \l{QTimer::}{timeout()} signal. Because of this, you
+ must start and stop the timer in its thread; it is not possible to
+ start a timer from another thread.
+
+ As a special case, a QTimer with a timeout of 0 will time out as soon as
+ possible, though the ordering between zero timers and other sources of
+ events is unspecified. Zero timers can be used to do some work while still
+ providing a snappy user interface:
+
+ \snippet timers/timers.cpp 4
+ \snippet timers/timers.cpp 5
+ \snippet timers/timers.cpp 6
+
+ From then on, \c processOneThing() will be called repeatedly. It
+ should be written in such a way that it always returns quickly
+ (typically after processing one data item) so that Qt can deliver
+ events to the user interface and stop the timer as soon as it has done all
+ its work. This is the traditional way of implementing heavy work
+ in GUI applications, but as multithreading is nowadays becoming available on
+ more and more platforms, we expect that zero-millisecond
+ QTimer objects will gradually be replaced by \l{QThread}s.
+
+ \section1 Accuracy and Timer Resolution
+
+ The accuracy of timers depends on the underlying operating system
+ and hardware. Most platforms support a resolution of 1 millisecond,
+ though the accuracy of the timer will not equal this resolution
+ in many real-world situations.
+
+ The accuracy also depends on the \l{Qt::TimerType}{timer type}. For
+ Qt::PreciseTimer, QTimer will try to keep the accuracy at 1 millisecond.
+ Precise timers will also never time out earlier than expected.
+
+ For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QTimer may wake up
+ earlier than expected, within the margins for those types: 5% of the
+ interval for Qt::CoarseTimer and 500 ms for Qt::VeryCoarseTimer.
+
+ All timer types may time out later than expected if the system is busy or
+ unable to provide the requested accuracy. In such a case of timeout
+ overrun, Qt will emit timeout() only once, even if multiple timeouts have
+ expired, and then will resume the original interval.
+
+ \section1 Alternatives to QTimer
+
+ An alternative to using QTimer is to call QObject::startTimer()
+ for your object and reimplement the QObject::timerEvent() event
+ handler in your class (which must inherit QObject). The
+ disadvantage is that timerEvent() does not support such
+ high-level features as single-shot timers or signals.
+
+ Another alternative is QBasicTimer. It is typically less
+ cumbersome than using QObject::startTimer()
+ directly. See \l{Timers} for an overview of all three approaches.
+
+ Some operating systems limit the number of timers that may be
+ used; Qt tries to work around these limitations.
+
+ \sa QBasicTimer, QTimerEvent, QObject::timerEvent(), Timers,
+ {Analog Clock}
+*/
+
+/*!
+ Constructs a timer with the given \a parent.
+*/
+
+QTimer::QTimer(QObject *parent)
+ : QObject(*new QTimerPrivate(this), parent)
+{
+ Q_ASSERT(d_func()->isQTimer);
+}
+
+
+/*!
+ Destroys the timer.
+*/
+
+QTimer::~QTimer()
+{
+ if (d_func()->id != QTimerPrivate::INV_TIMER) // stop running timer
+ stop();
+}
+
+
+/*!
+ \fn void QTimer::timeout()
+
+ This signal is emitted when the timer times out.
+
+ \sa interval, start(), stop()
+*/
+
+/*!
+ \property QTimer::active
+ \since 4.3
+
+ This boolean property is \c true if the timer is running; otherwise
+ false.
+*/
+
+/*!
+ \fn bool QTimer::isActive() const
+
+ Returns \c true if the timer is running (pending); otherwise returns
+ false.
+*/
+bool QTimer::isActive() const
+{
+ return d_func()->isActiveData.value();
+}
+
+QBindable<bool> QTimer::bindableActive()
+{
+ return QBindable<bool>(&d_func()->isActiveData);
+}
+
+/*!
+ \fn int QTimer::timerId() const
+
+ Returns the ID of the timer if the timer is running; otherwise returns
+ -1.
+*/
+int QTimer::timerId() const
+{
+ return d_func()->id;
+}
+
+
+/*! \overload start()
+
+ Starts or restarts the timer with the timeout specified in \l interval.
+
+ If the timer is already running, it will be
+ \l{QTimer::stop()}{stopped} and restarted.
+
+ If \l singleShot is true, the timer will be activated only once.
+*/
+void QTimer::start()
+{
+ Q_D(QTimer);
+ if (d->id != QTimerPrivate::INV_TIMER) // stop running timer
+ stop();
+ const int id = QObject::startTimer(std::chrono::milliseconds{d->inter}, d->type);
+ if (id > 0) {
+ d->id = id;
+ d->isActiveData.notify();
+ }
+}
+
+/*!
+ Starts or restarts the timer with a timeout interval of \a msec
+ milliseconds.
+
+ If the timer is already running, it will be
+ \l{QTimer::stop()}{stopped} and restarted.
+
+ If \l singleShot is true, the timer will be activated only once. This is
+ equivalent to:
+
+ \code
+ timer.setInterval(msec);
+ timer.start();
+ \endcode
+
+ \note Keeping the event loop busy with a zero-timer is bound to
+ cause trouble and highly erratic behavior of the UI.
+*/
+void QTimer::start(int msec)
+{
+ start(msec * 1ms);
+}
+
+void QTimer::start(std::chrono::milliseconds interval)
+{
+ Q_D(QTimer);
+ // This could be narrowing as the interval is stored in an `int` QProperty,
+ // and the type can't be changed in Qt6.
+ const int msec = interval.count();
+ const bool intervalChanged = msec != d->inter;
+ d->inter.setValue(msec);
+ start();
+ if (intervalChanged)
+ d->inter.notify();
+}
+
+
+
+/*!
+ Stops the timer.
+
+ \sa start()
+*/
+
+void QTimer::stop()
+{
+ Q_D(QTimer);
+ if (d->id != QTimerPrivate::INV_TIMER) {
+ QObject::killTimer(d->id);
+ d->id = QTimerPrivate::INV_TIMER;
+ d->isActiveData.notify();
+ }
+}
+
+
+/*!
+ \reimp
+*/
+void QTimer::timerEvent(QTimerEvent *e)
+{
+ Q_D(QTimer);
+ if (e->timerId() == d->id) {
+ if (d->single)
+ stop();
+ emit timeout(QPrivateSignal());
+ }
+}
+
+/*!
+ \internal
+
+ Implementation of the template version of singleShot
+
+ \a msec is the timer interval
+ \a timerType is the timer type
+ \a receiver is the receiver object, can be null. In such a case, it will be the same
+ as the final sender class.
+ \a slotObj the slot object
+*/
+void QTimer::singleShotImpl(std::chrono::milliseconds msec, Qt::TimerType timerType,
+ const QObject *receiver,
+ QtPrivate::QSlotObjectBase *slotObj)
+{
+ if (msec == 0ms) {
+ bool deleteReceiver = false;
+ // Optimize: set a receiver context when none is given, such that we can use
+ // QMetaObject::invokeMethod which is more efficient than going through a timer.
+ // We need a QObject living in the current thread. But the QThread itself lives
+ // in a different thread - with the exception of the main QThread which lives in
+ // itself. And QThread::currentThread() is among the few QObjects we know that will
+ // most certainly be there. Note that one can actually call singleShot before the
+ // QApplication is created!
+ if (!receiver && QThread::currentThread() == QCoreApplicationPrivate::mainThread()) {
+ // reuse main thread as context object
+ receiver = QThread::currentThread();
+ } else if (!receiver) {
+ // Create a receiver context object on-demand. According to the benchmarks,
+ // this is still more efficient than going through a timer.
+ receiver = new QObject;
+ deleteReceiver = true;
+ }
+
+ auto h = QtPrivate::invokeMethodHelper({});
+ QMetaObject::invokeMethodImpl(const_cast<QObject *>(receiver), slotObj,
+ Qt::QueuedConnection, h.parameterCount(), h.parameters.data(), h.typeNames.data(),
+ h.metaTypes.data());
+
+ if (deleteReceiver)
+ const_cast<QObject *>(receiver)->deleteLater();
+ return;
+ }
+
+ new QSingleShotTimer(msec, timerType, receiver, slotObj);
+}
+
+/*!
+ \fn void QTimer::singleShot(int msec, const QObject *receiver, const char *member)
+ \reentrant
+ \deprecated [6.8] Use the chrono overloads.
+ This static function calls a slot after a given time interval.
+
+ It is very convenient to use this function because you do not need
+ to bother with a \l{QObject::timerEvent()}{timerEvent} or
+ create a local QTimer object.
+
+ Example:
+ \snippet code/src_corelib_kernel_qtimer.cpp 0
+
+ This sample program automatically terminates after 10 minutes
+ (600,000 milliseconds).
+
+ The \a receiver is the receiving object and the \a member is the
+ slot. The time interval is \a msec milliseconds.
+
+ \sa start()
+*/
+
+/*!
+ \fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
+ \overload
+ \reentrant
+ \deprecated [6.8] Use the chrono overloads.
+ This static function calls a slot after a given time interval.
+
+ It is very convenient to use this function because you do not need
+ to bother with a \l{QObject::timerEvent()}{timerEvent} or
+ create a local QTimer object.
+
+ The \a receiver is the receiving object and the \a member is the slot. The
+ time interval is \a msec milliseconds. The \a timerType affects the
+ accuracy of the timer.
+
+ \sa start()
+*/
+
+void QTimer::singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType,
+ const QObject *receiver, const char *member)
+{
+ if (Q_UNLIKELY(msec < 0ms)) {
+ qWarning("QTimer::singleShot: Timers cannot have negative timeouts");
+ return;
+ }
+ if (receiver && member) {
+ if (msec == 0ms) {
+ // special code shortpath for 0-timers
+ const char* bracketPosition = strchr(member, '(');
+ if (!bracketPosition || !(member[0] >= '0' && member[0] <= '2')) {
+ qWarning("QTimer::singleShot: Invalid slot specification");
+ return;
+ }
+ const auto methodName = QByteArrayView(member + 1, // extract method name
+ bracketPosition - 1 - member).trimmed();
+ QMetaObject::invokeMethod(const_cast<QObject *>(receiver), methodName.toByteArray().constData(),
+ Qt::QueuedConnection);
+ return;
+ }
+ (void) new QSingleShotTimer(msec, timerType, receiver, member);
+ }
+}
+
+/*! \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, const QObject *context, Functor &&functor)
+ \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Qt::TimerType timerType, const QObject *context, Functor &&functor)
+ \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Functor &&functor)
+ \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Qt::TimerType timerType, Functor &&functor)
+ \since 5.4
+
+ \reentrant
+ This static function calls \a functor after \a msec milliseconds.
+
+ It is very convenient to use this function because you do not need
+ to bother with a \l{QObject::timerEvent()}{timerEvent} or
+ create a local QTimer object.
+
+ If \a context is specified, then the \a functor will be called only if the
+ \a context object has not been destroyed before the interval occurs. The functor
+ will then be run the thread of \a context. The context's thread must have a
+ running Qt event loop.
+
+ If \a functor is a member
+ function of \a context, then the function will be called on the object.
+
+ The \a msec parameter can be an \c int or a \c std::chrono::milliseconds value.
+
+ \sa start()
+*/
+
+/*!
+ \fn void QTimer::singleShot(std::chrono::milliseconds msec, const QObject *receiver, const char *member)
+ \since 5.8
+ \overload
+ \reentrant
+
+ This static function calls a slot after a given time interval.
+
+ It is very convenient to use this function because you do not need
+ to bother with a \l{QObject::timerEvent()}{timerEvent} or
+ create a local QTimer object.
+
+ The \a receiver is the receiving object and the \a member is the slot. The
+ time interval is given in the duration object \a msec.
+
+ \sa start()
+*/
+
+/*!
+ \fn void QTimer::singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
+ \since 5.8
+ \overload
+ \reentrant
+
+ This static function calls a slot after a given time interval.
+
+ It is very convenient to use this function because you do not need
+ to bother with a \l{QObject::timerEvent()}{timerEvent} or
+ create a local QTimer object.
+
+ The \a receiver is the receiving object and the \a member is the slot. The
+ time interval is given in the duration object \a msec. The \a timerType affects the
+ accuracy of the timer.
+
+ \sa start()
+*/
+
+/*!
+ \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(Functor &&slot)
+ \since 5.12
+
+ Creates a connection from the timer's timeout() signal to \a slot.
+ Returns a handle to the connection.
+
+ This method is provided for convenience. It's equivalent to calling:
+ \code
+ QObject::connect(timer, &QTimer::timeout, timer, slot, Qt::DirectConnection);
+ \endcode
+
+ \note This overload is not available when \c {QT_NO_CONTEXTLESS_CONNECT} is
+ defined, instead use the callOnTimeout() overload that takes a context object.
+
+ \sa QObject::connect(), timeout()
+*/
+
+/*!
+ \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection)
+ \since 5.12
+ \overload callOnTimeout()
+
+ Creates a connection from the timeout() signal to \a slot to be placed in a specific
+ event loop of \a context, and returns a handle to the connection.
+
+ This method is provided for convenience. It's equivalent to calling:
+ \code
+ QObject::connect(timer, &QTimer::timeout, context, slot, connectionType);
+ \endcode
+
+ \sa QObject::connect(), timeout()
+*/
+
+/*!
+ \fn void QTimer::start(std::chrono::milliseconds msec)
+ \since 5.8
+ \overload
+
+ Starts or restarts the timer with a timeout of duration \a msec milliseconds.
+
+ If the timer is already running, it will be
+ \l{QTimer::stop()}{stopped} and restarted.
+
+ If \l singleShot is true, the timer will be activated only once. This is
+ equivalent to:
+
+ \code
+ timer.setInterval(msec);
+ timer.start();
+ \endcode
+*/
+
+/*!
+ \fn std::chrono::milliseconds QTimer::intervalAsDuration() const
+ \since 5.8
+
+ Returns the interval of this timer as a \c std::chrono::milliseconds object.
+
+ \sa interval
+*/
+
+/*!
+ \fn std::chrono::milliseconds QTimer::remainingTimeAsDuration() const
+ \since 5.8
+
+ Returns the time remaining in this timer object as a \c
+ std::chrono::milliseconds object. If this timer is due or overdue, the
+ returned value is \c std::chrono::milliseconds::zero(). If the remaining
+ time could not be found or the timer is not active, this function returns a
+ negative duration.
+
+ \sa remainingTime()
+*/
+
+/*!
+ \property QTimer::singleShot
+ \brief whether the timer is a single-shot timer
+
+ A single-shot timer fires only once, non-single-shot timers fire
+ every \l interval milliseconds.
+
+ The default value for this property is \c false.
+
+ \sa interval, singleShot()
+*/
+void QTimer::setSingleShot(bool singleShot)
+{
+ d_func()->single = singleShot;
+}
+
+bool QTimer::isSingleShot() const
+{
+ return d_func()->single;
+}
+
+QBindable<bool> QTimer::bindableSingleShot()
+{
+ return QBindable<bool>(&d_func()->single);
+}
+
+/*!
+ \property QTimer::interval
+ \brief the timeout interval in milliseconds
+
+ The default value for this property is 0. A QTimer with a timeout
+ interval of 0 will time out as soon as all the events in the window
+ system's event queue have been processed.
+
+ Setting the interval of an active timer changes its timerId().
+
+ \sa singleShot
+*/
+void QTimer::setInterval(int msec)
+{
+ setInterval(std::chrono::milliseconds{msec});
+}
+
+void QTimer::setInterval(std::chrono::milliseconds interval)
+{
+ Q_D(QTimer);
+ // This could be narrowing as the interval is stored in an `int` QProperty,
+ // and the type can't be changed in Qt6.
+ const int msec = interval.count();
+ d->inter.removeBindingUnlessInWrapper();
+ const bool intervalChanged = msec != d->inter.valueBypassingBindings();
+ d->inter.setValueBypassingBindings(msec);
+ if (d->id != QTimerPrivate::INV_TIMER) { // create new timer
+ QObject::killTimer(d->id); // restart timer
+ const int id = QObject::startTimer(std::chrono::milliseconds{msec}, d->type);
+ if (id > 0) {
+ // Restarted successfully. No need to update the active state.
+ d->id = id;
+ } else {
+ // Failed to start the timer.
+ // Need to notify about active state change.
+ d->id = QTimerPrivate::INV_TIMER;
+ d->isActiveData.notify();
+ }
+ }
+ if (intervalChanged)
+ d->inter.notify();
+}
+
+int QTimer::interval() const
+{
+ return d_func()->inter;
+}
+
+QBindable<int> QTimer::bindableInterval()
+{
+ return QBindable<int>(&d_func()->inter);
+}
+
+/*!
+ \property QTimer::remainingTime
+ \since 5.0
+ \brief the remaining time in milliseconds
+
+ Returns the timer's remaining value in milliseconds left until the timeout.
+ If the timer is inactive, the returned value will be -1. If the timer is
+ overdue, the returned value will be 0.
+
+ \sa interval
+*/
+int QTimer::remainingTime() const
+{
+ Q_D(const QTimer);
+ if (d->id != QTimerPrivate::INV_TIMER) {
+ return QAbstractEventDispatcher::instance()->remainingTime(d->id);
+ }
+
+ return -1;
+}
+
+/*!
+ \property QTimer::timerType
+ \brief controls the accuracy of the timer
+
+ The default value for this property is \c Qt::CoarseTimer.
+
+ \sa Qt::TimerType
+*/
+void QTimer::setTimerType(Qt::TimerType atype)
+{
+ d_func()->type = atype;
+}
+
+Qt::TimerType QTimer::timerType() const
+{
+ return d_func()->type;
+}
+
+QBindable<Qt::TimerType> QTimer::bindableTimerType()
+{
+ return QBindable<Qt::TimerType>(&d_func()->type);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qtimer.cpp"
diff --git a/src/corelib/kernel/qchronotimer.h b/src/corelib/kernel/qchronotimer.h
new file mode 100644
index 0000000000..9b59895e60
--- /dev/null
+++ b/src/corelib/kernel/qchronotimer.h
@@ -0,0 +1,187 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTIMER_H
+#define QTIMER_H
+
+#include <QtCore/qglobal.h>
+
+#ifndef QT_NO_QOBJECT
+
+#include <QtCore/qbasictimer.h> // conceptual inheritance
+#include <QtCore/qobject.h>
+
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+class QTimerPrivate;
+class Q_CORE_EXPORT QTimer : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(bool singleShot READ isSingleShot WRITE setSingleShot BINDABLE bindableSingleShot)
+ Q_PROPERTY(int interval READ interval WRITE setInterval BINDABLE bindableInterval)
+ Q_PROPERTY(int remainingTime READ remainingTime)
+ Q_PROPERTY(Qt::TimerType timerType READ timerType WRITE setTimerType BINDABLE bindableTimerType)
+ Q_PROPERTY(bool active READ isActive STORED false BINDABLE bindableActive)
+public:
+ explicit QTimer(QObject *parent = nullptr);
+ ~QTimer();
+
+ bool isActive() const;
+ QBindable<bool> bindableActive();
+ int timerId() const;
+
+ void setInterval(int msec);
+ int interval() const;
+ QBindable<int> bindableInterval();
+
+ int remainingTime() const;
+
+ void setTimerType(Qt::TimerType atype);
+ Qt::TimerType timerType() const;
+ QBindable<Qt::TimerType> bindableTimerType();
+
+ void setSingleShot(bool singleShot);
+ bool isSingleShot() const;
+ QBindable<bool> bindableSingleShot();
+
+ QT_CORE_INLINE_SINCE(6, 8)
+ static void singleShot(int msec, const QObject *receiver, const char *member);
+
+ QT_CORE_INLINE_SINCE(6, 8)
+ static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member);
+
+ // singleShot with context
+#ifdef Q_QDOC
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval, const QObject *receiver, Functor &&slot);
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval, Qt::TimerType timerType,
+ const QObject *receiver, Functor &&slot);
+#else
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval,
+ const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
+ Functor &&slot)
+ {
+ singleShot(interval, defaultTypeFor(interval), receiver, std::forward<Functor>(slot));
+ }
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval, Qt::TimerType timerType,
+ const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
+ Functor &&slot)
+ {
+ using Prototype = void(*)();
+ singleShotImpl(interval, timerType, receiver,
+ QtPrivate::makeCallableObject<Prototype>(std::forward<Functor>(slot)));
+ }
+#endif
+
+ // singleShot without context
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval, Functor &&slot)
+ {
+ singleShot(interval, defaultTypeFor(interval), nullptr, std::forward<Functor>(slot));
+ }
+ template <typename Duration, typename Functor>
+ static inline void singleShot(Duration interval, Qt::TimerType timerType, Functor &&slot)
+ {
+ singleShot(interval, timerType, nullptr, std::forward<Functor>(slot));
+ }
+
+#ifdef Q_QDOC
+ template <typename Functor>
+ QMetaObject::Connection callOnTimeout(Functor &&slot);
+ template <typename Functor>
+ QMetaObject::Connection callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection);
+#else
+ template <typename ... Args>
+ QMetaObject::Connection callOnTimeout(Args && ...args)
+ {
+ return QObject::connect(this, &QTimer::timeout, std::forward<Args>(args)... );
+ }
+
+#endif
+
+public Q_SLOTS:
+ void start(int msec);
+
+ void start();
+ void stop();
+
+Q_SIGNALS:
+ void timeout(QPrivateSignal);
+
+public:
+ void setInterval(std::chrono::milliseconds value);
+
+ std::chrono::milliseconds intervalAsDuration() const
+ {
+ return std::chrono::milliseconds(interval());
+ }
+
+ std::chrono::milliseconds remainingTimeAsDuration() const
+ {
+ return std::chrono::milliseconds(remainingTime());
+ }
+
+ static void singleShot(std::chrono::milliseconds value, const QObject *receiver, const char *member)
+ {
+ singleShot(value, defaultTypeFor(value), receiver, member);
+ }
+ static void singleShot(std::chrono::milliseconds interval, Qt::TimerType timerType,
+ const QObject *receiver, const char *member);
+
+ void start(std::chrono::milliseconds value);
+
+protected:
+ void timerEvent(QTimerEvent *) override;
+
+private:
+ Q_DISABLE_COPY(QTimer)
+ Q_DECLARE_PRIVATE(QTimer)
+
+ inline int startTimer(int){ return -1;}
+ inline void killTimer(int){}
+
+ static constexpr Qt::TimerType defaultTypeFor(int msecs) noexcept
+ { return defaultTypeFor(std::chrono::milliseconds{msecs}); }
+
+ static constexpr Qt::TimerType defaultTypeFor(std::chrono::milliseconds interval) noexcept
+ {
+ // coarse timers are worst in their first firing
+ // so we prefer a high precision timer for something that happens only once
+ // unless the timeout is too big, in which case we go for coarse anyway
+ using namespace std::chrono_literals;
+ return interval >= 2s ? Qt::CoarseTimer : Qt::PreciseTimer;
+ }
+
+ QT_CORE_INLINE_SINCE(6, 8)
+ static void singleShotImpl(int msec, Qt::TimerType timerType,
+ const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj);
+
+ static void singleShotImpl(std::chrono::milliseconds interval, Qt::TimerType timerType,
+ const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj);
+};
+
+#if QT_CORE_INLINE_IMPL_SINCE(6, 8)
+void QTimer::singleShot(int msec, const QObject *receiver, const char *member)
+{ singleShot(std::chrono::milliseconds{msec}, receiver, member); }
+
+void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver,
+ const char *member)
+{ singleShot(std::chrono::milliseconds{msec}, timerType, receiver, member); }
+
+void QTimer::singleShotImpl(int msec, Qt::TimerType timerType,
+ const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj)
+{
+ singleShotImpl(std::chrono::milliseconds{msec}, timerType, receiver, slotObj);
+}
+#endif
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_QOBJECT
+
+#endif // QTIMER_H
diff --git a/tests/auto/corelib/kernel/qchronotimer/.gitignore b/tests/auto/corelib/kernel/qchronotimer/.gitignore
new file mode 100644
index 0000000000..14fd00629e
--- /dev/null
+++ b/tests/auto/corelib/kernel/qchronotimer/.gitignore
@@ -0,0 +1 @@
+tst_qtimer
diff --git a/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt b/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt
new file mode 100644
index 0000000000..6bb3b15850
--- /dev/null
+++ b/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qtimer LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+if (NOT QT_FEATURE_thread)
+ return()
+endif()
+
+function(addTimerTest test)
+ qt_internal_add_test(${test}
+ SOURCES
+ tst_qtimer.cpp
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::TestPrivate
+ )
+endfunction()
+
+addTimerTest(tst_qtimer)
+
+if(QT_FEATURE_glib AND UNIX)
+ addTimerTest(tst_qtimer_no_glib)
+ qt_internal_extend_target(tst_qtimer_no_glib
+ DEFINES
+ DISABLE_GLIB
+ tst_QTimer=tst_QTimer_no_glib # Class name in the unittest
+ )
+endif()
+
diff --git a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp
new file mode 100644
index 0000000000..467fc9abd7
--- /dev/null
+++ b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp
@@ -0,0 +1,1539 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/* WARNING: this source-code is reused by another test.
+
+ As Qt built with GUI support may use a different backend for its event loops
+ and other timer-related matters, it is important to test it in that form, as
+ well as in its GUI-less form. So this source file is reused by a build config
+ in the GUI module. Similarly, testing with and without glib is supported,
+ where relevant (see DISABLE_GLIB below).
+*/
+#ifdef QT_GUI_LIB
+// When compiled as tests/auto/gui/kernel/qguitimer/'s source-code:
+# include <QtGui/QGuiApplication>
+#else
+// When compiled as tests/auto/corelib/kernel/qtimer/'s source-code:
+# include <QtCore/QCoreApplication>
+#endif
+
+#include <QtCore/private/qglobal_p.h>
+#include <QTest>
+#include <QSignalSpy>
+#include <QtTest/private/qpropertytesthelper_p.h>
+
+#include <qtimer.h>
+#include <qthread.h>
+#include <qelapsedtimer.h>
+#include <qproperty.h>
+
+#if defined Q_OS_UNIX
+#include <unistd.h>
+#endif
+
+#ifdef DISABLE_GLIB
+static bool glibDisabled = []() {
+ qputenv("QT_NO_GLIB", "1");
+ return true;
+}();
+#endif
+
+using namespace std::chrono_literals;
+
+class tst_QTimer : public QObject
+{
+ Q_OBJECT
+public:
+ static void initMain();
+
+private slots:
+ void cleanupTestCase();
+ void zeroTimer();
+ void singleShotTimeout();
+ void timeout();
+ void singleShotNormalizes_data();
+ void singleShotNormalizes();
+ void sequentialTimers_data();
+ void sequentialTimers();
+ void singleShotSequentialTimers_data();
+ void singleShotSequentialTimers();
+ void remainingTime();
+ void remainingTimeInitial_data();
+ void remainingTimeInitial();
+ void remainingTimeDuringActivation_data();
+ void remainingTimeDuringActivation();
+ void basic_chrono();
+ void livelock_data();
+ void livelock();
+ void timerInfiniteRecursion_data();
+ void timerInfiniteRecursion();
+ void recurringTimer_data();
+ void recurringTimer();
+ void deleteLaterOnQTimer(); // long name, don't want to shadow QObject::deleteLater()
+ void moveToThread();
+ void restartedTimerFiresTooSoon();
+ void timerFiresOnlyOncePerProcessEvents_data();
+ void timerFiresOnlyOncePerProcessEvents();
+ void timerIdPersistsAfterThreadExit();
+ void cancelLongTimer();
+ void singleShotStaticFunctionZeroTimeout();
+ void recurseOnTimeoutAndStopTimer();
+ void singleShotToFunctors();
+ void singleShot_chrono();
+ void singleShot_static();
+ void crossThreadSingleShotToFunctor_data();
+ void crossThreadSingleShotToFunctor();
+ void timerOrder();
+ void timerOrder_data();
+ void timerOrderBackgroundThread();
+ void timerOrderBackgroundThread_data() { timerOrder_data(); }
+
+ void dontBlockEvents();
+ void postedEventsShouldNotStarveTimers();
+ void callOnTimeout();
+
+ void bindToTimer();
+ void bindTimer();
+ void automatedBindingTests();
+
+ void negativeInterval();
+};
+
+void tst_QTimer::zeroTimer()
+{
+ QTimer timer;
+ QVERIFY(!timer.isSingleShot());
+ timer.setInterval(0);
+ timer.setSingleShot(true);
+ QVERIFY(timer.isSingleShot());
+
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.start();
+
+ // Pass timeout to work round glib issue, see QTBUG-84291.
+ QCoreApplication::processEvents(QEventLoop::AllEvents, INT_MAX);
+
+ QCOMPARE(timeoutSpy.size(), 1);
+}
+
+void tst_QTimer::singleShotTimeout()
+{
+ QTimer timer;
+ QVERIFY(!timer.isSingleShot());
+ timer.setSingleShot(true);
+ QVERIFY(timer.isSingleShot());
+
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.start(100);
+
+ QVERIFY(timeoutSpy.wait(500));
+ QCOMPARE(timeoutSpy.size(), 1);
+ QTest::qWait(500);
+ QCOMPARE(timeoutSpy.size(), 1);
+}
+
+#define TIMEOUT_TIMEOUT 200
+
+void tst_QTimer::timeout()
+{
+ QTimer timer;
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.start(100);
+
+ QCOMPARE(timeoutSpy.size(), 0);
+
+ QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 0, TIMEOUT_TIMEOUT);
+ int oldCount = timeoutSpy.size();
+
+ QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > oldCount, TIMEOUT_TIMEOUT);
+}
+
+void tst_QTimer::singleShotNormalizes_data()
+{
+ QTest::addColumn<QByteArray>("slotName");
+
+ QTest::newRow("normalized") << QByteArray(SLOT(exitLoop()));
+
+ QTest::newRow("space-before") << QByteArray(SLOT( exitLoop()));
+ QTest::newRow("space-after") << QByteArray(SLOT(exitLoop ()));
+ QTest::newRow("space-around") << QByteArray(SLOT( exitLoop ()));
+ QTest::newRow("spaces-before") << QByteArray(SLOT( exitLoop()));
+ QTest::newRow("spaces-after") << QByteArray(SLOT(exitLoop ()));
+ QTest::newRow("spaces-around") << QByteArray(SLOT( exitLoop ()));
+
+ QTest::newRow("space-in-parens") << QByteArray(SLOT(exitLoop( )));
+ QTest::newRow("spaces-in-parens") << QByteArray(SLOT(exitLoop( )));
+ QTest::newRow("space-after-parens") << QByteArray(SLOT(exitLoop() ));
+ QTest::newRow("spaces-after-parens") << QByteArray(SLOT(exitLoop() ));
+}
+
+void tst_QTimer::singleShotNormalizes()
+{
+ static constexpr auto TestTimeout = 250ms;
+ QFETCH(QByteArray, slotName);
+ QEventLoop loop;
+
+ // control test: regular connection
+ {
+ QTimer timer;
+ QVERIFY(QObject::connect(&timer, SIGNAL(timeout()), &QTestEventLoop::instance(), slotName));
+ timer.setSingleShot(true);
+ timer.start(1);
+ QTestEventLoop::instance().enterLoop(TestTimeout);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ }
+
+ // non-zero time
+ QTimer::singleShot(1, &QTestEventLoop::instance(), slotName);
+ QTestEventLoop::instance().enterLoop(TestTimeout);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+
+ QTimer::singleShot(1ms, &QTestEventLoop::instance(), slotName);
+ QTestEventLoop::instance().enterLoop(TestTimeout);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+
+ // zero time
+ QTimer::singleShot(0, &QTestEventLoop::instance(), slotName);
+ QTestEventLoop::instance().enterLoop(TestTimeout);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+
+ QTimer::singleShot(0ms, &QTestEventLoop::instance(), slotName);
+ QTestEventLoop::instance().enterLoop(TestTimeout);
+ QVERIFY(!QTestEventLoop::instance().timeout());
+}
+
+void tst_QTimer::sequentialTimers_data()
+{
+#ifdef Q_OS_WIN
+ QSKIP("The API used by QEventDispatcherWin32 doesn't respect the order");
+#endif
+ QTest::addColumn<QList<int>>("timeouts");
+ auto addRow = [](const QList<int> &l) {
+ QByteArray name;
+ int last = -1;
+ for (int i = 0; i < l.size(); ++i) {
+ Q_ASSERT_X(l[i] >= last, "tst_QTimer", "input list must be sorted");
+ name += QByteArray::number(l[i]) + ',';
+ }
+ name.chop(1);
+ QTest::addRow("%s", name.constData()) << l;
+ };
+ // PreciseTimers
+ addRow({0, 0, 0, 0, 0, 0});
+ addRow({0, 1, 2});
+ addRow({1, 1, 1, 2, 2, 2, 2});
+ addRow({1, 2, 3});
+ addRow({19, 19, 19});
+ // CoarseTimer for setInterval
+ addRow({20, 20, 20, 20, 20});
+ addRow({25, 25, 25, 25, 25, 25, 50});
+}
+
+void tst_QTimer::sequentialTimers()
+{
+ QFETCH(const QList<int>, timeouts);
+ QByteArray result, expected;
+ std::vector<std::unique_ptr<QTimer>> timers;
+ expected.resize(timeouts.size());
+ result.reserve(timeouts.size());
+ timers.reserve(timeouts.size());
+ for (int i = 0; i < timeouts.size(); ++i) {
+ auto timer = std::make_unique<QTimer>();
+ timer->setSingleShot(true);
+ timer->setInterval(timeouts[i]);
+
+ char c = 'A' + i;
+ expected[i] = c;
+ QObject::connect(timer.get(), &QTimer::timeout, this, [&result, c = c]() {
+ result.append(c);
+ });
+ timers.push_back(std::move(timer));
+ }
+
+ // start the timers
+ for (auto &timer : timers)
+ timer->start();
+
+ QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10);
+
+ QCOMPARE(result, expected);
+}
+
+void tst_QTimer::singleShotSequentialTimers_data()
+{
+ sequentialTimers_data();
+}
+
+void tst_QTimer::singleShotSequentialTimers()
+{
+ QFETCH(const QList<int>, timeouts);
+ QByteArray result, expected;
+ expected.resize(timeouts.size());
+ result.reserve(timeouts.size());
+ for (int i = 0; i < timeouts.size(); ++i) {
+ char c = 'A' + i;
+ expected[i] = c;
+ QTimer::singleShot(timeouts[i], this, [&result, c = c]() {
+ result.append(c);
+ });
+ }
+
+ QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10);
+
+ QCOMPARE(result, expected);
+}
+
+void tst_QTimer::remainingTime()
+{
+ QTimer tested;
+ tested.setTimerType(Qt::PreciseTimer);
+
+ QTimer tester;
+ tester.setTimerType(Qt::PreciseTimer);
+ tester.setSingleShot(true);
+
+ const int testedInterval = 200;
+ const int testerInterval = 50;
+ const int expectedRemainingTime = testedInterval - testerInterval;
+
+ int testIteration = 0;
+ const int desiredTestCount = 2;
+
+ auto connection = QObject::connect(&tested, &QTimer::timeout, [&tester]() {
+ // We let tested (which isn't a single-shot) run repeatedly, to verify
+ // it *does* repeat, and check that the single-shot tester, starting
+ // at the same time, does finish first each time, by about the right duration.
+ tester.start(); // Start tester again.
+ });
+
+ QObject::connect(&tester, &QTimer::timeout, [&]() {
+ const int remainingTime = tested.remainingTime();
+ // We expect that remainingTime is at most 150 and not overdue.
+ const bool remainingTimeInRange = remainingTime > 0
+ && remainingTime <= expectedRemainingTime;
+ if (remainingTimeInRange)
+ ++testIteration;
+ else
+ testIteration = desiredTestCount; // We are going to fail on QVERIFY2()
+ // below, so we don't want to iterate
+ // anymore and quickly exit the QTRY_...()
+ // with this failure.
+ if (testIteration == desiredTestCount)
+ QObject::disconnect(connection); // Last iteration, don't start tester again.
+ QVERIFY2(remainingTimeInRange, qPrintable("Remaining time "
+ + QByteArray::number(remainingTime) + "ms outside expected range (0ms, "
+ + QByteArray::number(expectedRemainingTime) + "ms]"));
+ });
+
+ tested.start(testedInterval);
+ tester.start(testerInterval); // Start tester for the 1st time.
+
+ // Test it desiredTestCount times, give it reasonable amount of time
+ // (twice as much as needed).
+ QTRY_COMPARE_WITH_TIMEOUT(testIteration, desiredTestCount,
+ testedInterval * desiredTestCount * 2);
+}
+
+void tst_QTimer::remainingTimeInitial_data()
+{
+ QTest::addColumn<int>("startTimeMs");
+ QTest::addColumn<Qt::TimerType>("timerType");
+
+ QTest::addRow("precise time 0ms") << 0 << Qt::PreciseTimer;
+ QTest::addRow("precise time 1ms") << 1 << Qt::PreciseTimer;
+ QTest::addRow("precise time 10ms") << 10 << Qt::PreciseTimer;
+
+ QTest::addRow("coarse time 0ms") << 0 << Qt::CoarseTimer;
+ QTest::addRow("coarse time 1ms") << 1 << Qt::CoarseTimer;
+ QTest::addRow("coarse time 10ms") << 10 << Qt::CoarseTimer;
+}
+
+void tst_QTimer::remainingTimeInitial()
+{
+ QFETCH(int, startTimeMs);
+ QFETCH(Qt::TimerType, timerType);
+
+ QTimer timer;
+ QCOMPARE(timer.timerType(), Qt::CoarseTimer);
+ timer.setTimerType(timerType);
+ QCOMPARE(timer.timerType(), timerType);
+ timer.start(startTimeMs);
+
+ const int rt = timer.remainingTime();
+ QVERIFY2(rt >= 0 && rt <= startTimeMs, qPrintable(QString::number(rt)));
+}
+
+void tst_QTimer::remainingTimeDuringActivation_data()
+{
+ QTest::addColumn<bool>("singleShot");
+ QTest::newRow("repeating") << false;
+ QTest::newRow("single-shot") << true;
+}
+
+void tst_QTimer::remainingTimeDuringActivation()
+{
+ QFETCH(bool, singleShot);
+
+ QTimer timer;
+ timer.setSingleShot(singleShot);
+
+ int remainingTime = 0; // not the expected value in either case
+ connect(&timer, &QTimer::timeout,
+ [&]() {
+ remainingTime = timer.remainingTime();
+ });
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ const int timeout = 20; // 20 ms is short enough and should not round down to 0 in any timer mode
+ timer.start(timeout);
+
+ QVERIFY(timeoutSpy.wait());
+ if (singleShot)
+ QCOMPARE(remainingTime, -1); // timer not running
+ else
+ QVERIFY2(remainingTime <= timeout && remainingTime > 0,
+ qPrintable(QString::number(remainingTime)));
+
+ if (!singleShot) {
+ // do it again - see QTBUG-46940
+ remainingTime = -1;
+ QVERIFY(timeoutSpy.wait());
+ QVERIFY2(remainingTime <= timeout && remainingTime > 0,
+ qPrintable(QString::number(remainingTime)));
+ }
+}
+
+namespace {
+
+ template <typename T>
+ std::chrono::milliseconds to_ms(T t)
+ { return std::chrono::duration_cast<std::chrono::milliseconds>(t); }
+
+} // unnamed namespace
+
+void tst_QTimer::basic_chrono()
+{
+ // duplicates zeroTimer, singleShotTimeout, interval and remainingTime
+ using namespace std::chrono;
+ QTimer timer;
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.setInterval(to_ms(nanoseconds(0)));
+ timer.start();
+ QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(0));
+ QCOMPARE(timer.remainingTimeAsDuration().count(), milliseconds::rep(0));
+
+ QCoreApplication::processEvents();
+
+ QCOMPARE(timeoutSpy.size(), 1);
+
+ timeoutSpy.clear();
+ timer.start(milliseconds(100));
+ QCOMPARE(timeoutSpy.size(), 0);
+
+ QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT));
+ QVERIFY(timeoutSpy.size() > 0);
+ int oldCount = timeoutSpy.size();
+
+ QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT));
+ QVERIFY(timeoutSpy.size() > oldCount);
+
+ timeoutSpy.clear();
+ timer.start(to_ms(microseconds(200000)));
+ QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(200));
+ QTest::qWait(50);
+ QCOMPARE(timeoutSpy.size(), 0);
+
+ milliseconds rt = timer.remainingTimeAsDuration();
+ QVERIFY2(rt.count() >= 50 && rt.count() <= 200, qPrintable(QString::number(rt.count())));
+
+ timeoutSpy.clear();
+ timer.setSingleShot(true);
+ timer.start(milliseconds(100));
+ QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT));
+ QCOMPARE(timeoutSpy.size(), 1);
+ QTest::qWait(500);
+ QCOMPARE(timeoutSpy.size(), 1);
+}
+
+void tst_QTimer::livelock_data()
+{
+ QTest::addColumn<int>("interval");
+ QTest::newRow("zero timer") << 0;
+ QTest::newRow("non-zero timer") << 1;
+ QTest::newRow("longer than sleep") << 20;
+}
+
+/*!
+ *
+ * DO NOT "FIX" THIS TEST! it is written like this for a reason, do
+ * not *change it without first dicussing it with its maintainers.
+ *
+*/
+class LiveLockTester : public QObject
+{
+public:
+ LiveLockTester(int i)
+ : interval(i),
+ timeoutsForFirst(0), timeoutsForExtra(0), timeoutsForSecond(0),
+ postEventAtRightTime(false)
+ {
+ firstTimerId = startTimer(interval);
+ extraTimerId = startTimer(interval + 80);
+ secondTimerId = -1; // started later
+ }
+
+ bool event(QEvent *e) override
+ {
+ if (e->type() == 4002) {
+ // got the posted event
+ if (timeoutsForFirst == 1 && timeoutsForSecond == 0)
+ postEventAtRightTime = true;
+ return true;
+ }
+ return QObject::event(e);
+ }
+
+ void timerEvent(QTimerEvent *te) override
+ {
+ if (te->timerId() == firstTimerId) {
+ if (++timeoutsForFirst == 1) {
+ killTimer(extraTimerId);
+ extraTimerId = -1;
+ QCoreApplication::postEvent(this, new QEvent(static_cast<QEvent::Type>(4002)));
+ secondTimerId = startTimer(interval);
+ }
+ } else if (te->timerId() == secondTimerId) {
+ ++timeoutsForSecond;
+ } else if (te->timerId() == extraTimerId) {
+ ++timeoutsForExtra;
+ }
+
+ // sleep for 2ms
+ QTest::qSleep(2);
+ killTimer(te->timerId());
+ }
+
+ const int interval;
+ int firstTimerId;
+ int secondTimerId;
+ int extraTimerId;
+ int timeoutsForFirst;
+ int timeoutsForExtra;
+ int timeoutsForSecond;
+ bool postEventAtRightTime;
+};
+
+void tst_QTimer::livelock()
+{
+ /*
+ New timers created in timer event handlers should not be sent
+ until the next iteration of the eventloop. Note: this test
+ depends on the fact that we send posted events before timer
+ events (since new posted events are not sent until the next
+ iteration of the eventloop either).
+ */
+ QFETCH(int, interval);
+ LiveLockTester tester(interval);
+ QTest::qWait(180); // we have to use wait here, since we're testing timers with a non-zero timeout
+ QTRY_COMPARE(tester.timeoutsForFirst, 1);
+ QCOMPARE(tester.timeoutsForExtra, 0);
+ QTRY_COMPARE(tester.timeoutsForSecond, 1);
+ QVERIFY(tester.postEventAtRightTime);
+}
+
+class TimerInfiniteRecursionObject : public QObject
+{
+public:
+ bool inTimerEvent;
+ bool timerEventRecursed;
+ int interval;
+
+ TimerInfiniteRecursionObject(int interval)
+ : inTimerEvent(false), timerEventRecursed(false), interval(interval)
+ { }
+
+ void timerEvent(QTimerEvent *timerEvent) override
+ {
+ timerEventRecursed = inTimerEvent;
+ if (timerEventRecursed) {
+ // bug detected!
+ return;
+ }
+
+ inTimerEvent = true;
+
+ QEventLoop eventLoop;
+ QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit()));
+ eventLoop.exec();
+
+ inTimerEvent = false;
+
+ killTimer(timerEvent->timerId());
+ }
+};
+
+void tst_QTimer::timerInfiniteRecursion_data()
+{
+ QTest::addColumn<int>("interval");
+ QTest::newRow("zero timer") << 0;
+ QTest::newRow("non-zero timer") << 1;
+ QTest::newRow("10ms timer") << 10;
+ QTest::newRow("11ms timer") << 11;
+ QTest::newRow("100ms timer") << 100;
+ QTest::newRow("1s timer") << 1000;
+}
+
+
+void tst_QTimer::timerInfiniteRecursion()
+{
+ QFETCH(int, interval);
+ TimerInfiniteRecursionObject object(interval);
+ (void) object.startTimer(interval);
+
+ QEventLoop eventLoop;
+ QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit()));
+ eventLoop.exec();
+
+ QVERIFY(!object.timerEventRecursed);
+}
+
+class RecurringTimerObject : public QObject
+{
+Q_OBJECT
+public:
+ int times;
+ int target;
+ bool recurse;
+
+ RecurringTimerObject(int target)
+ : times(0), target(target), recurse(false)
+ { }
+
+ void timerEvent(QTimerEvent *timerEvent) override
+ {
+ if (++times == target) {
+ killTimer(timerEvent->timerId());
+ emit done();
+ } if (recurse) {
+ QEventLoop eventLoop;
+ QTimer::singleShot(100, &eventLoop, SLOT(quit()));
+ eventLoop.exec();
+ }
+ }
+
+signals:
+ void done();
+};
+
+void tst_QTimer::recurringTimer_data()
+{
+ QTest::addColumn<int>("interval");
+ QTest::addColumn<bool>("recurse");
+ // make sure that eventloop recursion doesn't affect timer recurrence
+ QTest::newRow("zero timer, don't recurse") << 0 << false;
+ QTest::newRow("zero timer, recurse") << 0 << true;
+ QTest::newRow("non-zero timer, don't recurse") << 1 << false;
+ QTest::newRow("non-zero timer, recurse") << 1 << true;
+}
+
+void tst_QTimer::recurringTimer()
+{
+ const int target = 5;
+ QFETCH(int, interval);
+ QFETCH(bool, recurse);
+
+ RecurringTimerObject object(target);
+ object.recurse = recurse;
+ QSignalSpy doneSpy(&object, &RecurringTimerObject::done);
+
+ (void) object.startTimer(interval);
+ QVERIFY(doneSpy.wait());
+
+ QCOMPARE(object.times, target);
+}
+
+void tst_QTimer::deleteLaterOnQTimer()
+{
+ QTimer *timer = new QTimer;
+ connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
+ QSignalSpy destroyedSpy(timer, &QObject::destroyed);
+ timer->setInterval(1);
+ timer->setSingleShot(true);
+ timer->start();
+ QPointer<QTimer> pointer = timer;
+ QVERIFY(destroyedSpy.wait());
+ QVERIFY(pointer.isNull());
+}
+
+#define MOVETOTHREAD_TIMEOUT 200
+#define MOVETOTHREAD_WAIT 300
+
+void tst_QTimer::moveToThread()
+{
+#if defined(Q_OS_WIN32)
+ QSKIP("Does not work reliably on Windows :(");
+#elif defined(Q_OS_MACOS)
+ QSKIP("Does not work reliably on macOS 10.12+ (QTBUG-59679)");
+#endif
+ QTimer ti1;
+ QTimer ti2;
+ ti1.setSingleShot(true);
+ ti1.start(MOVETOTHREAD_TIMEOUT);
+ ti2.start(MOVETOTHREAD_TIMEOUT);
+ QVERIFY((ti1.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff));
+ QThread tr;
+ ti1.moveToThread(&tr);
+ connect(&ti1,SIGNAL(timeout()), &tr, SLOT(quit()));
+ tr.start();
+ QTimer ti3;
+ ti3.start(MOVETOTHREAD_TIMEOUT);
+ QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff));
+ QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff));
+ QTest::qWait(MOVETOTHREAD_WAIT);
+ QVERIFY(tr.wait());
+ ti2.stop();
+ QTimer ti4;
+ ti4.start(MOVETOTHREAD_TIMEOUT);
+ ti3.stop();
+ ti2.start(MOVETOTHREAD_TIMEOUT);
+ ti3.start(MOVETOTHREAD_TIMEOUT);
+ QVERIFY((ti4.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff));
+ QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff));
+ QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff));
+}
+
+class RestartedTimerFiresTooSoonObject : public QObject
+{
+ Q_OBJECT
+
+public:
+ QBasicTimer m_timer;
+
+ int m_interval;
+ QElapsedTimer m_elapsedTimer;
+ QEventLoop eventLoop;
+
+ inline RestartedTimerFiresTooSoonObject()
+ : QObject(), m_interval(0)
+ { }
+
+ void timerFired()
+ {
+ static int interval = 1000;
+
+ m_interval = interval;
+ m_elapsedTimer.start();
+ m_timer.start(interval, this);
+
+ // alternate between single-shot and 1 sec
+ interval = interval ? 0 : 1000;
+ }
+
+ void timerEvent(QTimerEvent* ev) override
+ {
+ if (ev->timerId() != m_timer.timerId())
+ return;
+
+ m_timer.stop();
+
+ int elapsed = m_elapsedTimer.elapsed();
+
+ if (elapsed < m_interval / 2) {
+ // severely too early!
+ m_timer.stop();
+ eventLoop.exit(-1);
+ return;
+ }
+
+ timerFired();
+
+ // don't do this forever
+ static int count = 0;
+ if (count++ > 20) {
+ m_timer.stop();
+ eventLoop.quit();
+ return;
+ }
+ }
+};
+
+void tst_QTimer::restartedTimerFiresTooSoon()
+{
+ RestartedTimerFiresTooSoonObject object;
+ object.timerFired();
+ QCOMPARE(object.eventLoop.exec(), 0);
+}
+
+class LongLastingSlotClass : public QObject
+{
+ Q_OBJECT
+
+public:
+ LongLastingSlotClass(QTimer *timer) : count(0), timer(timer) {}
+
+public slots:
+ void longLastingSlot()
+ {
+ // Don't use QTimer for this, because we are testing it.
+ QElapsedTimer control;
+ control.start();
+ while (control.elapsed() < 200) {
+ for (int c = 0; c < 100000; c++) {} // Mindless looping.
+ }
+ if (++count >= 2) {
+ timer->stop();
+ }
+ }
+
+public:
+ int count;
+ QTimer *timer;
+};
+
+void tst_QTimer::timerFiresOnlyOncePerProcessEvents_data()
+{
+ QTest::addColumn<int>("interval");
+ QTest::newRow("zero timer") << 0;
+ QTest::newRow("non-zero timer") << 10;
+}
+
+void tst_QTimer::timerFiresOnlyOncePerProcessEvents()
+{
+ QFETCH(int, interval);
+
+ QTimer t;
+ LongLastingSlotClass longSlot(&t);
+ t.start(interval);
+ connect(&t, SIGNAL(timeout()), &longSlot, SLOT(longLastingSlot()));
+ // Loop because there may be other events pending.
+ while (longSlot.count == 0) {
+ QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
+ }
+
+ QCOMPARE(longSlot.count, 1);
+}
+
+class TimerIdPersistsAfterThreadExitThread : public QThread
+{
+public:
+ QTimer *timer;
+ int timerId, returnValue;
+
+ TimerIdPersistsAfterThreadExitThread()
+ : QThread(), timer(0), timerId(-1), returnValue(-1)
+ { }
+ ~TimerIdPersistsAfterThreadExitThread()
+ {
+ delete timer;
+ }
+
+ void run() override
+ {
+ QEventLoop eventLoop;
+ timer = new QTimer;
+ connect(timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
+ timer->start(100);
+ timerId = timer->timerId();
+ returnValue = eventLoop.exec();
+ }
+};
+
+void tst_QTimer::timerIdPersistsAfterThreadExit()
+{
+ TimerIdPersistsAfterThreadExitThread thread;
+ thread.start();
+ QVERIFY(thread.wait(30000));
+ QCOMPARE(thread.returnValue, 0);
+
+ // even though the thread has exited, and the event dispatcher destroyed, the timer is still
+ // "active", meaning the timer id should NOT be reused (i.e. the event dispatcher should not
+ // have unregistered it)
+ int timerId = thread.startTimer(100);
+ QVERIFY((timerId & 0xffffff) != (thread.timerId & 0xffffff));
+}
+
+void tst_QTimer::cancelLongTimer()
+{
+ QTimer timer;
+ timer.setSingleShot(true);
+ timer.start(1000 * 60 * 60); //set timer for 1 hour
+ QCoreApplication::processEvents();
+ QVERIFY(timer.isActive()); //if the timer completes immediately with an error, then this will fail
+ timer.stop();
+ QVERIFY(!timer.isActive());
+}
+
+class TimeoutCounter : public QObject
+{
+ Q_OBJECT
+public slots:
+ void timeout() { ++count; };
+public:
+ int count = 0;
+};
+
+void tst_QTimer::singleShotStaticFunctionZeroTimeout()
+{
+ {
+ TimeoutCounter counter;
+
+ QTimer::singleShot(0, &counter, SLOT(timeout()));
+ QTRY_COMPARE(counter.count, 1);
+ QTest::qWait(500);
+ QCOMPARE(counter.count, 1);
+ }
+
+ {
+ TimeoutCounter counter;
+
+ QTimer::singleShot(0, &counter, &TimeoutCounter::timeout);
+ QTRY_COMPARE(counter.count, 1);
+ QTest::qWait(500);
+ QCOMPARE(counter.count, 1);
+ }
+}
+
+class RecursOnTimeoutAndStopTimerTimer : public QObject
+{
+ Q_OBJECT
+
+public:
+ QTimer *one;
+ QTimer *two;
+
+public slots:
+ void onetrigger()
+ {
+ QCoreApplication::processEvents();
+ }
+
+ void twotrigger()
+ {
+ one->stop();
+ }
+};
+
+void tst_QTimer::recurseOnTimeoutAndStopTimer()
+{
+ QEventLoop eventLoop;
+ QTimer::singleShot(1000, &eventLoop, SLOT(quit()));
+
+ RecursOnTimeoutAndStopTimerTimer t;
+ t.one = new QTimer(&t);
+ t.two = new QTimer(&t);
+
+ QObject::connect(t.one, SIGNAL(timeout()), &t, SLOT(onetrigger()));
+ QObject::connect(t.two, SIGNAL(timeout()), &t, SLOT(twotrigger()));
+
+ t.two->setSingleShot(true);
+
+ t.one->start();
+ t.two->start();
+
+ (void) eventLoop.exec();
+
+ QVERIFY(!t.one->isActive());
+ QVERIFY(!t.two->isActive());
+}
+
+struct CountedStruct
+{
+ CountedStruct(int *count, QThread *t = nullptr) : count(count), thread(t) { }
+ ~CountedStruct() { }
+ void operator()() const { ++(*count); if (thread) QCOMPARE(QThread::currentThread(), thread); }
+
+ int *count;
+ QThread *thread;
+};
+
+static QScopedPointer<QEventLoop> _e;
+static QThread *_t = nullptr;
+
+class StaticEventLoop
+{
+public:
+ static void quitEventLoop()
+ {
+ quitEventLoop_noexcept();
+ }
+
+ static void quitEventLoop_noexcept() noexcept
+ {
+ QVERIFY(!_e.isNull());
+ _e->quit();
+ if (_t)
+ QCOMPARE(QThread::currentThread(), _t);
+ }
+};
+
+void tst_QTimer::singleShotToFunctors()
+{
+ int count = 0;
+ _e.reset(new QEventLoop);
+ QEventLoop e;
+
+ QTimer::singleShot(0, CountedStruct(&count));
+ QCoreApplication::processEvents();
+ QCOMPARE(count, 1);
+
+ QTimer::singleShot(0, &StaticEventLoop::quitEventLoop);
+ QCOMPARE(_e->exec(), 0);
+
+ QTimer::singleShot(0, &StaticEventLoop::quitEventLoop_noexcept);
+ QCOMPARE(_e->exec(), 0);
+
+ QThread t1;
+ QObject c1;
+ c1.moveToThread(&t1);
+
+ QObject::connect(&t1, SIGNAL(started()), &e, SLOT(quit()));
+ t1.start();
+ QCOMPARE(e.exec(), 0);
+
+ QTimer::singleShot(0, &c1, CountedStruct(&count, &t1));
+ QTRY_COMPARE(count, 2);
+
+ t1.quit();
+ t1.wait();
+
+ _t = new QThread;
+ QObject c2;
+ c2.moveToThread(_t);
+
+ QObject::connect(_t, SIGNAL(started()), &e, SLOT(quit()));
+ _t->start();
+ QCOMPARE(e.exec(), 0);
+
+ QTimer::singleShot(0, &c2, &StaticEventLoop::quitEventLoop);
+ QCOMPARE(_e->exec(), 0);
+
+ _t->quit();
+ _t->wait();
+ _t->deleteLater();
+ _t = nullptr;
+
+ {
+ QObject c3;
+ QTimer::singleShot(500, &c3, CountedStruct(&count));
+ }
+ QTest::qWait(800); // Wait until the singleshot timer would have timed out
+ QCOMPARE(count, 2);
+
+ QTimer::singleShot(0, [&count] { ++count; });
+ QTRY_COMPARE(count, 3);
+
+ QObject context;
+ QThread thread;
+
+ context.moveToThread(&thread);
+ QObject::connect(&thread, SIGNAL(started()), &e, SLOT(quit()));
+ thread.start();
+ QCOMPARE(e.exec(), 0);
+
+ QTimer::singleShot(0, &context, [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); });
+ QTRY_COMPARE(count, 4);
+
+ thread.quit();
+ thread.wait();
+
+ struct MoveOnly : CountedStruct {
+ Q_DISABLE_COPY(MoveOnly)
+ MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {};
+ MoveOnly(int *c) : CountedStruct(c) {}
+ };
+ QTimer::singleShot(0, MoveOnly(&count));
+ QTRY_COMPARE(count, 5);
+
+ _e.reset();
+ _t = nullptr;
+}
+
+void tst_QTimer::singleShot_chrono()
+{
+ // duplicates singleShotStaticFunctionZeroTimeout and singleShotToFunctors
+ using namespace std::chrono;
+ {
+ TimeoutCounter counter;
+
+ QTimer::singleShot(hours(0), &counter, SLOT(timeout()));
+ QTRY_COMPARE(counter.count, 1);
+ QTest::qWait(500);
+ QCOMPARE(counter.count, 1);
+ }
+
+ {
+ TimeoutCounter counter;
+
+ QTimer::singleShot(hours(0), &counter, &TimeoutCounter::timeout);
+ QTRY_COMPARE(counter.count, 1);
+ QTest::qWait(500);
+ QCOMPARE(counter.count, 1);
+ }
+
+ int count = 0;
+ QTimer::singleShot(to_ms(microseconds(0)), CountedStruct(&count));
+ QTRY_COMPARE(count, 1);
+
+ _e.reset(new QEventLoop);
+ QTimer::singleShot(0, &StaticEventLoop::quitEventLoop);
+ QCOMPARE(_e->exec(), 0);
+
+ QObject c3;
+ QTimer::singleShot(milliseconds(500), &c3, CountedStruct(&count));
+ QTRY_COMPARE(count, 2);
+
+ QTimer::singleShot(0, [&count] { ++count; });
+ QTRY_COMPARE(count, 3);
+
+ _e.reset();
+}
+
+class DontBlockEvents : public QObject
+{
+ Q_OBJECT
+public:
+ DontBlockEvents();
+ void timerEvent(QTimerEvent*) override;
+
+ int count;
+ int total;
+ QBasicTimer m_timer;
+
+public slots:
+ void paintEvent();
+
+};
+
+DontBlockEvents::DontBlockEvents()
+{
+ count = 0;
+ total = 0;
+
+ // need a few unrelated timers running to reproduce the bug.
+ (new QTimer(this))->start(2000);
+ (new QTimer(this))->start(2500);
+ (new QTimer(this))->start(3000);
+ (new QTimer(this))->start(5000);
+ (new QTimer(this))->start(1000);
+ (new QTimer(this))->start(2000);
+
+ m_timer.start(1, this);
+}
+
+void DontBlockEvents::timerEvent(QTimerEvent* event)
+{
+ if (event->timerId() == m_timer.timerId()) {
+ QMetaObject::invokeMethod(this, "paintEvent", Qt::QueuedConnection);
+ m_timer.start(0, this);
+ count++;
+ QCOMPARE(count, 1);
+ total++;
+ }
+}
+
+void DontBlockEvents::paintEvent()
+{
+ count--;
+ QCOMPARE(count, 0);
+}
+
+// This is a regression test for QTBUG-13633, where a timer with a zero
+// timeout that was restarted by the event handler could starve other timers.
+void tst_QTimer::dontBlockEvents()
+{
+ DontBlockEvents t;
+ QTest::qWait(60);
+ QTRY_VERIFY(t.total > 2);
+}
+
+class SlotRepeater : public QObject {
+ Q_OBJECT
+public:
+ SlotRepeater() {}
+
+public slots:
+ void repeatThisSlot()
+ {
+ QMetaObject::invokeMethod(this, "repeatThisSlot", Qt::QueuedConnection);
+ }
+};
+
+void tst_QTimer::postedEventsShouldNotStarveTimers()
+{
+ QTimer timer;
+ timer.setInterval(0);
+ timer.setSingleShot(false);
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.start();
+ SlotRepeater slotRepeater;
+ slotRepeater.repeatThisSlot();
+ QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 5, 100);
+}
+
+struct DummyFunctor {
+ static QThread *callThread;
+ void operator()() {
+ callThread = QThread::currentThread();
+ callThread->quit();
+ }
+};
+QThread *DummyFunctor::callThread = nullptr;
+
+void tst_QTimer::crossThreadSingleShotToFunctor_data()
+{
+ QTest::addColumn<int>("timeout");
+
+ QTest::addRow("zero-timer") << 0;
+ QTest::addRow("1ms") << 1;
+}
+
+void tst_QTimer::crossThreadSingleShotToFunctor()
+{
+ QFETCH(int, timeout);
+ // We're also testing for crashes here, so the test simply running to
+ // completion is part of the success
+ DummyFunctor::callThread = nullptr;
+
+ QThread t;
+ std::unique_ptr<QObject> o(new QObject());
+ o->moveToThread(&t);
+
+ QTimer::singleShot(timeout, o.get(), DummyFunctor());
+
+ // wait enough time for the timer to have timed out before the timer
+ // could be start in the receiver's thread.
+ QTest::qWait(10 + timeout * 10);
+ t.start();
+ t.wait();
+ QCOMPARE(DummyFunctor::callThread, &t);
+
+ // continue with a stress test - the calling thread is busy, the
+ // timer should still fire and no crashes.
+ DummyFunctor::callThread = nullptr;
+ t.start();
+ for (int i = 0; i < 10000; i++)
+ QTimer::singleShot(timeout, o.get(), DummyFunctor());
+
+ t.wait();
+ o.reset();
+
+ QCOMPARE(DummyFunctor::callThread, &t);
+}
+
+void tst_QTimer::callOnTimeout()
+{
+ QTimer timer;
+ QSignalSpy timeoutSpy(&timer, &QTimer::timeout);
+ timer.setInterval(0);
+ timer.start();
+
+ auto context = new QObject();
+
+ int count = 0;
+ timer.callOnTimeout([&count] { count++; });
+ QMetaObject::Connection connection = timer.callOnTimeout(context, [&count] { count++; });
+ timer.callOnTimeout(&timer, &QTimer::stop);
+
+
+ QTest::qWait(100);
+ QCOMPARE(count, 2);
+ QCOMPARE(timeoutSpy.size(), 1);
+
+ // Test that connection is bound to context lifetime
+ QVERIFY(connection);
+ delete context;
+ QVERIFY(!connection);
+}
+
+void tst_QTimer::bindToTimer()
+{
+ QTimer timer;
+
+ // singleShot property
+ QProperty<bool> singleShot;
+ singleShot.setBinding(timer.bindableSingleShot().makeBinding());
+ QCOMPARE(timer.isSingleShot(), singleShot);
+
+ timer.setSingleShot(true);
+ QVERIFY(singleShot);
+ timer.setSingleShot(false);
+ QVERIFY(!singleShot);
+
+ // interval property
+ QProperty<int> interval;
+ interval.setBinding([&](){ return timer.interval(); });
+ QCOMPARE(timer.interval(), interval);
+
+ timer.setInterval(10);
+ QCOMPARE(interval, 10);
+ timer.setInterval(100);
+ QCOMPARE(interval, 100);
+
+ // timerType property
+ QProperty<Qt::TimerType> timerType;
+ timerType.setBinding(timer.bindableTimerType().makeBinding());
+ QCOMPARE(timer.timerType(), timerType);
+
+ timer.setTimerType(Qt::PreciseTimer);
+ QCOMPARE(timerType, Qt::PreciseTimer);
+
+ timer.setTimerType(Qt::VeryCoarseTimer);
+ QCOMPARE(timerType, Qt::VeryCoarseTimer);
+
+ // active property
+ QProperty<bool> active;
+ active.setBinding([&](){ return timer.isActive(); });
+ QCOMPARE(active, timer.isActive());
+
+ timer.start(1000);
+ QVERIFY(active);
+
+ timer.stop();
+ QVERIFY(!active);
+
+ auto ignoreMsg = [] {
+ QTest::ignoreMessage(QtWarningMsg,
+ "QObject::startTimer: Timers cannot have negative intervals");
+ };
+
+ // also test that using negative interval updates the binding correctly
+ timer.start(100);
+ QVERIFY(active);
+ ignoreMsg();
+ timer.setInterval(-100);
+ QVERIFY(!active);
+ timer.start(100);
+ QVERIFY(active);
+ ignoreMsg();
+ timer.start(-100);
+ QVERIFY(!active);
+}
+
+void tst_QTimer::bindTimer()
+{
+ QTimer timer;
+
+ // singleShot property
+ QVERIFY(!timer.isSingleShot());
+
+ QProperty<bool> singleShot;
+ timer.bindableSingleShot().setBinding(Qt::makePropertyBinding(singleShot));
+
+ singleShot = true;
+ QVERIFY(timer.isSingleShot());
+ singleShot = false;
+ QVERIFY(!timer.isSingleShot());
+
+ // interval property
+ QCOMPARE(timer.interval(), 0);
+
+ QProperty<int> interval;
+ timer.bindableInterval().setBinding(Qt::makePropertyBinding(interval));
+
+ interval = 10;
+ QCOMPARE(timer.interval(), 10);
+ interval = 100;
+ QCOMPARE(timer.interval(), 100);
+ timer.setInterval(50);
+ QCOMPARE(timer.interval(), 50);
+ interval = 30;
+ QCOMPARE(timer.interval(), 50);
+
+ // timerType property
+ QCOMPARE(timer.timerType(), Qt::CoarseTimer);
+
+ QProperty<Qt::TimerType> timerType;
+ timer.bindableTimerType().setBinding(Qt::makePropertyBinding(timerType));
+
+ timerType = Qt::PreciseTimer;
+ QCOMPARE(timer.timerType(), Qt::PreciseTimer);
+ timerType = Qt::VeryCoarseTimer;
+ QCOMPARE(timer.timerType(), Qt::VeryCoarseTimer);
+}
+
+void tst_QTimer::automatedBindingTests()
+{
+ QTimer timer;
+
+ QVERIFY(!timer.isSingleShot());
+ QTestPrivate::testReadWritePropertyBasics(timer, true, false, "singleShot");
+ if (QTest::currentTestFailed()) {
+ qDebug("Failed property test for QTimer::singleShot");
+ return;
+ }
+
+ QCOMPARE_NE(timer.interval(), 10);
+ QTestPrivate::testReadWritePropertyBasics(timer, 10, 20, "interval");
+ if (QTest::currentTestFailed()) {
+ qDebug("Failed property test for QTimer::interval");
+ return;
+ }
+
+ QCOMPARE_NE(timer.timerType(), Qt::PreciseTimer);
+ QTestPrivate::testReadWritePropertyBasics(timer, Qt::PreciseTimer, Qt::CoarseTimer,
+ "timerType");
+ if (QTest::currentTestFailed()) {
+ qDebug("Failed property test for QTimer::timerType");
+ return;
+ }
+
+ timer.start(1000);
+ QVERIFY(timer.isActive());
+ QTestPrivate::testReadOnlyPropertyBasics(timer, true, false, "active",
+ [&timer]() { timer.stop(); });
+ if (QTest::currentTestFailed()) {
+ qDebug("Failed property test for QTimer::active");
+ return;
+ }
+}
+
+void tst_QTimer::negativeInterval()
+{
+ auto ignoreMsg = [] {
+ QTest::ignoreMessage(QtWarningMsg,
+ "QObject::startTimer: Timers cannot have negative intervals");
+ };
+
+ QTimer timer;
+
+ // Starting with a negative interval does not change active state.
+ ignoreMsg();
+ timer.start(-100ms);
+ QVERIFY(!timer.isActive());
+
+ // Updating the interval to a negative value stops the timer and changes
+ // the active state.
+ timer.start(100ms);
+ QVERIFY(timer.isActive());
+ ignoreMsg();
+ timer.setInterval(-100);
+ QVERIFY(!timer.isActive());
+
+ // Starting with a negative interval when already started leads to stop
+ // and inactive state.
+ timer.start(100);
+ QVERIFY(timer.isActive());
+ ignoreMsg();
+ timer.start(-100ms);
+ QVERIFY(!timer.isActive());
+}
+
+class OrderHelper : public QObject
+{
+ Q_OBJECT
+public:
+ enum CallType
+ {
+ String,
+ PMF,
+ Functor,
+ FunctorNoCtx
+ };
+ Q_ENUM(CallType)
+ QList<CallType> calls;
+
+ void triggerCall(CallType callType)
+ {
+ switch (callType)
+ {
+ case String:
+ QTimer::singleShot(0, this, SLOT(stringSlot()));
+ break;
+ case PMF:
+ QTimer::singleShot(0, this, &OrderHelper::pmfSlot);
+ break;
+ case Functor:
+ QTimer::singleShot(0, this, [this]() { functorSlot(); });
+ break;
+ case FunctorNoCtx:
+ QTimer::singleShot(0, [this]() { functorNoCtxSlot(); });
+ break;
+ }
+ }
+
+public slots:
+ void stringSlot() { calls << String; }
+ void pmfSlot() { calls << PMF; }
+ void functorSlot() { calls << Functor; }
+ void functorNoCtxSlot() { calls << FunctorNoCtx; }
+};
+
+Q_DECLARE_METATYPE(OrderHelper::CallType)
+
+void tst_QTimer::timerOrder()
+{
+ QFETCH(QList<OrderHelper::CallType>, calls);
+
+ OrderHelper helper;
+
+ for (const auto call : calls)
+ helper.triggerCall(call);
+
+ QTRY_COMPARE(helper.calls, calls);
+}
+
+void tst_QTimer::timerOrder_data()
+{
+ QTest::addColumn<QList<OrderHelper::CallType>>("calls");
+
+ QList<OrderHelper::CallType> calls = {
+ OrderHelper::String, OrderHelper::PMF,
+ OrderHelper::Functor, OrderHelper::FunctorNoCtx
+ };
+ std::sort(calls.begin(), calls.end());
+
+ int permutation = 0;
+ do {
+ QTest::addRow("permutation=%d", permutation) << calls;
+ ++permutation;
+ } while (std::next_permutation(calls.begin(), calls.end()));
+}
+
+void tst_QTimer::timerOrderBackgroundThread()
+{
+ auto *thread = QThread::create([this]() { timerOrder(); });
+ thread->start();
+ QVERIFY(thread->wait());
+ delete thread;
+}
+
+struct StaticSingleShotUser
+{
+ StaticSingleShotUser()
+ {
+ for (auto call : calls())
+ helper.triggerCall(call);
+ }
+ OrderHelper helper;
+
+ static QList<OrderHelper::CallType> calls()
+ {
+ return {OrderHelper::String, OrderHelper::PMF,
+ OrderHelper::Functor, OrderHelper::FunctorNoCtx};
+ }
+};
+
+// NOTE: to prevent any static initialization order fiasco, we implement
+// initMain() to instantiate staticSingleShotUser before qApp
+
+static StaticSingleShotUser *s_staticSingleShotUser = nullptr;
+
+void tst_QTimer::initMain()
+{
+ s_staticSingleShotUser = new StaticSingleShotUser;
+}
+
+void tst_QTimer::cleanupTestCase()
+{
+ delete s_staticSingleShotUser;
+}
+
+void tst_QTimer::singleShot_static()
+{
+ QCoreApplication::processEvents();
+ QCOMPARE(s_staticSingleShotUser->helper.calls, s_staticSingleShotUser->calls());
+}
+
+QTEST_MAIN(tst_QTimer)
+
+#include "tst_qtimer.moc"
diff --git a/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt b/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt
new file mode 100644
index 0000000000..bc292e133b
--- /dev/null
+++ b/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt
@@ -0,0 +1,38 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qguitimer Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qguitimer LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+function(addGuiTimerTest test)
+ qt_internal_add_test(${test}
+ SOURCES
+ ../../../corelib/kernel/qtimer/tst_qtimer.cpp
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::TestPrivate
+ )
+endfunction()
+
+addGuiTimerTest(tst_qguitimer)
+qt_internal_extend_target(tst_qguitimer
+ DEFINES
+ tst_Qtimer=tst_QGuiTimer
+)
+
+if(QT_FEATURE_glib AND UNIX)
+ addGuiTimerTest(tst_qguitimer_no_glib)
+ qt_internal_extend_target(tst_qguitimer_no_glib
+ DEFINES
+ DISABLE_GLIB
+ tst_QTimer=tst_QGuiTimer_no_glib # Class name in the unittest
+ )
+endif()