summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/Qt6AndroidGradleHelpers.cmake4
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake3
-rw-r--r--src/network/access/qhttp2connection.cpp222
-rw-r--r--src/network/access/qhttp2connection_p.h22
-rw-r--r--src/tools/syncqt/main.cpp2
-rw-r--r--src/widgets/widgets/qtabbar.cpp6
-rw-r--r--tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp77
-rw-r--r--tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp267
-rw-r--r--tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp12
9 files changed, 497 insertions, 118 deletions
diff --git a/src/corelib/Qt6AndroidGradleHelpers.cmake b/src/corelib/Qt6AndroidGradleHelpers.cmake
index fc8e009b9da..136a61abbcd 100644
--- a/src/corelib/Qt6AndroidGradleHelpers.cmake
+++ b/src/corelib/Qt6AndroidGradleHelpers.cmake
@@ -6,7 +6,7 @@
function(_qt_internal_android_get_template_path out_var target template_name)
if(template_name STREQUAL "")
message(FATAL_ERROR "Template name is empty."
- " This is the Qt issue, please report a bug at https://bugreports.qt.io.")
+ " This is a Qt issue, please report a bug at https://bugreports.qt.io.")
endif()
_qt_internal_android_template_dir(template_directory)
@@ -36,7 +36,7 @@ function(_qt_internal_android_get_template_path out_var target template_name)
if(template_path STREQUAL "")
message(FATAL_ERROR "'${template_name}' is not found."
- " This is the Qt issue, please report a bug at https://bugreports.qt.io.")
+ " This is a Qt issue, please report a bug at https://bugreports.qt.io.")
endif()
set(${out_var} "${template_path}" PARENT_SCOPE)
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index be362ba1925..ddc531a89f4 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -1790,10 +1790,11 @@ function(_qt_internal_android_app_runner_arguments target out_runner_path out_ar
set(${out_runner_path} "${runner_dir}/qt-android-runner.py" PARENT_SCOPE)
_qt_internal_android_get_target_android_build_dir(android_build_dir ${target})
+ _qt_internal_android_get_target_deployment_dir(android_deployment_dir ${target})
_qt_internal_android_get_platform_tools_path(platform_tools)
set(${out_arguments}
"--adb" "${platform_tools}/adb"
- "--build-path" "${android_build_dir}"
+ "--build-path" "${android_deployment_dir}"
"--apk" "${android_build_dir}/${target}.apk"
PARENT_SCOPE
)
diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp
index c0b07ddd652..62afd172d57 100644
--- a/src/network/access/qhttp2connection.cpp
+++ b/src/network/access/qhttp2connection.cpp
@@ -15,6 +15,7 @@
#include <algorithm>
#include <memory>
+#include <chrono>
QT_BEGIN_NAMESPACE
@@ -646,6 +647,8 @@ void QHttp2Stream::setState(State newState)
streamID(), int(m_state), int(newState));
m_state = newState;
emit stateChanged(newState);
+ if (m_state == State::Closed)
+ getConnection()->maybeCloseOnGoingAway();
}
// Changes the state as appropriate given the current state and the transition.
@@ -1033,6 +1036,27 @@ QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
return m_streams.value(streamID, nullptr).get();
}
+/*!
+ Initiates connection shutdown. When \a errorCode is \c{NO_ERROR}, graceful
+ shutdown is initiated, allowing existing streams to complete. Otherwise the
+ connection is closed immediately with an error.
+*/
+void QHttp2Connection::close(Http2::Http2Error errorCode)
+{
+ if (m_connectionAborted)
+ return;
+
+ if (errorCode == Http2::HTTP2_NO_ERROR) {
+ if (m_connectionType == Type::Server)
+ sendInitialServerGracefulShutdownGoaway();
+ else
+ sendClientGracefulShutdownGoaway();
+ } else {
+ // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
+ // condition, the endpoint MUST close the TCP connection
+ connectionError(errorCode, "Connection closed with error", false);
+ }
+}
/*!
\fn QHttp2Stream *QHttp2Connection::promisedStream(const QUrl &streamKey) const
@@ -1042,13 +1066,6 @@ QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
*/
/*!
- \fn void QHttp2Connection::close()
-
- This sends a GOAWAY frame on the connection stream, gracefully closing the
- connection.
-*/
-
-/*!
\fn bool QHttp2Connection::isGoingAway() const noexcept
Returns \c true if the connection is in the process of being closed, or
@@ -1154,13 +1171,6 @@ void QHttp2Connection::handleReadyRead()
if (m_connectionType == Type::Server && !serverCheckClientPreface())
return;
- const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
- return stream && stream->isActive();
- };
- if (m_goingAway && std::none_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
- close();
- return;
- }
QIODevice *socket = getSocket();
qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
@@ -1170,13 +1180,13 @@ void QHttp2Connection::handleReadyRead()
if (!m_prefaceSent)
return;
- while (!m_goingAway || std::any_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
+ while (!m_connectionAborted) {
const auto result = frameReader.read(*socket);
if (result != FrameStatus::goodFrame)
qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
switch (result) {
case FrameStatus::incompleteFrame:
- return;
+ return; // No more complete frames available
case FrameStatus::protocolError:
return connectionError(PROTOCOL_ERROR, "invalid frame");
case FrameStatus::sizeError: {
@@ -1297,24 +1307,35 @@ void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
encoder.setCompressStrings(m_config.huffmanCompressionEnabled());
}
-void QHttp2Connection::connectionError(Http2Error errorCode, const char *message)
+void QHttp2Connection::connectionError(Http2Error errorCode, const char *message, bool logAsError)
{
Q_ASSERT(message);
- // RFC 9113, 6.8: An endpoint MAY send multiple GOAWAY frames if circumstances change.
+ if (m_connectionAborted)
+ return;
+ m_connectionAborted = true;
- qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
- int(errorCode));
+ if (logAsError) {
+ qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
+ int(errorCode));
+ } else {
+ qCDebug(qHttp2ConnectionLog, "[%p] Closing connection: %s (%d)", this, message,
+ int(errorCode));
+ }
- // RFC 9113, 6.8: Endpoints SHOULD always send a GOAWAY frame before closing a connection so
- // that the remote peer can know whether a stream has been partially processed or not.
- sendGOAWAY(errorCode);
+ // Mark going away so other code paths will stop creating new streams
+ m_goingAway = true;
+ // RFC 9113 5.4.1: An endpoint that encounters a connection error SHOULD
+ // first send a GOAWAY frame with the last incoming stream ID.
+ m_lastStreamToProcess = std::min(m_lastIncomingStreamID, m_lastStreamToProcess);
+ sendGOAWAYFrame(errorCode, m_lastStreamToProcess);
auto messageView = QLatin1StringView(message);
for (QHttp2Stream *stream : std::as_const(m_streams)) {
if (stream && stream->isActive())
stream->finishWithError(errorCode, messageView);
}
-
+ // RFC 9113 5.4.1: After sending the GOAWAY frame for an error condition,
+ // the endpoint MUST close the TCP connection
closeSession();
}
@@ -1423,20 +1444,99 @@ bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
return frameWriter.write(*getSocket());
}
-bool QHttp2Connection::sendGOAWAY(Http2::Http2Error errorCode)
+void QHttp2Connection::sendClientGracefulShutdownGoaway()
{
+ // Clients send a single GOAWAY. No race condition since they control stream creation
+ Q_ASSERT(m_connectionType == Type::Client);
+
+ if (m_connectionAborted || m_goingAway) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Client graceful shutdown already in progress", this);
+ return;
+ }
+
m_goingAway = true;
- // If this is the first time, start the timer:
- if (m_lastStreamToProcess == Http2::lastValidStreamID)
- m_goawayGraceTimer.setRemainingTime(GoawayGracePeriod);
- m_lastStreamToProcess = std::min(m_lastIncomingStreamID, m_lastStreamToProcess);
+ m_gracefulShutdownState = GracefulShutdownState::FinalGOAWAYSent;
+ m_lastStreamToProcess = m_lastIncomingStreamID;
+ sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, m_lastStreamToProcess);
+
+ maybeCloseOnGoingAway();
+}
+
+void QHttp2Connection::sendInitialServerGracefulShutdownGoaway()
+{
+ Q_ASSERT(m_connectionType == Type::Server);
+ // RFC 9113, 6.8: A server that is attempting to gracefully shut down a
+ // connection SHOULD send an initial GOAWAY frame with the last stream
+ // identifier set to 2^31-1 and a NO_ERROR code.
+ if (m_connectionAborted || m_goingAway) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Server graceful shutdown already in progress", this);
+ return;
+ }
+
+ m_goingAway = true;
+ m_goawayGraceTimer.setRemainingTime(GoawayGracePeriod);
+ sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, Http2::lastValidStreamID);
+
+ // Send PING to measure RTT; handlePING() continues the shutdown on ACK.
+ // RFC 9113 6.8: After allowing time for any in-flight stream creation
+ // (at least one round-trip time)
+ if (sendPing())
+ m_gracefulShutdownState = GracefulShutdownState::AwaitingShutdownPing;
+ else
+ m_gracefulShutdownState = GracefulShutdownState::AwaitingPriorPing;
+}
+
+void QHttp2Connection::sendFinalServerGracefulShutdownGoaway()
+{
+ if (m_connectionAborted || !m_goingAway) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Server graceful shutdown not in progress", this);
+ return;
+ }
+ m_gracefulShutdownState = GracefulShutdownState::FinalGOAWAYSent;
+ m_lastStreamToProcess = m_lastIncomingStreamID;
+ sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, m_lastStreamToProcess);
+ maybeCloseOnGoingAway();
+}
+
+bool QHttp2Connection::sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastStreamID)
+{
+ QIODevice *socket = getSocket();
+ if (!socket || !socket->isOpen())
+ return false;
+
qCDebug(qHttp2ConnectionLog, "[%p] Sending GOAWAY frame, error code %u, last stream %u", this,
- errorCode, m_lastStreamToProcess);
+ errorCode, lastStreamID);
+
frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY,
Http2PredefinedParameters::connectionStreamID);
- frameWriter.append(m_lastStreamToProcess);
+ frameWriter.append(lastStreamID);
frameWriter.append(quint32(errorCode));
- return frameWriter.write(*getSocket());
+ return frameWriter.write(*socket);
+}
+
+void QHttp2Connection::maybeCloseOnGoingAway()
+{
+ // Only close if we've reached the final phase of graceful shutdown
+ // For the sender: after FinalGOAWAYSent
+ // For the receiver: after receiving GOAWAY and all our streams are done
+ if (m_connectionAborted || !m_goingAway) {
+ qCDebug(qHttp2ConnectionLog, "[%p] Connection close deferred, graceful shutdown not active",
+ this);
+ return;
+ }
+
+ // For graceful shutdown initiator, only close after final GOAWAY is sent
+ if (m_gracefulShutdownState == GracefulShutdownState::AwaitingShutdownPing)
+ return; // Still waiting for RTT measurement before final GOAWAY
+
+ const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
+ return stream && stream->isActive();
+ };
+
+ if (std::none_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
+ qCDebug(qHttp2ConnectionLog, "[%p] All streams closed, closing connection", this);
+ closeSession();
+ }
}
bool QHttp2Connection::sendSETTINGS_ACK()
@@ -1571,8 +1671,6 @@ void QHttp2Connection::handleHEADERS()
qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
streamID);
handlePRIORITY();
- if (m_goingAway)
- return;
}
const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
@@ -1834,6 +1932,17 @@ void QHttp2Connection::handlePING()
emit pingFrameReceived(PingState::PongSignatureIdentical);
}
m_lastPingSignature.reset();
+
+ // Handle sendInitialServerGracefulShutdownGoaway()
+ if (m_gracefulShutdownState == GracefulShutdownState::AwaitingShutdownPing) {
+ sendFinalServerGracefulShutdownGoaway();
+ } else if (m_gracefulShutdownState == GracefulShutdownState::AwaitingPriorPing) {
+ // Prior PING completed, now send our RTT measurement PING. This shouldn't fail!
+ m_gracefulShutdownState = GracefulShutdownState::AwaitingShutdownPing;
+ [[maybe_unused]] const bool ok = sendPing();
+ Q_ASSERT(ok);
+ }
+
return;
} else {
emit pingFrameReceived(PingState::Ping);
@@ -1876,27 +1985,44 @@ void QHttp2Connection::handleGOAWAY()
if (lastStreamID != 0 && (lastStreamID & 0x1) != LocalMask)
return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
+ // 6.8 - An endpoint MAY send multiple GOAWAY frames if circumstances
+ // change. Endpoints MUST NOT increase the value they send in the last
+ // stream identifier
+ if (m_lastGoAwayLastStreamID && lastStreamID > *m_lastGoAwayLastStreamID)
+ return connectionError(PROTOCOL_ERROR, "Repeated GOAWAY with invalid last stream ID");
+ m_lastGoAwayLastStreamID = lastStreamID;
+
qCDebug(qHttp2ConnectionLog, "[%p] Received GOAWAY frame, error code %u, last stream %u",
this, errorCode, lastStreamID);
m_goingAway = true;
emit receivedGOAWAY(errorCode, lastStreamID);
- // Since the embedded stream ID is the last one that was or _might be_ processed,
- // we cancel anything that comes after it. 0 can be used in the special case that
- // no streams at all were or will be processed.
- const quint32 firstPossibleStream = m_connectionType == Type::Client ? 1 : 2;
- const quint32 firstCancelledStream = lastStreamID ? lastStreamID + 2 : firstPossibleStream;
- Q_ASSERT((firstCancelledStream & 0x1) == LocalMask);
- for (quint32 id = firstCancelledStream; id < m_nextStreamID; id += 2) {
- QHttp2Stream *stream = m_streams.value(id, nullptr);
- if (stream && stream->isActive())
- stream->finishWithError(errorCode, "Received GOAWAY"_L1);
- }
-
- const auto isActive = [](const QHttp2Stream *stream) { return stream && stream->isActive(); };
- if (std::none_of(m_streams.cbegin(), m_streams.cend(), isActive))
+ if (errorCode == HTTP2_NO_ERROR) {
+ // Graceful GOAWAY (NO_ERROR): Only cancel streams the peer explicitly won't process
+ // (those with IDs > lastStreamID). Streams with ID <= lastStreamID can still complete.
+ // '0' can be used in the special case that no streams at all were or will be processed.
+ const quint32 firstPossibleStream = m_connectionType == Type::Client ? 1 : 2;
+ const quint32 firstCancelledStream = lastStreamID ? lastStreamID + 2 : firstPossibleStream;
+ Q_ASSERT((firstCancelledStream & 0x1) == LocalMask);
+ for (quint32 id = firstCancelledStream; id < m_nextStreamID; id += 2) {
+ QHttp2Stream *stream = m_streams.value(id, nullptr);
+ if (stream && stream->isActive())
+ stream->finishWithError(errorCode, "Received GOAWAY"_L1);
+ }
+ maybeCloseOnGoingAway(); // check if we can close now
+ } else {
+ // RFC 9113, 5.4.1: After sending the GOAWAY frame for an error
+ // condition, the endpoint MUST close the TCP connection.
+ // As the peer is closing the connection immediately, they won't
+ // process any more data, so we close the connection here already.
+ m_connectionAborted = true;
+ for (QHttp2Stream *stream : std::as_const(m_streams)) {
+ if (stream && stream->isActive())
+ stream->finishWithError(errorCode, "Received GOAWAY"_L1);
+ }
closeSession();
+ }
}
void QHttp2Connection::handleWINDOW_UPDATE()
diff --git a/src/network/access/qhttp2connection_p.h b/src/network/access/qhttp2connection_p.h
index e2af7d7ab33..d9f2bccc58a 100644
--- a/src/network/access/qhttp2connection_p.h
+++ b/src/network/access/qhttp2connection_p.h
@@ -260,7 +260,7 @@ public:
return nullptr;
}
- void close(Http2::Http2Error error = Http2::HTTP2_NO_ERROR) { sendGOAWAY(error); }
+ void close(Http2::Http2Error errorCode = Http2::HTTP2_NO_ERROR);
bool isGoingAway() const noexcept { return m_goingAway; }
@@ -302,8 +302,7 @@ private:
Q_ALWAYS_INLINE
bool streamIsIgnored(quint32 streamID) const noexcept;
- void connectionError(Http2::Http2Error errorCode,
- const char *message); // Connection failed to be established?
+ void connectionError(Http2::Http2Error errorCode, const char *message, bool logAsError = true);
void setH2Configuration(QHttp2Configuration config);
void closeSession();
void registerStreamAsResetLocally(quint32 streamID);
@@ -316,7 +315,11 @@ private:
bool sendServerPreface();
bool serverCheckClientPreface();
bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
- bool sendGOAWAY(Http2::Http2Error errorCode);
+ void sendClientGracefulShutdownGoaway();
+ void sendInitialServerGracefulShutdownGoaway();
+ void sendFinalServerGracefulShutdownGoaway();
+ bool sendGOAWAYFrame(Http2::Http2Error errorCode, quint32 lastSreamID);
+ void maybeCloseOnGoingAway();
bool sendSETTINGS_ACK();
void handleDATA();
@@ -423,6 +426,17 @@ private:
static constexpr std::chrono::duration GoawayGracePeriod = std::chrono::seconds(60);
QDeadlineTimer m_goawayGraceTimer;
+ std::optional<quint32> m_lastGoAwayLastStreamID;
+ bool m_connectionAborted = false;
+
+ enum class GracefulShutdownState {
+ None,
+ AwaitingPriorPing,
+ AwaitingShutdownPing,
+ FinalGOAWAYSent,
+ };
+ GracefulShutdownState m_gracefulShutdownState = GracefulShutdownState::None;
+
bool m_prefaceSent = false;
// Server-side only:
diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp
index cfef5be7e2f..3ec70bcdfcd 100644
--- a/src/tools/syncqt/main.cpp
+++ b/src/tools/syncqt/main.cpp
@@ -1673,7 +1673,7 @@ public:
void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file,
SymbolDescriptor::SourceType type)
{
- if (m_commandLineArgs->showOnly())
+ if (m_commandLineArgs->showOnly() || m_commandLineArgs->debug())
std::cout << " SYMBOL: " << symbol << std::endl;
m_symbols[symbol].update(file, type);
}
diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp
index 44218d41ded..84fe8c3f44a 100644
--- a/src/widgets/widgets/qtabbar.cpp
+++ b/src/widgets/widgets/qtabbar.cpp
@@ -834,7 +834,7 @@ void QTabBarPrivate::scrollTabs()
const auto &tabRect = tab->rect;
int start = horizontal ? tabRect.left() : tabRect.top();
int end = horizontal ? tabRect.right() : tabRect.bottom();
- if (end > scrollRect.right() && start > scrollOffset) {
+ if (end > scrollRect.right() && start > scrollRect.left()) {
makeVisible(i);
return;
}
@@ -2449,10 +2449,12 @@ void QTabBar::wheelEvent(QWheelEvent *event)
if (!d->rightB->isVisible())
scrollRectExtent += tabsVertical ? d->rightB->height() : d->rightB->width();
+ const QRect scrollRect0 = d->normalizedScrollRect(0);
+ const int minScrollOffset = -1 * scrollRect0.left();
const int maxScrollOffset = qMax((tabsVertical ?
lastTabRect.bottom() :
lastTabRect.right()) - scrollRectExtent, 0);
- d->scrollOffset = qBound(0, d->scrollOffset - delta, maxScrollOffset);
+ d->scrollOffset = qBound(minScrollOffset, d->scrollOffset - delta, maxScrollOffset);
d->leftB->setEnabled(d->scrollOffset > -scrollRect.left());
d->rightB->setEnabled(maxScrollOffset > d->scrollOffset);
if (oldScrollOffset != d->scrollOffset) {
diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
index 215b3bf3b78..af6a6a41efe 100644
--- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
+++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
@@ -10,8 +10,19 @@
#if defined(__cpp_lib_expected)
# include <expected>
-#else
-# include <QtCore/private/qexpected_p.h>
+
+template <typename T>
+using QJniReturnValue = std::expected<T, jthrowable>;
+using BadAccessException = std::bad_expected_access<jthrowable>;
+
+// even with __cpp_lib_expected >= 202211L, monadic functions seem to be rather
+// broken or not reliably available
+#define EXPECTED_HAS_MONADIC (__cpp_lib_expected >= 202211L)
+
+static_assert(QtJniTypes::Traits<QJniReturnValue<int>>::signature() ==
+ QtJniTypes::Traits<int>::signature());
+static_assert(QtJniTypes::Traits<QJniReturnValue<QString>>::signature() ==
+ QtJniTypes::Traits<QString>::signature());
#endif
QT_BEGIN_NAMESPACE
@@ -2510,35 +2521,12 @@ void tst_QJniObject::implicitExceptionHandling_setStaticField()
void>);
}
-#if __cpp_lib_expected
-template <typename T>
-using QJniReturnValue = std::expected<T, jthrowable>;
-using BadAccessException = std::bad_expected_access<jthrowable>;
-// even with __cpp_lib_expected >= 202211L, monadic functions seem to be rather
-// broken or not reliably available
-#define EXPECTED_HAS_MONADIC false
-#elif TL_EXPECTED_VERSION_MAJOR
-#define EXPECTED_HAS_MONADIC true
-template <typename T>
-using QJniReturnValue = tl::expected<T, jthrowable>;
-using BadAccessException = tl::bad_expected_access<jthrowable>;
-#endif
-
-static_assert(QtJniTypes::Traits<QJniReturnValue<int>>::signature() ==
- QtJniTypes::Traits<int>::signature());
-static_assert(QtJniTypes::Traits<QJniReturnValue<QString>>::signature() ==
- QtJniTypes::Traits<QString>::signature());
-
-
void tst_QJniObject::constructWithException()
{
-#if __cpp_lib_expected
- qInfo() << "Testing explicit exception handling with std::expected" << __cpp_lib_expected;
-#elif defined(TL_EXPECTED_VERSION_MAJOR)
- qInfo() << "Testing explicit exception handling with tl::expected";
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
#else
- qInfo() << "Testing explicit exception handling with QJniReturnValue";
-#endif
+ qInfo() << "Testing explicit exception handling with std::expected" << __cpp_lib_expected;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
{
@@ -2575,10 +2563,14 @@ void tst_QJniObject::constructWithException()
}
QVERIFY(!QJniEnvironment().checkAndClearExceptions());
+#endif
}
void tst_QJniObject::callMethodWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
TestClass testObject;
{
auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod");
@@ -2640,11 +2632,14 @@ void tst_QJniObject::callMethodWithException()
QCOMPARE_GE(stackTrace.size(), 1);
QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
}
+#endif
}
void tst_QJniObject::callMethodWithMonadic()
{
-#if !EXPECTED_HAS_MONADIC
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#elif !EXPECTED_HAS_MONADIC
QSKIP("Used version of std::expected does not have monadic functions");
#else
enum Monadic {
@@ -2750,6 +2745,9 @@ void tst_QJniObject::callMethodWithMonadic()
void tst_QJniObject::callMethodWithTryCatch()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
TestClass testObject;
const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
@@ -2762,10 +2760,14 @@ void tst_QJniObject::callMethodWithTryCatch()
catch (BadAccessException &e) {
qWarning().noquote() << QJniEnvironment::stackTrace(e.error()).join('\n');
}
+#endif
}
void tst_QJniObject::callStaticMethodWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
{
auto result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod");
QVERIFY(result);
@@ -2790,10 +2792,14 @@ void tst_QJniObject::callStaticMethodWithException()
QCOMPARE_GE(stackTrace.size(), 1);
QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
}
+#endif
}
void tst_QJniObject::getFieldWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
TestClass testObject;
{
auto result = testObject.getField<QJniReturnValue<jboolean>>("BOOL_FIELD");
@@ -2808,10 +2814,14 @@ void tst_QJniObject::getFieldWithException()
result = testObject.getField<QJniReturnValue<QString>>("INVALID_STRING");
QVERIFY(!result && result.error());
}
+#endif
}
void tst_QJniObject::setFieldWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
TestClass testObject;
{
auto result = testObject.setField<QJniReturnValue<jboolean>>("BOOL_FIELD", true);
@@ -2828,10 +2838,14 @@ void tst_QJniObject::setFieldWithException()
QVERIFY(!result);
QVERIFY(result.error());
}
+#endif
}
void tst_QJniObject::getStaticFieldWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
{
auto result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_SHORT_VAR");
QVERIFY(result);
@@ -2847,10 +2861,14 @@ void tst_QJniObject::getStaticFieldWithException()
QVERIFY(!result);
QVERIFY(result.error());
}
+#endif
}
void tst_QJniObject::setStaticFieldWithException()
{
+#ifndef __cpp_lib_expected
+ QSKIP("std::expected not available.");
+#else
{
auto result = TestClass::setStaticField<QJniReturnValue<jboolean>>("S_BOOLEAN_VAR", true);
QVERIFY(result);
@@ -2866,6 +2884,7 @@ void tst_QJniObject::setStaticFieldWithException()
QVERIFY(!result);
QVERIFY(result.error());
}
+#endif
}
QTEST_MAIN(tst_QJniObject)
diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
index 22aa9d44262..547dc9de4f7 100644
--- a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
+++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
@@ -9,6 +9,7 @@
#include <QtNetwork/private/bitstreams_p.h>
#include <QtCore/qregularexpression.h>
+#include <QtCore/qthread.h>
#include <limits>
@@ -41,6 +42,9 @@ private slots:
void testCONTINUATIONFrame();
void goaway_data();
void goaway();
+ void serverInitiatedGoaways_data();
+ void serverInitiatedGoaways();
+ void clientInitiatedGoaway();
private:
enum PeerType { Client, Server };
@@ -1132,10 +1136,10 @@ void tst_QHttp2Connection::goaway()
QVERIFY(waitForSettingsExchange(connection, serverConnection));
QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream };
-
- QSignalSpy clientIncomingStreamSpy{ connection, &QHttp2Connection::newIncomingStream };
- QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived };
QSignalSpy clientGoawaySpy{ connection, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy clientClosedSpy{ connection, &QHttp2Connection::connectionClosed };
+ QSignalSpy serverGoawaySpy{ serverConnection, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy serverClosedSpy{ serverConnection, &QHttp2Connection::connectionClosed };
const HPack::HttpHeader headers = getRequiredHeaders();
clientStream->sendHEADERS(headers, false);
@@ -1143,57 +1147,78 @@ void tst_QHttp2Connection::goaway()
QVERIFY(newIncomingStreamSpy.wait());
auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>();
QVERIFY(serverStream);
- QVERIFY(serverConnection->sendGOAWAY(Http2::CANCEL));
+ serverConnection->close(); // NO_ERROR -> graceful shutdown
+
+ // New stream creation is illegal now
auto createStreamResult = serverConnection->createLocalStreamInternal();
QVERIFY(createStreamResult.has_error());
QCOMPARE(createStreamResult.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+ // Client received initial GOAWAY
QVERIFY(clientGoawaySpy.wait());
- QCOMPARE(clientGoawaySpy.size(), 1);
- // The error code used:
- QCOMPARE(clientGoawaySpy.first().first().value<Http2::Http2Error>(), Http2::CANCEL);
- // Last ID that will be processed
- QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), clientStream->streamID());
+ QCOMPARE(clientGoawaySpy.first().first().value<Http2::Http2Error>(), Http2::HTTP2_NO_ERROR);
+ QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), Http2::lastValidStreamID);
clientGoawaySpy.clear();
- // Test that creating a stream the normal way results in an error:
- QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
- invalidStream = connection->createStream();
+ // New client-stream creation is illegal now
+ auto invalidStream = connection->createStream();
QVERIFY(!invalidStream.ok());
QVERIFY(invalidStream.has_error());
QCOMPARE(invalidStream.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+ // Client receives final GOAWAY with actual lastStreamID (after PING RTT)
+ QVERIFY(clientGoawaySpy.wait());
+ QCOMPARE(clientGoawaySpy.first().at(0).value<Http2::Http2Error>(), Http2::HTTP2_NO_ERROR);
+ QCOMPARE(clientGoawaySpy.first().at(1).value<quint32>(), clientStream->streamID());
+ clientGoawaySpy.clear();
+
// Directly create a stream to avoid the GOAWAY check:
quint32 nextStreamId = clientStream->streamID() + 2;
- QHttp2Stream *secondClientStream = connection->createStreamInternal_impl(nextStreamId);
- QSignalSpy streamResetSpy{ secondClientStream, &QHttp2Stream::rstFrameReceived };
- secondClientStream->sendHEADERS(headers, endStreamOnHEADERS);
+ QHttp2Stream *ignoredClientStream = connection->createStreamInternal_impl(nextStreamId);
+ QSignalSpy streamResetSpy{ ignoredClientStream, &QHttp2Stream::rstFrameReceived };
+ ignoredClientStream->sendHEADERS(headers, endStreamOnHEADERS);
// The stream should be ignored:
using namespace std::chrono_literals;
QVERIFY(!streamResetSpy.wait(100ms)); // We don't get reset because we are ignored
if (endStreamOnHEADERS)
return;
- secondClientStream->sendDATA("my data", createNewStreamAfterDelay);
+ ignoredClientStream->sendDATA("my data", createNewStreamAfterDelay);
// We cheat and try to send data after the END_STREAM flag has been sent
if (!createNewStreamAfterDelay) {
// Manually send a frame with END_STREAM so the QHttp2Stream thinks it's fine to send more
// DATA
connection->frameWriter.start(Http2::FrameType::DATA, Http2::FrameFlag::END_STREAM,
- secondClientStream->streamID());
+ ignoredClientStream->streamID());
connection->frameWriter.write(*connection->getSocket());
QVERIFY(!streamResetSpy.wait(100ms)); // We don't get reset because we are ignored
- // Even without the GOAWAY this should fail (more activity after END_STREAM)
- secondClientStream->sendDATA("my data", true);
- QTest::ignoreMessage(QtCriticalMsg,
- QRegularExpression(u".*Connection error: DATA on invalid stream.*"_s));
- QVERIFY(clientGoawaySpy.wait());
- QCOMPARE(clientGoawaySpy.size(), 1);
- QCOMPARE(clientGoawaySpy.first().first().value<Http2::Http2Error>(),
+ const auto tstStream = [](const auto &spy, Http2::Http2Error code,
+ QLatin1StringView errMsg) {
+ QCOMPARE(spy.first().at(0).template value<Http2::Http2Error>(), code);
+ QCOMPARE(spy.first().at(1).template value<QString>(), errMsg);
+ };
+ QLatin1StringView serverErrorMsg("DATA on invalid stream");
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression(".*" + serverErrorMsg + ".*"));
+ QSignalSpy clientStreamErrorSpy(clientStream, &QHttp2Stream::errorOccurred);
+ QSignalSpy secondclientStreamErrorSpy(ignoredClientStream, &QHttp2Stream::errorOccurred);
+ QSignalSpy serverStreamErrorSpy(serverStream, &QHttp2Stream::errorOccurred);
+
+ // Triggers a connectionError of 'ENHANCE_YOUR_CALM' on the server
+ // (more activity after END_STREAM)
+ ignoredClientStream->sendDATA("my data", true);
+ QTRY_COMPARE(serverClosedSpy.count(), 1);
+ tstStream(serverStreamErrorSpy, Http2::ENHANCE_YOUR_CALM, serverErrorMsg);
+
+ QTRY_COMPARE(clientGoawaySpy.count(), 1);
+ QCOMPARE(clientGoawaySpy.first().at(0).value<Http2::Http2Error>(),
Http2::ENHANCE_YOUR_CALM);
- QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), clientStream->streamID());
- return; // connection is dead by now
+ QCOMPARE(clientGoawaySpy.first().at(1).value<quint32>(), clientStream->streamID());
+ QTRY_COMPARE(clientClosedSpy.count(), 1);
+ QLatin1StringView clientErrorMsg("Received GOAWAY");
+ tstStream(clientStreamErrorSpy, Http2::ENHANCE_YOUR_CALM, clientErrorMsg);
+ tstStream(secondclientStreamErrorSpy, Http2::ENHANCE_YOUR_CALM, clientErrorMsg);
+ return;
}
// Override the deadline timer so we don't have to wait too long
@@ -1216,6 +1241,196 @@ void tst_QHttp2Connection::goaway()
QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), clientStream->streamID());
}
+void tst_QHttp2Connection::serverInitiatedGoaways_data()
+{
+ QTest::addColumn<QString>("scenario");
+
+ QTest::newRow("graceful-shutdown") << u"graceful-shutdown"_s;
+ QTest::newRow("graceful-then-error") << u"graceful-then-error"_s;
+ QTest::newRow("increasing-lastStreamId") << u"increasing-lastStreamId"_s;
+}
+
+void tst_QHttp2Connection::serverInitiatedGoaways()
+{
+ QFETCH(QString, scenario);
+
+ auto [client, server] = makeFakeConnectedSockets();
+ auto clientConn = makeHttp2Connection(client.get(), {}, Client);
+ auto serverConn = makeHttp2Connection(server.get(), {}, Server);
+
+ // Client creates stream
+ auto *clientStream = clientConn->createStream().unwrap();
+ QVERIFY(clientStream);
+ QVERIFY(waitForSettingsExchange(clientConn, serverConn));
+ QVERIFY(clientStream->sendHEADERS(getRequiredHeaders(), false));
+
+ // Server receives stream
+ QSignalSpy newStreamSpy{ serverConn, &QHttp2Connection::newIncomingStream };
+ QVERIFY(newStreamSpy.wait());
+ auto *serverStream = newStreamSpy.front().front().value<QHttp2Stream *>();
+ QVERIFY(serverStream);
+
+ QSignalSpy clientGoawaySpy{ clientConn, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy clientClosedSpy{ clientConn, &QHttp2Connection::connectionClosed };
+ QSignalSpy serverGoawaySpy{ serverConn, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy serverClosedSpy{ serverConn, &QHttp2Connection::connectionClosed };
+ QSignalSpy clientErrorSpy{ clientStream, &QHttp2Stream::errorOccurred };
+
+ serverConn->close(); // Server initiates graceful shutdown
+
+ // Client receives initial GOAWAY with 2^31-1
+ QVERIFY(clientGoawaySpy.wait());
+ QCOMPARE(clientGoawaySpy.count(), 1);
+ QCOMPARE(clientGoawaySpy.at(0).at(0).value<Http2::Http2Error>(), Http2::HTTP2_NO_ERROR);
+ QCOMPARE(clientGoawaySpy.at(0).at(1).value<quint32>(), Http2::lastValidStreamID);
+
+ // After receiving a GOAWAY we should not be able to create new streams
+ auto clientStream2 = clientConn->createStream();
+ QCOMPARE_EQ(clientStream2.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+
+ // Client receives final GOAWAY with actual lastStreamID (after PING RTT)
+ QVERIFY(clientGoawaySpy.wait(500));
+ QCOMPARE(clientGoawaySpy.count(), 2);
+ QCOMPARE(clientGoawaySpy.at(1).at(0).value<Http2::Http2Error>(), Http2::HTTP2_NO_ERROR);
+ const quint32 finalLastStreamId = clientGoawaySpy.at(1).at(1).value<quint32>();
+ QCOMPARE(finalLastStreamId, clientStream->streamID());
+
+ // Stream still active - graceful shutdown allows completion
+ QVERIFY(clientStream->isActive());
+ QCOMPARE(clientClosedSpy.count(), 0);
+
+ if (scenario == "increasing-lastStreamId"_L1) {
+ QLatin1StringView errMsg("Repeated GOAWAY with invalid last stream ID");
+ QTest::ignoreMessage(QtCriticalMsg, QRegularExpression(".*" + errMsg + ".*"));
+
+ // Send GOAWAY with higher lastStreamId than the final one (protocol violation)
+ const quint32 invalidHigherId = finalLastStreamId + 2;
+ serverConn->sendGOAWAYFrame(Http2::HTTP2_NO_ERROR, invalidHigherId);
+
+ QTRY_COMPARE(clientErrorSpy.count(), 1);
+ QCOMPARE(clientErrorSpy.count(), 1);
+ QCOMPARE(clientErrorSpy.first().first().value<Http2::Http2Error>(), Http2::PROTOCOL_ERROR);
+ QCOMPARE(clientErrorSpy.first().last().value<QString>(), errMsg);
+
+ // Client detects violation and responds with PROTOCOL_ERROR
+ QVERIFY(serverGoawaySpy.wait());
+ QCOMPARE(serverGoawaySpy.last().at(0).value<Http2::Http2Error>(), Http2::PROTOCOL_ERROR);
+
+ QTRY_COMPARE(clientClosedSpy.count(), 1);
+ QTRY_COMPARE(serverClosedSpy.count(), 1);
+ return;
+ } else if (scenario == "graceful-then-error") {
+ // RFC 9113 6.8: An endpoint MAY send multiple GOAWAY frames if circumstances change
+ serverConn->close(Http2::INTERNAL_ERROR);
+
+ // Client receives error GOAWAY
+ QTRY_COMPARE(clientGoawaySpy.count(), 3);
+ QCOMPARE(clientGoawaySpy.at(2).at(0).value<Http2::Http2Error>(), Http2::INTERNAL_ERROR);
+
+ // Error GOAWAY lastStreamId must not exceed previous
+ QVERIFY(clientGoawaySpy.at(2).at(1).value<quint32>() <= finalLastStreamId);
+
+ // Server closes immediately after error
+ QTRY_COMPARE(serverClosedSpy.count(), 1);
+
+ // Client stream should receive error
+ QVERIFY(!clientStream->isActive());
+ QTRY_COMPARE(clientErrorSpy.count(), 1);
+ QCOMPARE(clientErrorSpy.count(), 1);
+ QCOMPARE(clientErrorSpy.first().first().value<Http2::Http2Error>(), Http2::INTERNAL_ERROR);
+ QTRY_COMPARE(clientClosedSpy.count(), 1);
+
+ // Additional close() calls should be ignored
+ serverConn->close();
+ serverConn->close(Http2::FLOW_CONTROL_ERROR);
+ qApp->processEvents();
+ QCOMPARE(clientGoawaySpy.count(), 3); // No new GOAWAY
+ return;
+ } else if ("graceful-shutdown") {
+ QSignalSpy serverDataSpy{ serverStream, &QHttp2Stream::dataReceived };
+ QVERIFY(clientStream->sendDATA("final-data", true));
+ QVERIFY(serverDataSpy.wait());
+
+ QSignalSpy clientHeadersSpy{ clientStream, &QHttp2Stream::headersReceived };
+ const HPack::HttpHeader responseHeaders{ { ":status", "200" } };
+ QVERIFY(serverStream->sendHEADERS(responseHeaders, true));
+ QVERIFY(clientHeadersSpy.wait());
+
+ QCOMPARE(clientStream->state(), QHttp2Stream::State::Closed);
+
+ // Connection closes after all streams complete
+ QTRY_COMPARE(serverClosedSpy.count(), 1);
+ QTRY_COMPARE(clientClosedSpy.count(), 1);
+
+ // No additional GOAWAYs
+ QCOMPARE(clientGoawaySpy.count(), 2);
+ }
+}
+
+void tst_QHttp2Connection::clientInitiatedGoaway()
+{
+ // Clients don't need two-phase GOAWAY because they control their own
+ // stream creation (no race condition). Client sends single GOAWAY with the
+ // last server-initiated (even) stream ID it processed.
+ auto [client, server] = makeFakeConnectedSockets();
+ auto clientConn = makeHttp2Connection(client.get(), {}, Client);
+ auto serverConn = makeHttp2Connection(server.get(), {}, Server);
+
+ // Client creates stream
+ auto *clientStream = clientConn->createStream().unwrap();
+ QVERIFY(clientStream);
+ QVERIFY(waitForSettingsExchange(clientConn, serverConn));
+ QVERIFY(clientStream->sendHEADERS(getRequiredHeaders(), false));
+
+ // Server receives stream
+ QSignalSpy newStreamSpy{ serverConn, &QHttp2Connection::newIncomingStream };
+ QVERIFY(newStreamSpy.wait());
+ auto *serverStream = newStreamSpy.front().front().value<QHttp2Stream *>();
+ QVERIFY(serverStream);
+
+ QSignalSpy clientGoawaySpy{ clientConn, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy clientClosedSpy{ clientConn, &QHttp2Connection::connectionClosed };
+ QSignalSpy serverGoawaySpy{ serverConn, &QHttp2Connection::receivedGOAWAY };
+ QSignalSpy serverClosedSpy{ serverConn, &QHttp2Connection::connectionClosed };
+
+ // Client initiates graceful shutdown
+ clientConn->close();
+
+ // Client should not be able to create new streams now
+ auto rejectedClientStream = clientConn->createStream();
+ QCOMPARE_EQ(rejectedClientStream.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+
+ // Server receives GOAWAY
+ QVERIFY(serverGoawaySpy.wait());
+ QCOMPARE(serverGoawaySpy.count(), 1);
+ QCOMPARE(serverGoawaySpy.at(0).at(0).value<Http2::Http2Error>(), Http2::HTTP2_NO_ERROR);
+
+ const quint32 lastStreamId = serverGoawaySpy.at(0).at(1).value<quint32>();
+ QCOMPARE(lastStreamId, 0u);
+
+ // Existing streams can still complete
+ QVERIFY(clientStream->isActive());
+ QVERIFY(serverStream->isActive());
+
+ // Complete the stream exchange
+ QSignalSpy serverDataSpy{ serverStream, &QHttp2Stream::dataReceived };
+ QVERIFY(clientStream->sendDATA("final-data", true));
+ QVERIFY(serverDataSpy.wait());
+
+ QSignalSpy clientHeadersSpy{ clientStream, &QHttp2Stream::headersReceived };
+ const HPack::HttpHeader responseHeaders{ { ":status", "200" } };
+ QVERIFY(serverStream->sendHEADERS(responseHeaders, true));
+ QVERIFY(clientHeadersSpy.wait());
+
+ QCOMPARE(clientStream->state(), QHttp2Stream::State::Closed);
+ QCOMPARE(serverStream->state(), QHttp2Stream::State::Closed);
+
+ QTRY_COMPARE(clientClosedSpy.count(), 1);
+
+ QCOMPARE(serverGoawaySpy.count(), 1);
+ QTRY_COMPARE(serverClosedSpy.count(), 1);
+}
+
QTEST_MAIN(tst_QHttp2Connection)
#include "tst_qhttp2connection.moc"
diff --git a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
index 16a69e4337d..4de2b255dd5 100644
--- a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
+++ b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
@@ -1100,12 +1100,14 @@ void tst_QTabBar::kineticWheel()
leftEdge = QPoint(0, 0);
rightEdge = leftButton->geometry().topLeft();
}
- // avoid border lines
- leftEdge += QPoint(2, 2);
+ // make sure the point is inside tabbar rect
+ const auto tabbarCenter = tabbar.geometry().center();
if (horizontal) {
- rightEdge += QPoint(-2, 2);
+ leftEdge = QPoint(leftEdge.x() + 10, tabbarCenter.y());
+ rightEdge = QPoint(rightEdge.x() - 10, tabbarCenter.y());
} else {
- rightEdge += QPoint(2, -2);
+ leftEdge = QPoint(tabbarCenter.x(), leftEdge.y() + 10);
+ rightEdge = QPoint(tabbarCenter.x(), rightEdge.y() - 10);
}
QCOMPARE(tabbar.tabAt(leftEdge), 0);
@@ -1393,7 +1395,7 @@ void tst_QTabBar::hoverTab()
QCOMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_None);
// inserting a tab at index 2 again should paint the new tab hovered
- tabbar.insertTab(2, "C2");
+ tabbar.insertTab(2, "X");
QTRY_COMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_MouseOver);
QCOMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_None);
}