tst_Q*Application: add tests for unusual qApp creations and exits

This commit adds one unusual creation and three more unusual exits:
* creation of the Q*Application in a thread (QTBUG-130895)
* exits using ::exit() instead of returning from main():
  * from the main thread's event loop
  * from an auxiliary thread
  * from inside an auxiliary thread's event loop

All of these exercise the moment the QAdoptedThread & QThreadData for
the main thread is destroyed, which are, respectively:
* thread exit time, running thread-specific destructors
* inside exit(), running atexit()-like callbacks (qthread_*.cpp)
* [tst_static_q*application] inside exit(), running static destructors
  (added in commit 1da7558bfd7626bcc40a214a90ae5027f32f6c7f)

Unlike the tst_static_q*application tests, the calls to ::exit() cannot
be a regular QtTest because that would cause the log output to be
incomplete. The threaded Q*Application could be a test of its own, but
since I needed to add the helper anyway, I chose to add the test there.

For the majority of the tests, the failure mode is going to be a crash
on exit.

Task-number: QTBUG-12673
Task-number: QTBUG-132429
Pick-to: 6.8 6.9
Change-Id: I1062ef500356bd97dd0cfffda4aeeda9afa138e8
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Thiago Macieira 2024-12-23 12:24:41 -03:00
parent 239474710d
commit bfbd1a281d
9 changed files with 287 additions and 0 deletions

View File

@ -41,6 +41,21 @@ endif()
qt_internal_add_test(tst_static_qcoreapplication
SOURCES
tst_static_qcoreapplication.cpp
maybeshow.h
LIBRARIES
Qt::CorePrivate
)
qt_internal_add_executable(apphelper_core
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
SOURCES
apphelper.cpp
maybeshow.h
LIBRARIES
Qt::CorePrivate
)
set_target_properties(apphelper_core PROPERTIES OUTPUT_NAME apphelper)
add_dependencies(tst_qcoreapplication
apphelper_core
)

View File

