diff options
Diffstat (limited to 'tests')
50 files changed, 1668 insertions, 189 deletions
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 7c4f33cddb6..f4789d115a6 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -70,7 +70,7 @@ enable_testing() # doesn't seem to be the case. # Until this is figured out, disable the tests when cross-compiling to Linux. if(UNIX AND NOT APPLE AND NOT WIN32 AND CMAKE_CROSSCOMPILING AND NOT QT_ENABLE_CMAKE_BOOT2QT_TESTS - AND NOT QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS) + AND NOT QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS AND NOT ANDROID) message(STATUS "Running CMake tests is disabled when cross-compiling to Linux / Boot2Qt.") return() endif() @@ -83,6 +83,12 @@ if(WIN32 AND CMAKE_CROSSCOMPILING) return() endif() +if(TARGET Qt6::Core) + # Tests are built as part of the qtbase build tree. + # Setup paths so that the Qt packages are found, similar to examples. + qt_internal_set_up_build_dir_package_paths() +endif() + set(required_packages Core Network Xml Sql Test TestInternalsPrivate) set(optional_packages DBus Gui Widgets PrintSupport OpenGL Concurrent) @@ -117,53 +123,73 @@ endif() include("${_Qt6CTestMacros}") -# Test only multi-abi specific functionality when QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS is ON. -# Qt::Gui is the prerequisite for all Android tests. -if(QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS AND NOT NO_GUI) - unset(multi_abi_vars) - foreach(abi IN LISTS QT_ANDROID_ABIS) - list(APPEND multi_abi_vars "-DQT_PATH_ANDROID_ABI_${abi}=${QT_PATH_ANDROID_ABI_${abi}}") - endforeach() - if(QT_ANDROID_BUILD_ALL_ABIS) - list(APPEND multi_abi_vars "-DQT_ANDROID_BUILD_ALL_ABIS=${QT_ANDROID_BUILD_ALL_ABIS}") + +if(ANDROID) + # Qt::Gui is the prerequisite for all Android tests. + if(NO_GUI) + return() endif() - list(APPEND multi_abi_vars "-DQT_HOST_PATH=${QT_HOST_PATH}") + set(common_android_vars "-DQT_HOST_PATH=${QT_HOST_PATH}") - set(multi_abi_forward_vars - TEST_SINGLE_VALUE_ARG - TEST_SPACES_VALUE_ARG - TEST_LIST_VALUE_ARG - TEST_ESCAPING_VALUE_ARG + _qt_internal_test_expect_pass(test_android_signing + BUILD_OPTIONS "${common_android_vars}" + BINARY ${CMAKE_CTEST_COMMAND} + BINARY_ARGS -V ) - string(REPLACE ";" "[[;]]" multi_abi_forward_vars "${multi_abi_forward_vars}") - - set(single_value "TestValue") - set(list_value "TestValue[[;]]TestValue2[[;]]TestValue3") - set(escaping_value "TestValue\\\\[[;]]TestValue2\\\\[[;]]TestValue3") - set(spaces_value "TestValue TestValue2 TestValue3") - _qt_internal_test_expect_pass(test_android_multi_abi_forward_vars - BUILD_OPTIONS - ${multi_abi_vars} - "-DQT_ANDROID_MULTI_ABI_FORWARD_VARS=${multi_abi_forward_vars}" - "-DTEST_SINGLE_VALUE_ARG=${single_value}" - "-DTEST_LIST_VALUE_ARG=${list_value}" - "-DTEST_ESCAPING_VALUE_ARG=${escaping_value}" - "-DTEST_SPACES_VALUE_ARG=${spaces_value}" + set_property(TEST test_android_signing APPEND PROPERTY ENVIRONMENT + "QT_ANDROID_KEYSTORE_PATH=${CMAKE_CURRENT_BINARY_DIR}/test_android_signing/qttest.jks" + "QT_ANDROID_KEYSTORE_ALIAS=qttest" + "QT_ANDROID_KEYSTORE_STORE_PASS=qttest" + "QT_ANDROID_KEYSTORE_KEY_PASS=qttest" ) - #Run test_android_aar only if the host is Unix and zipinfo is available - find_program(ZIPINFO_EXECUTABLE zipinfo) - if(CMAKE_HOST_UNIX AND ZIPINFO_EXECUTABLE) - _qt_internal_test_expect_pass(test_android_aar + # Test only multi-abi specific functionality when QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS is + # ON. + if(QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS) + set(multi_abi_vars "${common_android_vars}") + foreach(abi IN LISTS QT_ANDROID_ABIS) + list(APPEND multi_abi_vars "-DQT_PATH_ANDROID_ABI_${abi}=${QT_PATH_ANDROID_ABI_${abi}}") + endforeach() + if(QT_ANDROID_BUILD_ALL_ABIS) + list(APPEND multi_abi_vars "-DQT_ANDROID_BUILD_ALL_ABIS=${QT_ANDROID_BUILD_ALL_ABIS}") + endif() + + set(multi_abi_forward_vars + TEST_SINGLE_VALUE_ARG + TEST_SPACES_VALUE_ARG + TEST_LIST_VALUE_ARG + TEST_ESCAPING_VALUE_ARG + ) + string(REPLACE ";" "[[;]]" multi_abi_forward_vars "${multi_abi_forward_vars}") + + set(single_value "TestValue") + set(list_value "TestValue[[;]]TestValue2[[;]]TestValue3") + set(escaping_value "TestValue\\\\[[;]]TestValue2\\\\[[;]]TestValue3") + set(spaces_value "TestValue TestValue2 TestValue3") + _qt_internal_test_expect_pass(test_android_multi_abi_forward_vars BUILD_OPTIONS ${multi_abi_vars} - --build-target verify_aar + "-DQT_ANDROID_MULTI_ABI_FORWARD_VARS=${multi_abi_forward_vars}" + "-DTEST_SINGLE_VALUE_ARG=${single_value}" + "-DTEST_LIST_VALUE_ARG=${list_value}" + "-DTEST_ESCAPING_VALUE_ARG=${escaping_value}" + "-DTEST_SPACES_VALUE_ARG=${spaces_value}" ) - else() - message(WARNING - "Skipping test_android_aar CMake build test because \ - the host is not Unix or zipinfo is not found") + + #Run test_android_aar only if the host is Unix and zipinfo is available + find_program(ZIPINFO_EXECUTABLE zipinfo) + if(CMAKE_HOST_UNIX AND ZIPINFO_EXECUTABLE) + _qt_internal_test_expect_pass(test_android_aar + BUILD_OPTIONS + ${multi_abi_vars} + --build-target verify_aar + ) + else() + message(WARNING + "Skipping test_android_aar CMake build test because \ + the host is not Unix or zipinfo is not found") + endif() endif() return() diff --git a/tests/auto/cmake/test_android_signing/CMakeLists.txt b/tests/auto/cmake/test_android_signing/CMakeLists.txt new file mode 100644 index 00000000000..b2d67676a69 --- /dev/null +++ b/tests/auto/cmake/test_android_signing/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.20) + +project(test_android_signing) + +enable_testing() + +find_program(keytool NAMES keytool) + +if(NOT keytool) + message("Skipping test_android_signing: keytool not found.") + return() +endif() + +if("$ENV{QT_ANDROID_KEYSTORE_PATH}" STREQUAL "" + OR "$ENV{QT_ANDROID_KEYSTORE_STORE_PASS}" STREQUAL "" + OR "$ENV{QT_ANDROID_KEYSTORE_ALIAS}" STREQUAL "" + OR "$ENV{QT_ANDROID_KEYSTORE_KEY_PASS}" STREQUAL "") + message(FATAL_ERROR "Environment variables QT_ANDROID_KEYSTORE_PATH," + " QT_ANDROID_KEYSTORE_STORE_PASS, QT_ANDROID_KEYSTORE_ALIAS, and" + " QT_ANDROID_KEYSTORE_KEY_PASS must be set to run this test.") +endif() + +find_package(Qt6 COMPONENTS Core Gui REQUIRED) + +set(QT_ANDROID_SIGN_AAB ON) +set(QT_ANDROID_SIGN_APK ON) + +execute_process(COMMAND ${keytool} + -genkey -alias "$ENV{QT_ANDROID_KEYSTORE_ALIAS}" -keyalg RSA + -keystore "$ENV{QT_ANDROID_KEYSTORE_PATH}" + -noprompt + -dname "CN=qttest, OU=ID, O=Qt, L=Qt, S=Test, C=DE" + -storepass "$ENV{QT_ANDROID_KEYSTORE_STORE_PASS}" + -keypass "$ENV{QT_ANDROID_KEYSTORE_KEY_PASS}" + RESULT_VARIABLE keytool_result +) + + +qt6_add_executable(test_android_signing main.cpp) + +target_link_libraries(test_android_signing PRIVATE + Qt6::Core + Qt6::Gui +) + +add_test(NAME test_android_signing_verify_signature + COMMAND "${CMAKE_COMMAND}" "-DKEYTOOL_PATH=${keytool}" + "-DAPK_FILE=${CMAKE_CURRENT_BINARY_DIR}/android-build/test_android_signing.apk" + -P "${CMAKE_CURRENT_SOURCE_DIR}/verify_signature.cmake" +) diff --git a/tests/auto/cmake/test_android_signing/main.cpp b/tests/auto/cmake/test_android_signing/main.cpp new file mode 100644 index 00000000000..2112a0de739 --- /dev/null +++ b/tests/auto/cmake/test_android_signing/main.cpp @@ -0,0 +1,4 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +int main(int, char *[]) { return 0; } diff --git a/tests/auto/cmake/test_android_signing/verify_signature.cmake b/tests/auto/cmake/test_android_signing/verify_signature.cmake new file mode 100644 index 00000000000..d4405a30201 --- /dev/null +++ b/tests/auto/cmake/test_android_signing/verify_signature.cmake @@ -0,0 +1,25 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT KEYTOOL_PATH) + message(FATAL_ERROR "KEYTOOL_PATH is missing.") +endif() + +if(NOT APK_FILE) + message(FATAL_ERROR "APK_FILE is missing.") +endif() + +execute_process( + COMMAND ${KEYTOOL_PATH} -printcert -jarfile ${APK_FILE} + RESULT_VARIABLE keytool_result + OUTPUT_VARIABLE keytool_output + ERROR_VARIABLE keytool_error +) + +if(NOT keytool_result EQUAL 0) + message(FATAL_ERROR "Keytool command failed with error: ${keytool_error}") +endif() + +if(NOT keytool_output MATCHES ".+Certificate #1:.+") + message(FATAL_ERROR "APK is not signed.") +endif() diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 781f64a31ab..66c3db2e2f0 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -228,6 +228,7 @@ private slots: void getCharFF(); void remove_and_exists(); void removeOpenFile(); + void removedFileDoesntExist(); void fullDisk(); void writeLargeDataBlock_data(); void writeLargeDataBlock(); @@ -2526,6 +2527,37 @@ void tst_QFile::removeOpenFile() } } +void tst_QFile::removedFileDoesntExist() +{ +#ifdef Q_OS_WIN + QSKIP("Not relevant for Windows - can't remove still-open files"); +#endif + QFile::remove("remove_unclosed.txt"); + QFile f("remove_unclosed.txt"); + QVERIFY(!f.exists()); + bool opened = f.open(QIODevice::ReadWrite | QIODevice::Unbuffered); + QVERIFY(opened); + f.write("blah blah blah"); + + QVERIFY(f.exists()); + + // delete by path, not using f.remove() (that's tested above) + QVERIFY(QFile::remove(f.fileName())); + QVERIFY(!QFile::exists(f.fileName())); + QVERIFY(!f.exists()); + +#ifdef Q_OS_LINUX + QString procPath = u"/proc/self/fd/"_s; + if (QFile::exists(procPath)) { + // reopen the deleted file + procPath += QString::number(f.handle()); + QFile f2(procPath); + QVERIFY(f2.open(QIODevice::ReadOnly)); + QVERIFY(!f2.exists()); + } +#endif +} + void tst_QFile::fullDisk() { QFile file("/dev/full"); diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index 761f0552eec..e2190477441 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -141,6 +141,7 @@ private slots: void exists_data(); void exists(); + void deletedFileLinuxProcExists(); void absolutePath_data(); void absolutePath(); @@ -615,6 +616,34 @@ void tst_QFileInfo::exists() QVERIFY(!exists); } +void tst_QFileInfo::deletedFileLinuxProcExists() +{ +#ifdef Q_OS_LINUX + static const char msg[] = "Hello, World\n"; + QFileInfo fi("/proc/self/fd/"); + if (!fi.isDir()) + QSKIP("/proc appears not to be mounted"); + + QFile f("removed_file.txt"); + QVERIFY(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered)); + f.write(msg, strlen(msg)); + + fi.setFile(fi.filePath() + QString::number(f.handle())); + QVERIFY(fi.exists()); + QCOMPARE(fi.size(), strlen(msg)); + + QFile::remove("removed_file.txt"); + fi.refresh(); + QVERIFY(fi.exists()); + + fi.refresh(); + QCOMPARE(fi.size(), strlen(msg)); // this stats, so may change flags + QVERIFY(fi.exists()); +#else + QSKIP("Linux-only test"); +#endif +} + void tst_QFileInfo::absolutePath_data() { QTest::addColumn<QString>("file"); diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 282c7f2e360..71389abc976 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -513,6 +513,7 @@ void tst_QUrl::comparison2() QCOMPARE(url1.toString(), url2.toString()); QCOMPARE(url1, url2); QCOMPARE(qHash(url1), qHash(url2)); + QCOMPARE(qHash(url1, 1), qHash(url2, 1)); } else if (ordering < 0) { QCOMPARE_LT(url1.toString(), url2.toString()); QCOMPARE_NE(url1, url2); @@ -1387,12 +1388,14 @@ void tst_QUrl::toString_constructed() QUrl parsed(asString); QCOMPARE(url, parsed); QCOMPARE(qHash(url), qHash(parsed)); + QCOMPARE(qHash(url, 1), qHash(parsed, 1)); } // clear it and ensure no memory of the previous state remains url.setUrl(QString()); QCOMPARE(url, QUrl()); QCOMPARE(qHash(url), qHash(QUrl())); + QCOMPARE(qHash(url, 1), qHash(QUrl(), 1)); } void tst_QUrl::toDisplayString_PreferLocalFile_data() @@ -4265,6 +4268,7 @@ void tst_QUrl::setComponents() QUrl recreated(toString); QCOMPARE(copy, recreated); QCOMPARE(qHash(copy), qHash(recreated)); + QCOMPARE(qHash(copy, 1), qHash(recreated, 1)); } else { QVERIFY(copy.toString().isEmpty()); } diff --git a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp index b1e2e8164af..a5f23014b94 100644 --- a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp +++ b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp @@ -708,6 +708,24 @@ void tst_QMetaObjectBuilder::property() prop2.setEnumOrFlag(false); \ prop2.setConstant(false); \ prop2.setFinal(false); \ + prop2.setBindable(false); \ + prop2.setRequired(false); \ + } while (0) +#define SET_ALL_FLAGS() \ + do { \ + prop2.setReadable(true); \ + prop2.setWritable(true); \ + prop2.setResettable(true); \ + prop2.setDesignable(true); \ + prop2.setScriptable(true); \ + prop2.setStored(true); \ + prop2.setUser(true); \ + prop2.setStdCppSet(true); \ + prop2.setEnumOrFlag(true); \ + prop2.setConstant(true); \ + prop2.setFinal(true); \ + prop2.setBindable(true); \ + prop2.setRequired(true); \ } while (0) #define COUNT_FLAGS() \ ((prop2.isReadable() ? 1 : 0) + \ @@ -720,15 +738,19 @@ void tst_QMetaObjectBuilder::property() (prop2.hasStdCppSet() ? 1 : 0) + \ (prop2.isEnumOrFlag() ? 1 : 0) + \ (prop2.isConstant() ? 1 : 0) + \ - (prop2.isFinal() ? 1 : 0)) + (prop2.isFinal() ? 1 : 0) + \ + (prop2.isBindable() ? 1 : 0) + \ + (prop2.isRequired() ? 1 : 0)) #define CHECK_FLAG(setFunc,isFunc) \ do { \ + ++flagCounter; \ CLEAR_FLAGS(); \ QCOMPARE(COUNT_FLAGS(), 0); \ prop2.setFunc(true); \ QVERIFY(prop2.isFunc()); \ QCOMPARE(COUNT_FLAGS(), 1); \ } while (0) + int flagCounter = 0; CHECK_FLAG(setReadable, isReadable); CHECK_FLAG(setWritable, isWritable); CHECK_FLAG(setResettable, isResettable); @@ -739,7 +761,11 @@ void tst_QMetaObjectBuilder::property() CHECK_FLAG(setStdCppSet, hasStdCppSet); CHECK_FLAG(setEnumOrFlag, isEnumOrFlag); CHECK_FLAG(setConstant, isConstant); + CHECK_FLAG(setBindable, isBindable); CHECK_FLAG(setFinal, isFinal); + CHECK_FLAG(setRequired, isRequired); + SET_ALL_FLAGS(); + QCOMPARE(COUNT_FLAGS(), flagCounter); // Check that nothing else changed. QVERIFY(checkForSideEffects(builder, QMetaObjectBuilder::Properties)); diff --git a/tests/auto/corelib/platform/CMakeLists.txt b/tests/auto/corelib/platform/CMakeLists.txt index 3a66ec2eae6..9810947e3c6 100644 --- a/tests/auto/corelib/platform/CMakeLists.txt +++ b/tests/auto/corelib/platform/CMakeLists.txt @@ -5,7 +5,9 @@ if(ANDROID) add_subdirectory(android) add_subdirectory(android_appless) add_subdirectory(androiditemmodel) - add_subdirectory(android_legacy_packaging) + if(NOT QT_USE_ANDROID_MODERN_BUNDLE) + add_subdirectory(android_legacy_packaging) + endif() endif() if(WIN32) add_subdirectory(windows) diff --git a/tests/auto/corelib/platform/android_legacy_packaging/testdata/build.gradle b/tests/auto/corelib/platform/android_legacy_packaging/testdata/build.gradle index cbdd833e2ae..1d796d9b6b9 100644 --- a/tests/auto/corelib/platform/android_legacy_packaging/testdata/build.gradle +++ b/tests/auto/corelib/platform/android_legacy_packaging/testdata/build.gradle @@ -70,8 +70,8 @@ android { abortOnError = false } - // Do not compress Qt binary resources file aaptOptions { + // Do not compress Qt binary resources file noCompress 'rcc' } diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index 3756937c94a..bfc6074689b 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -2470,7 +2470,8 @@ void tst_QLocale::formatTimeZone() // Time definitely in Standard Time const QStringList knownCETus = { u"GMT+1"_s, // ICU - u"CET"_s // Standard abbreviation + u"CET"_s, // Standard abbreviation + u"UTC+0100"_s, // used by Emscripten }; const QString cet = enUS.toString(QDate(2013, 1, 1).startOfDay(), u"t"); QVERIFY2(knownCETus.contains(cet), cet.isEmpty() ? "[empty]" : qPrintable(cet)); @@ -2478,7 +2479,8 @@ void tst_QLocale::formatTimeZone() // Time definitely in Daylight Time const QStringList knownCESTus = { u"GMT+2"_s, // ICU - u"CEST"_s // Standard abbreviation + u"CEST"_s, // Standard abbreviation + u"UTC+0200"_s, // used by Emscripten }; const QString cest = enUS.toString(QDate(2013, 6, 1).startOfDay(), u"t"); QVERIFY2(knownCESTus.contains(cest), cest.isEmpty() ? "[empty]" : qPrintable(cest)); diff --git a/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp index 89bd1d7ff61..b53362b43e9 100644 --- a/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp +++ b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp @@ -5,6 +5,10 @@ #include <QTest> +#include <chrono> + +using namespace std::chrono_literals; + class tst_QAtomicScopedValueRollback : public QObject { Q_OBJECT @@ -15,6 +19,7 @@ private Q_SLOTS: void rollbackToPreviousCommit(); void exceptions(); void earlyExitScope(); + void mixedTypes(); private: void earlyExitScope_helper(int exitpoint, std::atomic<int> &member); }; @@ -132,6 +137,90 @@ void tst_QAtomicScopedValueRollback::earlyExitScope() } } +template <typename T> +struct Wrap { +#if __cpp_deduction_guides < 201907L // no CTAD for aggregates + Q_IMPLICIT Wrap(T &t) : t{t} {} + Q_IMPLICIT Wrap(T &&t) : t{std::move(t)} {} +#endif + T t; + Q_IMPLICIT operator T() const { return t; } +}; + +void tst_QAtomicScopedValueRollback::mixedTypes() +{ + const auto relaxed = std::memory_order_relaxed; + { + std::atomic a{10'000ms}; + { + QAtomicScopedValueRollback rb(a, 5s); + QCOMPARE(a.load(relaxed), 5s); + } + QCOMPARE(a.load(relaxed), 10s); + { + QAtomicScopedValueRollback rb(a, 5s, relaxed); + QCOMPARE(a.load(relaxed), 5s); + } + QCOMPARE(a.load(relaxed), 10s); + } + { + QBasicAtomicInteger a{10'000}; + { + QAtomicScopedValueRollback rb(a, Wrap{5'000}); + QCOMPARE(a.loadRelaxed(), 5'000); + } + QCOMPARE(a.loadRelaxed(), 10'000); + { + QAtomicScopedValueRollback rb(a, Wrap{5'000}, relaxed); + QCOMPARE(a.loadRelaxed(), 5'000); + } + QCOMPARE(a.loadRelaxed(), 10'000); + } + { + QAtomicInteger a{10'000}; + { + QAtomicScopedValueRollback rb(a, Wrap{5'000}); + QCOMPARE(a.loadRelaxed(), 5'000); + } + QCOMPARE(a.loadRelaxed(), 10'000); + { + QAtomicScopedValueRollback rb(a, Wrap{5'000}, relaxed); + QCOMPARE(a.loadRelaxed(), 5'000); + } + QCOMPARE(a.loadRelaxed(), 10'000); + } + { + int i = 10'000; + int j = 5'000; + QBasicAtomicPointer a{&i}; + { + QAtomicScopedValueRollback rb(a, Wrap{&j}); + QCOMPARE(a.loadRelaxed(), &j); + } + QCOMPARE(a.loadRelaxed(), &i); + { + QAtomicScopedValueRollback rb(a, Wrap{&j}, relaxed); + QCOMPARE(a.loadRelaxed(), &j); + } + QCOMPARE(a.loadRelaxed(), &i); + } + { + int i = 10'000; + int j = 5'000; + QAtomicPointer a{&i}; + { + QAtomicScopedValueRollback rb(a, Wrap{&j}); + QCOMPARE(a.loadRelaxed(), &j); + } + QCOMPARE(a.loadRelaxed(), &i); + { + QAtomicScopedValueRollback rb(a, Wrap{&j}, relaxed); + QCOMPARE(a.loadRelaxed(), &j); + } + QCOMPARE(a.loadRelaxed(), &i); + } +} + static void operator*=(std::atomic<int> &lhs, int rhs) { int expected = lhs.load(); diff --git a/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp b/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp index 36cf8ac8fd8..18fe43fbcea 100644 --- a/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp +++ b/tests/auto/gui/image/qicoimageformat/tst_qicoimageformat.cpp @@ -35,6 +35,8 @@ private slots: void write(); void icoMask_data(); void icoMask(); + void origBitDepth_data(); + void origBitDepth(); private: QString m_IconPath; @@ -354,6 +356,40 @@ void tst_QIcoImageFormat::icoMask() QCOMPARE(inImage, outImage); } +void tst_QIcoImageFormat::origBitDepth_data() +{ + QTest::addColumn<QString>("file"); + QTest::addColumn<QList<int>>("origBitDepths"); + + QTest::newRow("35FLOPPY") << "35FLOPPY.ICO" << QList<int>{4}; + QTest::newRow("abcardWindow") << "abcardWindow.ico" << QList<int>{8}; + QTest::newRow("AddPerf") << "AddPerfMon.ico" << QList<int>{4}; + QTest::newRow("App") << "App.ico" << QList<int>{4}; + QTest::newRow("Obj_N2_Internal_Mem") << "Obj_N2_Internal_Mem.ico" << QList<int>{4, 8, 32}; + QTest::newRow("Qt") << "Qt.ico" << QList<int>{32}; + QTest::newRow("semitransparent") << "semitransparent.ico" << QList<int>{4}; + QTest::newRow("Status_Play") << "Status_Play.ico" << QList<int>{4, 8, 32}; + QTest::newRow("TIMER01") << "TIMER01.ICO" << QList<int>{4}; + QTest::newRow("trolltechlogo_tiny") << "trolltechlogo_tiny.ico" << QList<int>{8}; + QTest::newRow("WORLD") << "WORLD.ico" << QList<int>{8, 4, 4}; + QTest::newRow("yellow") << "yellow.cur" << QList<int>{32}; +} + +void tst_QIcoImageFormat::origBitDepth() +{ + QFETCH(QString, file); + QFETCH(QList<int>, origBitDepths); + + QImage image; + QImageReader reader(m_IconPath + QLatin1String("/valid/") + file); + + for (int depth : origBitDepths) { + reader.read(&image); + QCOMPARE(image.text("_q_icoOrigDepth").toInt(), depth); + reader.jumpToNextImage(); + } +} + QTEST_MAIN(tst_QIcoImageFormat) #include "tst_qicoimageformat.moc" diff --git a/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp b/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp index 6019261f792..06a1ffb2960 100644 --- a/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp +++ b/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp @@ -26,9 +26,6 @@ private slots: void tst_QOpenGLWindow::initTestCase() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) QSKIP("OpenGL is not supported on this platform."); } diff --git a/tests/auto/gui/kernel/qwindow/BLACKLIST b/tests/auto/gui/kernel/qwindow/BLACKLIST index a92d89c6123..4f994fb3059 100644 --- a/tests/auto/gui/kernel/qwindow/BLACKLIST +++ b/tests/auto/gui/kernel/qwindow/BLACKLIST @@ -25,6 +25,5 @@ android windows-10 windows-11 android -rhel [windowExposedAfterReparent] xcb ubuntu-24.04 # QTBUG-129023 diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index 1c8dd14eba4..66bb0fa84d8 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -11,6 +11,7 @@ #include <QtGui/QPainter> #include <QTest> +#include <QtTest/private/qtesthelpers_p.h> #include <QSignalSpy> #include <QEvent> #include <QStyleHints> @@ -1237,6 +1238,7 @@ void tst_QWindow::testInputEvents() window.setGeometry(QRect(m_availableTopLeft + QPoint(80, 80), m_testWindowSize)); window.showNormal(); QTRY_VERIFY(window.isActive()); + QVERIFY(QTestPrivate::ensurePositionTopLeft(&window)); QTest::keyClick(&window, Qt::Key_A, Qt::NoModifier); QCoreApplication::processEvents(); diff --git a/tests/auto/gui/qopengl/tst_qopengl.cpp b/tests/auto/gui/qopengl/tst_qopengl.cpp index b9b4c3aa11c..36ff1d1f2e9 100644 --- a/tests/auto/gui/qopengl/tst_qopengl.cpp +++ b/tests/auto/gui/qopengl/tst_qopengl.cpp @@ -185,9 +185,6 @@ static QSurface *createSurface(int surfaceClass) void tst_QOpenGL::initTestCase() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) QSKIP("OpenGL is not supported on this platform."); } diff --git a/tests/auto/gui/qopenglconfig/tst_qopenglconfig.cpp b/tests/auto/gui/qopenglconfig/tst_qopenglconfig.cpp index f597dac53d7..423f9419daf 100644 --- a/tests/auto/gui/qopenglconfig/tst_qopenglconfig.cpp +++ b/tests/auto/gui/qopenglconfig/tst_qopenglconfig.cpp @@ -139,9 +139,6 @@ static void dumpConfiguration(QTextStream &str) void tst_QOpenGlConfig::initTestCase() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) QSKIP("OpenGL is not supported on this platform."); } diff --git a/tests/auto/gui/text/qfont/BLACKLIST b/tests/auto/gui/text/qfont/BLACKLIST index 2d2440255ae..3f63b678bb5 100644 --- a/tests/auto/gui/text/qfont/BLACKLIST +++ b/tests/auto/gui/text/qfont/BLACKLIST @@ -1,12 +1,9 @@ [defaultFamily:cursive] centos b2qt -rhel [defaultFamily:fantasy] centos b2qt -rhel - # QTBUG-130738 [familyNameWithCommaQuote:weird] vxworks diff --git a/tests/auto/network/kernel/qnetworkinterface/BLACKLIST b/tests/auto/network/kernel/qnetworkinterface/BLACKLIST deleted file mode 100644 index e9f4678e8ef..00000000000 --- a/tests/auto/network/kernel/qnetworkinterface/BLACKLIST +++ /dev/null @@ -1,5 +0,0 @@ -# QTBUG-130070 -[localAddress:enet0-fe80::8f41:f072:e573:3caa%enet0] -vxworks -[localAddress:localhost-ipv6] -vxworks diff --git a/tests/auto/other/android/CMakeLists.txt b/tests/auto/other/android/CMakeLists.txt index bcbf5b657d7..0eb2d00b46a 100644 --- a/tests/auto/other/android/CMakeLists.txt +++ b/tests/auto/other/android/CMakeLists.txt @@ -2,3 +2,11 @@ # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(deployment_settings) +add_subdirectory(permissions) + +if(QT_USE_TARGET_ANDROID_BUILD_DIR) + add_subdirectory(package_source_dir) + if(QT_USE_ANDROID_MODERN_BUNDLE) + add_subdirectory(dynamic_feature) + endif() +endif() diff --git a/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp b/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp index 1687ed9de92..571570e370f 100644 --- a/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp +++ b/tests/auto/other/android/deployment_settings/tst_android_deployment_settings.cpp @@ -83,10 +83,11 @@ void tst_android_deployment_settings::DeploymentSettings_data() << "org.qtproject.android_deployment_settings_test"; QTest::newRow("android-app-name") << "android-app-name" << "Android Deployment Settings Test"; - QTest::newRow("permissions") << "permissions" - << "[{\"name\":\"PERMISSION_WITH_ATTRIBUTES\"," - "\"extras\":\"android:minSdkVersion='32' android:maxSdkVersion='34' \"}," - "{\"name\":\"PERMISSION_WITHOUT_ATTRIBUTES\"}]"; + QTest::newRow("permissions") + << "permissions" + << "[{\"maxSdkVersion\":\"34\",\"minSdkVersion\":\"32\",\"name\":\"PERMISSION_WITH_" + "ATTRIBUTES\"},{\"name\":\"PERMISSION_WITHOUT_ATTRIBUTES\"},{\"name\":\"android." + "permission.INTERNET\"},{\"name\":\"android.permission.WRITE_EXTERNAL_STORAGE\"}]"; } void tst_android_deployment_settings::DeploymentSettings() diff --git a/tests/auto/other/android/dynamic_feature/CMakeLists.txt b/tests/auto/other/android/dynamic_feature/CMakeLists.txt new file mode 100644 index 00000000000..b12c2af4dc7 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_android_dynamic_feature LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +add_subdirectory(feature) + +qt_internal_add_test(tst_android_dynamic_feature + SOURCES + tst_android_dynamic_feature.cpp + storeloader/storeloader.h + storeloader/storeloader.cpp + INCLUDE_DIRECTORIES + storeloader + LIBRARIES + Qt6::Test + Qt6::Gui + Qt6::CorePrivate +) + +set_property(TARGET tst_android_dynamic_feature PROPERTY + QT_ANDROID_PACKAGE_NAME "org.qtproject.example.android_dynamic_feature") +set_property(TARGET tst_android_dynamic_feature APPEND PROPERTY + _qt_android_gradle_java_source_dirs "${CMAKE_CURRENT_SOURCE_DIR}/storeloader/java") + +qt6_add_android_dynamic_features(tst_android_dynamic_feature FEATURE_TARGETS + tst_android_dynamic_feature_resources) diff --git a/tests/auto/other/android/dynamic_feature/feature/CMakeLists.txt b/tests/auto/other/android/dynamic_feature/feature/CMakeLists.txt new file mode 100644 index 00000000000..bfe29cc0f5e --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/feature/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) + +qt6_add_library(tst_android_dynamic_feature_resources SHARED) +qt6_add_resources(tst_android_dynamic_feature_resources "dynamic_resources" + PREFIX "/dynamic_resources" + FILES "qtlogo.png") + +target_link_libraries(tst_android_dynamic_feature_resources PRIVATE Qt6::Core) diff --git a/tests/auto/other/android/dynamic_feature/feature/qtlogo.png b/tests/auto/other/android/dynamic_feature/feature/qtlogo.png Binary files differnew file mode 100644 index 00000000000..b63f1384b11 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/feature/qtlogo.png diff --git a/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoader.java b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoader.java new file mode 100644 index 00000000000..5a72351c802 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoader.java @@ -0,0 +1,175 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +package org.qtproject.example.android_dynamic_feature; + +import com.google.android.play.core.splitcompat.SplitCompat; +import com.google.android.play.core.splitinstall.SplitInstallManager; +import com.google.android.play.core.splitinstall.SplitInstallManagerFactory; +import com.google.android.play.core.splitinstall.SplitInstallRequest; +import com.google.android.play.core.splitinstall.SplitInstallHelper; +import com.google.android.play.core.splitinstall.testing.FakeSplitInstallManagerFactory; +import android.content.pm.PackageManager.NameNotFoundException; + +import java.util.Arrays; +import java.util.HashMap; +import java.io.File; +import android.util.Log; +import android.content.Context; +import android.os.Build; + +/** + * Android implementation of store loader. + */ +public class StoreLoader implements StoreLoaderListenerCallback +{ + private static final String TAG = "StoreLoader"; + private final SplitInstallManager m_splitInstallManager; + private HashMap<String, StoreLoaderListener> m_listeners = + new HashMap(); + private Context m_context; + private String m_moduleName; + + public static native void stateChangedNative(String callId, int state); + public static native void errorOccurredNative(String callId, int errorCode, + String errorMessage); + public static native void userConfirmationRequestedNative(String callId, int errorCode, + String errorMessage); + public static native void downloadProgressChangedNative(String callId, long bytes, long total); + public static native void finishedNative(String callId); + + public StoreLoader(Context context) { + m_context = context; + + SplitCompat.install(context); + + m_splitInstallManager = SplitInstallManagerFactory.create(context); + if (m_splitInstallManager == null) + Log.e(TAG,"Constructor: Did not get splitInstallManager"); + } + + /** + * Loads Feature Delivery module from a play store. + */ + public void installModuleFromStore(String moduleName, String callId) { + Log.d(TAG, "installModuleFromStore: " + moduleName + " " + callId); + m_moduleName = moduleName; + + registerListener(callId); + + SplitInstallRequest request = + SplitInstallRequest.newBuilder().addModule(m_moduleName).build(); + if (request == null) + Log.e(TAG, "Null request"); + + if (m_splitInstallManager == null) + Log.e(TAG, "Null m_splitInstallManager"); + + m_splitInstallManager.startInstall(request) + .addOnSuccessListener(sessionId -> { + Log.d(TAG, "Install start call succesfull"); + StoreLoaderListener listener = m_listeners.get(callId); + if (listener != null) { + if (!listener.isCancelled()) + listener.setSessionId(sessionId); + else + m_splitInstallManager.cancelInstall(sessionId); + } else { + Log.d(TAG, "Listener for callId '" + callId + "' is not found"); + } + }); + } + + public void onStateChanged(String callId, int state) { + stateChangedNative(callId, state); + if (state == StoreLoaderListenerCallback.LOADED || + state == StoreLoaderListenerCallback.CANCELED) { + finished(callId); + } + } + + public void onErrorOccurred(String callId, int errorCode, String errorMessage) { + onStateChanged(callId, ERROR); + + Log.e(TAG, "Error occurred " + errorCode + " " + errorMessage); + errorOccurredNative(callId, errorCode, errorMessage); + finished(callId); + } + + public void onUserConfirmationRequested(String callId, int errorCode, String errorMessage) { + Log.d(TAG, "Requires user confirmation " + errorCode); + userConfirmationRequestedNative(callId, errorCode, errorMessage); + } + + public void onDownloadProgressChanged(String callId, long bytes, long total) { + Log.d(TAG, "Downloading " + bytes + "/" + total); + downloadProgressChangedNative(callId, bytes, total); + } + + public void onLoadLibrary(String callId) { + Log.d(TAG, "Load library for the module " + m_moduleName); + + stateChangedNative(callId, StoreLoaderListenerCallback.LOADING); + // update context. + try { + m_context = m_context.createPackageContext(m_context.getPackageName(), 0); + } catch (NameNotFoundException ignored) { + Log.e(TAG, "Could not get package name"); + } + // install splitcompat for new context. + SplitCompat.install(m_context); + // try to load new library + boolean isLoaded = false; + for (String abi : Build.SUPPORTED_ABIS) { + String fullLibraryName = m_moduleName + "_" + abi; + try { + System.loadLibrary(fullLibraryName); + isLoaded = true; + break; + } catch (Exception e) { + Log.d(TAG, "Exception occurred when loading the library " + fullLibraryName + ":" + + e.getClass().getCanonicalName()); + } + } + + if (isLoaded) + onStateChanged(callId, StoreLoaderListenerCallback.LOADED); + else + onErrorOccurred(callId, -1, "Error loading library. Check logcat for details."); + + } + + public void cancelInstall(String callId) { + StoreLoaderListener listener = m_listeners.get(callId); + if (listener == null) { + Log.e(TAG, "The listener for callId " + callId + " is not found"); + return; + } + + if (listener.getSessionId() < 0) + listener.postponeCancel(); + else + m_splitInstallManager.cancelInstall(listener.getSessionId()); + } + + private void finished(String callId) { + unregisterListener(callId); + finishedNative(callId); + } + + private void registerListener(String callId) { + Log.d(TAG, "registerListener"); + StoreLoaderListener listener = new StoreLoaderListener(this, callId); + if (listener != null) { + m_listeners.put(callId, listener); + m_splitInstallManager.registerListener(listener); + } + } + + private void unregisterListener(String callId) { + Log.d(TAG, "unregisterListener"); + StoreLoaderListener listener = m_listeners.remove(callId); + if (listener != null) + m_splitInstallManager.unregisterListener(listener); + } +} diff --git a/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListener.java b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListener.java new file mode 100644 index 00000000000..3e83a0fa7f9 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListener.java @@ -0,0 +1,92 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +package org.qtproject.example.android_dynamic_feature; + +import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener; +import com.google.android.play.core.splitinstall.SplitInstallSessionState; +import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; + +import android.util.Log; + +public class StoreLoaderListener implements SplitInstallStateUpdatedListener { + + private final StoreLoaderListenerCallback m_callback; + private final String m_callId; + private int m_installSessionId = -1; + private static final String TAG = "StoreLoaderListener"; + private int m_currentStatus = SplitInstallSessionStatus.UNKNOWN; + private boolean m_postponeCancel = false; + + public StoreLoaderListener(StoreLoaderListenerCallback callback, String callId) { + m_callback = callback; + m_callId = callId; + } + + public void setSessionId(int sessionId) { m_installSessionId = sessionId; } + public int getSessionId() { return m_installSessionId; } + + public void postponeCancel() { m_postponeCancel = true; } + public boolean isCancelled() { return m_postponeCancel; } + + @Override + public void onStateUpdate(SplitInstallSessionState state) { + Log.d(TAG, + "onStateUpdate, status: " + state.status() + " session id: " + state.sessionId()); + if (state.sessionId() != m_installSessionId) { + // Not mine + return; + } + + switch (state.status()) { + case SplitInstallSessionStatus.DOWNLOADING: + Log.d(TAG, + "SplitInstallSessionState: DOWNLOADING " + state.bytesDownloaded() + "/" + + state.totalBytesToDownload()); + if (m_currentStatus != SplitInstallSessionStatus.DOWNLOADING) + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.DOWNLOADING); + + m_callback.onDownloadProgressChanged(m_callId, state.bytesDownloaded(), + state.totalBytesToDownload()); + break; + case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: + Log.d(TAG, "SplitInstallSessionState: REQUIRES_USER_CONFIRMATION"); + m_callback.onStateChanged(m_callId, + StoreLoaderListenerCallback.REQUIRES_USER_CONFIRMATION); + + m_callback.onUserConfirmationRequested(m_callId, state.errorCode(), "" /*noop for now*/); + break; + case SplitInstallSessionStatus.INSTALLED: + Log.d(TAG, "SplitInstallSessionState: INSTALLED"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.INSTALLED); + m_callback.onLoadLibrary(m_callId); + break; + case SplitInstallSessionStatus.INSTALLING: + Log.d(TAG, "SplitInstallSessionState: INSTALLING"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.INSTALLING); + break; + case SplitInstallSessionStatus.FAILED: + Log.d(TAG, + "SplitInstallSessionState: FAILED with error code: " + state.errorCode()); + m_callback.onErrorOccurred(m_callId, state.errorCode(), "" /*noop for now*/); + break; + case SplitInstallSessionStatus.PENDING: + Log.d(TAG, "SplitInstallSessionState: PENDING"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.PENDING); + break; + case SplitInstallSessionStatus.DOWNLOADED: + Log.d(TAG, "SplitInstallSessionState: DOWNLOADED"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.DOWNLOADED); + break; + case SplitInstallSessionStatus.CANCELED: + Log.d(TAG, "SplitInstallSessionState: CANCELED"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.CANCELED); + break; + case SplitInstallSessionStatus.CANCELING: + Log.d(TAG, "SplitInstallSessionState: CANCELING"); + m_callback.onStateChanged(m_callId, StoreLoaderListenerCallback.CANCELING); + break; + } + m_currentStatus = state.status(); + } +} diff --git a/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListenerCallback.java b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListenerCallback.java new file mode 100644 index 00000000000..99c7327e328 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/storeloader/java/src/main/java/org/qtproject/example/android_dynamic_feature/StoreLoaderListenerCallback.java @@ -0,0 +1,26 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +package org.qtproject.example.android_dynamic_feature; + +public interface StoreLoaderListenerCallback { + public static final int UNKNOWN = 0; // Unused but native part has and uses one. + public static final int INITIALIZED = 1; + public static final int PENDING = 2; + public static final int DOWNLOADING = 3; + public static final int DOWNLOADED = 4; + public static final int REQUIRES_USER_CONFIRMATION = 5; + public static final int CANCELING = 6; + public static final int CANCELED = 7; + public static final int INSTALLING = 8; + public static final int INSTALLED = 9; + public static final int LOADING = 10; + public static final int LOADED = 11; + public static final int ERROR = 12; + + public void onStateChanged(String callId, int state); + public void onErrorOccurred(String callId, int errorCode, String errorMessage); + public void onUserConfirmationRequested(String callId, int errorCode, String errorMessage); + public void onDownloadProgressChanged(String callId, long bytes, long total); + public void onLoadLibrary(String callId); +} diff --git a/tests/auto/other/android/dynamic_feature/storeloader/storeloader.cpp b/tests/auto/other/android/dynamic_feature/storeloader/storeloader.cpp new file mode 100644 index 00000000000..7abce59b79e --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/storeloader/storeloader.cpp @@ -0,0 +1,227 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "storeloader.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qhash.h> +#include <QtCore/qjniobject.h> +#include <QtCore/qlogging.h> +#include <QtCore/qmutex.h> +#include <QtCore/quuid.h> + +#include <jni.h> + +Q_DECLARE_JNI_CLASS(StoreLoader, + "org/qtproject/example/android_dynamic_feature/StoreLoader"); + +class StoreLoaderHandlerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(StoreLoaderHandler) +public: + void setState(StoreLoader::State state) + { + if (m_state == state) + return; + Q_Q(StoreLoaderHandler); + m_state = state; + q->stateChanged(m_state); + } + + const QString &callId() const & noexcept { return m_callId; } + +private: + StoreLoader::State m_state = StoreLoader::State::Unknown; + QString m_callId = QUuid::createUuid().toString(); +}; + +namespace QtPrivate { +QString asString(const jstring &s) +{ + return QJniObject(s).toString(); +} +} // namespace QtPrivate + +namespace { + +class StoreLoaderImpl +{ +public: + StoreLoaderImpl(); + bool registerNatives() const; + + void addHandler(StoreLoaderHandler *handler); + StoreLoaderHandler *findHandler(const jstring &callId); + void removeHandler(const jstring &callId); + + QtJniTypes::StoreLoader loader = nullptr; + +private: + QHash<QString, QPointer<StoreLoaderHandler>> m_handlers; + QMutex m_lock; +}; + +Q_GLOBAL_STATIC(StoreLoaderImpl, loaderInstance) + +void stateChangedNative(JNIEnv *, jobject, jstring callId, int state) +{ + qDebug("State changed %s.", qPrintable(callId)); + + if (auto *handler = loaderInstance->findHandler(callId)) + emit handler->stateChanged(static_cast<StoreLoader::State>(state)); +} + +void errorOccurredNative(JNIEnv *, jobject, jstring callId, int errorCode, jstring errorMessage) +{ + qDebug("Error occurred %s %d %s.", qPrintable(callId), errorCode, qPrintable(errorMessage)); + auto *handler = loaderInstance->findHandler(callId); + if (!handler) + return; + + emit handler->errorOccured(errorCode, QJniObject(errorMessage).toString()); +} + +void userConfirmationRequestedNative(JNIEnv *, jobject, jstring callId, int errorCode, + jstring errorMessage) +{ + qDebug("User confirmation requested %s %d %s.", qPrintable(callId), errorCode, + qPrintable(errorMessage)); + auto *handler = loaderInstance->findHandler(callId); + if (!handler) + return; + + emit handler->confirmationRequest(errorCode, QJniObject(errorMessage).toString()); +} + +void downloadProgressChangedNative(JNIEnv *, jobject, jstring callId, long bytes, long total) +{ + qDebug("Download progress changed %ld/%ld.", bytes, total); + auto *handler = loaderInstance->findHandler(callId); + if (!handler) + return; + + emit handler->downloadProgress(bytes, total); +} + +void finishedNative(JNIEnv *, jobject, jstring callId) +{ + auto *handler = loaderInstance->findHandler(callId); + if (!handler) + return; + + emit handler->finished(); +} + +} // namespace + +Q_DECLARE_JNI_NATIVE_METHOD(stateChangedNative) +Q_DECLARE_JNI_NATIVE_METHOD(errorOccurredNative) +Q_DECLARE_JNI_NATIVE_METHOD(userConfirmationRequestedNative) +Q_DECLARE_JNI_NATIVE_METHOD(downloadProgressChangedNative) +Q_DECLARE_JNI_NATIVE_METHOD(finishedNative) + +StoreLoaderImpl::StoreLoaderImpl() +{ + loader = QJniObject::construct<QtJniTypes::StoreLoader, QtJniTypes::Context>( + QNativeInterface::QAndroidApplication::context()); +} + +bool StoreLoaderImpl::registerNatives() const +{ + static bool result = [] { + return QtJniTypes::StoreLoader::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(stateChangedNative), + Q_JNI_NATIVE_METHOD(errorOccurredNative), + Q_JNI_NATIVE_METHOD(userConfirmationRequestedNative), + Q_JNI_NATIVE_METHOD(downloadProgressChangedNative), + Q_JNI_NATIVE_METHOD(finishedNative), + }); + }(); + + if (!result) + qCritical("Unable to register native methods."); + + return result; +} + +void StoreLoaderImpl::addHandler(StoreLoaderHandler *handler) +{ + Q_ASSERT(handler); + + QMutexLocker lock(&m_lock); + const auto &callId = handler->callId(); + Q_ASSERT_X(m_handlers.constFind(callId) != m_handlers.constEnd(), "StoreLoaderImpl::addHandler", + qPrintable(QString("Handler with callId %1 already exists.").arg(callId))); + + m_handlers[callId] = QPointer(handler); +} + + +StoreLoaderHandler *StoreLoaderImpl::findHandler(const jstring &callId) +{ + QMutexLocker lock(&m_lock); + const auto it = m_handlers.constFind(QJniObject(callId).toString()); + if (it == m_handlers.constEnd()) { + qCritical("The handler for the call %s was not found.", qPrintable(callId)); + return nullptr; + } + + if (it.value().isNull()) { + qCritical("The handler for the call %s expired.", qPrintable(callId)); + m_handlers.erase(it); + } + + return it.value().get(); +} + +void StoreLoaderImpl::removeHandler(const jstring &callId) +{ + QMutexLocker lock(&m_lock); + m_handlers.remove(QJniObject(callId).toString()); +} + +std::unique_ptr<StoreLoaderHandler> +StoreLoader::loadModule(const QString &moduleName) +{ + if (moduleName.isEmpty()) + return {}; + + if (!loaderInstance->registerNatives()) + return {}; + + if (!loaderInstance->loader.isValid()) { + qCritical("StoreLoader not constructed"); + return {}; + } + + auto handlerPtr = std::make_unique<StoreLoaderHandler>( + nullptr, StoreLoaderHandler::PrivateConstructor{}); + loaderInstance->addHandler(handlerPtr.get()); + + const auto &callId = handlerPtr->callId(); + qDebug("Loading module %s, callId: %s.", qPrintable(moduleName), qPrintable(callId)); + + loaderInstance->loader.callMethod<void>("installModuleFromStore", moduleName, callId); + return handlerPtr; +} + +StoreLoaderHandler::StoreLoaderHandler(QObject *parent, PrivateConstructor) + : QObject(*new StoreLoaderHandlerPrivate(), parent) +{ +} + +StoreLoaderHandler::~StoreLoaderHandler() = default; + +const QString &StoreLoaderHandler::callId() const & noexcept +{ + Q_D(const StoreLoaderHandler); + return d->callId(); +} + +void StoreLoaderHandler::cancel() +{ + Q_D(StoreLoaderHandler); + loaderInstance->loader.callMethod<void>("cancelInstall", d->callId()); +} + +#include "moc_storeloader.cpp" diff --git a/tests/auto/other/android/dynamic_feature/storeloader/storeloader.h b/tests/auto/other/android/dynamic_feature/storeloader/storeloader.h new file mode 100644 index 00000000000..be4e103207e --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/storeloader/storeloader.h @@ -0,0 +1,65 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef STORELOADER_H +#define STORELOADER_H + +#include <QtCore/qtconfigmacros.h> +#include <QtCore/qobject.h> + +#include <memory> + +class StoreLoaderHandler; + +namespace StoreLoader { + Q_NAMESPACE +enum class State { + Unknown, + Initialized, + Pending, + Downloading, + Downloaded, + RequiresUserConfirmation, + Canceling, + Canceled, + Installing, + Installed, + Loading, + Loaded, + Error, +}; +Q_ENUM_NS(State) + +std::unique_ptr<StoreLoaderHandler> loadModule(const QString &moduleName); +}; // namespace StoreLoader + +class StoreLoaderHandlerPrivate; + +class StoreLoaderHandler : public QObject +{ + Q_OBJECT + QT_DEFINE_TAG_STRUCT(PrivateConstructor); + +public: + explicit StoreLoaderHandler(QObject *parent, PrivateConstructor); + ~StoreLoaderHandler() override; + + const QString &callId() const & noexcept; + + void cancel(); +signals: + void stateChanged(StoreLoader::State state); + void downloadProgress(qsizetype bytes, qsizetype total); + void errorOccured(int errorCode, const QString &errorString); + void confirmationRequest(int errorCode, const QString &errorString); + void finished(); + +private: + Q_DISABLE_COPY_MOVE(StoreLoaderHandler) + Q_DECLARE_PRIVATE(StoreLoaderHandler) + + friend std::unique_ptr<StoreLoaderHandler> + StoreLoader::loadModule(const QString &); +}; + +#endif // STORELOADER_H diff --git a/tests/auto/other/android/dynamic_feature/tst_android_dynamic_feature.cpp b/tests/auto/other/android/dynamic_feature/tst_android_dynamic_feature.cpp new file mode 100644 index 00000000000..739a645c5d5 --- /dev/null +++ b/tests/auto/other/android/dynamic_feature/tst_android_dynamic_feature.cpp @@ -0,0 +1,37 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <storeloader.h> + +#include <QtTest/QTest> +#include <QtTest/QSignalSpy> + +#include <QtCore/QVariant> + +using namespace Qt::Literals::StringLiterals; + +class QtDynamicFeatureTest : public QObject +{ + Q_OBJECT +public: + QtDynamicFeatureTest(){} + +private Q_SLOTS: + void loadResourcesFeature(); +}; + +void QtDynamicFeatureTest::loadResourcesFeature() +{ + QVERIFY(!QFile::exists(":/dynamic_resources/qtlogo.png")); + + auto handler = StoreLoader::loadModule("tst_android_dynamic_feature_resources"_L1); + + QSignalSpy spy(handler.get(), &StoreLoaderHandler::finished); + + QVERIFY(spy.wait(20000)); + QVERIFY(QFile::exists(":/dynamic_resources/qtlogo.png")); +} + +QTEST_MAIN(QtDynamicFeatureTest) + +#include "tst_android_dynamic_feature.moc" diff --git a/tests/auto/other/android/package_source_dir/CMakeLists.txt b/tests/auto/other/android/package_source_dir/CMakeLists.txt new file mode 100644 index 00000000000..360aa50d87d --- /dev/null +++ b/tests/auto/other/android/package_source_dir/CMakeLists.txt @@ -0,0 +1,54 @@ +# 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_android_package_source_dir LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +# Package name is generated and written to AndroidManifest.xml +qt_internal_add_test(tst_package_source_dir_not_defined + SOURCES + tst_android_package_source_dir.cpp + DEFINES + EXPECTED_APP_NAME="tst_package_source_dir_not_defined" +) + +# Package name from user-provided AndroidManifest.xml +qt_internal_add_test(tst_package_source_dir_custom_android_manifest + SOURCES + tst_android_package_source_dir.cpp + DEFINES + EXPECTED_APP_NAME="tst_package_source_dir_custom_android_manifest_my" +) + +if(QT_USE_ANDROID_MODERN_BUNDLE) + set_target_properties(tst_package_source_dir_custom_android_manifest + PROPERTIES + QT_ANDROID_PACKAGE_SOURCE_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/custom_android_manifest_bundle" + ) +else() + set_target_properties(tst_package_source_dir_custom_android_manifest + PROPERTIES + QT_ANDROID_PACKAGE_SOURCE_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/custom_android_manifest" + ) +endif() + +if(QT_USE_ANDROID_MODERN_BUNDLE) + # Partial gradle template is only supported by QT_USE_ANDROID_MODERN_BUNDLE + # + # Package name from user-provided gradle.build.in + qt_internal_add_test(tst_package_source_dir_partial_template + SOURCES + tst_android_package_source_dir.cpp + DEFINES + EXPECTED_APP_NAME="tst_package_source_dir_partial_template_my" + ) + set_target_properties(tst_package_source_dir_partial_template + PROPERTIES + QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/partial_template" + ) +endif() diff --git a/tests/auto/other/android/package_source_dir/custom_android_manifest/AndroidManifest.xml b/tests/auto/other/android/package_source_dir/custom_android_manifest/AndroidManifest.xml new file mode 100644 index 00000000000..4f53295e979 --- /dev/null +++ b/tests/auto/other/android/package_source_dir/custom_android_manifest/AndroidManifest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.qtproject.example.tst_package_source_dir_custom_android_manifest" + android:installLocation="auto" + android:versionCode="1" + android:versionName="1.0"> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <supports-screens + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" /> + <application + android:name="org.qtproject.qt.android.bindings.QtApplication" + android:hardwareAccelerated="true" + android:label="tst_package_source_dir_custom_android_manifest_my" + android:requestLegacyExternalStorage="true" + android:allowBackup="true" + android:fullBackupOnly="false"> + <activity + android:name="org.qtproject.qt.android.bindings.QtActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <meta-data + android:name="android.app.lib_name" + android:value="tst_package_source_dir_custom_android_manifest" /> + + <meta-data + android:name="android.app.arguments" + android:value="" /> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.qtprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/qtprovider_paths"/> + </provider> + </application> +</manifest> diff --git a/tests/auto/other/android/package_source_dir/custom_android_manifest_bundle/app/AndroidManifest.xml b/tests/auto/other/android/package_source_dir/custom_android_manifest_bundle/app/AndroidManifest.xml new file mode 100644 index 00000000000..4f53295e979 --- /dev/null +++ b/tests/auto/other/android/package_source_dir/custom_android_manifest_bundle/app/AndroidManifest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.qtproject.example.tst_package_source_dir_custom_android_manifest" + android:installLocation="auto" + android:versionCode="1" + android:versionName="1.0"> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <supports-screens + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" /> + <application + android:name="org.qtproject.qt.android.bindings.QtApplication" + android:hardwareAccelerated="true" + android:label="tst_package_source_dir_custom_android_manifest_my" + android:requestLegacyExternalStorage="true" + android:allowBackup="true" + android:fullBackupOnly="false"> + <activity + android:name="org.qtproject.qt.android.bindings.QtActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <meta-data + android:name="android.app.lib_name" + android:value="tst_package_source_dir_custom_android_manifest" /> + + <meta-data + android:name="android.app.arguments" + android:value="" /> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.qtprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/qtprovider_paths"/> + </provider> + </application> +</manifest> diff --git a/tests/auto/other/android/package_source_dir/partial_template/app/AndroidManifest.xml b/tests/auto/other/android/package_source_dir/partial_template/app/AndroidManifest.xml new file mode 100644 index 00000000000..838d239e5f4 --- /dev/null +++ b/tests/auto/other/android/package_source_dir/partial_template/app/AndroidManifest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.qtproject.example.tst_package_source_dir_partial_template" + android:installLocation="auto" + android:versionCode="1" + android:versionName="1.0"> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <supports-screens + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true" /> + <application + android:name="org.qtproject.qt.android.bindings.QtApplication" + android:hardwareAccelerated="true" + android:label="${appName}" + android:requestLegacyExternalStorage="true" + android:allowBackup="true" + android:fullBackupOnly="false"> + <activity + android:name="org.qtproject.qt.android.bindings.QtActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <meta-data + android:name="android.app.lib_name" + android:value="tst_package_source_dir_partial_template" /> + + <meta-data + android:name="android.app.arguments" + android:value="" /> + </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.qtprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/qtprovider_paths"/> + </provider> + </application> +</manifest> diff --git a/tests/auto/other/android/package_source_dir/partial_template/app/build.gradle.in b/tests/auto/other/android/package_source_dir/partial_template/app/build.gradle.in new file mode 100644 index 00000000000..538f1ca6217 --- /dev/null +++ b/tests/auto/other/android/package_source_dir/partial_template/app/build.gradle.in @@ -0,0 +1,54 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.6.0' + } +} + +apply plugin: '@GRADLE_PLUGIN_TYPE@' + +dependencies { + @GRADLE_DEPENDENCIES@ +} + +android { + namespace '@PACKAGE_NAME@' + compileSdkVersion '@ANDROID_COMPILE_SDK_VERSION@' + buildToolsVersion '@ANDROID_BUILD_TOOLS_VERSION@' + ndkVersion '@ANDROID_NDK_REVISION@' + + defaultConfig { + @DEFAULT_CONFIG_VALUES@ + manifestPlaceholders = [ appName:"tst_package_source_dir_partial_template_my" ] + } + + sourceSets { + main { +@SOURCE_SETS@ + } + } + + tasks.withType(JavaCompile) { + options.incremental = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } + + aaptOptions { + // Do not compress Qt binary resources file + noCompress 'rcc' + } + + @ANDROID_DEPLOYMENT_EXTRAS@ +} diff --git a/tests/auto/other/android/package_source_dir/tst_android_package_source_dir.cpp b/tests/auto/other/android/package_source_dir/tst_android_package_source_dir.cpp new file mode 100644 index 00000000000..d74d388371f --- /dev/null +++ b/tests/auto/other/android/package_source_dir/tst_android_package_source_dir.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QCoreApplication> +#include <QJniObject> +#include <QTest> +#include <QDebug> + +using namespace QNativeInterface; + +Q_DECLARE_JNI_CLASS(ApplicationInfo, "android/content/pm/ApplicationInfo") +Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager") +Q_DECLARE_JNI_CLASS(CharSequence, "java/lang/CharSequence") + +class tst_android_package_source_dir : public QObject +{ + Q_OBJECT + +private slots: + void applicationName(); + +private: +}; + +void tst_android_package_source_dir::applicationName() +{ + QJniObject appCtx = QAndroidApplication::context(); + QVERIFY(appCtx.isValid()); + + const auto appInfo = appCtx.callMethod<QtJniTypes::ApplicationInfo>("getApplicationInfo"); + QVERIFY(appInfo.isValid()); + + const auto packageManager = appCtx.callMethod<QtJniTypes::PackageManager>("getPackageManager"); + QVERIFY(packageManager.isValid()); + + const auto appNameLabel = + appInfo.callMethod<QtJniTypes::CharSequence>("loadLabel", packageManager); + QVERIFY(appNameLabel.isValid()); + + const auto appName = appNameLabel.callMethod<jstring>("toString").toString(); + + QCOMPARE_EQ(appName, QString::fromLatin1(EXPECTED_APP_NAME)); +} + +QTEST_MAIN(tst_android_package_source_dir); + +#include "tst_android_package_source_dir.moc" diff --git a/tests/auto/other/android/permissions/CMakeLists.txt b/tests/auto/other/android/permissions/CMakeLists.txt new file mode 100644 index 00000000000..0424c674e1b --- /dev/null +++ b/tests/auto/other/android/permissions/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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_android_permissions LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_android_permissions + SOURCES + tst_android_permissions.cpp +) + +qt_add_android_permission(tst_android_permissions + NAME android.permission.ACCESS_COARSE_LOCATION +) + +qt_add_android_permission(tst_android_permissions + NAME android.permission.ACCESS_FINE_LOCATION + ATTRIBUTES + maxSdkVersion 31 +) diff --git a/tests/auto/other/android/permissions/tst_android_permissions.cpp b/tests/auto/other/android/permissions/tst_android_permissions.cpp new file mode 100644 index 00000000000..5d7a255d3ac --- /dev/null +++ b/tests/auto/other/android/permissions/tst_android_permissions.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QCoreApplication> +#include <QJniObject> +#include <QSet> +#include <QString> +#include <QTest> + +constexpr int GET_PERMISSIONS(0x00001000); + +using namespace QNativeInterface; +using namespace Qt::StringLiterals; + +Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager") +Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo") + +class tst_android_permissions : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void checkExpectedDefaults(); + void checkNonExisting(); + void checkNonDefaultPermissions(); + +private: + QJniArray<QString> m_requestedPermissions; +}; + +void tst_android_permissions::initTestCase() +{ + QJniObject appCtx = QAndroidApplication::context(); + QVERIFY(appCtx.isValid()); + + const auto packageName = appCtx.callMethod<QString>("getPackageName"); + const auto packageManager = appCtx.callMethod<QtJniTypes::PackageManager>("getPackageManager"); + QVERIFY(packageManager.isValid()); + + const auto packageInfo = QJniObject(packageManager.callMethod<QtJniTypes::PackageInfo>( + "getPackageInfo", packageName, jint(GET_PERMISSIONS))); + QVERIFY(packageInfo.isValid()); + + m_requestedPermissions = packageInfo.getField<QJniArray<QString>>("requestedPermissions"); + QVERIFY(m_requestedPermissions.isValid()); +} + +void tst_android_permissions::checkExpectedDefaults() +{ + QSet<QString> expectedDefaults{ { "android.permission.INTERNET"_L1 }, + { "android.permission.WRITE_EXTERNAL_STORAGE"_L1 }, + { "android.permission.READ_EXTERNAL_STORAGE"_L1 } }; + + for (const auto &permission : m_requestedPermissions) + expectedDefaults.remove(permission); + + QVERIFY(expectedDefaults.empty()); +} + +void tst_android_permissions::checkNonExisting() +{ + for (const auto &permission : m_requestedPermissions) + QCOMPARE_NE(permission, "android.permission.BLUETOOTH_SCAN"); +} + +void tst_android_permissions::checkNonDefaultPermissions() +{ + bool hasNonDefaultPermissions = false; + for (const auto &permission : m_requestedPermissions) { + if (permission == "android.permission.ACCESS_COARSE_LOCATION") + hasNonDefaultPermissions = true; + } + + QVERIFY(hasNonDefaultPermissions); +} + +QTEST_MAIN(tst_android_permissions); + +#include "tst_android_permissions.moc" diff --git a/tests/auto/testlib/selftests/mouse/tst_mouse.cpp b/tests/auto/testlib/selftests/mouse/tst_mouse.cpp index 4af55adde91..ae8fa54dcea 100644 --- a/tests/auto/testlib/selftests/mouse/tst_mouse.cpp +++ b/tests/auto/testlib/selftests/mouse/tst_mouse.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> +#include <QtTest/private/qtesthelpers_p.h> #include <QtGui/QWindow> #include <QtGui/QCursor> #include <QtGui/private/qguiapplication_p.h> @@ -256,8 +257,9 @@ void tst_Mouse::doubleClick() { MouseWindow w; w.show(); - w.setGeometry(100, 100, 200, 200); + w.resize(200, 200); QVERIFY(QTest::qWaitForWindowActive(&w)); + QVERIFY(QTestPrivate::ensurePositionTopLeft(&w)); // click QPoint point(10, 10); diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index ab88dd78111..217122100a0 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -7,7 +7,7 @@ https://developercommunity.visualstudio.com/t/Regression:-c-compilation-failure-in-c/10926790 */ #include <QtCore/qcompilerdetection.h> -#if defined(Q_CC_MSVC_ONLY) && (_MSC_FULL_VER >= 194435209) && (_MSC_FULL_VER < 194500000) +#if defined(Q_CC_MSVC_ONLY) && (_MSC_FULL_VER >= 194435208) && (_MSC_FULL_VER < 194500000) # define MSVC_ENUM_BUG #endif diff --git a/tests/auto/tools/qmake/testdata/windows_resources/REUSE.toml b/tests/auto/tools/qmake/testdata/windows_resources/REUSE.toml index 8d05566c678..65bfc74258c 100644 --- a/tests/auto/tools/qmake/testdata/windows_resources/REUSE.toml +++ b/tests/auto/tools/qmake/testdata/windows_resources/REUSE.toml @@ -3,5 +3,5 @@ version = 1 [[annotations]] path = ["version.inc"] precedence = "override" -SPDX-FileCopyrightText = "Copyright (C) 2019 The Qt Company Ltd." +SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" diff --git a/tests/auto/tools/uic/baseline/REUSE.toml b/tests/auto/tools/uic/baseline/REUSE.toml index 30f460c00b0..c147a71dca5 100644 --- a/tests/auto/tools/uic/baseline/REUSE.toml +++ b/tests/auto/tools/uic/baseline/REUSE.toml @@ -3,5 +3,5 @@ version = 1 [[annotations]] path = ["Widget.ui"] precedence = "override" -SPDX-FileCopyrightText = "Copyright (C) 2016 The Qt Company Ltd." +SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" diff --git a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp index 3e5ff5ce640..df19ea1568e 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp @@ -657,9 +657,6 @@ void tst_QGraphicsView::viewport() #if QT_CONFIG(opengl) void tst_QGraphicsView::openGLViewport() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) QSKIP("QOpenGL is not supported on this platform."); if (isPlatformEGLFS()) diff --git a/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp b/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp index 03be7aa0fe9..b651668cabd 100644 --- a/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp +++ b/tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp @@ -213,6 +213,7 @@ private slots: void storeRestoreLowMemoryMode(); void setSectionResizeModeWithSectionWillTakeMemory(); void setModelWithAutoSizeWillSwitchToMemoryMode(); + void tableViewResizeSectionsWillSwitchToMemoryMode(); void setDefaultSectionSizeRespectsColumnWidth(); @@ -3559,12 +3560,17 @@ struct TableViewWithBasicModel : public QTableView emptyState = verticalHeader()->saveState(); setModel(&m); header = verticalHeader(); + horizontal_header = horizontalHeader(); } bool hasLowMemoryUsage() const { return emptyState.size() == header->saveState().size(); } + bool horizontalLowMememorUsage() const { + return emptyState.size() == horizontal_header->saveState().size(); + } + bool hasHigherMemoryUsage() const { const int delta = 1000; return header->saveState().size() > delta + emptyState.size(); @@ -3572,6 +3578,7 @@ struct TableViewWithBasicModel : public QTableView BasicModel m; QHeaderView *header; + QHeaderView *horizontal_header; QByteArray emptyState; }; @@ -3770,6 +3777,15 @@ void tst_QHeaderView::setModelWithAutoSizeWillSwitchToMemoryMode() QCOMPARE_GT(nonEmptyState.size(), emptyState.size() + delta); } +void tst_QHeaderView::tableViewResizeSectionsWillSwitchToMemoryMode() +{ + TableViewWithBasicModel tv; + QVERIFY(tv.horizontalLowMememorUsage()); + tv.horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + tv.resizeColumnsToContents(); + QVERIFY(!tv.horizontalLowMememorUsage()); +} + void tst_QHeaderView::setDefaultSectionSizeRespectsColumnWidth() { QTreeWidget tree; diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp index c460eb1db8c..f418d5ead14 100644 --- a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp +++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp @@ -655,121 +655,122 @@ void tst_QWidgetRepaintManager::evaluateRhi() #if QT_CONFIG(opengl) -#if QT_CONFIG(run_opengl_tests) - { - // Non-native child RHI widget enables RHI for top level regular widget - QWidget topLevel; - RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); - // Only the native widget that actually flushes will report usesRhiFlush - QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); - // But it should have an RHI it can use - QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); - } - - { - // Native child RHI widget does not enable RHI for top level - QWidget topLevel; - RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); - nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); - QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); - QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); - - if (!usesRhiBackingStore) - QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); - } - - { - // Non-native RHI child of native child enables RHI for native child, - // but not top level. - QWidget topLevel; - QWidget nativeChild(&topLevel); - nativeChild.setAttribute(Qt::WA_NativeWindow); - RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - - QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); - QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); - QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); - QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); - if (!usesRhiBackingStore) - QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); - } - - { - // Native child RHI widget does not prevent RHI for top level - // if non-native RHI child widget is also present. - QWidget topLevel; - RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); - RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); - nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) { - QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); - QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); - QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); - } + { + // Non-native child RHI widget enables RHI for top level regular widget + QWidget topLevel; + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + // Only the native widget that actually flushes will report usesRhiFlush + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + // But it should have an RHI it can use + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + } - { - // Reparenting into a window that already matches the required - // surface type should still mark the parent as flushing with RHI. - QWidget topLevel; + { + // Native child RHI widget does not enable RHI for top level + QWidget topLevel; + RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); + QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); + + if (!usesRhiBackingStore) + QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); + } - RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::Null); - rhiWidget.show(); - QVERIFY(QTest::qWaitForWindowExposed(&rhiWidget)); - QVERIFY(QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + { + // Non-native RHI child of native child enables RHI for native child, + // but not top level. + QWidget topLevel; + QWidget nativeChild(&topLevel); + nativeChild.setAttribute(Qt::WA_NativeWindow); + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), defaultSurfaceType); + QVERIFY(!QWidgetPrivate::get(&topLevel)->usesRhiFlush); + if (!usesRhiBackingStore) + QVERIFY(!QWidgetPrivate::get(&topLevel)->rhi()); + } - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); - rhiWidget.setParent(&topLevel); - QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); - } + { + // Native child RHI widget does not prevent RHI for top level + // if non-native RHI child widget is also present. + QWidget topLevel; + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + RhiWidget nativeRhiWidget(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + nativeRhiWidget.setAttribute(Qt::WA_NativeWindow); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeRhiWidget.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeRhiWidget)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + } - { - // Non-native RHI child of native child enables RHI for native child, - // but does not prevent top level from flushing with RHI. - QWidget topLevel; - QWidget nativeChild(&topLevel); - nativeChild.setAttribute(Qt::WA_NativeWindow); - RhiWidget rhiGranchild(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); - RhiWidget rhiChild(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); - topLevel.show(); - QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + { + // Reparenting into a window that already matches the required + // surface type should still mark the parent as flushing with RHI. + QWidget topLevel; + + RhiWidget rhiWidget(QPlatformBackingStoreRhiConfig::Null); + rhiWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&rhiWidget)); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiWidget)->rhi()); + + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + rhiWidget.setParent(&topLevel); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + } - QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); - QVERIFY(!QWidgetPrivate::get(&rhiGranchild)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&rhiGranchild)->rhi()); - QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); - QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); - QVERIFY(!QWidgetPrivate::get(&rhiChild)->usesRhiFlush); - QVERIFY(QWidgetPrivate::get(&rhiChild)->rhi()); + { + // Non-native RHI child of native child enables RHI for native child, + // but does not prevent top level from flushing with RHI. + QWidget topLevel; + QWidget nativeChild(&topLevel); + nativeChild.setAttribute(Qt::WA_NativeWindow); + RhiWidget rhiGranchild(QPlatformBackingStoreRhiConfig::OpenGL, &nativeChild); + RhiWidget rhiChild(QPlatformBackingStoreRhiConfig::OpenGL, &topLevel); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + + QCOMPARE(nativeChild.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&nativeChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&nativeChild)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiGranchild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiGranchild)->rhi()); + QCOMPARE(topLevel.windowHandle()->surfaceType(), QSurface::OpenGLSurface); + QVERIFY(QWidgetPrivate::get(&topLevel)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&topLevel)->rhi()); + QVERIFY(!QWidgetPrivate::get(&rhiChild)->usesRhiFlush); + QVERIFY(QWidgetPrivate::get(&rhiChild)->rhi()); + } } -#endif // QT_CONFIG(run_opengl_tests) #if QT_CONFIG(metal) QRhiMetalInitParams metalParams; @@ -805,10 +806,6 @@ void tst_QWidgetRepaintManager::evaluateRhi() void tst_QWidgetRepaintManager::rhiRecreateMaintainsWindowProperties() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif - const auto *integration = QGuiApplicationPrivate::platformIntegration(); if (!integration->hasCapability(QPlatformIntegration::RhiBasedRendering)) QSKIP("Platform does not support RHI based rendering"); @@ -817,6 +814,9 @@ void tst_QWidgetRepaintManager::rhiRecreateMaintainsWindowProperties() QSKIP("Platform does not support OpenGL RHI based rendering"); #endif + if (!integration->hasCapability(QPlatformIntegration::OpenGL)) + QSKIP("OpenGL is not supported on this platform."); + // Reparenting Rhi widget into a window causes the window to be // recreated, but after recreation the window properties such as // window position must remain the same diff --git a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp index 1b3afa0a4dc..cbe554f997f 100644 --- a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp +++ b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp @@ -8,10 +8,12 @@ #include <QPushButton> #include <QMainWindow> #include <QMenuBar> +#include <QPlainTextEdit> #include <QToolBar> #include <QToolButton> #include <QStatusBar> #include <QListWidget> +#include <QVBoxLayout> #include <QWidgetAction> #include <QScreen> #include <QSpinBox> @@ -96,6 +98,8 @@ private slots: void QTBUG_89082_actionTipsHide(); void QTBUG8122_widgetActionCrashOnClose(); void widgetActionTriggerClosesMenu(); + void widgetActionContextMenu(); + void transientParent(); void QTBUG_10735_crashWithDialog(); @@ -1590,6 +1594,36 @@ void tst_QMenu::widgetActionTriggerClosesMenu() QCOMPARE(actionTriggered, &widgetAction); } +void tst_QMenu::widgetActionContextMenu() // QTBUG-134757 +{ + QPushButton openButton("open"); + QMenu *menu = new QMenu(&openButton); + QVBoxLayout *layout = new QVBoxLayout; + QWidgetAction widgetAction(menu); + QWidget menuWidget(menu); + QPlainTextEdit edit; + openButton.setMenu(menu); + menuWidget.setLayout(layout); + widgetAction.setDefaultWidget(&menuWidget); + menu->addAction(&widgetAction); + layout->addWidget(&edit); + + openButton.show(); + QVERIFY(QTest::qWaitForWindowExposed(&openButton)); + + // Click the QPushButton to open its menu + QTest::mouseClick(&openButton, Qt::LeftButton); + QVERIFY(QTest::qWaitForWindowExposed(&menuWidget)); + QWindow *popupWindow = edit.window()->windowHandle(); + QVERIFY(popupWindow); + QCOMPARE(QApplication::activePopupWidget(), menu); + + // Right-click the QPlainTextEdit to open its context menu + QTest::mouseClick(popupWindow, Qt::RightButton); + QVERIFY(qobject_cast<QMenu *>(QApplication::activePopupWidget())); + QCOMPARE_NE(QApplication::activePopupWidget(), menu); +} + void tst_QMenu::transientParent() { QMainWindow window; diff --git a/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp b/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp index 609a0847862..f4baf6b1714 100644 --- a/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp +++ b/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp @@ -1233,12 +1233,41 @@ void tst_QMenuBar::taskQTBUG4965_escapeEaten() } #endif +#ifdef Q_OS_LINUX +class ResizeCounter : public QObject +{ +public: + explicit ResizeCounter(QMenuBar *bar) + { + Q_ASSERT(bar); + bar->installEventFilter(this); + } + + int resizeCount() const { return m_resizeCount; } + +protected: + bool eventFilter(QObject *o, QEvent *event) override + { + Q_UNUSED(o); + if (event->type() == QEvent::Resize) + ++m_resizeCount; + return false; + } + +private: + int m_resizeCount = 0; +}; +#endif + void tst_QMenuBar::taskQTBUG11823_crashwithInvisibleActions() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); QMenuBar menubar; +#ifdef Q_OS_LINUX + ResizeCounter counter(&menubar); +#endif menubar.setNativeMenuBar(false); //we can't check the geometry of native menubars QAction * m = menubar.addAction( "&m" ); @@ -1247,6 +1276,12 @@ void tst_QMenuBar::taskQTBUG11823_crashwithInvisibleActions() centerOnScreen(&menubar); menubar.show(); QVERIFY(QTest::qWaitForWindowActive(&menubar)); + +#ifdef Q_OS_LINUX + if (QSysInfo::productType().contains("opensuse")) + QVERIFY(QTest::qWaitFor([&]{ return counter.resizeCount() == 2;})); +#endif + menubar.setActiveAction(m); QCOMPARE(menubar.activeAction(), m); QTest::keyClick(static_cast<QWidget *>(0), Qt::Key_Right); diff --git a/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp b/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp index b2cdd6b72c2..c5df30dd7d1 100644 --- a/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp +++ b/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp @@ -63,12 +63,12 @@ private slots: void tst_QOpenGLWidget::initTestCase() { -#if !QT_CONFIG(run_opengl_tests) - QSKIP("Skip test as run-opengl-tests feature is off."); -#endif // See QOpenGLWidget constructor if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)) QSKIP("QOpenGLWidget is not supported on this platform."); + + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) + QSKIP("OpenGL is not supported on this platform."); } void tst_QOpenGLWidget::create() diff --git a/tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp b/tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp index da4fee72f65..457df2159b1 100644 --- a/tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp +++ b/tests/auto/widgets/widgets/qrhiwidget/tst_qrhiwidget.cpp @@ -64,7 +64,7 @@ void tst_QRhiWidget::testData() QTest::newRow("Null") << QRhiWidget::Api::Null; #endif -#if QT_CONFIG(opengl) && QT_CONFIG(run_opengl_tests) +#if QT_CONFIG(opengl) if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) QTest::newRow("OpenGL") << QRhiWidget::Api::OpenGL; #endif |