summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/cmake/RunCMake/CMakeLists.txt4
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/CMakeLists.txt3
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsConfigExtras.cmake.in1
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsExtraInclude.cmake1
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/RunCMakeTest.cmake56
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/build_and_install_tools_package.cmake48
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/cmake/FindWrapScrewdriver.cmake2
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package.cmake16
-rw-r--r--tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package_via_module.cmake16
-rw-r--r--tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp6
-rw-r--r--tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp21
-rw-r--r--tests/auto/corelib/platform/android/tst_android.cpp11
-rw-r--r--tests/auto/corelib/text/qlocaledata/tst_qlocaledata.cpp2
-rw-r--r--tests/auto/gui/image/qimagereader/images/image16.pgm260
-rw-r--r--tests/auto/gui/image/qimagereader/tst_qimagereader.cpp1
-rw-r--r--tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST2
-rw-r--r--tests/auto/gui/kernel/qwindow/BLACKLIST1
-rw-r--r--tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp110
-rw-r--r--tests/auto/tools/moc/allmocs_baseline_in.json4
-rw-r--r--tests/auto/tools/moc/qtbug-35657-gadget.h2
-rw-r--r--tests/auto/tools/moc/related-metaobjects-in-namespaces.h2
-rw-r--r--tests/auto/tools/moc/related-metaobjects-name-conflict.h4
-rw-r--r--tests/auto/widgets/kernel/qwidget/BLACKLIST3
-rw-r--r--tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp25
-rw-r--r--tests/manual/sandboxed_file_access/CMakeLists.txt71
-rw-r--r--tests/manual/sandboxed_file_access/Info.plist.ios90
-rw-r--r--tests/manual/sandboxed_file_access/Info.plist.macos68
-rw-r--r--tests/manual/sandboxed_file_access/app.entitlements10
-rw-r--r--tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp422
-rw-r--r--tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp8
30 files changed, 1252 insertions, 18 deletions
diff --git a/tests/auto/cmake/RunCMake/CMakeLists.txt b/tests/auto/cmake/RunCMake/CMakeLists.txt
index ec182debccd..aac9b43bac7 100644
--- a/tests/auto/cmake/RunCMake/CMakeLists.txt
+++ b/tests/auto/cmake/RunCMake/CMakeLists.txt
@@ -30,3 +30,7 @@ if(TARGET Qt6::Platform)
qt_internal_add_RunCMake_test(Sbom ${extra_run_cmake_args})
endif()
endif()
+
+list(APPEND extra_run_cmake_args "-DQT_WILL_INSTALL=${QT_WILL_INSTALL}")
+list(APPEND extra_run_cmake_args "-DQT_REPO_MODULE_VERSION=${QT_REPO_MODULE_VERSION}")
+qt_internal_add_RunCMake_test(StandaloneToolsPackage ${extra_run_cmake_args})
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/CMakeLists.txt b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/CMakeLists.txt
new file mode 100644
index 00000000000..d490ae0713c
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.16)
+project(${RunCMake_TEST} LANGUAGES CXX VERSION "${QT_REPO_MODULE_VERSION}")
+include(${RunCMake_TEST}.cmake)
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsConfigExtras.cmake.in b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsConfigExtras.cmake.in
new file mode 100644
index 00000000000..35271634fde
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsConfigExtras.cmake.in
@@ -0,0 +1 @@
+set(QT_GARAGE_TOOLS_CONFIG_EXTRAS_LOADED TRUE)
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsExtraInclude.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsExtraInclude.cmake
new file mode 100644
index 00000000000..4997381e6f4
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/Qt6GarageToolsExtraInclude.cmake
@@ -0,0 +1 @@
+set(QT_GARAGE_TOOLS_EXTRA_INCLUDE_LOADED TRUE)
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/RunCMakeTest.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/RunCMakeTest.cmake
new file mode 100644
index 00000000000..4581f694f5a
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/RunCMakeTest.cmake
@@ -0,0 +1,56 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+include(QtRunCMake)
+
+set(build_case "build_and_install_tools_package")
+set(consume_case "consume_tools_package")
+set(consume_case_via_module "consume_tools_package_via_module")
+
+function(run_cmake_and_build case)
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
+
+ # Set an install prefix that is common to both projects.
+ set(cmake_install_prefix ${RunCMake_BINARY_DIR}/installed)
+
+ set(options
+ "-DQt6_DIR=${Qt6_DIR}"
+ "-DQT_REPO_MODULE_VERSION=${QT_REPO_MODULE_VERSION}"
+ )
+
+ # For prefix builds, install into a separate dir rather than the Qt one.
+ # For non-prefix, files will end up being copied to the Qt dir.
+ if(QT_WILL_INSTALL)
+ list(APPEND options
+ "-DCMAKE_INSTALL_PREFIX=${cmake_install_prefix}"
+ "-DQT_ADDITIONAL_PACKAGES_PREFIX_PATH=${cmake_install_prefix}"
+ "-DQT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH=${cmake_install_prefix}"
+ )
+ endif()
+
+ # Merge output, because some of configure also outputs some stuff to stderr even when
+ # everything is fine and CMake treats it as an error.
+ set(RunCMake_TEST_OUTPUT_MERGE 1)
+
+ # Configure.
+ run_cmake_with_options(${case} ${options})
+
+ # Do not remove the current RunCMake_TEST_BINARY_DIR for the next operations.
+ set(RunCMake_TEST_NO_CLEAN 1)
+
+ # Build and install
+ run_cmake_command(${case}-build "${CMAKE_COMMAND}" --build .)
+
+ if(QT_WILL_INSTALL)
+ run_cmake_command(${case}-install "${CMAKE_COMMAND}" --install .)
+ endif()
+endfunction()
+
+# Build and install the tools package.
+run_cmake_and_build("${build_case}")
+
+# Find the tools package.
+run_cmake_and_build("${consume_case}")
+
+# Find the tools package via module.
+run_cmake_and_build("${consume_case_via_module}")
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/build_and_install_tools_package.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/build_and_install_tools_package.cmake
new file mode 100644
index 00000000000..7cc9ecc38d1
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/build_and_install_tools_package.cmake
@@ -0,0 +1,48 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+find_package(Qt6 REQUIRED COMPONENTS Core BuildInternals)
+
+# Avoid erorrs in CI about unsupported SDK and Xcode versions on older CI macOS versions.
+if(APPLE)
+ set(QT_NO_APPLE_SDK_AND_XCODE_CHECK ON)
+endif()
+
+qt_internal_project_setup()
+
+qt_build_repo_begin()
+
+set(base_name "Garage")
+set(extra_file_base_path "${CMAKE_CURRENT_SOURCE_DIR}/${QT_CMAKE_EXPORT_NAMESPACE}${base_name}")
+
+# Add a standalone tools package.
+qt_internal_add_tools_package(
+ PACKAGE_BASE_NAME ${base_name}
+ EXTRA_CMAKE_FILES
+ "${extra_file_base_path}ToolsExtraInclude.cmake"
+ EXTRA_CMAKE_INCLUDES
+ "${QT_CMAKE_EXPORT_NAMESPACE}${base_name}ToolsExtraInclude.cmake"
+)
+
+# Check that we can add third party dependencies to the tools package.
+qt_internal_record_tools_package_extra_third_party_dependency(
+ PACKAGE_BASE_NAME ${base_name}
+ DEPENDENCY_PACKAGE_NAME WrapScrewdriver)
+
+# Add a module that should be look up the standalone tools package when the module itself is
+# looked up.
+qt_internal_add_module(Workshop
+ HEADER_MODULE
+ NO_MODULE_HEADERS
+ NO_PRIVATE_MODULE
+ NO_GENERATE_CPP_EXPORTS
+ NO_ADDITIONAL_TARGET_INFO
+ NO_GENERATE_METATYPES
+ NO_PACKAGE_CONFIG_FILE
+ NO_MODULE_JSON_FILE
+ NO_QMAKE_SUPPORT_FILES
+)
+qt_record_extra_qt_main_tools_package_dependency(Workshop GarageTools "6")
+
+qt_build_repo_post_process()
+qt_build_repo_end()
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/cmake/FindWrapScrewdriver.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/cmake/FindWrapScrewdriver.cmake
new file mode 100644
index 00000000000..484dc8f35d8
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/cmake/FindWrapScrewdriver.cmake
@@ -0,0 +1,2 @@
+set(WrapScrewdriver_FOUND TRUE)
+set(QT_SCREW_DRIVER_LOADED TRUE)
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package.cmake
new file mode 100644
index 00000000000..9b37905d569
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package.cmake
@@ -0,0 +1,16 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+find_package(Qt6 REQUIRED COMPONENTS GarageTools)
+
+if(NOT QT_GARAGE_TOOLS_CONFIG_EXTRAS_LOADED)
+ message(FATAL_ERROR "Qt6GarageToolsConfigExtras.cmake was not loaded.")
+endif()
+
+if(NOT QT_GARAGE_TOOLS_EXTRA_INCLUDE_LOADED)
+ message(FATAL_ERROR "Qt6GarageToolsExtraInclude.cmake was not loaded.")
+endif()
+
+if(NOT QT_SCREW_DRIVER_LOADED)
+ message(FATAL_ERROR "FindWrapScrewdriver.cmake was not loaded.")
+endif()
diff --git a/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package_via_module.cmake b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package_via_module.cmake
new file mode 100644
index 00000000000..4fe592a1f69
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/StandaloneToolsPackage/consume_tools_package_via_module.cmake
@@ -0,0 +1,16 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+find_package(Qt6 REQUIRED COMPONENTS Workshop)
+
+if(NOT QT_GARAGE_TOOLS_CONFIG_EXTRAS_LOADED)
+ message(FATAL_ERROR "Qt6GarageToolsConfigExtras.cmake was not loaded.")
+endif()
+
+if(NOT QT_GARAGE_TOOLS_EXTRA_INCLUDE_LOADED)
+ message(FATAL_ERROR "Qt6GarageToolsExtraInclude.cmake was not loaded.")
+endif()
+
+if(NOT QT_SCREW_DRIVER_LOADED)
+ message(FATAL_ERROR "FindWrapScrewdriver.cmake was not loaded.")
+endif()
diff --git a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
index f6d08c8107f..67643606fa3 100644
--- a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
+++ b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
@@ -77,10 +77,6 @@ class SomethingOfEverything : public QObject
Q_PROPERTY(SomethingEnum eprop READ eprop)
Q_PROPERTY(SomethingFlagEnum fprop READ fprop)
Q_PROPERTY(QLocale::Language language READ language)
- Q_ENUMS(SomethingEnum)
- Q_FLAGS(SomethingFlag)
- Q_ENUMS(SomethingEnum64)
- Q_FLAGS(SomethingFlag64)
public:
Q_INVOKABLE SomethingOfEverything() {}
~SomethingOfEverything() {}
@@ -105,6 +101,7 @@ public:
UVW = 8
};
Q_DECLARE_FLAGS(SomethingFlag, SomethingFlagEnum)
+ Q_FLAG(SomethingFlag)
enum SomethingFlagEnum64 : quint64
{
@@ -112,6 +109,7 @@ public:
OPQ = Q_UINT64_C(1) << 63,
};
Q_DECLARE_FLAGS(SomethingFlag64, SomethingFlagEnum64)
+ Q_FLAG(SomethingFlag64)
Q_INVOKABLE Q_SCRIPTABLE void method1() const {}
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
index 9be046c75be..b05a055252b 100644
--- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
+++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
@@ -397,6 +397,7 @@ private slots:
void iterateAssociativeContainerElements_data();
void iterateAssociativeContainerElements() { runTestFunction(); }
void iterateContainerElements();
+ void emptyContainerInterface();
void pairElements_data();
void pairElements() { runTestFunction(); }
@@ -5324,6 +5325,26 @@ void tst_QVariant::iterateContainerElements()
}
}
+void tst_QVariant::emptyContainerInterface()
+{
+ // An empty container interface should implicitly be of invalid size
+ // and its begin and end iterators should be equal.
+
+ const QtMetaContainerPrivate::QMetaContainerInterface emptyContainerInterface {};
+ QIterable emptyIterable(QMetaContainer(&emptyContainerInterface), nullptr);
+
+ QCOMPARE(emptyIterable.size(), -1);
+ auto constBegin = emptyIterable.constBegin();
+ auto constEnd = emptyIterable.constEnd();
+ QVERIFY(constBegin == constEnd);
+ QCOMPARE(constEnd - constBegin, 0);
+
+ auto mutableBegin = emptyIterable.mutableBegin();
+ auto mutableEnd = emptyIterable.mutableEnd();
+ QVERIFY(mutableBegin == mutableEnd);
+ QCOMPARE(mutableEnd - mutableBegin, 0);
+}
+
template <typename Pair> static void testVariantPairElements()
{
QFETCH(std::function<void(void *)>, makeValue);
diff --git a/tests/auto/corelib/platform/android/tst_android.cpp b/tests/auto/corelib/platform/android/tst_android.cpp
index 3665f100a61..b4bb0323f8a 100644
--- a/tests/auto/corelib/platform/android/tst_android.cpp
+++ b/tests/auto/corelib/platform/android/tst_android.cpp
@@ -430,6 +430,8 @@ void tst_Android::testFullScreenDimensions()
widget.showNormal();
}
+ // TODO needs fix to work in local and CI on same fashion
+ const bool runsOnCI = qgetenv("QTEST_ENVIRONMENT").split(' ').contains("ci");
{
// Translucent
// available geometry == full display size (system bars visible but drawable under)
@@ -437,14 +439,11 @@ void tst_Android::testFullScreenDimensions()
widget.show();
QCoreApplication::processEvents();
QTRY_COMPARE(screen->availableGeometry().width(), realSize.getField<jint>("x"));
- QTRY_COMPARE(screen->availableGeometry().height(), realSize.getField<jint>("y"));
-
- QTRY_COMPARE(screen->geometry().width(), realSize.getField<jint>("x"));
- // TODO needs fix to work in local and CI on same fashion
- const bool runsOnCI = qgetenv("QTEST_ENVIRONMENT").split(' ').contains("ci");
if ((sdkVersion > __ANDROID_API_V__) && runsOnCI)
QEXPECT_FAIL("", "Fails on Android 16 (QTBUG-141712).", Continue);
+ QTRY_COMPARE(screen->availableGeometry().height(), realSize.getField<jint>("y"));
+ QTRY_COMPARE(screen->geometry().width(), realSize.getField<jint>("x"));
QTRY_COMPARE(screen->geometry().height(), realSize.getField<jint>("y"));
widget.showNormal();
}
@@ -455,6 +454,8 @@ void tst_Android::testFullScreenDimensions()
widget.showMaximized();
QCoreApplication::processEvents();
QTRY_COMPARE(screen->availableGeometry().width(), realSize.getField<jint>("x"));
+ if ((sdkVersion > __ANDROID_API_V__) && runsOnCI)
+ QEXPECT_FAIL("", "Fails on Android 16 (QTBUG-141712).", Continue);
QTRY_COMPARE(screen->availableGeometry().height(), realSize.getField<jint>("y"));
QTRY_COMPARE(screen->geometry().width(), realSize.getField<jint>("x"));
diff --git a/tests/auto/corelib/text/qlocaledata/tst_qlocaledata.cpp b/tests/auto/corelib/text/qlocaledata/tst_qlocaledata.cpp
index a63dea4c679..93024bb4a6c 100644
--- a/tests/auto/corelib/text/qlocaledata/tst_qlocaledata.cpp
+++ b/tests/auto/corelib/text/qlocaledata/tst_qlocaledata.cpp
@@ -260,7 +260,7 @@ void tst_QLocaleData::numericData_data()
<< u"\u00D7\u06F1\u06F0^"_s << GS(1, 3, 3) << U'\u06F0' << false;
// Grouping separator variants:
- QTest::newRow("gsw-Latn-CH/exp") // Right single quote for grouping:
+ QTest::newRow("gsw-Latn-CH/exp") // Uses apostrophe for grouping (matching C++):
<< LOCALE_DATA_PTR(SwissGerman, LatinScript, Switzerland)
<< QLocaleData::DoubleScientificMode
<< u"."_s << u"'"_s << u"\u2212"_s << u"+"_s << u"E"_s
diff --git a/tests/auto/gui/image/qimagereader/images/image16.pgm b/tests/auto/gui/image/qimagereader/images/image16.pgm
new file mode 100644
index 00000000000..4e0b55131b0
--- /dev/null
+++ b/tests/auto/gui/image/qimagereader/images/image16.pgm
@@ -0,0 +1,260 @@
+P2
+16
+16
+65535
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+65535
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
+32767
diff --git a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp
index de9fea78ea6..8ccaf435f0b 100644
--- a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp
+++ b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp
@@ -610,6 +610,7 @@ void tst_QImageReader::imageFormat_data()
QTest::newRow("pbm") << QString("image.pbm") << QByteArray("pbm") << QImage::Format_Mono;
QTest::newRow("pgm") << QString("image.pgm") << QByteArray("pgm") << QImage::Format_Grayscale8;
+ QTest::newRow("pgm") << QString("image16.pgm") << QByteArray("pgm") << QImage::Format_Grayscale16;
QTest::newRow("ppm-1") << QString("image.ppm") << QByteArray("ppm") << QImage::Format_RGB32;
QTest::newRow("ppm-2") << QString("teapot.ppm") << QByteArray("ppm") << QImage::Format_RGB32;
QTest::newRow("ppm-3") << QString("runners.ppm") << QByteArray("ppm") << QImage::Format_RGB32;
diff --git a/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST b/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST
new file mode 100644
index 00000000000..a8f73d73f4d
--- /dev/null
+++ b/tests/auto/gui/kernel/qguieventdispatcher/BLACKLIST
@@ -0,0 +1,2 @@
+[postEventFromThread]
+macos-26 developer-build # QTBUG-142185
diff --git a/tests/auto/gui/kernel/qwindow/BLACKLIST b/tests/auto/gui/kernel/qwindow/BLACKLIST
index 1ef54f0bfbf..55003c7ec18 100644
--- a/tests/auto/gui/kernel/qwindow/BLACKLIST
+++ b/tests/auto/gui/kernel/qwindow/BLACKLIST
@@ -25,5 +25,6 @@ android
windows-10
windows-11
android
+macos-26 # QTBUG-142157
[stateChangeSignal]
macos # QTBUG-140388
diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
index 8e8c90e14de..417655c31d9 100644
--- a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
+++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
@@ -8,6 +8,8 @@
#include <QtNetwork/private/hpack_p.h>
#include <QtNetwork/private/bitstreams_p.h>
+#include <QtCore/qregularexpression.h>
+
#include <limits>
using namespace Qt::StringLiterals;
@@ -35,6 +37,8 @@ private slots:
void connectToServer();
void WINDOW_UPDATE();
void testCONTINUATIONFrame();
+ void goaway_data();
+ void goaway();
private:
enum PeerType { Client, Server };
@@ -1051,6 +1055,112 @@ void tst_QHttp2Connection::testCONTINUATIONFrame()
}
}
+void tst_QHttp2Connection::goaway_data()
+{
+ QTest::addColumn<bool>("endStreamOnHEADERS");
+ QTest::addColumn<bool>("createNewStreamAfterDelay");
+ QTest::addRow("end-on-headers") << true << false;
+ QTest::addRow("end-after-data") << false << false;
+ QTest::addRow("end-after-new-late-stream") << false << true;
+}
+
+void tst_QHttp2Connection::goaway()
+{
+ QFETCH(const bool, endStreamOnHEADERS);
+ QFETCH(const bool, createNewStreamAfterDelay);
+ auto [client, server] = makeFakeConnectedSockets();
+ auto connection = makeHttp2Connection(client.get(), {}, Client);
+ auto serverConnection = makeHttp2Connection(server.get(), {}, Server);
+
+ QHttp2Stream *clientStream = connection->createStream().unwrap();
+ QVERIFY(clientStream);
+ QVERIFY(waitForSettingsExchange(connection, serverConnection));
+
+ QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream };
+
+ QSignalSpy clientIncomingStreamSpy{ connection, &QHttp2Connection::newIncomingStream };
+ QSignalSpy clientHeaderReceivedSpy{ clientStream, &QHttp2Stream::headersReceived };
+ QSignalSpy clientGoawaySpy{ connection, &QHttp2Connection::receivedGOAWAY };
+
+ const HPack::HttpHeader headers = getRequiredHeaders();
+ clientStream->sendHEADERS(headers, false);
+
+ QVERIFY(newIncomingStreamSpy.wait());
+ auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>();
+ QVERIFY(serverStream);
+ QVERIFY(serverConnection->sendGOAWAY(Http2::CANCEL));
+ auto createStreamResult = serverConnection->createLocalStreamInternal();
+ QVERIFY(createStreamResult.has_error());
+ QCOMPARE(createStreamResult.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+
+ 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());
+ clientGoawaySpy.clear();
+
+ // Test that creating a stream the normal way results in an error:
+ QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
+ invalidStream = connection->createStream();
+ QVERIFY(!invalidStream.ok());
+ QVERIFY(invalidStream.has_error());
+ QCOMPARE(invalidStream.error(), QHttp2Connection::CreateStreamError::ReceivedGOAWAY);
+
+ // 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);
+ // 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);
+ // 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());
+ 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>(),
+ Http2::ENHANCE_YOUR_CALM);
+ QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), clientStream->streamID());
+ return; // connection is dead by now
+ }
+
+ // Override the deadline timer so we don't have to wait too long
+ serverConnection->m_goawayGraceTimer.setRemainingTime(50ms);
+
+ // We can create the stream whenever, it is not noticed by the server until we send something.
+ nextStreamId += 2;
+ QHttp2Stream *rejectedStream = connection->createStreamInternal_impl(nextStreamId);
+ // Sleep until the grace period is over:
+ QTRY_VERIFY(serverConnection->m_goawayGraceTimer.hasExpired());
+
+ QVERIFY(rejectedStream->sendHEADERS(headers, true));
+
+ QTest::ignoreMessage(QtCriticalMsg,
+ QRegularExpression(u".*Connection error: Peer refused to GOAWAY\\..*"_s));
+ QVERIFY(clientGoawaySpy.wait());
+ QCOMPARE(clientGoawaySpy.size(), 1);
+ QCOMPARE(clientGoawaySpy.first().first().value<Http2::Http2Error>(), Http2::PROTOCOL_ERROR);
+ // The first stream is still the last processed one:
+ QCOMPARE(clientGoawaySpy.first().last().value<quint32>(), clientStream->streamID());
+}
+
QTEST_MAIN(tst_QHttp2Connection)
#include "tst_qhttp2connection.moc"
diff --git a/tests/auto/tools/moc/allmocs_baseline_in.json b/tests/auto/tools/moc/allmocs_baseline_in.json
index 8f7757a7272..d8e6c4df538 100644
--- a/tests/auto/tools/moc/allmocs_baseline_in.json
+++ b/tests/auto/tools/moc/allmocs_baseline_in.json
@@ -2444,7 +2444,7 @@
{
"isClass": false,
"isFlag": false,
- "lineNumber": 14,
+ "lineNumber": 13,
"name": "SomeEnum",
"values": [
"SomeEnumValue"
@@ -2501,7 +2501,7 @@
{
"isClass": false,
"isFlag": false,
- "lineNumber": 14,
+ "lineNumber": 13,
"name": "SomeEnum",
"values": [
"SomeEnumValue"
diff --git a/tests/auto/tools/moc/qtbug-35657-gadget.h b/tests/auto/tools/moc/qtbug-35657-gadget.h
index d97e1f7f45e..ca225faca1c 100644
--- a/tests/auto/tools/moc/qtbug-35657-gadget.h
+++ b/tests/auto/tools/moc/qtbug-35657-gadget.h
@@ -9,9 +9,9 @@
namespace QTBUG_35657 {
class A {
Q_GADGET
- Q_ENUMS(SomeEnum)
public:
enum SomeEnum { SomeEnumValue = 0 };
+ Q_ENUM(SomeEnum)
};
}
diff --git a/tests/auto/tools/moc/related-metaobjects-in-namespaces.h b/tests/auto/tools/moc/related-metaobjects-in-namespaces.h
index efd82107673..2513094ed0c 100644
--- a/tests/auto/tools/moc/related-metaobjects-in-namespaces.h
+++ b/tests/auto/tools/moc/related-metaobjects-in-namespaces.h
@@ -9,9 +9,9 @@
namespace QTBUG_2151 {
class A : public QObject {
Q_OBJECT
- Q_ENUMS(SomeEnum)
public:
enum SomeEnum { SomeEnumValue = 0 };
+ Q_ENUM(SomeEnum)
};
class B : public QObject
diff --git a/tests/auto/tools/moc/related-metaobjects-name-conflict.h b/tests/auto/tools/moc/related-metaobjects-name-conflict.h
index cccd97e4e74..d88826f696a 100644
--- a/tests/auto/tools/moc/related-metaobjects-name-conflict.h
+++ b/tests/auto/tools/moc/related-metaobjects-name-conflict.h
@@ -9,15 +9,15 @@
#define DECLARE_GADGET_AND_OBJECT_CLASSES \
class Gadget { \
Q_GADGET \
- Q_ENUMS(SomeEnum) \
public: \
enum SomeEnum { SomeEnumValue = 0 }; \
+ Q_ENUM(SomeEnum) \
}; \
class Object : public QObject{ \
Q_OBJECT \
- Q_ENUMS(SomeEnum) \
public: \
enum SomeEnum { SomeEnumValue = 0 }; \
+ Q_ENUM(SomeEnum) \
};
#define DECLARE_DEPENDING_CLASSES \
diff --git a/tests/auto/widgets/kernel/qwidget/BLACKLIST b/tests/auto/widgets/kernel/qwidget/BLACKLIST
index dd2cb1dcee9..9651c1480c8 100644
--- a/tests/auto/widgets/kernel/qwidget/BLACKLIST
+++ b/tests/auto/widgets/kernel/qwidget/BLACKLIST
@@ -41,6 +41,9 @@ android
android
[hoverPosition]
macos-14 x86
+macos-26 # QTBUG-142157
# QTBUG-124291
[setParentChangesFocus:make dialog parentless, after]
android
+[enterLeaveOnWindowShowHide]
+macos-26 # QTBUG-142157
diff --git a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
index 03131cebe47..16a69e4337d 100644
--- a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
+++ b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
@@ -63,6 +63,7 @@ private slots:
void removeLastVisibleTab();
void closeButton();
+ void requestCloseOnMiddleClick();
void tabButton_data();
void tabButton();
@@ -551,6 +552,28 @@ void tst_QTabBar::closeButton()
QCOMPARE(spy.size(), 1);
}
+void tst_QTabBar::requestCloseOnMiddleClick()
+{
+ QTabBar tabbar;
+ tabbar.addTab("foo");
+ tabbar.addTab("bar");
+ QCOMPARE(tabbar.count(), 2);
+
+ QSignalSpy spy(&tabbar, SIGNAL(tabCloseRequested(int)));
+
+ QCOMPARE(tabbar.tabsClosable(), false);
+ QTest::mouseClick(&tabbar, Qt::MiddleButton, {}, tabbar.tabRect(0).center());
+ QCOMPARE(spy.size(), 0);
+
+ tabbar.setTabsClosable(true);
+ QCOMPARE(tabbar.tabsClosable(), true);
+ QTest::mouseClick(&tabbar, Qt::MiddleButton, {}, tabbar.tabRect(0).center());
+ QCOMPARE(spy.size(), 1);
+
+ QTest::mouseClick(&tabbar, Qt::MiddleButton, {}, tabbar.rect().bottomRight() * 1.1);
+ QCOMPARE(spy.size(), 1);
+}
+
Q_DECLARE_METATYPE(QTabBar::ButtonPosition)
void tst_QTabBar::tabButton_data()
{
@@ -1512,7 +1535,7 @@ void tst_QTabBar::checkPositionsAfterShapeChange()
using QTabWidget::QTabWidget;
using QTabWidget::setTabBar;
};
-
+
class TabBar : public QTabBar
{
public:
diff --git a/tests/manual/sandboxed_file_access/CMakeLists.txt b/tests/manual/sandboxed_file_access/CMakeLists.txt
new file mode 100644
index 00000000000..8df09401cf9
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/CMakeLists.txt
@@ -0,0 +1,71 @@
+# Copyright (C) 2025 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_manual_sandboxed_file_access LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_standard_project_setup()
+
+qt_add_executable(tst_manual_sandboxed_file_access
+ tst_sandboxed_file_access.cpp
+)
+
+target_link_libraries(tst_manual_sandboxed_file_access PRIVATE
+ Qt::CorePrivate
+ Qt::Widgets
+ Qt::Test
+)
+
+enable_language(OBJCXX)
+set_source_files_properties(tst_sandboxed_file_access.cpp PROPERTIES LANGUAGE OBJCXX)
+
+if(MACOS)
+ target_sources(tst_manual_sandboxed_file_access PRIVATE app.entitlements)
+ set_target_properties(tst_manual_sandboxed_file_access PROPERTIES
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.dev.tst-manual-sandboxed-file-access"
+ XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements"
+ XCODE_ATTRIBUTE_COPY_PHASE_STRIP FALSE
+ )
+ if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
+ set_target_properties(tst_manual_sandboxed_file_access PROPERTIES
+ RESOURCE "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements"
+ )
+ endif()
+
+ set(platform_plugin "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/platforms/libqcocoa.dylib")
+ target_sources(tst_manual_sandboxed_file_access PRIVATE ${platform_plugin})
+ set_source_files_properties(${platform_plugin}
+ PROPERTIES
+ MACOSX_PACKAGE_LOCATION PlugIns/platforms
+ )
+
+ target_compile_definitions(tst_manual_sandboxed_file_access PRIVATE
+ QTEST_THROW_ON_FAIL
+ QTEST_THROW_ON_SKIP
+ )
+endif()
+
+if(IOS)
+ set(plist_path "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.ios")
+else()
+ set(plist_path "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.macos")
+endif()
+
+set_target_properties(tst_manual_sandboxed_file_access
+ PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${plist_path}")
+
+install(TARGETS tst_manual_sandboxed_file_access
+ BUNDLE DESTINATION .
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
+
+qt_generate_deploy_app_script(
+ TARGET tst_manual_sandboxed_file_access
+ OUTPUT_SCRIPT deploy_script
+ NO_UNSUPPORTED_PLATFORM_ERROR
+)
+install(SCRIPT ${deploy_script})
diff --git a/tests/manual/sandboxed_file_access/Info.plist.ios b/tests/manual/sandboxed_file_access/Info.plist.ios
new file mode 100644
index 00000000000..c6072cffa92
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/Info.plist.ios
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>25B78</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleExecutable</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleIdentifier</key>
+ <string>io.qt.fb.tst-manual-sandboxed-file-access</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>6.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>iPhoneOS</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>6.0.0</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>23B77</string>
+ <key>DTPlatformName</key>
+ <string>iphoneos</string>
+ <key>DTPlatformVersion</key>
+ <string>26.1</string>
+ <key>DTSDKBuild</key>
+ <string>23B77</string>
+ <key>DTSDKName</key>
+ <string>iphoneos26.1</string>
+ <key>DTXcode</key>
+ <string>2610</string>
+ <key>DTXcodeBuild</key>
+ <string>17B55</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>MinimumOSVersion</key>
+ <string>17</string>
+ <key>NOTE</key>
+ <string>This file was generated by Qt's default CMake support.</string>
+ <key>UIDeviceFamily</key>
+ <array>
+ <integer>1</integer>
+ <integer>2</integer>
+ </array>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.text</string>
+ </array>
+ <!-- These two don't seem to be needed to make things work -->
+ <key>LSHandlerRank</key>
+ <string>Default</string>
+ <key>CFBundleTypeName</key>
+ <string>Text files</string>
+ </dict>
+ </array>
+ <key>UISupportsDocumentBrowser</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/Info.plist.macos b/tests/manual/sandboxed_file_access/Info.plist.macos
new file mode 100644
index 00000000000..81a93f0353f
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/Info.plist.macos
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>25B78</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundleIdentifier</key>
+ <string>io.qt.dev.tst-manual-sandboxed-file-access</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>tst_manual_sandboxed_file_access</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>6.0</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>MacOSX</string>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>6.0.0</string>
+ <key>DTCompiler</key>
+ <string>com.apple.compilers.llvm.clang.1_0</string>
+ <key>DTPlatformBuild</key>
+ <string>25B74</string>
+ <key>DTPlatformName</key>
+ <string>macosx</string>
+ <key>DTPlatformVersion</key>
+ <string>26.1</string>
+ <key>DTSDKBuild</key>
+ <string>25B74</string>
+ <key>DTSDKName</key>
+ <string>macosx26.1</string>
+ <key>DTXcode</key>
+ <string>2610</string>
+ <key>DTXcodeBuild</key>
+ <string>17B55</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>13</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.text</string>
+ </array>
+ <!-- These two don't seem to be needed to make things work -->
+ <key>LSHandlerRank</key>
+ <string>Default</string>
+ <key>CFBundleTypeName</key>
+ <string>Text files</string>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/app.entitlements b/tests/manual/sandboxed_file_access/app.entitlements
new file mode 100644
index 00000000000..6d968edb4f8
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/app.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.files.user-selected.read-write</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp b/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp
new file mode 100644
index 00000000000..18381ce0c8c
--- /dev/null
+++ b/tests/manual/sandboxed_file_access/tst_sandboxed_file_access.cpp
@@ -0,0 +1,422 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore>
+#include <QtWidgets>
+#include <QtTest>
+
+#include <Foundation/Foundation.h>
+
+#if defined(Q_OS_MACOS) && defined(QT_BUILD_INTERNAL)
+#include <private/qcore_mac_p.h>
+Q_CONSTRUCTOR_FUNCTION(qt_mac_ensureResponsible);
+#endif
+
+class tst_SandboxedFileAccess : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+
+ void alwaysAccessibleLocations();
+
+ void standardPaths_data();
+ void standardPaths();
+
+ void readSingleFile();
+ void writeSingleFile();
+ void writeSingleFileNonCanonical();
+
+ void removeFile();
+ void trashFile();
+
+ void readFileAfterRestart();
+
+ void directoryAccess();
+
+ void securityScopedTargetFile();
+
+ void fileOpenEvent();
+
+private:
+ void writeFile(const QString &fileName);
+ QByteArray readFile(const QString &fileName);
+
+ QString getFileName(QFileDialog::AcceptMode, QFileDialog::FileMode,
+ const QString &action = QString(), const QString &fileName = QString());
+
+ QString sandboxPath() const
+ {
+ return QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first();
+ }
+
+ QString bundlePath() const
+ {
+ QString path = QCoreApplication::applicationDirPath();
+#if defined(Q_OS_MACOS)
+ path.remove("/Contents/MacOS");
+#endif
+ return path;
+ }
+
+ QStringList m_persistedFileNames;
+ QPointer<QWidget> m_widget;
+};
+
+void tst_SandboxedFileAccess::initTestCase()
+{
+ qDebug() << "đŸ“Ļ App bundle" << bundlePath();
+ qDebug() << "🔐 App container" << sandboxPath();
+
+ m_widget = new QWidget;
+ m_widget->show();
+ QVERIFY(QTest::qWaitForWindowExposed(m_widget));
+}
+
+void tst_SandboxedFileAccess::cleanupTestCase()
+{
+ NSURL *appSupportDir = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).toNSURL();
+ NSURL *bookmarksFile = [appSupportDir URLByAppendingPathComponent:@"SecurityScopedBookmarks.plist"];
+ NSError *error = nullptr;
+ NSMutableDictionary *bookmarks = [[NSDictionary dictionaryWithContentsOfURL:bookmarksFile
+ error:&error] mutableCopy];
+ for (NSString *path in bookmarks.allKeys) {
+ if (m_persistedFileNames.contains(QString::fromNSString(path))) {
+ qDebug() << "Keeping knowledge of persisted path" << path;
+ continue;
+ }
+ qDebug() << "Wiping knowledge of path" << path;
+ [bookmarks removeObjectForKey:path];
+ }
+ [bookmarks writeToURL:bookmarksFile error:&error];
+
+ qGuiApp->quit();
+}
+
+void tst_SandboxedFileAccess::alwaysAccessibleLocations()
+{
+ readFile(QCoreApplication::applicationFilePath());
+
+ // The documents location is inside the sandbox and writable on both iOS and macOS
+ auto documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ writeFile(documents + "/test-writable-file.txt");
+}
+
+void tst_SandboxedFileAccess::standardPaths_data()
+{
+ QTest::addColumn<QStandardPaths::StandardLocation>("location");
+ auto standardLocations = QMetaEnum::fromType<QStandardPaths::StandardLocation>();
+ for (int i = 0; i < standardLocations.keyCount(); ++i)
+ QTest::newRow(standardLocations.key(i)) << QStandardPaths::StandardLocation(standardLocations.value(i));
+}
+
+void tst_SandboxedFileAccess::standardPaths()
+{
+ QFETCH(QStandardPaths::StandardLocation, location);
+ auto writableLocation = QStandardPaths::writableLocation(location);
+
+ if (writableLocation.isEmpty())
+ QSKIP("There's no writable location for this location");
+
+ QFileInfo info(writableLocation);
+ if (info.isSymLink() && !info.symLinkTarget().startsWith(sandboxPath()))
+ QSKIP("This location is a symlink to outside the sandbox and requires access");
+
+ QVERIFY(QDir().mkpath(writableLocation));
+
+#if !defined(Q_OS_MACOS)
+ QEXPECT_FAIL("HomeLocation", "The sandbox root is not writable on iOS", Abort);
+#endif
+ writeFile(writableLocation + QString("/test-writable-file-%1.txt").arg(QTest::currentDataTag()));
+}
+
+void tst_SandboxedFileAccess::readSingleFile()
+{
+ QString filePath = getFileName(QFileDialog::AcceptOpen,
+ QFileDialog::ExistingFile, "Choose file to read");
+ readFile(filePath);
+
+ {
+ QFile file(QCoreApplication::applicationFilePath());
+ QVERIFY(file.open(QFile::ReadOnly));
+ QByteArray plistContent = file.read(100);
+ file.close();
+
+ // Check that setFileName can target a security scoped file
+ file.setFileName(filePath);
+ QVERIFY(file.open(QFile::ReadOnly));
+ QVERIFY(file.isReadable());
+ QCOMPARE_NE(file.read(100), plistContent);
+ }
+
+ QDir dir;
+ QString fileName;
+
+ {
+ QFileInfo info(filePath);
+ dir = info.path();
+ fileName = info.fileName();
+ QVERIFY(dir.exists());
+ QVERIFY(!fileName.isEmpty());
+ }
+
+ // Check that we're able to access files via non-canonical paths
+ readFile(dir.absolutePath() + "/../" + dir.dirName() + "/" + fileName);
+}
+
+QByteArray tst_SandboxedFileAccess::readFile(const QString &fileName)
+{
+ QFile file(fileName);
+ QVERIFY(file.exists());
+ QVERIFY(file.open(QFile::ReadOnly));
+ QVERIFY(file.isReadable());
+ QByteArray data = file.read(100);
+ QVERIFY(!data.isEmpty());
+ return data;
+}
+
+void tst_SandboxedFileAccess::writeSingleFile()
+{
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write", "write-single-file.txt");
+ writeFile(filePath);
+ readFile(filePath);
+}
+
+void tst_SandboxedFileAccess::writeSingleFileNonCanonical()
+{
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write", "write-single-file-non-canonical.txt");
+ QDir dir;
+ QString fileName;
+
+ {
+ QFileInfo info(filePath);
+ dir = info.path();
+ fileName = info.fileName();
+ QVERIFY(dir.exists());
+ QVERIFY(!fileName.isEmpty());
+ }
+
+ writeFile(dir.absolutePath() + "/../" + dir.dirName() + "/" + fileName);
+ readFile(filePath);
+}
+
+void tst_SandboxedFileAccess::writeFile(const QString &fileName)
+{
+ QFile file(fileName);
+ QVERIFY(file.open(QFile::WriteOnly));
+ QVERIFY(file.isWritable());
+ QVERIFY(file.write("Hello world"));
+}
+
+void tst_SandboxedFileAccess::removeFile()
+{
+ QString fileName = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write and then remove", "write-and-remove-file.txt");
+ writeFile(fileName);
+
+ {
+ QFile file(fileName);
+ QVERIFY(file.remove());
+ }
+}
+
+void tst_SandboxedFileAccess::trashFile()
+{
+ QString fileName = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write and then trash", "write-and-trash-file.txt");
+ writeFile(fileName);
+
+ {
+ QFile file(fileName);
+ QVERIFY(file.moveToTrash());
+ }
+}
+
+void tst_SandboxedFileAccess::readFileAfterRestart()
+{
+ // Every other restart of the app will save a file or load a previously saved file
+
+ QSettings settings;
+ QString savedFile = settings.value("savedFile").toString();
+ if (savedFile.isEmpty()) {
+ QString filePath = getFileName(QFileDialog::AcceptSave, QFileDialog::AnyFile,
+ "Choose a file to write for reading after restart", "write-and-read-after-restart.txt");
+ qDebug() << "Writing" << filePath << "and saving to preferences";
+ writeFile(filePath);
+ settings.setValue("savedFile", filePath);
+ m_persistedFileNames << filePath;
+ } else {
+ qDebug() << "Loading" << savedFile << "from preferences";
+ settings.remove("savedFile"); // Remove up front, in case this fails
+ readFile(savedFile);
+ QFile file(savedFile);
+ QVERIFY(file.remove());
+ }
+}
+
+void tst_SandboxedFileAccess::directoryAccess()
+{
+ // Every other restart of the app will re-establish access to the folder,
+ // or re-use previous access.
+
+ QSettings settings;
+ QString directory = settings.value("savedDirectory").toString();
+ if (directory.isEmpty()) {
+ directory = getFileName(QFileDialog::AcceptOpen, QFileDialog::Directory,
+ "Choose a directory we can create some files in");
+ auto canonical = QFileInfo(directory).canonicalFilePath();
+ QVERIFY(!canonical.isEmpty());
+ directory = canonical;
+ settings.setValue("savedDirectory", directory);
+ m_persistedFileNames << QFileInfo(directory).canonicalFilePath();
+ } else {
+ settings.remove("savedDirectory");
+ }
+ settings.sync();
+
+ QString fileInDir;
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.exists());
+ QVERIFY(dir.isReadable());
+ fileInDir = dir.filePath("file-in-dir.txt");
+ }
+
+ writeFile(fileInDir);
+ readFile(fileInDir);
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.count() > 0);
+ QVERIFY(dir.entryList().contains("file-in-dir.txt"));
+ }
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.mkdir("subdirectory"));
+ QVERIFY(dir.entryList().contains("subdirectory"));
+ fileInDir = dir.filePath("subdirectory/file-in-subdir.txt");
+ }
+
+ writeFile(fileInDir);
+ readFile(fileInDir);
+
+ // Check that we can write to a non-canonical path within the directory
+ // we have access to, and then read it from the canonical path.
+ writeFile(directory + "/subdirectory/../non-existing-non-canonical.txt");
+ readFile(directory + "/non-existing-non-canonical.txt");
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.cd("subdirectory"));
+ dir.removeRecursively();
+ }
+}
+
+void tst_SandboxedFileAccess::securityScopedTargetFile()
+{
+ // This is a non-security scoped file
+ auto documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ QString sourceFilePath = documents + "/test-security-scoped-target-file.txt";
+ writeFile(sourceFilePath);
+ QFile sourceFile(sourceFilePath);
+
+ QString directory = getFileName(QFileDialog::AcceptOpen, QFileDialog::Directory,
+ "Choose a directory we can link/copy some to");
+
+ QString subDirectory;
+ {
+ QDir dir(directory);
+ QVERIFY(dir.mkdir("subdirectory"));
+ QVERIFY(dir.entryList().contains("subdirectory"));
+ subDirectory = dir.filePath("subdirectory");
+ }
+
+ QVERIFY(sourceFile.copy(subDirectory + "/copied-file.txt"));
+ QVERIFY(sourceFile.link(subDirectory + "/linked-file.txt"));
+ QVERIFY(sourceFile.rename(subDirectory + "/renamed-file.txt"));
+
+ {
+ QDir dir(directory);
+ QVERIFY(dir.cd("subdirectory"));
+ dir.removeRecursively();
+ }
+}
+
+void tst_SandboxedFileAccess::fileOpenEvent()
+{
+ struct OpenEventFilter : public QObject
+ {
+ bool eventFilter(QObject *watched, QEvent *event) override
+ {
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
+ fileName = openEvent->file();
+ }
+
+ return QObject::eventFilter(watched, event);
+ }
+
+ QString fileName;
+ };
+
+ OpenEventFilter openEventFilter;
+ qGuiApp->installEventFilter(&openEventFilter);
+
+ m_widget->setLayout(new QVBoxLayout);
+ QLabel label;
+ label.setWordWrap(true);
+ m_widget->layout()->addWidget(&label);
+#if defined(Q_OS_MACOS)
+ label.setText("Drag a text file to the app's Dock icon, or open in the app via Finder's 'Open With' menu");
+#else
+ label.setText("Open the Files app, and choose 'Open With' or share a text document with this app");
+#endif
+ label.show();
+
+ QTRY_VERIFY_WITH_TIMEOUT(!openEventFilter.fileName.isNull(), 30s);
+ label.setText("Got file: " + openEventFilter.fileName);
+
+ readFile(openEventFilter.fileName);
+
+ QTest::qWait(3000);
+}
+
+QString tst_SandboxedFileAccess::getFileName(QFileDialog::AcceptMode acceptMode, QFileDialog::FileMode fileMode,
+ const QString &action, const QString &fileName)
+{
+ QFileDialog dialog(m_widget);
+ dialog.setAcceptMode(acceptMode);
+ dialog.setFileMode(fileMode);
+ dialog.setWindowTitle(action);
+ dialog.setLabelText(QFileDialog::Accept, action);
+ dialog.selectFile(fileName);
+ if (!action.isEmpty())
+ qDebug() << "â„šī¸" << action;
+ dialog.exec();
+ auto selectedFiles = dialog.selectedFiles();
+ return selectedFiles.count() ? selectedFiles.first() : QString();
+}
+
+int main(int argc, char** argv)
+{
+ QApplication app(argc, argv);
+
+ tst_SandboxedFileAccess testObject;
+
+ // Run tests with QApp running
+ int testExecResult = 0;
+ QMetaObject::invokeMethod(&testObject, [&]{
+ testExecResult = QTest::qExec(&testObject, argc, argv);
+ }, Qt::QueuedConnection);
+
+ [[maybe_unused]] int appExecResult = app.exec();
+ return testExecResult;
+}
+
+#include "tst_sandboxed_file_access.moc"
diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
index 1e49847c97f..484c28a484b 100644
--- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
+++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
@@ -9,6 +9,12 @@
#include <emscripten.h>
#include <emscripten/threading.h>
+#if QT_CONFIG(wasm_jspi)
+# define QT_WASM_EMSCRIPTEN_ASYNC ,emscripten::async()
+#else
+# define QT_WASM_EMSCRIPTEN_ASYNC
+#endif
+
namespace QtWasmTest {
namespace {
QObject *g_testObject = nullptr;
@@ -127,7 +133,7 @@ void passTest()
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
- emscripten::function("runTestFunction", &runTestFunction, emscripten::async());
+ emscripten::function("runTestFunction", &runTestFunction QT_WASM_EMSCRIPTEN_ASYNC);
emscripten::function("qtWasmFail", &failTest);
emscripten::function("qtWasmPass", &passTest);
}