@ -0,0 +1,141 @@
// Copyright (C) 2024 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <private/qcoreapplication_p.h>
#include <qthread.h>
#include <qtimer.h>
#include <string_view>
#include <thread>
#include <stdlib.h>
#include <stdio.h>
#if defined(QT_WIDGETS_LIB)
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
using TestApplication = QApplication;
#elif defined(QT_GUI_LIB)
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
using TestApplication = QGuiApplication;
#else
using TestApplication = QCoreApplication;
#endif
#include "maybeshow.h"
static int exitFromEventLoop(int argc, char **argv)
{
TestApplication app(argc, argv);
[[maybe_unused]] auto w = maybeShowSomething();
QTimer::singleShot(200, &app, [] { ::exit(EXIT_SUCCESS); });
return app.exec();
}
static int exitFromThread(int argc, char **argv)
{
TestApplication app(argc, argv);
[[maybe_unused]] auto w = maybeShowSomething();
auto thr = QThread::create([] {
if (!QThread::currentThread())
fprintf(stderr, "QThread::currentThread is null!\n");
::exit(EXIT_SUCCESS);
});
QTimer::singleShot(200, thr, [&thr] { thr->start(); });
app.exec();
Q_UNREACHABLE_RETURN(EXIT_FAILURE);
}
// mix of the two above
static int exitFromThreadedEventLoop(int argc, char **argv)
{
// disable the Glib event loop because that causes at exit:
// GLib-CRITICAL **: 11:48:09.717: g_source_unref_internal: assertion 'source != NULL' failed
// (I don't think it's *our* bug).
qputenv("QT_NO_GLIB", "1");
TestApplication app(argc, argv);
[[maybe_unused]] auto w = maybeShowSomething();
struct Thread : public QThread {
void run() override
{
QObject dummy;
QTimer::singleShot(1, &dummy, [] { ::exit(EXIT_SUCCESS); });
exec();
}
} thr;
QTimer::singleShot(200, &thr, [&thr] { thr.start(); });
app.exec();
Q_UNREACHABLE_RETURN(EXIT_FAILURE);
}
// see QTBUG-130895
static int mainAppInAThread(int argc, char **argv)
{
// note: when using std::thread in MinGW, we exercise different code paths
// from QThread (winpthreads vs native)
auto callable = [](int argc, char **argv) {
TestApplication app(argc, argv);
};
if (auto id = QCoreApplicationPrivate::theMainThreadId.loadRelaxed()) {
fprintf(stderr, "QCoreApplicationPrivate::theMainThreadId (%p) predates QCoreApplication!\n",
id);
}
std::thread thr(callable, argc, argv);
auto handle = thr.native_handle();
thr.join();
auto id = QCoreApplicationPrivate::theMainThreadId.loadRelaxed();
if (id) {
FILE *stream = stdout;
#if defined(QT_GUI_LIB)
// QtGui has several Q_GLOBAL_STATICs surviving with live QObject (e.g.
// the QFactoryLoaders), so theMainThreadId has not been unset yet.
#elif defined(Q_OS_WIN) && defined(Q_CC_GNU)
// Windows threading and cleaning of global static is messed up.
#else
// Ensure that theMainThread is gone, so we could now start a new
// thread with QCoreApplication.
stream = stderr;
#endif
fprintf(stream, "QCoreApplicationPrivate::theMainThreadId (%p) still extant (native handle %#llx)\n",
id, quint64(quintptr(handle)));
if (stream == stderr)
return EXIT_FAILURE;
fflush(stream);
}
return EXIT_SUCCESS;
}
static int usage(const char *name)
{
printf("%s <subtest>\n", name);
return EXIT_FAILURE;
}
int main(int argc, char **argv)
{
if (argc == 1)
return usage(argv[0]);
// remove some environment options that cause Qt to print warnings
qunsetenv("QT_SCALE_FACTOR_ROUNDING_POLICY");
std::string_view subtest(argv[1]);
if (subtest == "exitFromEventLoop")
return exitFromEventLoop(argc - 1, argv + 1);
if (subtest == "exitFromThread")
return exitFromThread(argc - 1, argv + 1);
if (subtest == "exitFromThreadedEventLoop")
return exitFromThreadedEventLoop(argc - 1, argv + 1);
if (subtest == "mainAppInAThread")
return mainAppInAThread(argc - 1, argv + 1);
fprintf(stderr, "%s: unknown test %s\n", argv[0], argv[1]);
return EXIT_FAILURE;
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2024 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#if defined(QT_WIDGETS_LIB)
#include <QtWidgets/QDialog>
#elif defined(QT_GUI_LIB)
#include <QtGui/QWindow>
#endif
inline auto maybeShowSomething()
{
#if defined(QT_WIDGETS_LIB)
auto w = std::make_unique<QDialog>();
w->setModal(true);
w->show();
#elif defined(QT_GUI_LIB)
auto w = std::make_unique<QWindow>();
w->setModality(Qt::ApplicationModal);
w->show();
#else
void *w = nullptr;
#endif
return w;
}

View File

@ -1001,6 +1001,29 @@ void tst_QCoreApplication::threadedEventDelivery()
}
#if QT_CONFIG(process)
#if defined(Q_OS_WIN)
# define EXE ".exe"
#else
# define EXE ""
#endif
void tst_QCoreApplication::runHelperTest()
{
# ifdef Q_OS_ANDROID
QSKIP("Skipped on Android: helper not present");
# endif
int argc = 0;
QCoreApplication app(argc, nullptr);
QProcess process;
process.start(QFINDTESTDATA("apphelper" EXE), { QTest::currentTestFunction() });
QVERIFY2(process.waitForFinished(5000), qPrintable(process.errorString()));
QCOMPARE(process.readAllStandardError(), QString());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
}
#undef EXE
#endif
void tst_QCoreApplication::testTrWithPercantegeAtTheEnd()
{
QCoreApplication::translate("testcontext", "this will crash%", "testdisamb", 3);

View File

@ -10,6 +10,8 @@
class tst_QCoreApplication: public QObject
{
Q_OBJECT
void runHelperTest();
private slots:
void sendEventsOnProcessEvents(); // this must be the first test
void getSetCheck();
@ -40,6 +42,19 @@ private slots:
void applicationEventFilters_auxThread();
void threadedEventDelivery_data();
void threadedEventDelivery();
#if QT_CONFIG(process)
// also add to tst_qapplication.cpp
void exitFromEventLoop() { runHelperTest(); }
void exitFromThread() { runHelperTest(); }
void exitFromThreadedEventLoop() { runHelperTest(); }
# if defined(Q_OS_APPLE) && defined(QT_GUI_LIB)
// QGuiApplication in a thread fails inside Apple libs:
// *** Assertion failure in -[NSMenu _setMenuName:], NSMenu.m:777
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API misuse: setting the main menu on a non-main thread. Main menu contents should only be modified from the main thread.'
# else
void mainAppInAThread() { runHelperTest(); }
# endif
#endif
void testTrWithPercantegeAtTheEnd();
#if QT_CONFIG(library)
void addRemoveLibPaths();

View File

@ -15,6 +15,8 @@ using App = QGuiApplication;
using App = QCoreApplication;
#endif
#include "maybeshow.h"
class tst_Static_QCoreApplication : public QObject
{
Q_OBJECT
@ -31,6 +33,7 @@ void tst_Static_QCoreApplication::staticApplication()
static int argc = 1;
const char *argv[] = { staticMetaObject.className(), nullptr };
static App app(argc, const_cast<char **>(argv));
[[maybe_unused]] static auto w = maybeShowSomething();
}
struct HookManager

View File

@ -50,7 +50,23 @@ endif()
qt_internal_add_test(tst_static_qguiapplication
SOURCES
../../../corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp
../../../corelib/kernel/qcoreapplication/maybeshow.h
LIBRARIES
Qt::CorePrivate
Qt::Gui
)
qt_internal_add_executable(apphelper_gui
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
SOURCES
../../../corelib/kernel/qcoreapplication/apphelper.cpp
../../../corelib/kernel/qcoreapplication/maybeshow.h
LIBRARIES
Qt::CorePrivate
Qt::Gui
)
set_target_properties(apphelper_gui PROPERTIES OUTPUT_NAME apphelper)
add_dependencies(tst_qguiapplication
apphelper_gui
)

View File

@ -11,14 +11,27 @@ add_subdirectory(desktopsettingsaware)
add_subdirectory(modal)
add_subdirectory(test)
qt_internal_add_executable(apphelper_widgets
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
SOURCES
../../../corelib/kernel/qcoreapplication/apphelper.cpp
../../../corelib/kernel/qcoreapplication/maybeshow.h
LIBRARIES
Qt::CorePrivate
Qt::Widgets
)
set_target_properties(apphelper_widgets PROPERTIES OUTPUT_NAME apphelper)
add_dependencies(tst_qapplication
desktopsettingsaware_helper
modal_helper
apphelper_widgets
)
qt_internal_add_test(tst_static_qapplication
SOURCES
../../../corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp
../../../corelib/kernel/qcoreapplication/maybeshow.h
LIBRARIES
Qt::CorePrivate
Qt::Widgets

View File

@ -59,6 +59,7 @@ class tst_QApplication : public QObject
{
Q_OBJECT
void runHelperTest();
private slots:
void cleanup();
void sendEventsOnProcessEvents(); // this must be the first test
@ -99,6 +100,18 @@ private slots:
void sendPostedEvents();
#endif // ifdef QT_BUILD_INTERNAL
#if QT_CONFIG(process)
void exitFromEventLoop() { runHelperTest(); }
void exitFromThread() { runHelperTest(); }
void exitFromThreadedEventLoop() { runHelperTest(); }
# if defined(Q_OS_APPLE)
// QGuiApplication in a thread fails inside Apple libs:
// *** Assertion failure in -[NSMenu _setMenuName:], NSMenu.m:777
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API misuse: setting the main menu on a non-main thread. Main menu contents should only be modified from the main thread.'
# else
void mainAppInAThread() { runHelperTest(); }
# endif
#endif
void thread();
void desktopSettingsAware();
@ -1197,6 +1210,29 @@ void tst_QApplication::sendPostedEvents()
}
#endif
#if QT_CONFIG(process)
#if defined(Q_OS_WIN)
# define EXE ".exe"
#else
# define EXE ""
#endif
void tst_QApplication::runHelperTest()
{
# ifdef Q_OS_ANDROID
QSKIP("Skipped on Android: helper not present");
# endif
int argc = 0;
QCoreApplication app(argc, nullptr);
QProcess process;
process.start(QFINDTESTDATA("apphelper" EXE), { QTest::currentTestFunction() });
QVERIFY2(process.waitForFinished(5000), qPrintable(process.errorString()));
QCOMPARE(process.readAllStandardError(), QString());
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
}
#undef EXE
#endif
void tst_QApplication::thread()
{
QThread *currentThread = QThread::currentThread();