diff --git a/tests/auto/corelib/kernel/qcoreapplication/CMakeLists.txt b/tests/auto/corelib/kernel/qcoreapplication/CMakeLists.txt index afe6c22b40f..9a4e57a0972 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/CMakeLists.txt +++ b/tests/auto/corelib/kernel/qcoreapplication/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/corelib/kernel/qcoreapplication/apphelper.cpp b/tests/auto/corelib/kernel/qcoreapplication/apphelper.cpp new file mode 100644 index 00000000000..c61ce2eb8c8 --- /dev/null +++ b/tests/auto/corelib/kernel/qcoreapplication/apphelper.cpp @@ -0,0 +1,141 @@ +// Copyright (C) 2024 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include +#include + +#include +#include + +#include +#include + +#if defined(QT_WIDGETS_LIB) +#include +#include +using TestApplication = QApplication; +#elif defined(QT_GUI_LIB) +#include +#include +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 \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; +} diff --git a/tests/auto/corelib/kernel/qcoreapplication/maybeshow.h b/tests/auto/corelib/kernel/qcoreapplication/maybeshow.h new file mode 100644 index 00000000000..a3c93d3c60a --- /dev/null +++ b/tests/auto/corelib/kernel/qcoreapplication/maybeshow.h @@ -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 +#elif defined(QT_GUI_LIB) +#include +#endif + +inline auto maybeShowSomething() +{ +#if defined(QT_WIDGETS_LIB) + auto w = std::make_unique(); + w->setModal(true); + w->show(); +#elif defined(QT_GUI_LIB) + auto w = std::make_unique(); + w->setModality(Qt::ApplicationModal); + w->show(); +#else + void *w = nullptr; +#endif + return w; +} + diff --git a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp index d7dfca824bb..3839ed28294 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp +++ b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp @@ -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); diff --git a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.h b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.h index 1c25f635347..11abb52d6d8 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.h +++ b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.h @@ -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(); diff --git a/tests/auto/corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp b/tests/auto/corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp index 4f10aaed6e9..810e0a43d52 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp +++ b/tests/auto/corelib/kernel/qcoreapplication/tst_static_qcoreapplication.cpp @@ -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(argv)); + [[maybe_unused]] static auto w = maybeShowSomething(); } struct HookManager diff --git a/tests/auto/gui/kernel/qguiapplication/CMakeLists.txt b/tests/auto/gui/kernel/qguiapplication/CMakeLists.txt index 109c0813128..9883bdb10ba 100644 --- a/tests/auto/gui/kernel/qguiapplication/CMakeLists.txt +++ b/tests/auto/gui/kernel/qguiapplication/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt index 7825ed51c4e..aa2b947728c 100644 --- a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt @@ -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 diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp index 45bb9415d60..42ddfc71381 100644 --- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp +++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp @@ -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();