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:
parent
239474710d
commit
bfbd1a281d
@ -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
|
||||
)
|
||||
|
141
tests/auto/corelib/kernel/qcoreapplication/apphelper.cpp
Normal file
141
tests/auto/corelib/kernel/qcoreapplication/apphelper.cpp
Normal 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;
|
||||
}
|
25
tests/auto/corelib/kernel/qcoreapplication/maybeshow.h
Normal file
25
tests/auto/corelib/kernel/qcoreapplication/maybeshow.h
Normal 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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user