diff options
143 files changed, 2151 insertions, 1057 deletions
diff --git a/.cmake.conf b/.cmake.conf index e1e19d2ed2a..09e591bd25b 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -7,7 +7,7 @@ if (NOT DEFINED QT_SUPERBUILD OR DEFINED QT_REPO_MODULE_VERSION) set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_LEAN_HEADERS=1") endif() -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_COPYRIGHT "Copyright (C) The Qt Company Ltd. and other contributors.") diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake index 91983636712..3ef292b27bc 100644 --- a/cmake/QtBuildHelpers.cmake +++ b/cmake/QtBuildHelpers.cmake @@ -6,6 +6,7 @@ function(qt_internal_validate_cmake_generator) if(NOT warning_shown AND NOT CMAKE_GENERATOR MATCHES "Ninja" + AND NOT (IOS AND QT_INTERNAL_IS_STANDALONE_TEST) AND NOT QT_SILENCE_CMAKE_GENERATOR_WARNING AND NOT DEFINED ENV{QT_SILENCE_CMAKE_GENERATOR_WARNING}) set_property(GLOBAL PROPERTY _qt_validate_cmake_generator_warning_shown TRUE) diff --git a/cmake/QtBuildRepoHelpers.cmake b/cmake/QtBuildRepoHelpers.cmake index 05876c1ad6d..ba8e2e57660 100644 --- a/cmake/QtBuildRepoHelpers.cmake +++ b/cmake/QtBuildRepoHelpers.cmake @@ -523,6 +523,7 @@ function(qt_internal_show_extra_ide_sources) add_custom_target(${target_name}) set(recursive_glob_patterns + REUSE.toml ${QT_BUILD_EXTRA_IDE_FILE_RECURSIVE_PATTERNS} ) set(simple_glob_patterns diff --git a/cmake/QtHeadersClean.cmake b/cmake/QtHeadersClean.cmake index 3e59d68629c..9d8c86cf70d 100644 --- a/cmake/QtHeadersClean.cmake +++ b/cmake/QtHeadersClean.cmake @@ -137,6 +137,10 @@ function(qt_internal_add_headersclean_target module_target module_headers) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") list(APPEND hcleanFLAGS -Wzero-as-null-pointer-constant -Wdouble-promotion -Wfloat-conversion) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") + list(APPEND hcleanFLAGS + -Wnrvo) + endif() endif() if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang|IntelLLVM") diff --git a/cmake/QtInitProject.cmake b/cmake/QtInitProject.cmake index 8860b3578ed..c2a21202108 100644 --- a/cmake/QtInitProject.cmake +++ b/cmake/QtInitProject.cmake @@ -209,5 +209,5 @@ message("The project file is successfully generated. To build the project run:" "\nmkdir build" "\ncd build" "\nqt-cmake ${project_abs_dir}" - "\ncmake --build ${project_abs_dir}" + "\ncmake --build ." ) diff --git a/cmake/QtInternalTargets.cmake b/cmake/QtInternalTargets.cmake index 0815dbe8e42..f0d7d06fb84 100644 --- a/cmake/QtInternalTargets.cmake +++ b/cmake/QtInternalTargets.cmake @@ -23,6 +23,9 @@ function(qt_internal_set_warnings_are_errors_flags target target_scope) COMPILERS CLANG AppleClang OPTIONS -Werror -Wno-error=\#warnings -Wno-error=deprecated-declarations + CONDITIONS VERSION_GREATER_EQUAL 14 + OPTIONS + -Wno-error=deprecated-pragma COMMON_CONDITIONS ${common_conditions} ${clang_msvc_frontend_args} @@ -382,7 +385,10 @@ if(QT_FEATURE_stdlib_libcpp) target_compile_definitions(PlatformCommonInternal INTERFACE _LIBCPP_REMOVE_TRANSITIVE_INCLUDES) endif() -if(QT_USE_CCACHE AND CLANG AND BUILD_WITH_PCH) +if((QT_USE_CCACHE + OR (CMAKE_CXX_COMPILER_LAUNCHER MATCHES "^(.*[/\\])?sccache$")) + AND CLANG + AND BUILD_WITH_PCH) # The ccache man page says we must compile with -fno-pch-timestamp when using clang and pch. foreach(language IN ITEMS C CXX OBJC OBJCXX) target_compile_options(PlatformCommonInternal INTERFACE diff --git a/cmake/QtPrecompiledHeadersHelpers.cmake b/cmake/QtPrecompiledHeadersHelpers.cmake index b47e4e74e33..7fe94664da3 100644 --- a/cmake/QtPrecompiledHeadersHelpers.cmake +++ b/cmake/QtPrecompiledHeadersHelpers.cmake @@ -14,6 +14,18 @@ function(qt_update_precompiled_header_with_library target library) get_target_property(target_type "${library}" TYPE) if(target_type STREQUAL "INTERFACE_LIBRARY") + # If target links against QtFooPrivate then QtFoo is transitively pulled + # in. We assume that headers from QtFoo will be used and add this + # library to the target's precompiled headers too. + get_target_property(is_private_module "${library}" _qt_is_private_module) + if(is_private_module) + get_target_property(public_module_target "${library}" _qt_public_module_target_name) + qt_update_precompiled_header_with_library("${target}" + "${QT_CMAKE_EXPORT_NAMESPACE}::${public_module_target}" + ) + endif() + + # Don't handle interface libraries any further. return() endif() diff --git a/cmake/QtPublicAppleHelpers.cmake b/cmake/QtPublicAppleHelpers.cmake index 3ffe53c6f34..b8f3fb4818e 100644 --- a/cmake/QtPublicAppleHelpers.cmake +++ b/cmake/QtPublicAppleHelpers.cmake @@ -1028,7 +1028,6 @@ function(_qt_internal_check_apple_sdk_and_xcode_versions) endif() _qt_internal_get_cached_apple_sdk_version(sdk_version) - _qt_internal_get_cached_xcode_version(xcode_version) if(NOT max_sdk_version MATCHES "^[0-9]+$") message(FATAL_ERROR @@ -1075,12 +1074,15 @@ function(_qt_internal_check_apple_sdk_and_xcode_versions) ) endif() - if(xcode_version VERSION_LESS min_xcode_version AND NOT QT_NO_XCODE_MIN_VERSION_CHECK) - message(${message_type} - "Qt requires at least version ${min_xcode_version} of Xcode, " - "you're building against version ${xcode_version}. Please upgrade." - ${extra_message} - ) + if(NOT QT_NO_XCODE_MIN_VERSION_CHECK) + _qt_internal_get_cached_xcode_version(xcode_version) + if(xcode_version VERSION_LESS min_xcode_version) + message(${message_type} + "Qt requires at least version ${min_xcode_version} of Xcode, " + "you're building against version ${xcode_version}. Please upgrade." + ${extra_message} + ) + endif() endif() if(QT_NO_APPLE_SDK_MAX_VERSION_CHECK) diff --git a/cmake/QtTestHelpers.cmake b/cmake/QtTestHelpers.cmake index 06228ac41de..781bdf4143a 100644 --- a/cmake/QtTestHelpers.cmake +++ b/cmake/QtTestHelpers.cmake @@ -1240,7 +1240,7 @@ function(qt_internal_add_test_finalizers target) # specific platforms. # TODO: Remove once we confirm that the new way of running test finalizers for all platforms # doesn't cause any issues. - if(QT_INTERNAL_SKIP_TEST_FINALIZERS_V2) + if(NOT QT_INTERNAL_SKIP_TEST_FINALIZERS_V2) return() endif() diff --git a/cmake/QtToolchainHelpers.cmake b/cmake/QtToolchainHelpers.cmake index 348a3c25603..b2a5575f130 100644 --- a/cmake/QtToolchainHelpers.cmake +++ b/cmake/QtToolchainHelpers.cmake @@ -183,6 +183,7 @@ endif()") list(LENGTH CMAKE_OSX_ARCHITECTURES _qt_osx_architectures_count) if(cmake_sysroot_name AND (MACOS OR (UIKIT AND NOT _qt_osx_architectures_count GREATER 1))) list(APPEND init_platform " +set(__qt_initial_apple_sdk \"${QT_APPLE_SDK}\") if(NOT DEFINED CMAKE_OSX_SYSROOT) set(CMAKE_OSX_SYSROOT \"${cmake_sysroot_name}\" CACHE STRING \"\") endif()") @@ -254,7 +255,8 @@ endif()") qt_internal_get_first_osx_arch(osx_first_arch) list(APPEND init_platform "if((NOT CMAKE_GENERATOR STREQUAL \"Xcode\" AND NOT __qt_toolchain_building_qt_repo) - OR (CMAKE_GENERATOR STREQUAL \"Xcode\" AND __qt_apple_sdk AND NOT QT_NO_SET_OSX_ARCHITECTURES))") + OR (CMAKE_GENERATOR STREQUAL \"Xcode\" AND __qt_initial_apple_sdk + AND NOT QT_NO_SET_OSX_ARCHITECTURES))") list(APPEND init_platform " set(CMAKE_OSX_ARCHITECTURES \"${osx_first_arch}\" CACHE STRING \"\")") list(APPEND init_platform "endif()") diff --git a/coin/instructions/cmake_run_ctest.yaml b/coin/instructions/cmake_run_ctest.yaml index 43963fc172b..03312101117 100644 --- a/coin/instructions/cmake_run_ctest.yaml +++ b/coin/instructions/cmake_run_ctest.yaml @@ -92,19 +92,10 @@ instructions: variableName: CTEST_ARGS variableValue: " --no-label-summary" - # Enable CTest's JUnit XML summary + # Enable CTest's JUnit XML summary, supported in CMake >= v3.21 - type: AppendToEnvironmentVariable variableName: CTEST_ARGS variableValue: " --output-junit {{.Env.COIN_CTEST_RESULTSDIR}}{{.Env.CI_PATH_SEP}}test_summary.ctest_junit_xml" - disable_if: # CMake < v3.21 does not support it - condition: and - conditions: - - condition: runtime - env_var: CMAKE_MIN_SUPPORTED_BIN_PATH - not_equals_value: null - - condition: runtime - env_var: PATH - contains_value: "{{.Env.CMAKE_MIN_SUPPORTED_BIN_PATH}}" - !include "{{qt/qtbase}}/coin_module_test_android_start_emulator.yaml" diff --git a/coin/instructions/coin_module_axivion_template_v2.yaml b/coin/instructions/coin_module_axivion_template_v2.yaml index 90d57de281c..9840d1138b6 100644 --- a/coin/instructions/coin_module_axivion_template_v2.yaml +++ b/coin/instructions/coin_module_axivion_template_v2.yaml @@ -16,6 +16,13 @@ analysis_instructions_axivion: &analysis_instructions_axivion condition: runtime env_var: TESTED_MODULE_COIN not_equals_value: "qtbase" + - type: EnvironmentVariable + variableName: TESTED_MODULE_BRANCH_COIN + variableValue: "unknown" + enable_if: + condition: runtime + env_var: TESTED_MODULE_BRANCH_COIN + equals_value: null - type: Group instructions: - type: Rename diff --git a/doc/global/externalsites/external-resources.qdoc b/doc/global/externalsites/external-resources.qdoc index 51756842ad0..8670305b00c 100644 --- a/doc/global/externalsites/external-resources.qdoc +++ b/doc/global/externalsites/external-resources.qdoc @@ -58,68 +58,27 @@ */ /*! - \externalpage https://www.freedesktop.org/wiki/Standards/xembed-spec/ - \title XEmbed Specification -*/ - -/*! - \externalpage https://www.freedesktop.org/wiki/Standards/icon-theme-spec/ - \title Icon Themes Specification -*/ - -/*! \externalpage https://www.cups.org/ \title Common Unix Printing System (CUPS) \keyword CUPS */ /*! - \externalpage https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/ - \title Desktop Entry Specification -*/ - -/*! - \externalpage https://kde.org/ - \title The K Desktop Environment - \keyword KDE -*/ - -/*! \externalpage https://cmake.org/cmake/help/latest/ \title CMake Documentation */ /*! - \externalpage https://cmake.org/cmake/help/latest/command/find_package.html - \title CMake find_package Documentation -*/ - -/*! \externalpage https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#automoc \title CMake AUTOMOC Documentation */ /*! - \externalpage https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#autorcc - \title CMake AUTORCC Documentation -*/ - -/*! \externalpage https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#autouic \title CMake AUTOUIC Documentation */ /*! - \externalpage https://cmake.org/cmake/help/latest/prop_tgt/LOCATION.html - \title CMake LOCATION Documentation -*/ - -/*! - \externalpage https://cmake.org/cmake/help/latest/prop_tgt/POSITION_INDEPENDENT_CODE.html - \title CMake POSITION_INDEPENDENT_CODE Documentation -*/ - -/*! \externalpage https://cmake.org/cmake/help/latest/command/target_link_libraries.html \title CMake target_link_libraries Documentation */ @@ -130,50 +89,11 @@ */ /*! - \externalpage https://conan.io/ - \title Conan -*/ - -/*! \externalpage https://www.gnome.org/ \title GNOME */ /*! - \externalpage https://www.gnu.org/software/emacs/ - \title GNU Emacs -*/ - -/*! - \externalpage https://gnuwin32.sourceforge.net/packages.html - \title GnuWin32 Project -*/ - -/*! - \externalpage https://www.w3.org/Graphics/SVG/About.html - \title About SVG - \keyword Scalable Vector Graphics -*/ - -/*! - \externalpage https://www.w3.org/TR/SVG/types.html#ColorKeywords - \title SVG color keyword names -*/ - -/*! - \externalpage https://www.w3.org/Graphics/SVG/ - \title SVG Working Group -*/ - -/*! - \externalpage https://www.w3.org/TR/SVGMobile/ - \title Mobile SVG Profiles - \omit - Mobile SVG Profiles: SVG Tiny and SVG Basic - \endomit -*/ - -/*! \externalpage https://www.w3.org/TR/SVGMobile12/ \title SVG 1.2 Tiny */ @@ -186,36 +106,16 @@ /*! - \externalpage https://jmeubank.github.io/tdm-gcc/ - \title TDM-GCC -*/ - -/*! \externalpage https://www.dependencywalker.com/ \title Dependency Walker */ /*! - \externalpage https://webkit.org/ - \title WebKit Open Source Project -*/ - -/*! - \externalpage https://www.informit.com/store/c-plus-plus-gui-programming-with-qt4-9780132354165 - \title C++ GUI Programming with Qt 4, 2nd Edition -*/ - -/*! \externalpage https://www.openssl.org/ \title OpenSSL Toolkit */ /*! - \externalpage https://www.activestate.com/platform/supported-languages/perl/ - \title ActivePerl -*/ - -/*! \externalpage https://chromium.googlesource.com/angle/angle/+/master/README.md \title ANGLE */ @@ -226,16 +126,6 @@ */ /*! - \externalpage https://www.w3.org/TR/html401/ - \title HTML 4 -*/ - -/*! - \externalpage https://html.spec.whatwg.org/multipage/ - \title HTML 5 -*/ - -/*! \externalpage https://icu.unicode.org/ \title ICU */ @@ -251,41 +141,16 @@ */ /*! - \externalpage https://pyxml.sourceforge.net/topics/xbel/ - \title XML Bookmark Exchange Language Resource Page -*/ - -/*! - \externalpage https://www.w3.org/TR/xquery/#errors - \title error handling in the XQuery language -*/ - -/*! \externalpage https://xaos-project.github.io/ \title XaoS */ /*! - \externalpage https://www.unixodbc.org - \title https://www.unixodbc.org -*/ - -/*! \externalpage https://www.postgresql.org \title https://www.postgresql.org */ /*! - \externalpage https://www.postgresql.org/docs/current/installation-platform-notes.html - \title PostgreSQL MinGW/Native Windows -*/ - -/*! - \externalpage https://www.freetds.org - \title https://www.freetds.org -*/ - -/*! \externalpage https://www.sqlite.org \title https://www.sqlite.org */ @@ -301,11 +166,6 @@ */ /*! - \externalpage https://tldp.org/HOWTO/Framebuffer-HOWTO.html - \title Framebuffer HOWTO -*/ - -/*! \externalpage https://www.w3.org/TR/scxml/ \title State Chart XML: State Machine Notation for Control Abstraction */ @@ -321,71 +181,21 @@ */ /*! - \externalpage https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html - \title GNU Lesser General Public License, version 2.1 -*/ - -/*! - \externalpage https://www.qtcentre.org - \title Qt Centre -*/ - -/*! \externalpage https://kde.org \title KDE */ /*! - \externalpage https://cplusplus.com/reference/cstring/memcpy/ - \title C++ Reference - memcpy -*/ - -/*! \externalpage https://en.cppreference.com/w/cpp/symbol_index/chrono_literals.html \title chrono_literals Symbol Index */ /*! - \externalpage https://www.w3.org/TR/CSS2/selector.html - \title Standard CSS2 selector -*/ - -/*! - \externalpage https://www.w3.org/XML/Core/#Publications - \title W3C XML specifications -*/ - -/*! - \externalpage https://www.w3.org/XML/Schema - \title XML Schema -*/ - -/*! - \externalpage https://opensource.org/license/bsd-3-clause - \title New and Modified BSD Licenses -*/ - -/*! - \externalpage https://ecma-international.org/publications-and-standards/standards/ecma-262/ - \title ECMAScript Language Specification -*/ - -/*! \externalpage https://developer.mozilla.org/en-US/docs/Web/JavaScript \title JavaScript Resources */ /*! - \externalpage https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide - \title JavaScript Guide -*/ - -/*! - \externalpage https://developer.mozilla.org/en-US/docs/Web/JavaScript - \title About JavaScript -*/ - -/*! \externalpage https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords \title JavaScript Reserved Words */ @@ -406,41 +216,16 @@ */ /*! - \externalpage https://registry.khronos.org/OpenGL/index_gl.php - \title OpenGL Registry -*/ - -/*! - \externalpage https://registry.khronos.org/OpenGL/index_es.php - \title Khronos OpenGL ES API Registry -*/ - -/*! \externalpage https://www.khronos.org/opengl/wiki/Array_Texture \title Array Texture */ /*! - \externalpage https://github.com/iksaif/qsslkey-p11 - \title qsslkey example -*/ - -/*! \externalpage https://www.w3.org/TR/2009/WD-webdatabase-20091029/ \title HTML5 Web Database API */ /*! - \externalpage https://lldb.llvm.org/ - \title The LLDB Debugger -*/ - -/*! - \externalpage https://account.qt.io/s/ - \title Qt Account Sign-up -*/ - -/*! \externalpage https://facebook.github.io/zstd/ \title Zstandard Site */ @@ -476,6 +261,6 @@ */ /*! - \externalpage https://specifications.freedesktop.org/trash-spec/1.0/ + \externalpage https://specifications.freedesktop.org/trash/1.0/ \title FreeDesktop.org Trash specification version 1.0 */ diff --git a/doc/global/externalsites/qt-webpages.qdoc b/doc/global/externalsites/qt-webpages.qdoc index 7b659fa5295..6cbf5f377b6 100644 --- a/doc/global/externalsites/qt-webpages.qdoc +++ b/doc/global/externalsites/qt-webpages.qdoc @@ -17,76 +17,15 @@ \title The Qt Company */ /*! - \externalpage http://qt.io/licensing/ - \title Qt Licensing Overview -*/ -/*! - \externalpage http://doc.qt.io/archives/qq/ - \title Qt Quarterly -*/ -/*! - \externalpage http://doc.qt.io/archives/qq/qq19-plurals.html - \title Qt Quarterly: Plural Form in Translation -*/ -/*! \externalpage https://code.qt.io/ \title Public Qt Repository */ /*! - \externalpage https://code.qt.io/cgit/%7bnon-gerrit%7d/qt-labs/qtestlib-tools.git/ - \title qtestlib-tools -*/ - -/*! - \externalpage http://wiki.qt.io/Qt_Coding_Style - \title Qt Coding Style -*/ -/*! - \externalpage http://doc.qt.io/archives/qt-eclipse-1.6/index.html - \title Eclipse Plugin -*/ -/*! - \externalpage http://doc.qt.io/archives/qq/qq11-events.html - \title Qt Quarterly: Another Look at Events -*/ -/*! - \externalpage https://www.youtube.com/watch?v=P4kv-AoAJ-Q - \title Livecoding video effects with Qt5 -*/ -/*! - \externalpage http://blog.qt.io/2012/02/29/pimp-my-video-shader-effects-and-multimedia/ - \title Pimp my video -*/ -/*! - \externalpage http://wiki.qt.io/QtMediaHub - \title QtMediaHub -*/ -/*! - \externalpage http://wiki.qt.io/Qt_RaspberryPi - \title QtonPi -*/ - -/*! - \externalpage http://wiki.qt.io/jom - \title jom -*/ - -/*! - \externalpage http://doc.qt.io/qt-4.8 - \title Qt 4.8 Reference Documentation -*/ - -/*! \externalpage http://wiki.qt.io/Qt_Localization \title external: Translating Qt Into Other Languages */ /*! - \externalpage http://wiki.qt.io/Qt_Multimedia_Backends - \title Qt Multimedia Backends -*/ - -/*! \externalpage https://doc.qt.io/qt3dstudio/index.html \title Qt 3D Studio Manual */ diff --git a/doc/global/externalsites/rfc.qdoc b/doc/global/externalsites/rfc.qdoc index d0c127fb41e..071977b9a0e 100644 --- a/doc/global/externalsites/rfc.qdoc +++ b/doc/global/externalsites/rfc.qdoc @@ -12,12 +12,6 @@ */ /*! - \externalpage https://datatracker.ietf.org/doc/html/rfc1179 - \title RFC 1179 - \keyword lpr -*/ - -/*! \externalpage https://datatracker.ietf.org/doc/html/rfc1738 \title RFC 1738 */ @@ -28,43 +22,16 @@ */ /*! - \externalpage https://datatracker.ietf.org/doc/html/rfc1928 - \title RFC 1928 -*/ - -/*! - \externalpage https://datatracker.ietf.org/doc/html/rfc1929 - \title RFC 1929 -*/ - -/*! \externalpage https://datatracker.ietf.org/doc/html/rfc2045 \title RFC 2045 */ /*! - \externalpage https://datatracker.ietf.org/doc/html/rfc2109 - \title RFC 2109 - HTTP State Management Mechanism -*/ - -/*! \externalpage https://datatracker.ietf.org/doc/html/rfc2822 \title RFC 2822 */ /*! - \externalpage https://datatracker.ietf.org/doc/html/rfc2965 - \title RFC 2965 - HTTP State Management Mechanism -*/ - -/*! - \externalpage https://datatracker.ietf.org/doc/html/rfc3174 - \title RFC 3174 -*/ - -/*! \externalpage https://datatracker.ietf.org/doc/html/rfc3491 \title RFC 3491 */ diff --git a/mkspecs/common/clang.conf b/mkspecs/common/clang.conf index 82c6173a037..83867e66f94 100644 --- a/mkspecs/common/clang.conf +++ b/mkspecs/common/clang.conf @@ -32,14 +32,14 @@ QMAKE_CXXFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG QMAKE_CXXFLAGS_LTCG_FATOBJECTS = $$QMAKE_CFLAGS_LTCG_FATOBJECTS QMAKE_CXXFLAGS_DISABLE_LTCG = $$QMAKE_CFLAGS_DISABLE_LTCG QMAKE_CXXFLAGS_CXX11 = -std=c++11 -QMAKE_CXXFLAGS_CXX14 = -std=c++1y -QMAKE_CXXFLAGS_CXX1Z = -std=c++1z +QMAKE_CXXFLAGS_CXX14 = -std=c++14 +QMAKE_CXXFLAGS_CXX1Z = -std=c++17 QMAKE_CXXFLAGS_CXX2A = -std=c++2a QMAKE_CXXFLAGS_CXX2B = -std=c++2b QMAKE_CXXFLAGS_CXX2C = -std=c++2c QMAKE_CXXFLAGS_GNUCXX11 = -std=gnu++11 -QMAKE_CXXFLAGS_GNUCXX14 = -std=gnu++1y -QMAKE_CXXFLAGS_GNUCXX1Z = -std=gnu++1z +QMAKE_CXXFLAGS_GNUCXX14 = -std=gnu++14 +QMAKE_CXXFLAGS_GNUCXX1Z = -std=gnu++17 QMAKE_CXXFLAGS_GNUCXX2A = -std=gnu++2a QMAKE_CXXFLAGS_GNUCXX2B = -std=gnu++2b QMAKE_CXXFLAGS_GNUCXX2C = -std=gnu++2c diff --git a/mkspecs/common/g++-base.conf b/mkspecs/common/g++-base.conf index e12e41506ae..66248fb7d01 100644 --- a/mkspecs/common/g++-base.conf +++ b/mkspecs/common/g++-base.conf @@ -30,14 +30,14 @@ QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE QMAKE_CFLAGS_GNUC99 = -std=gnu99 QMAKE_CFLAGS_GNUC11 = -std=gnu11 QMAKE_CXXFLAGS_CXX11 = -std=c++11 -QMAKE_CXXFLAGS_CXX14 = -std=c++1y -QMAKE_CXXFLAGS_CXX1Z = -std=c++1z +QMAKE_CXXFLAGS_CXX14 = -std=c++14 +QMAKE_CXXFLAGS_CXX1Z = -std=c++17 QMAKE_CXXFLAGS_CXX2A = -std=c++2a QMAKE_CXXFLAGS_CXX2B = -std=c++2b QMAKE_CXXFLAGS_CXX2C = -std=c++2c QMAKE_CXXFLAGS_GNUCXX11 = -std=gnu++11 -QMAKE_CXXFLAGS_GNUCXX14 = -std=gnu++1y -QMAKE_CXXFLAGS_GNUCXX1Z = -std=gnu++1z +QMAKE_CXXFLAGS_GNUCXX14 = -std=gnu++14 +QMAKE_CXXFLAGS_GNUCXX1Z = -std=gnu++17 QMAKE_CXXFLAGS_GNUCXX2A = -std=gnu++2a QMAKE_CXXFLAGS_GNUCXX2B = -std=gnu++2b QMAKE_CXXFLAGS_GNUCXX2C = -std=gnu++2c diff --git a/mkspecs/common/msvc-based-version.conf b/mkspecs/common/msvc-based-version.conf index adc893836a9..a687fa5f449 100644 --- a/mkspecs/common/msvc-based-version.conf +++ b/mkspecs/common/msvc-based-version.conf @@ -42,4 +42,10 @@ greaterThan(QMAKE_MSC_VER, 1929) { MSVC_TOOLSET_VER = 143 } +greaterThan(QMAKE_MSC_VER, 1949) { + # Visual Studio 2026 (18.0) / Visual C++ 19.50 and up + MSVC_VER = 18.0 + MSVC_TOOLSET_VER = 145 +} + !isEmpty(COMPAT_MKSPEC):!$$COMPAT_MKSPEC: CONFIG += $$COMPAT_MKSPEC diff --git a/mkspecs/common/msvc-version.conf b/mkspecs/common/msvc-version.conf index 0130542ddb9..8270f02e890 100644 --- a/mkspecs/common/msvc-version.conf +++ b/mkspecs/common/msvc-version.conf @@ -147,4 +147,10 @@ greaterThan(QMAKE_MSC_VER, 1929) { MSVC_TOOLSET_VER = 143 } +greaterThan(QMAKE_MSC_VER, 1949) { + # Visual Studio 2026 (18.0) / Visual C++ 19.50 and up + MSVC_VER = 18.0 + MSVC_TOOLSET_VER = 145 +} + !isEmpty(COMPAT_MKSPEC):!$$COMPAT_MKSPEC: CONFIG += $$COMPAT_MKSPEC diff --git a/qmake/generators/win32/msvc_objectmodel.cpp b/qmake/generators/win32/msvc_objectmodel.cpp index 6517e5c4516..0776a4a5267 100644 --- a/qmake/generators/win32/msvc_objectmodel.cpp +++ b/qmake/generators/win32/msvc_objectmodel.cpp @@ -25,6 +25,8 @@ DotNET vsVersionFromString(const ProString &versionString) int versionMajor = versionView.left(idx).toInt(); int versionMinor = versionView.mid(idx + 1).toInt(); + if (versionMajor == 18) + return NET2026; if (versionMajor == 17) return NET2022; if (versionMajor == 16) diff --git a/qmake/generators/win32/msvc_objectmodel.h b/qmake/generators/win32/msvc_objectmodel.h index 190d6c727fa..58f818a3bc5 100644 --- a/qmake/generators/win32/msvc_objectmodel.h +++ b/qmake/generators/win32/msvc_objectmodel.h @@ -29,7 +29,8 @@ enum DotNET { NET2015 = 0xd0, NET2017 = 0xe0, NET2019, - NET2022 + NET2022, + NET2026 }; DotNET vsVersionFromString(const ProString &versionString); diff --git a/qmake/generators/win32/msvc_vcproj.cpp b/qmake/generators/win32/msvc_vcproj.cpp index 1566f72ba66..7c2e0562aa7 100644 --- a/qmake/generators/win32/msvc_vcproj.cpp +++ b/qmake/generators/win32/msvc_vcproj.cpp @@ -56,6 +56,8 @@ const char _slnHeader142[] = "Microsoft Visual Studio Solution File, Format "\n# Visual Studio Version 16"; const char _slnHeader143[] = "Microsoft Visual Studio Solution File, Format Version 12.00" "\n# Visual Studio Version 17"; +const char _slnHeader145[] = "Microsoft Visual Studio Solution File, Format Version 12.00" + "\n# Visual Studio Version 18"; // The following UUID _may_ change for later servicepacks... // If so we need to search through the registry at // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\Projects @@ -502,6 +504,9 @@ void VcprojGenerator::writeSubDirs(QTextStream &t) } switch (vcProject.Configuration.CompilerVersion) { + case NET2026: + t << _slnHeader145; + break; case NET2022: t << _slnHeader143; break; @@ -882,6 +887,9 @@ void VcprojGenerator::initProject() // Own elements ----------------------------- vcProject.Name = project->first("QMAKE_ORIG_TARGET").toQString(); switch (vcProject.Configuration.CompilerVersion) { + case NET2026: + vcProject.Version = "18.00"; + break; case NET2022: vcProject.Version = "17.00"; break; diff --git a/src/3rdparty/libjpeg/COPYRIGHT.txt b/src/3rdparty/libjpeg/COPYRIGHT.txt index f5eae868466..ce9d95bfebc 100644 --- a/src/3rdparty/libjpeg/COPYRIGHT.txt +++ b/src/3rdparty/libjpeg/COPYRIGHT.txt @@ -1,4 +1,4 @@ -Copyright (C) 2009-2025 D. R. Commander +Copyright (C) 2009-2024 D. R. Commander Copyright (C) 2015, 2020 Google, Inc. Copyright (C) 2019-2020 Arm Limited Copyright (C) 2015-2016, 2018 Matthieu Darbois diff --git a/src/3rdparty/libjpeg/ChangeLog.md b/src/3rdparty/libjpeg/ChangeLog.md index 4bdbf53dd34..17390d80bd0 100644 --- a/src/3rdparty/libjpeg/ChangeLog.md +++ b/src/3rdparty/libjpeg/ChangeLog.md @@ -1,3 +1,34 @@ +3.1.3 +===== + +### Significant changes relative to 3.1.2: + +1. Hardened the TurboJPEG API against hypothetical applications that may +erroneously call `tj*Compress*()` or `tj*Transform()` with a reused JPEG +destination buffer pointer while specifying a destination buffer size of 0. + +2. Hardened the TurboJPEG API against hypothetical applications that may +erroneously set `TJPARAM_LOSSLESS` or `TJPARAM_COLORSPACE` prior to calling +`tj3EncodeYUV*8()` or `tj3CompressFromYUV*8()`. `tj3EncodeYUV*8()` and +`tj3CompressFromYUV*8()` now ignore `TJPARAM_LOSSLESS` and +`TJPARAM_COLORSPACE`. + +3. Hardened the TurboJPEG Java API against hypothetical applications that may +erroneously pass huge X or Y offsets to one of the compression, YUV encoding, +decompression, or YUV decoding methods, leading to signed integer overflow in +the JNI wrapper's buffer size checks that rendered those checks ineffective. + +4. Fixed an issue in the TurboJPEG Java API whereby +`TJCompressor.getSourceBuf()` sometimes returned the buffer from a previous +invocation of `TJCompressor.loadSourceImage()` if the target data precision was +changed before the most recent invocation. + +5. Fixed an issue in the PPM reader that caused incorrect pixels to be +generated when using `tj3LoadImage*()` or `TJCompressor.loadSourceImage()` to +load a PBMPLUS (PPM/PGM) file into a CMYK buffer with a different data +precision than that of the file. + + 3.1.2 ===== @@ -962,9 +993,9 @@ storage. 64-bit libjpeg-turbo SDK for Visual C++ were installed on the same system, only one of them could be uninstalled. -2. Fixed a signed integer overflow and subsequent segfault that occurred when -attempting to decompress images with more than 715827882 pixels using the -64-bit C version of TJBench. +2. Fixed a signed integer overflow and subsequent segfault (CVE-2019-2201) that +occurred when attempting to decompress images with more than 715827882 pixels +using the 64-bit C version of TJBench. 3. Fixed out-of-bounds write in `tjDecompressToYUV2()` and `tjDecompressToYUVPlanes()` (sometimes manifesting as a double free) that @@ -1016,9 +1047,9 @@ regardless of whether a 4:2:2 JPEG image is rotated or transposed prior to decompression (in the frequency domain) or after decompression (in the spatial domain), the final image will be similar. -4. Fixed an integer overflow and subsequent segfault that occurred when -attempting to compress or decompress images with more than 1 billion pixels -using the TurboJPEG API. +4. Fixed an integer overflow and subsequent segfault (CVE-2019-2201) that +occurred when attempting to compress or decompress images with more than 1 +billion pixels using the TurboJPEG API. 5. Fixed a regression introduced by 2.0 beta1[15] whereby attempting to generate a progressive JPEG image on an SSE2-capable CPU using a scan script diff --git a/src/3rdparty/libjpeg/qt_attribution.json b/src/3rdparty/libjpeg/qt_attribution.json index fe38aec1f68..5ac811a12b4 100644 --- a/src/3rdparty/libjpeg/qt_attribution.json +++ b/src/3rdparty/libjpeg/qt_attribution.json @@ -7,8 +7,8 @@ "Description": "The Independent JPEG Group's JPEG software", "Homepage": "http://libjpeg-turbo.virtualgl.org/", - "Version": "3.1.2", - "DownloadLocation": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.1.2/libjpeg-turbo-3.1.2.tar.gz", + "Version": "3.1.3", + "DownloadLocation": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.1.3/libjpeg-turbo-3.1.3.tar.gz", "PURL": "pkg:github/libjpeg-turbo/libjpeg-turbo@$<VERSION>", "CPE": "cpe:2.3:a:libjpeg-turbo:libjpeg-turbo:$<VERSION>:*:*:*:*:*:*:*", diff --git a/src/3rdparty/libjpeg/src/jconfig.h b/src/3rdparty/libjpeg/src/jconfig.h index e81574b9a48..85a1509fb68 100644 --- a/src/3rdparty/libjpeg/src/jconfig.h +++ b/src/3rdparty/libjpeg/src/jconfig.h @@ -2,9 +2,9 @@ #define JPEG_LIB_VERSION 80 -#define LIBJPEG_TURBO_VERSION 3.0.3 +#define LIBJPEG_TURBO_VERSION 3.1.3 -#define LIBJPEG_TURBO_VERSION_NUMBER 3000003 +#define LIBJPEG_TURBO_VERSION_NUMBER 3001003 #define C_ARITH_CODING_SUPPORTED 1 diff --git a/src/3rdparty/libjpeg/src/jconfigint.h b/src/3rdparty/libjpeg/src/jconfigint.h index 6e7dbd75a1e..6d5f9633928 100644 --- a/src/3rdparty/libjpeg/src/jconfigint.h +++ b/src/3rdparty/libjpeg/src/jconfigint.h @@ -10,7 +10,7 @@ #define PACKAGE_NAME "libjpeg-turbo" -#define VERSION "3.0.3" +#define VERSION "3.1.3" #if SIZE_MAX == 0xffffffff #define SIZEOF_SIZE_T 4 diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java index f0d304f9e7f..c63cd1a77e3 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java @@ -32,6 +32,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { private QtWindow m_parentWindow; private GestureDetector m_gestureDetector; private final QtEditText m_editText; + private boolean m_editTextFocusInitialized = false; private final QtInputConnection.QtInputConnectionListener m_inputConnectionListener; private boolean m_firstSafeMarginsDelivered = false; private int m_actionBarHeight = -1; @@ -62,6 +63,8 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { if (!isForeignWindow && context instanceof Activity) { // TODO QTBUG-122552 - Service keyboard input not implemented m_editText = new QtEditText(context, listener); + m_editText.setFocusable(false); + m_editText.setFocusableInTouchMode(false); m_editText.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -242,6 +245,14 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { @Override public boolean onTouchEvent(MotionEvent event) { + // Enable focus for the edit text on first touch event to avoid + // early QtInputConnection callbacks from blocking the UI thread. + if (!m_editTextFocusInitialized) { + m_editTextFocusInitialized = true; + m_editText.setFocusable(true); + m_editText.setFocusableInTouchMode(true); + } + windowFocusChanged(true, getId()); if (m_editText != null && m_inputConnectionListener != null) m_inputConnectionListener.onEditTextChanged(m_editText); diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index ea8cf7b9c8e..539ad753ca6 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -590,17 +590,14 @@ if(QT_FEATURE_async_io) DEFINES QT_RANDOMACCESSASYNCFILE_QIORING ) - elseif(QT_FEATURE_thread AND QT_FEATURE_future) - # TODO: This should become the last (fallback) condition later. - # We migth also want to rewrite it so that it does not depend on - # QT_FEATURE_future. - qt_internal_extend_target(Core - SOURCES - io/qrandomaccessasyncfile_threadpool.cpp - DEFINES - QT_RANDOMACCESSASYNCFILE_THREAD - ) endif() + # This is the fallback condition that should be always available. + # TODO: try to rewrite it so that it does not depend on + # QT_FEATURE_future. + qt_internal_extend_target(Core + SOURCES + io/qrandomaccessasyncfile_threadpool.cpp + ) endif() # This needs to be done before one below adds kernel32 because the symbols we use diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index b69d0285de2..c1f9aee0180 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -3677,21 +3677,6 @@ macro(qt6_standard_project_setup) if(NOT DEFINED QT_I18N_SOURCE_LANGUAGE) set(QT_I18N_SOURCE_LANGUAGE ${__qt_sps_arg_I18N_SOURCE_LANGUAGE}) endif() - - if(CMAKE_GENERATOR STREQUAL "Xcode") - # Ensure we always use device SDK for Xcode for single-arch Qt builds - set(qt_osx_arch_count 0) - if(QT_OSX_ARCHITECTURES) - list(LENGTH QT_OSX_ARCHITECTURES qt_osx_arch_count) - endif() - if(NOT qt_osx_arch_count GREATER 1 AND "${CMAKE_OSX_SYSROOT}" MATCHES "^[a-z]+simulator$") - # Xcode expects the base SDK to be the device SDK - set(simulator_sysroot "${CMAKE_OSX_SYSROOT}") - string(REGEX REPLACE "simulator" "os" CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}") - set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}" CACHE STRING "" FORCE) - set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "${simulator_sysroot}") - endif() - endif() endif() endmacro() diff --git a/src/corelib/animation/qabstractanimation.cpp b/src/corelib/animation/qabstractanimation.cpp index c3e1ba4010f..8a343b15eeb 100644 --- a/src/corelib/animation/qabstractanimation.cpp +++ b/src/corelib/animation/qabstractanimation.cpp @@ -858,6 +858,7 @@ qint64 QAnimationDriver::elapsed() const */ /*! + \internal The default animation driver just spins the timer... */ QDefaultAnimationDriver::QDefaultAnimationDriver(QUnifiedTimer *timer) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index 4386b907c13..7274b51cc0a 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -620,6 +620,10 @@ int main(void) HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, 1, 1, &ioRingHandle); if (hr == IORING_E_SUBMISSION_QUEUE_FULL) // not valid, but test that this #define exists return 0; + IORING_HANDLE_REF ref(HANDLE(nullptr)); + IORING_BUFFER_REF bufRef(nullptr); + // The newest API addition that we require: + BuildIoRingWriteFile(ioRingHandle, ref, bufRef, -1, 0, FILE_WRITE_FLAGS_NONE, 0, IOSQE_FLAGS_NONE); /* END TEST: */ return 0; } @@ -806,7 +810,7 @@ qt_feature("winsdkicu" PRIVATE CONDITION TEST_winsdkicu DISABLE QT_FEATURE_icu ) -qt_feature("windows_ioring" PRIVATE +qt_feature("windows-ioring" PRIVATE LABEL "Windows I/O Ring" AUTODETECT WIN32 AND CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 10.0.22000 CONDITION TEST_windows_ioring @@ -1278,14 +1282,13 @@ qt_feature("permissions" PUBLIC ) qt_feature("openssl-hash" PRIVATE LABEL "OpenSSL based cryptographic hash" - AUTODETECT OFF CONDITION QT_FEATURE_openssl_linked AND QT_FEATURE_opensslv30 PURPOSE "Uses OpenSSL based implementation of cryptographic hash algorithms." ) qt_feature("async-io" PRIVATE LABEL "Async File I/O" PURPOSE "Provides support for asynchronous file I/O." - CONDITION (QT_FEATURE_thread AND QT_FEATURE_future) OR APPLE OR (LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring) + CONDITION QT_FEATURE_thread AND QT_FEATURE_future ) qt_configure_add_summary_section(NAME "Qt Core") @@ -1298,7 +1301,7 @@ qt_configure_add_summary_entry(ARGS "glib") qt_configure_add_summary_entry(ARGS "icu") qt_configure_add_summary_entry(ARGS "jemalloc") qt_configure_add_summary_entry(ARGS "liburing") -qt_configure_add_summary_entry(ARGS "windows_ioring") +qt_configure_add_summary_entry(ARGS "windows-ioring") qt_configure_add_summary_entry(ARGS "timezone_tzdb") qt_configure_add_summary_entry(ARGS "system-libb2") qt_configure_add_summary_entry(ARGS "mimetype-database") diff --git a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc index 0a0dc0b3c50..b8e5e038a33 100644 --- a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc +++ b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc @@ -133,7 +133,6 @@ use, for example, library files referenced from a Qt installation. \summary {Forces or disables release package signing regardless of the build type.} \cmakevariablesince 6.7 -\preliminarycmakevariable \cmakevariableandroidonly When set to \c Release, the \c --release flag is passed to the \c @@ -143,6 +142,10 @@ effectively disables release package signing even in Release or RelWithDebInfo builds. When not set, the default behavior is to use release package signing in build types other than Debug. +This variable is not supposed to be set in CMake project files. Rather set it +when configuring your project on the command line or in the CMake settings of +your IDE. + \sa {androiddeployqt} */ diff --git a/src/corelib/doc/src/external-resources.qdoc b/src/corelib/doc/src/external-resources.qdoc index 2232b49bf23..5d357c65496 100644 --- a/src/corelib/doc/src/external-resources.qdoc +++ b/src/corelib/doc/src/external-resources.qdoc @@ -28,11 +28,6 @@ */ /*! - \externalpage https://marcmutz.wordpress.com/effective-qt/containers/#containers-qlist - \title Pros and Cons of Using QList -*/ - -/*! \externalpage https://marcmutz.wordpress.com/effective-qt/containers/ \title Understand the Qt Containers */ diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h index 0b42af7686c..b2d79cca603 100644 --- a/src/corelib/global/qcompilerdetection.h +++ b/src/corelib/global/qcompilerdetection.h @@ -1404,7 +1404,7 @@ static_assert(!std::is_convertible_v<std::nullptr_t, bool>, #if defined(__cplusplus) #ifdef __cpp_constinit -# if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) +# if defined(Q_CC_MSVC) && _MSC_VER < 1940 && !defined(Q_CC_CLANG) // https://developercommunity.visualstudio.com/t/C:-constinit-for-an-optional-fails-if-/1406069 # define Q_CONSTINIT # else diff --git a/src/corelib/global/qttranslation.qdoc b/src/corelib/global/qttranslation.qdoc index 191b3777db6..c8b3764614e 100644 --- a/src/corelib/global/qttranslation.qdoc +++ b/src/corelib/global/qttranslation.qdoc @@ -179,7 +179,7 @@ \fn QString qTrId(const char *id, int n = -1) \relates <QtTranslation> \reentrant - \since 6.9 + \since 6.11 \brief The qTrId function is an alias for qtTrId. diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index 0771c15584b..d3c398f0860 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -249,6 +249,12 @@ bool QFSFileEngine::open(QIODevice::OpenMode openMode, FILE *fh, QFile::FileHand } /*! + \class QFSFileEnginePrivate + \inmodule QtCore + \internal +*/ + +/*! Opens the file handle \a fh using the open mode \a flags. */ bool QFSFileEnginePrivate::openFh(QIODevice::OpenMode openMode, FILE *fh) diff --git a/src/corelib/io/qiooperation_p.h b/src/corelib/io/qiooperation_p.h index 56845167ede..1486719a7e8 100644 --- a/src/corelib/io/qiooperation_p.h +++ b/src/corelib/io/qiooperation_p.h @@ -72,6 +72,9 @@ protected: Q_DECLARE_PRIVATE(QIOOperation) friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; class Q_CORE_EXPORT QIOReadWriteOperationBase : public QIOOperation @@ -86,6 +89,11 @@ protected: QIOReadWriteOperationBase() = delete; Q_DISABLE_COPY_MOVE(QIOReadWriteOperationBase) explicit QIOReadWriteOperationBase(QIOOperationPrivate &dd, QObject *parent = nullptr); + + friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; class Q_CORE_EXPORT QIOReadOperation : public QIOReadWriteOperationBase @@ -101,6 +109,9 @@ protected: explicit QIOReadOperation(QIOOperationPrivate &dd, QObject *parent = nullptr); friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; class Q_CORE_EXPORT QIOWriteOperation : public QIOReadWriteOperationBase @@ -116,6 +127,9 @@ protected: explicit QIOWriteOperation(QIOOperationPrivate &dd, QObject *parent = nullptr); friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; class Q_CORE_EXPORT QIOVectoredReadOperation : public QIOReadWriteOperationBase @@ -131,6 +145,9 @@ protected: explicit QIOVectoredReadOperation(QIOOperationPrivate &dd, QObject *parent = nullptr); friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; class Q_CORE_EXPORT QIOVectoredWriteOperation : public QIOReadWriteOperationBase @@ -146,6 +163,9 @@ protected: explicit QIOVectoredWriteOperation(QIOOperationPrivate &dd, QObject *parent = nullptr); friend class QRandomAccessAsyncFilePrivate; + friend class QRandomAccessAsyncFileBackend; + friend class QRandomAccessAsyncFileNativeBackend; + friend class QRandomAccessAsyncFileThreadPoolBackend; }; QT_END_NAMESPACE diff --git a/src/corelib/io/qioring_p.h b/src/corelib/io/qioring_p.h index 0db832bc6bf..8fdaf48f8f6 100644 --- a/src/corelib/io/qioring_p.h +++ b/src/corelib/io/qioring_p.h @@ -91,6 +91,10 @@ enum class Operation : quint8 { // clang-format on Q_ENUM_NS(Operation); #undef DEFINE_ENTRY + +#ifdef Q_OS_WIN +struct IORingApiTable; +#endif }; // namespace QtPrivate template <QtPrivate::Operation Op> @@ -221,9 +225,11 @@ private: std::optional<QWinEventNotifier> notifier; HIORING ioRingHandle = nullptr; HANDLE eventHandle = INVALID_HANDLE_VALUE; + const QtPrivate::IORingApiTable *apiTable; bool initialized = false; bool queueWasFull = false; + [[nodiscard]] RequestPrepResult prepareRequest(GenericRequestType &request); QIORing::ReadWriteStatus handleReadCompletion( diff --git a/src/corelib/io/qioring_win.cpp b/src/corelib/io/qioring_win.cpp index 42c51f428d6..b2f188486d0 100644 --- a/src/corelib/io/qioring_win.cpp +++ b/src/corelib/io/qioring_win.cpp @@ -24,6 +24,59 @@ static_assert(sizeof(qsizetype) > sizeof(UINT32), using namespace Qt::StringLiterals; +namespace QtPrivate { +#define FOREACH_WIN_IORING_FUNCTION(Fn) \ + Fn(BuildIoRingReadFile) \ + Fn(BuildIoRingWriteFile) \ + Fn(BuildIoRingFlushFile) \ + Fn(BuildIoRingCancelRequest) \ + Fn(QueryIoRingCapabilities) \ + Fn(CreateIoRing) \ + Fn(GetIoRingInfo) \ + Fn(SubmitIoRing) \ + Fn(CloseIoRing) \ + Fn(PopIoRingCompletion) \ + Fn(SetIoRingCompletionEvent) \ + /**/ +struct IORingApiTable +{ +#define DefineIORingFunction(Name) \ + using Name##Fn = decltype(&::Name); \ + Name##Fn Name = nullptr; + + FOREACH_WIN_IORING_FUNCTION(DefineIORingFunction) + +#undef DefineIORingFunction +}; + +static const IORingApiTable *getApiTable() +{ + static const IORingApiTable apiTable = []() { + IORingApiTable apiTable; + const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (Q_UNLIKELY(!kernel32)) // how would this happen + return apiTable; + +#define ResolveFunction(Name) \ + apiTable.Name = IORingApiTable::Name##Fn(QFunctionPointer(GetProcAddress(kernel32, #Name))); + + FOREACH_WIN_IORING_FUNCTION(ResolveFunction) + +#undef ResolveFunction + return apiTable; + }(); + +#define TEST_TABLE_OK(X) \ + apiTable.X && /* chain */ +#define BOOL_CHAIN(...) (__VA_ARGS__ true) + const bool success = BOOL_CHAIN(FOREACH_WIN_IORING_FUNCTION(TEST_TABLE_OK)); +#undef BOOL_CHAIN +#undef TEST_TABLE_OK + + return success ? std::addressof(apiTable) : nullptr; +} +} // namespace QtPrivate + static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination, quint64 offset, quintptr userData) { @@ -32,8 +85,10 @@ static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::b const IORING_BUFFER_REF bufferRef(destination.data()); const auto maxSize = q26::saturate_cast<UINT32>(destination.size()); Q_ASSERT(maxSize == destination.size()); - return BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, userData, - IOSQE_FLAGS_NONE); + const auto *apiTable = QtPrivate::getApiTable(); + Q_ASSERT(apiTable); // If we got this far it needs to be here + return apiTable->BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, + userData, IOSQE_FLAGS_NONE); } static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source, @@ -44,16 +99,18 @@ static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data())); const auto maxSize = q26::saturate_cast<UINT32>(source.size()); Q_ASSERT(maxSize == source.size()); + const auto *apiTable = QtPrivate::getApiTable(); + Q_ASSERT(apiTable); // If we got this far it needs to be here // @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode. - return BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, - FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE); + return apiTable->BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, + FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE); } QIORing::~QIORing() { if (initialized) { CloseHandle(eventHandle); - CloseIoRing(ioRingHandle); + apiTable->CloseIoRing(ioRingHandle); } } @@ -62,8 +119,13 @@ bool QIORing::initializeIORing() if (initialized) return true; + if (apiTable = QtPrivate::getApiTable(); !apiTable) { + qCWarning(lcQIORing, "Failed to retrieve API table"); + return false; + } + IORING_CAPABILITIES capabilities; - QueryIoRingCapabilities(&capabilities); + apiTable->QueryIoRingCapabilities(&capabilities); if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain return false; if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0) @@ -75,7 +137,8 @@ bool QIORing::initializeIORing() IORING_CREATE_FLAGS flags; memset(&flags, 0, sizeof(flags)); - HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries, &ioRingHandle); + HRESULT hr = apiTable->CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries, + &ioRingHandle); if (FAILED(hr)) { qErrnoWarning(hr, "failed to initialize QIORing"); return false; @@ -83,7 +146,7 @@ bool QIORing::initializeIORing() auto earlyExitCleanup = qScopeGuard([this]() { if (eventHandle != INVALID_HANDLE_VALUE) CloseHandle(eventHandle); - CloseIoRing(ioRingHandle); + apiTable->CloseIoRing(ioRingHandle); }); eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr); if (eventHandle == INVALID_HANDLE_VALUE) { @@ -91,13 +154,13 @@ bool QIORing::initializeIORing() return false; } notifier.emplace(eventHandle); - hr = SetIoRingCompletionEvent(ioRingHandle, eventHandle); + hr = apiTable->SetIoRingCompletionEvent(ioRingHandle, eventHandle); if (FAILED(hr)) { qErrnoWarning(hr, "Failed to assign the event handle to QIORing"); return false; } IORING_INFO info; - if (SUCCEEDED(GetIoRingInfo(ioRingHandle, &info))) { + if (SUCCEEDED(apiTable->GetIoRingInfo(ioRingHandle, &info))) { sqEntries = info.SubmissionQueueSize; cqEntries = info.CompletionQueueSize; qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries @@ -274,7 +337,7 @@ void QIORing::completionReady() { ResetEvent(eventHandle); IORING_CQE entry; - while (PopIoRingCompletion(ioRingHandle, &entry) == S_OK) { + while (apiTable->PopIoRingCompletion(ioRingHandle, &entry) == S_OK) { // NOLINTNEXTLINE(performance-no-int-to-ptr) auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData); if (!addrItMap.contains(request)) { @@ -458,7 +521,8 @@ void QIORing::submitRequests() const bool shouldTryWait = std::exchange(queueWasFull, false); const auto submitToRing = [this, &shouldTryWait] { quint32 submittedEntries = 0; - HRESULT hr = SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1, &submittedEntries); + HRESULT hr = apiTable->SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1, + &submittedEntries); qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests"; unstagedRequests -= submittedEntries; if (FAILED(hr)) { @@ -558,9 +622,9 @@ auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult auto *closeRequest = request.requestData<Operation::Close>(); // NOLINTNEXTLINE(performance-no-int-to-ptr) const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd)); - hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA, - quintptr(std::addressof(request)), - IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); break; } case Operation::Read: { @@ -645,9 +709,9 @@ auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult auto *flushRequest = request.requestData<Operation::Flush>(); // NOLINTNEXTLINE(performance-no-int-to-ptr) const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd)); - hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT, - quintptr(std::addressof(request)), - IOSQE_FLAGS_DRAIN_PRECEDING_OPS); + hr = apiTable->BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT, + quintptr(std::addressof(request)), + IOSQE_FLAGS_DRAIN_PRECEDING_OPS); break; } case QtPrivate::Operation::Stat: { @@ -703,8 +767,8 @@ auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult } // NOLINTNEXTLINE(performance-no-int-to-ptr) const IORING_HANDLE_REF fileRef((HANDLE(fd))); - hr = BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation), - quintptr(std::addressof(request))); + hr = apiTable->BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation), + quintptr(std::addressof(request))); break; } case Operation::NumOperations: diff --git a/src/corelib/io/qrandomaccessasyncfile.cpp b/src/corelib/io/qrandomaccessasyncfile.cpp index c544585de9d..34e216efe27 100644 --- a/src/corelib/io/qrandomaccessasyncfile.cpp +++ b/src/corelib/io/qrandomaccessasyncfile.cpp @@ -7,6 +7,31 @@ QT_BEGIN_NAMESPACE +QRandomAccessAsyncFileBackend::QRandomAccessAsyncFileBackend(QRandomAccessAsyncFile *owner) + : m_owner(owner) +{ +} +QRandomAccessAsyncFileBackend::~QRandomAccessAsyncFileBackend() = default; +QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() = default; +QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() = default; + +void QRandomAccessAsyncFilePrivate::init() +{ + Q_Q(QRandomAccessAsyncFile); + +#if defined(QT_RANDOMACCESSASYNCFILE_QIORING) || defined(Q_OS_DARWIN) + m_backend = std::make_unique<QRandomAccessAsyncFileNativeBackend>(q); +#endif + if (!m_backend || !m_backend->init()) { +#if QT_CONFIG(thread) && QT_CONFIG(future) + m_backend = std::make_unique<QRandomAccessAsyncFileThreadPoolBackend>(q); + [[maybe_unused]] + bool result = m_backend->init(); + Q_ASSERT(result); // it always succeeds +#endif + } +} + QRandomAccessAsyncFile::QRandomAccessAsyncFile(QObject *parent) : QObject{*new QRandomAccessAsyncFilePrivate, parent} { diff --git a/src/corelib/io/qrandomaccessasyncfile_darwin.mm b/src/corelib/io/qrandomaccessasyncfile_darwin.mm index 7231d12fe7d..80c1affa642 100644 --- a/src/corelib/io/qrandomaccessasyncfile_darwin.mm +++ b/src/corelib/io/qrandomaccessasyncfile_darwin.mm @@ -27,14 +27,14 @@ static bool isBarrierOperation(QIOOperation::Type type) // only! template <typename Operation, typename ...Args> Operation * -QRandomAccessAsyncFilePrivate::addOperation(QIOOperation::Type type, qint64 offset, Args &&...args) +QRandomAccessAsyncFileNativeBackend::addOperation(QIOOperation::Type type, qint64 offset, Args &&...args) { auto dataStorage = new QtPrivate::QIOOperationDataStorage(std::forward<Args>(args)...); auto *priv = new QIOOperationPrivate(dataStorage); priv->offset = offset; priv->type = type; - Operation *op = new Operation(*priv, q_ptr); + Operation *op = new Operation(*priv, m_owner); auto opId = getNextId(); m_operations.push_back(OperationInfo(opId, op)); startOperationsUntilBarrier(); @@ -42,19 +42,20 @@ QRandomAccessAsyncFilePrivate::addOperation(QIOOperation::Type type, qint64 offs return op; } -QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() - : QObjectPrivate() +QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner) + : QRandomAccessAsyncFileBackend(owner) { } -QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() +QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend() = default; -void QRandomAccessAsyncFilePrivate::init() +bool QRandomAccessAsyncFileNativeBackend::init() { + return true; } -void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) +void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op) { auto it = std::find_if(m_operations.cbegin(), m_operations.cend(), [op](const auto &opInfo) { @@ -87,7 +88,7 @@ void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) startOperationsUntilBarrier(); } -void QRandomAccessAsyncFilePrivate::close() +void QRandomAccessAsyncFileNativeBackend::close() { if (m_fileState == FileState::Closed) return; @@ -95,10 +96,8 @@ void QRandomAccessAsyncFilePrivate::close() // cancel all operations m_mutex.lock(); m_opToCancel = kAllOperationIds; - m_numChannelsToClose = m_ioChannel ? 1 : 0; for (const auto &op : m_operations) { if (op.channel) { - ++m_numChannelsToClose; closeIoChannel(op.channel); } } @@ -127,7 +126,7 @@ void QRandomAccessAsyncFilePrivate::close() m_fileState = FileState::Closed; } -qint64 QRandomAccessAsyncFilePrivate::size() const +qint64 QRandomAccessAsyncFileNativeBackend::size() const { if (m_fileState != FileState::Opened) return -1; @@ -140,7 +139,7 @@ qint64 QRandomAccessAsyncFilePrivate::size() const } QIOOperation * -QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode) +QRandomAccessAsyncFileNativeBackend::open(const QString &path, QIODeviceBase::OpenMode mode) { if (m_fileState == FileState::Closed) { m_filePath = path; @@ -153,44 +152,44 @@ QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode return addOperation<QIOOperation>(QIOOperation::Type::Open, 0); } -QIOOperation *QRandomAccessAsyncFilePrivate::flush() +QIOOperation *QRandomAccessAsyncFileNativeBackend::flush() { return addOperation<QIOOperation>(QIOOperation::Type::Flush, 0); } -QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize) +QIOReadOperation *QRandomAccessAsyncFileNativeBackend::read(qint64 offset, qint64 maxSize) { QByteArray array(maxSize, Qt::Uninitialized); return addOperation<QIOReadOperation>(QIOOperation::Type::Read, offset, std::move(array)); } -QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) +QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data) { QByteArray copy = data; return write(offset, std::move(copy)); } -QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) +QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, QByteArray &&data) { return addOperation<QIOWriteOperation>(QIOOperation::Type::Write, offset, std::move(data)); } QIOVectoredReadOperation * -QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<std::byte> buffer) +QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<std::byte> buffer) { return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset, QSpan<const QSpan<std::byte>>{buffer}); } QIOVectoredWriteOperation * -QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const std::byte> buffer) +QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const std::byte> buffer) { return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset, QSpan<const QSpan<const std::byte>>{buffer}); } QIOVectoredReadOperation * -QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) +QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) { // GCD implementation does not have vectored read. Spawning several read // operations (each with an updated offset), is not ideal, because some @@ -204,32 +203,40 @@ QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::by } QIOVectoredWriteOperation * -QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) +QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) { return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset, buffers); } -void QRandomAccessAsyncFilePrivate::notifyIfOperationsAreCompleted() +void QRandomAccessAsyncFileNativeBackend::notifyIfOperationsAreCompleted() { QMutexLocker locker(&m_mutex); + --m_numChannelsToClose; if (m_opToCancel == kAllOperationIds) { - --m_numChannelsToClose; if (m_numChannelsToClose == 0 && m_runningOps.isEmpty()) m_cancellationCondition.wakeOne(); } } -dispatch_io_t QRandomAccessAsyncFilePrivate::createMainChannel(int fd) +dispatch_io_t QRandomAccessAsyncFileNativeBackend::createMainChannel(int fd) { auto sharedThis = this; - return dispatch_io_create(DISPATCH_IO_RANDOM, fd, - dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), - ^(int /*error*/) { - sharedThis->notifyIfOperationsAreCompleted(); - }); + auto channel = + dispatch_io_create(DISPATCH_IO_RANDOM, fd, + dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), + ^(int /*error*/) { + // main I/O channel uses kInvalidOperationId + // as its identifier + sharedThis->notifyIfOperationsAreCompleted(); + }); + if (channel) { + QMutexLocker locker(&m_mutex); + ++m_numChannelsToClose; + } + return channel; } -dispatch_io_t QRandomAccessAsyncFilePrivate::duplicateIoChannel(OperationId opId) +dispatch_io_t QRandomAccessAsyncFileNativeBackend::duplicateIoChannel(OperationId opId) { if (!m_ioChannel) return nullptr; @@ -247,17 +254,18 @@ dispatch_io_t QRandomAccessAsyncFilePrivate::duplicateIoChannel(OperationId opId if (channel) { QMutexLocker locker(&m_mutex); m_runningOps.insert(opId); + ++m_numChannelsToClose; } return channel; } -void QRandomAccessAsyncFilePrivate::closeIoChannel(dispatch_io_t channel) +void QRandomAccessAsyncFileNativeBackend::closeIoChannel(dispatch_io_t channel) { if (channel) dispatch_io_close(channel, DISPATCH_IO_STOP); } -void QRandomAccessAsyncFilePrivate::releaseIoChannel(dispatch_io_t channel) +void QRandomAccessAsyncFileNativeBackend::releaseIoChannel(dispatch_io_t channel) { if (channel) { dispatch_release(channel); @@ -265,7 +273,7 @@ void QRandomAccessAsyncFilePrivate::releaseIoChannel(dispatch_io_t channel) } } -void QRandomAccessAsyncFilePrivate::handleOperationComplete(const OperationResult &opResult) +void QRandomAccessAsyncFileNativeBackend::handleOperationComplete(const OperationResult &opResult) { // try to start next operations on return auto onReturn = qScopeGuard([this] { @@ -372,15 +380,15 @@ void QRandomAccessAsyncFilePrivate::handleOperationComplete(const OperationResul } } -void QRandomAccessAsyncFilePrivate::queueCompletion(OperationId opId, int error) +void QRandomAccessAsyncFileNativeBackend::queueCompletion(OperationId opId, int error) { const OperationResult res = { opId, 0LL, error }; - QMetaObject::invokeMethod(q_ptr, [this, res] { + QMetaObject::invokeMethod(m_owner, [this, res] { handleOperationComplete(res); }, Qt::QueuedConnection); } -void QRandomAccessAsyncFilePrivate::startOperationsUntilBarrier() +void QRandomAccessAsyncFileNativeBackend::startOperationsUntilBarrier() { // starts all operations until barrier, or a barrier operation if it's the // first one @@ -414,7 +422,7 @@ void QRandomAccessAsyncFilePrivate::startOperationsUntilBarrier() } } -void QRandomAccessAsyncFilePrivate::executeRead(OperationInfo &opInfo) +void QRandomAccessAsyncFileNativeBackend::executeRead(OperationInfo &opInfo) { opInfo.channel = duplicateIoChannel(opInfo.opId); if (!opInfo.channel) { @@ -445,7 +453,7 @@ void QRandomAccessAsyncFilePrivate::executeRead(OperationInfo &opInfo) } } -void QRandomAccessAsyncFilePrivate::executeWrite(OperationInfo &opInfo) +void QRandomAccessAsyncFileNativeBackend::executeWrite(OperationInfo &opInfo) { opInfo.channel = duplicateIoChannel(opInfo.opId); if (!opInfo.channel) { @@ -494,7 +502,7 @@ void QRandomAccessAsyncFilePrivate::executeWrite(OperationInfo &opInfo) } } -void QRandomAccessAsyncFilePrivate::executeFlush(OperationInfo &opInfo) +void QRandomAccessAsyncFileNativeBackend::executeFlush(OperationInfo &opInfo) { opInfo.channel = duplicateIoChannel(opInfo.opId); if (!opInfo.channel) { @@ -523,7 +531,7 @@ void QRandomAccessAsyncFilePrivate::executeFlush(OperationInfo &opInfo) } } } else { - auto context = sharedThis->q_ptr; + auto context = sharedThis->m_owner; const OperationResult res = { opId, 0LL, err }; QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) { sharedThis->handleOperationComplete(r); @@ -555,7 +563,7 @@ static inline int openModeToOpenFlags(QIODevice::OpenMode mode) return oflags; } -void QRandomAccessAsyncFilePrivate::executeOpen(OperationInfo &opInfo) +void QRandomAccessAsyncFileNativeBackend::executeOpen(OperationInfo &opInfo) { if (m_fileState != FileState::OpenPending) { queueCompletion(opInfo.opId, EINVAL); @@ -593,9 +601,10 @@ void QRandomAccessAsyncFilePrivate::executeOpen(OperationInfo &opInfo) // So we need to notify the condition variable in // both cases. Q_ASSERT(sharedThis->m_runningOps.isEmpty()); + Q_ASSERT(sharedThis->m_numChannelsToClose == 0); sharedThis->m_cancellationCondition.wakeOne(); } else { - auto context = sharedThis->q_ptr; + auto context = sharedThis->m_owner; const OperationResult res = { opId, qint64(fd), err }; QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) { @@ -605,7 +614,7 @@ void QRandomAccessAsyncFilePrivate::executeOpen(OperationInfo &opInfo) }); } -void QRandomAccessAsyncFilePrivate::readOneBuffer(OperationId opId, qsizetype bufferIdx, +void QRandomAccessAsyncFileNativeBackend::readOneBuffer(OperationId opId, qsizetype bufferIdx, qint64 alreadyRead) { // we need to lookup the operation again, because it could have beed removed @@ -642,7 +651,7 @@ void QRandomAccessAsyncFilePrivate::readOneBuffer(OperationId opId, qsizetype bu readBuffers.size(), alreadyRead); } -void QRandomAccessAsyncFilePrivate::readOneBufferHelper(OperationId opId, dispatch_io_t channel, +void QRandomAccessAsyncFileNativeBackend::readOneBufferHelper(OperationId opId, dispatch_io_t channel, qint64 offset, void *bytesPtr, qint64 maxSize, qsizetype currentBufferIdx, qsizetype totalBuffers, qint64 alreadyRead) @@ -690,7 +699,7 @@ void QRandomAccessAsyncFilePrivate::readOneBufferHelper(OperationId opId, dispat } } else { sharedThis->m_runningOps.remove(opId); - auto context = sharedThis->q_ptr; + auto context = sharedThis->m_owner; // if error, or last buffer, or read less than expected, // report operation completion qint64 totalRead = qint64(readFromBuffer) + alreadyRead; @@ -713,7 +722,7 @@ void QRandomAccessAsyncFilePrivate::readOneBufferHelper(OperationId opId, dispat }); } -void QRandomAccessAsyncFilePrivate::writeHelper(OperationId opId, dispatch_io_t channel, +void QRandomAccessAsyncFileNativeBackend::writeHelper(OperationId opId, dispatch_io_t channel, qint64 offset, dispatch_data_t dataToWrite, qint64 dataSize) { @@ -752,7 +761,7 @@ void QRandomAccessAsyncFilePrivate::writeHelper(OperationId opId, dispatch_io_t const size_t written = dataSize - toBeWritten; [dataToWrite release]; - auto context = sharedThis->q_ptr; + auto context = sharedThis->m_owner; const OperationResult res = { opId, qint64(written), error }; QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) { @@ -762,7 +771,7 @@ void QRandomAccessAsyncFilePrivate::writeHelper(OperationId opId, dispatch_io_t }); } -QRandomAccessAsyncFilePrivate::OperationId QRandomAccessAsyncFilePrivate::getNextId() +QRandomAccessAsyncFileNativeBackend::OperationId QRandomAccessAsyncFileNativeBackend::getNextId() { // never return reserved values static OperationId opId = kInvalidOperationId; diff --git a/src/corelib/io/qrandomaccessasyncfile_p_p.h b/src/corelib/io/qrandomaccessasyncfile_p_p.h index 11ad788c884..2eb53058780 100644 --- a/src/corelib/io/qrandomaccessasyncfile_p_p.h +++ b/src/corelib/io/qrandomaccessasyncfile_p_p.h @@ -22,7 +22,7 @@ #include <QtCore/qstring.h> -#ifdef QT_RANDOMACCESSASYNCFILE_THREAD +#if QT_CONFIG(future) && QT_CONFIG(thread) #include <QtCore/private/qfsfileengine_p.h> @@ -30,7 +30,7 @@ #include <QtCore/qmutex.h> #include <QtCore/qqueue.h> -#endif // QT_RANDOMACCESSASYNCFILE_THREAD +#endif // future && thread #ifdef Q_OS_DARWIN @@ -50,41 +50,36 @@ QT_BEGIN_NAMESPACE -class QRandomAccessAsyncFilePrivate : public QObjectPrivate +class QRandomAccessAsyncFileBackend { - Q_DECLARE_PUBLIC(QRandomAccessAsyncFile) - Q_DISABLE_COPY_MOVE(QRandomAccessAsyncFilePrivate) + Q_DISABLE_COPY_MOVE(QRandomAccessAsyncFileBackend) public: - QRandomAccessAsyncFilePrivate(); - ~QRandomAccessAsyncFilePrivate() override; - - static QRandomAccessAsyncFilePrivate *get(QRandomAccessAsyncFile *file) - { return file->d_func(); } + explicit QRandomAccessAsyncFileBackend(QRandomAccessAsyncFile *owner); + virtual ~QRandomAccessAsyncFileBackend(); - void init(); - void cancelAndWait(QIOOperation *op); + virtual bool init() = 0; + virtual void cancelAndWait(QIOOperation *op) = 0; - void close(); - qint64 size() const; + virtual void close() = 0; + virtual qint64 size() const = 0; - [[nodiscard]] QIOOperation *open(const QString &path, QIODeviceBase::OpenMode mode); - [[nodiscard]] QIOOperation *flush(); + [[nodiscard]] virtual QIOOperation *open(const QString &path, QIODeviceBase::OpenMode mode) = 0; + [[nodiscard]] virtual QIOOperation *flush() = 0; - [[nodiscard]] QIOReadOperation *read(qint64 offset, qint64 maxSize); - [[nodiscard]] QIOWriteOperation *write(qint64 offset, const QByteArray &data); - [[nodiscard]] QIOWriteOperation *write(qint64 offset, QByteArray &&data); + [[nodiscard]] virtual QIOReadOperation *read(qint64 offset, qint64 maxSize) = 0; + [[nodiscard]] virtual QIOWriteOperation *write(qint64 offset, const QByteArray &data) = 0; + [[nodiscard]] virtual QIOWriteOperation *write(qint64 offset, QByteArray &&data) = 0; - [[nodiscard]] QIOVectoredReadOperation * - readInto(qint64 offset, QSpan<std::byte> buffer); - [[nodiscard]] QIOVectoredWriteOperation * - writeFrom(qint64 offset, QSpan<const std::byte> buffer); - - [[nodiscard]] QIOVectoredReadOperation * - readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers); - [[nodiscard]] QIOVectoredWriteOperation * - writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers); + [[nodiscard]] virtual QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<std::byte> buffer) = 0; + [[nodiscard]] virtual QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const std::byte> buffer) = 0; -private: + [[nodiscard]] virtual QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) = 0; + [[nodiscard]] virtual QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) = 0; +protected: // common for all backends enum class FileState : quint8 { @@ -94,32 +89,131 @@ private: }; QString m_filePath; + QRandomAccessAsyncFile *m_owner = nullptr; QIODeviceBase::OpenMode m_openMode; FileState m_fileState = FileState::Closed; +}; -#ifdef QT_RANDOMACCESSASYNCFILE_THREAD +class QRandomAccessAsyncFilePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QRandomAccessAsyncFile) + Q_DISABLE_COPY_MOVE(QRandomAccessAsyncFilePrivate) public: - struct OperationResult + QRandomAccessAsyncFilePrivate(); + ~QRandomAccessAsyncFilePrivate() override; + + static QRandomAccessAsyncFilePrivate *get(QRandomAccessAsyncFile *file) + { return file->d_func(); } + + void init(); + void cancelAndWait(QIOOperation *op) { - qint64 bytesProcessed; // either read or written - QIOOperation::Error error; - }; + checkValid(); + m_backend->cancelAndWait(op); + } + + void close() + { + checkValid(); + m_backend->close(); + } + qint64 size() const + { + checkValid(); + return m_backend->size(); + } + + [[nodiscard]] QIOOperation *open(const QString &path, QIODeviceBase::OpenMode mode) + { + checkValid(); + return m_backend->open(path, mode); + } + [[nodiscard]] QIOOperation *flush() + { + checkValid(); + return m_backend->flush(); + } + + [[nodiscard]] QIOReadOperation *read(qint64 offset, qint64 maxSize) + { + checkValid(); + return m_backend->read(offset, maxSize); + } + [[nodiscard]] QIOWriteOperation *write(qint64 offset, const QByteArray &data) + { + checkValid(); + return m_backend->write(offset, data); + } + [[nodiscard]] QIOWriteOperation *write(qint64 offset, QByteArray &&data) + { + checkValid(); + return m_backend->write(offset, std::move(data)); + } + + [[nodiscard]] QIOVectoredReadOperation *readInto(qint64 offset, QSpan<std::byte> buffer) + { + checkValid(); + return m_backend->readInto(offset, buffer); + } + [[nodiscard]] QIOVectoredWriteOperation *writeFrom(qint64 offset, QSpan<const std::byte> buffer) + { + checkValid(); + return m_backend->writeFrom(offset, buffer); + } + + [[nodiscard]] QIOVectoredReadOperation *readInto(qint64 offset, + QSpan<const QSpan<std::byte>> buffers) + { + checkValid(); + return m_backend->readInto(offset, buffers); + } + [[nodiscard]] QIOVectoredWriteOperation *writeFrom(qint64 offset, + QSpan<const QSpan<const std::byte>> buffers) + { + checkValid(); + return m_backend->writeFrom(offset, buffers); + } private: - mutable QBasicMutex m_engineMutex; - std::unique_ptr<QFSFileEngine> m_engine; - QFutureWatcher<OperationResult> m_watcher; + void checkValid() const { Q_ASSERT(m_backend); } + std::unique_ptr<QRandomAccessAsyncFileBackend> m_backend; - QQueue<QPointer<QIOOperation>> m_operations; - QPointer<QIOOperation> m_currentOperation; - qsizetype numProcessedBuffers = 0; +}; - void executeNextOperation(); - void processBufferAt(qsizetype idx); - void processFlush(); - void processOpen(); - void operationComplete(); -#elif defined(QT_RANDOMACCESSASYNCFILE_QIORING) + +#if defined(QT_RANDOMACCESSASYNCFILE_QIORING) || defined(Q_OS_DARWIN) +class QRandomAccessAsyncFileNativeBackend final : public QRandomAccessAsyncFileBackend +{ + Q_DISABLE_COPY_MOVE(QRandomAccessAsyncFileNativeBackend) +public: + explicit QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner); + ~QRandomAccessAsyncFileNativeBackend(); + + bool init() override; + void cancelAndWait(QIOOperation *op) override; + + void close() override; + qint64 size() const override; + + [[nodiscard]] QIOOperation *open(const QString &path, QIODeviceBase::OpenMode mode) override; + [[nodiscard]] QIOOperation *flush() override; + + [[nodiscard]] QIOReadOperation *read(qint64 offset, qint64 maxSize) override; + [[nodiscard]] QIOWriteOperation *write(qint64 offset, const QByteArray &data) override; + [[nodiscard]] QIOWriteOperation *write(qint64 offset, QByteArray &&data) override; + + [[nodiscard]] QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<std::byte> buffer) override; + [[nodiscard]] QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const std::byte> buffer) override; + + [[nodiscard]] QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) override; + [[nodiscard]] QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) override; + +private: +#if defined(QT_RANDOMACCESSASYNCFILE_QIORING) void queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error); void startReadIntoSingle(QIOOperation *op, const QSpan<std::byte> &to); void startWriteFromSingle(QIOOperation *op, const QSpan<const std::byte> &from); @@ -210,6 +304,61 @@ private: dispatch_data_t dataToWrite, qint64 dataSize); #endif }; +#endif // QIORing || macOS + +#if QT_CONFIG(future) && QT_CONFIG(thread) +class QRandomAccessAsyncFileThreadPoolBackend : public QRandomAccessAsyncFileBackend +{ + Q_DISABLE_COPY_MOVE(QRandomAccessAsyncFileThreadPoolBackend) +public: + explicit QRandomAccessAsyncFileThreadPoolBackend(QRandomAccessAsyncFile *owner); + ~QRandomAccessAsyncFileThreadPoolBackend(); + + bool init() override; + void cancelAndWait(QIOOperation *op) override; + + void close() override; + qint64 size() const override; + + [[nodiscard]] QIOOperation *open(const QString &path, QIODeviceBase::OpenMode mode) override; + [[nodiscard]] QIOOperation *flush() override; + + [[nodiscard]] QIOReadOperation *read(qint64 offset, qint64 maxSize) override; + [[nodiscard]] QIOWriteOperation *write(qint64 offset, const QByteArray &data) override; + [[nodiscard]] QIOWriteOperation *write(qint64 offset, QByteArray &&data) override; + + [[nodiscard]] QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<std::byte> buffer) override; + [[nodiscard]] QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const std::byte> buffer) override; + + [[nodiscard]] QIOVectoredReadOperation * + readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) override; + [[nodiscard]] QIOVectoredWriteOperation * + writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) override; + + struct OperationResult + { + qint64 bytesProcessed; // either read or written + QIOOperation::Error error; + }; +private: + + mutable QBasicMutex m_engineMutex; + std::unique_ptr<QFSFileEngine> m_engine; + QFutureWatcher<OperationResult> m_watcher; + + QQueue<QPointer<QIOOperation>> m_operations; + QPointer<QIOOperation> m_currentOperation; + qsizetype numProcessedBuffers = 0; + + void executeNextOperation(); + void processBufferAt(qsizetype idx); + void processFlush(); + void processOpen(); + void operationComplete(); +}; +#endif // future && thread QT_END_NAMESPACE diff --git a/src/corelib/io/qrandomaccessasyncfile_qioring.cpp b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp index c9783ea2856..8e1145325d8 100644 --- a/src/corelib/io/qrandomaccessasyncfile_qioring.cpp +++ b/src/corelib/io/qrandomaccessasyncfile_qioring.cpp @@ -18,18 +18,21 @@ QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcQRandomAccessIORing, "qt.core.qrandomaccessasyncfile.ioring", QtCriticalMsg); -QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() = default; +QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner) + : QRandomAccessAsyncFileBackend(owner) +{} -QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() = default; +QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend() = default; -void QRandomAccessAsyncFilePrivate::init() +bool QRandomAccessAsyncFileNativeBackend::init() { m_ioring = QIORing::sharedInstance(); if (!m_ioring) - qCCritical(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize"); + qCWarning(lcQRandomAccessIORing, "QRandomAccessAsyncFile: ioring failed to initialize"); + return m_ioring != nullptr; } -QIORing::RequestHandle QRandomAccessAsyncFilePrivate::cancel(QIORing::RequestHandle handle) +QIORing::RequestHandle QRandomAccessAsyncFileNativeBackend::cancel(QIORing::RequestHandle handle) { if (handle) { QIORingRequest<QIORing::Operation::Cancel> cancelRequest; @@ -39,7 +42,7 @@ QIORing::RequestHandle QRandomAccessAsyncFilePrivate::cancel(QIORing::RequestHan return nullptr; } -void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) +void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op) { auto *opHandle = m_opHandleMap.value(op); if (auto *handle = cancel(opHandle)) { @@ -48,7 +51,7 @@ void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) } } -void QRandomAccessAsyncFilePrivate::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error) +void QRandomAccessAsyncFileNativeBackend::queueCompletion(QIOOperationPrivate *priv, QIOOperation::Error error) { // Remove the handle now in case the user cancels or deletes the io-operation // before operationComplete is called - the null-handle will protect from @@ -61,14 +64,14 @@ void QRandomAccessAsyncFilePrivate::queueCompletion(QIOOperationPrivate *priv, Q }, Qt::QueuedConnection); } -QIOOperation *QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode) +QIOOperation *QRandomAccessAsyncFileNativeBackend::open(const QString &path, QIODeviceBase::OpenMode mode) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Open; - auto *op = new QIOOperation(*priv, q_ptr); + auto *op = new QIOOperation(*priv, m_owner); if (m_fileState != FileState::Closed) { queueCompletion(priv, QIOOperation::Error::Open); return op; @@ -115,7 +118,7 @@ QIOOperation *QRandomAccessAsyncFilePrivate::open(const QString &path, QIODevice return op; } -void QRandomAccessAsyncFilePrivate::close() +void QRandomAccessAsyncFileNativeBackend::close() { // all the operations should be aborted const auto ops = std::exchange(m_operations, {}); @@ -123,7 +126,7 @@ void QRandomAccessAsyncFilePrivate::close() // Request to cancel all of the in-flight operations: for (const auto &op : ops) { if (op) { - op->d_func()->error = QIOOperation::Error::Aborted; + QIOOperationPrivate::get(op)->error = QIOOperation::Error::Aborted; if (auto *opHandle = m_opHandleMap.value(op)) { tasksToAwait.append(cancel(opHandle)); tasksToAwait.append(opHandle); @@ -142,7 +145,7 @@ void QRandomAccessAsyncFilePrivate::close() m_fd = -1; } -qint64 QRandomAccessAsyncFilePrivate::size() const +qint64 QRandomAccessAsyncFileNativeBackend::size() const { QIORingRequest<QIORing::Operation::Stat> statRequest; statRequest.fd = m_fd; @@ -161,14 +164,14 @@ qint64 QRandomAccessAsyncFilePrivate::size() const return finalSize; } -QIOOperation *QRandomAccessAsyncFilePrivate::flush() +QIOOperation *QRandomAccessAsyncFileNativeBackend::flush() { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Flush; - auto *op = new QIOOperation(*priv, q_ptr); + auto *op = new QIOOperation(*priv, m_owner); m_operations.append(op); QIORingRequest<QIORing::Operation::Flush> flushRequest; @@ -192,7 +195,7 @@ QIOOperation *QRandomAccessAsyncFilePrivate::flush() return op; } -void QRandomAccessAsyncFilePrivate::startReadIntoSingle(QIOOperation *op, +void QRandomAccessAsyncFileNativeBackend::startReadIntoSingle(QIOOperation *op, const QSpan<std::byte> &to) { QIORingRequest<QIORing::Operation::Read> readRequest; @@ -231,7 +234,7 @@ void QRandomAccessAsyncFilePrivate::startReadIntoSingle(QIOOperation *op, m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(readRequest))); } -QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize) +QIOReadOperation *QRandomAccessAsyncFileNativeBackend::read(qint64 offset, qint64 maxSize) { QByteArray array; array.resizeForOverwrite(maxSize); @@ -241,7 +244,7 @@ QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxS priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOReadOperation(*priv, q_ptr); + auto *op = new QIOReadOperation(*priv, m_owner); m_operations.append(op); startReadIntoSingle(op, as_writable_bytes(QSpan(dataStorage->getByteArray()))); @@ -249,12 +252,12 @@ QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxS return op; } -QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) +QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data) { return write(offset, QByteArray(data)); } -void QRandomAccessAsyncFilePrivate::startWriteFromSingle(QIOOperation *op, +void QRandomAccessAsyncFileNativeBackend::startWriteFromSingle(QIOOperation *op, const QSpan<const std::byte> &from) { QIORingRequest<QIORing::Operation::Write> writeRequest; @@ -288,7 +291,7 @@ void QRandomAccessAsyncFilePrivate::startWriteFromSingle(QIOOperation *op, m_opHandleMap.insert(priv->q_func(), m_ioring->queueRequest(std::move(writeRequest))); } -QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) +QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, QByteArray &&data) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data)); @@ -296,7 +299,7 @@ QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArra priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOWriteOperation(*priv, q_ptr); + auto *op = new QIOWriteOperation(*priv, m_owner); m_operations.append(op); startWriteFromSingle(op, as_bytes(QSpan(dataStorage->getByteArray()))); @@ -304,7 +307,7 @@ QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArra return op; } -QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset, +QIOVectoredReadOperation *QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<std::byte> buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage( @@ -314,7 +317,7 @@ QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset, priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + auto *op = new QIOVectoredReadOperation(*priv, m_owner); m_operations.append(op); startReadIntoSingle(op, dataStorage->getReadSpans().first()); @@ -322,7 +325,7 @@ QIOVectoredReadOperation *QRandomAccessAsyncFilePrivate::readInto(qint64 offset, return op; } -QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, +QIOVectoredWriteOperation *QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const std::byte> buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage( @@ -332,7 +335,7 @@ QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offse priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + auto *op = new QIOVectoredWriteOperation(*priv, m_owner); m_operations.append(op); startWriteFromSingle(op, dataStorage->getWriteSpans().first()); @@ -341,7 +344,7 @@ QIOVectoredWriteOperation *QRandomAccessAsyncFilePrivate::writeFrom(qint64 offse } QIOVectoredReadOperation * -QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) +QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) { if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredRead)) return nullptr; @@ -351,7 +354,7 @@ QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::by priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + auto *op = new QIOVectoredReadOperation(*priv, m_owner); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); return op; @@ -393,7 +396,7 @@ QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::by } QIOVectoredWriteOperation * -QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) +QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) { if (!QIORing::supportsOperation(QtPrivate::Operation::VectoredWrite)) return nullptr; @@ -403,7 +406,7 @@ QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + auto *op = new QIOVectoredWriteOperation(*priv, m_owner); if (priv->offset < 0) { // The QIORing offset is unsigned, so error out now queueCompletion(priv, QIOOperation::Error::IncorrectOffset); return op; diff --git a/src/corelib/io/qrandomaccessasyncfile_threadpool.cpp b/src/corelib/io/qrandomaccessasyncfile_threadpool.cpp index 4ebcf554655..774fbadc5ad 100644 --- a/src/corelib/io/qrandomaccessasyncfile_threadpool.cpp +++ b/src/corelib/io/qrandomaccessasyncfile_threadpool.cpp @@ -64,28 +64,29 @@ static SharedThreadPool asyncFileThreadPool; } // anonymous namespace -QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate() : - QObjectPrivate() +QRandomAccessAsyncFileThreadPoolBackend::QRandomAccessAsyncFileThreadPoolBackend(QRandomAccessAsyncFile *owner) : + QRandomAccessAsyncFileBackend(owner) { asyncFileThreadPool.ref(); } -QRandomAccessAsyncFilePrivate::~QRandomAccessAsyncFilePrivate() +QRandomAccessAsyncFileThreadPoolBackend::~QRandomAccessAsyncFileThreadPoolBackend() { asyncFileThreadPool.deref(); } -void QRandomAccessAsyncFilePrivate::init() +bool QRandomAccessAsyncFileThreadPoolBackend::init() { - QObject::connect(&m_watcher, &QFutureWatcherBase::finished, q_ptr, [this]{ + QObject::connect(&m_watcher, &QFutureWatcherBase::finished, m_owner, [this]{ operationComplete(); }); - QObject::connect(&m_watcher, &QFutureWatcherBase::canceled, q_ptr, [this]{ + QObject::connect(&m_watcher, &QFutureWatcherBase::canceled, m_owner, [this]{ operationComplete(); }); + return true; } -void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) +void QRandomAccessAsyncFileThreadPoolBackend::cancelAndWait(QIOOperation *op) { if (op == m_currentOperation) { m_currentOperation = nullptr; // to discard the result @@ -97,7 +98,7 @@ void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op) } QIOOperation * -QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode) +QRandomAccessAsyncFileThreadPoolBackend::open(const QString &path, QIODeviceBase::OpenMode mode) { // We generate the command in any case. But if the file is already opened, // it will finish with an error @@ -112,14 +113,14 @@ QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Open; - auto *op = new QIOOperation(*priv, q_ptr); + auto *op = new QIOOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } -void QRandomAccessAsyncFilePrivate::close() +void QRandomAccessAsyncFileThreadPoolBackend::close() { // all the operations should be aborted for (const auto &op : std::as_const(m_operations)) { @@ -148,7 +149,7 @@ void QRandomAccessAsyncFilePrivate::close() m_fileState = FileState::Closed; } -qint64 QRandomAccessAsyncFilePrivate::size() const +qint64 QRandomAccessAsyncFileThreadPoolBackend::size() const { QMutexLocker locker(&m_engineMutex); if (m_engine) @@ -156,20 +157,20 @@ qint64 QRandomAccessAsyncFilePrivate::size() const return -1; } -QIOOperation *QRandomAccessAsyncFilePrivate::flush() +QIOOperation *QRandomAccessAsyncFileThreadPoolBackend::flush() { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(); auto *priv = new QIOOperationPrivate(dataStorage); priv->type = QIOOperation::Type::Flush; - auto *op = new QIOOperation(*priv, q_ptr); + auto *op = new QIOOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } -QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxSize) +QIOReadOperation *QRandomAccessAsyncFileThreadPoolBackend::read(qint64 offset, qint64 maxSize) { QByteArray array; array.resizeForOverwrite(maxSize); @@ -179,14 +180,14 @@ QIOReadOperation *QRandomAccessAsyncFilePrivate::read(qint64 offset, qint64 maxS priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOReadOperation(*priv, q_ptr); + auto *op = new QIOReadOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOWriteOperation * -QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) +QRandomAccessAsyncFileThreadPoolBackend::write(qint64 offset, const QByteArray &data) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(data); @@ -194,14 +195,14 @@ QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data) priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOWriteOperation(*priv, q_ptr); + auto *op = new QIOWriteOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOWriteOperation * -QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) +QRandomAccessAsyncFileThreadPoolBackend::write(qint64 offset, QByteArray &&data) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(std::move(data)); @@ -209,14 +210,14 @@ QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data) priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOWriteOperation(*priv, q_ptr); + auto *op = new QIOWriteOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOVectoredReadOperation * -QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<std::byte> buffer) +QRandomAccessAsyncFileThreadPoolBackend::readInto(qint64 offset, QSpan<std::byte> buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(QSpan<const QSpan<std::byte>>{buffer}); @@ -225,14 +226,14 @@ QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<std::byte> buffer) priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + auto *op = new QIOVectoredReadOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOVectoredWriteOperation * -QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const std::byte> buffer) +QRandomAccessAsyncFileThreadPoolBackend::writeFrom(qint64 offset, QSpan<const std::byte> buffer) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(QSpan<const QSpan<const std::byte>>{buffer}); @@ -241,14 +242,14 @@ QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const std::byte> b priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + auto *op = new QIOVectoredWriteOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOVectoredReadOperation * -QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) +QRandomAccessAsyncFileThreadPoolBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); @@ -256,14 +257,14 @@ QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::by priv->offset = offset; priv->type = QIOOperation::Type::Read; - auto *op = new QIOVectoredReadOperation(*priv, q_ptr); + auto *op = new QIOVectoredReadOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } QIOVectoredWriteOperation * -QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) +QRandomAccessAsyncFileThreadPoolBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers) { auto *dataStorage = new QtPrivate::QIOOperationDataStorage(buffers); @@ -271,16 +272,16 @@ QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const priv->offset = offset; priv->type = QIOOperation::Type::Write; - auto *op = new QIOVectoredWriteOperation(*priv, q_ptr); + auto *op = new QIOVectoredWriteOperation(*priv, m_owner); m_operations.append(op); executeNextOperation(); return op; } -static QRandomAccessAsyncFilePrivate::OperationResult +static QRandomAccessAsyncFileThreadPoolBackend::OperationResult executeRead(QFSFileEngine *engine, QBasicMutex *mutex, qint64 offset, char *buffer, qint64 maxSize) { - QRandomAccessAsyncFilePrivate::OperationResult result{0, QIOOperation::Error::None}; + QRandomAccessAsyncFileThreadPoolBackend::OperationResult result{0, QIOOperation::Error::None}; QMutexLocker locker(mutex); if (engine) { @@ -299,11 +300,11 @@ executeRead(QFSFileEngine *engine, QBasicMutex *mutex, qint64 offset, char *buff return result; } -static QRandomAccessAsyncFilePrivate::OperationResult +static QRandomAccessAsyncFileThreadPoolBackend::OperationResult executeWrite(QFSFileEngine *engine, QBasicMutex *mutex, qint64 offset, const char *buffer, qint64 size) { - QRandomAccessAsyncFilePrivate::OperationResult result{0, QIOOperation::Error::None}; + QRandomAccessAsyncFileThreadPoolBackend::OperationResult result{0, QIOOperation::Error::None}; QMutexLocker locker(mutex); if (engine) { @@ -322,7 +323,7 @@ executeWrite(QFSFileEngine *engine, QBasicMutex *mutex, qint64 offset, return result; } -void QRandomAccessAsyncFilePrivate::executeNextOperation() +void QRandomAccessAsyncFileThreadPoolBackend::executeNextOperation() { if (m_currentOperation.isNull()) { // start next @@ -351,7 +352,7 @@ void QRandomAccessAsyncFilePrivate::executeNextOperation() } } -void QRandomAccessAsyncFilePrivate::processBufferAt(qsizetype idx) +void QRandomAccessAsyncFileThreadPoolBackend::processBufferAt(qsizetype idx) { Q_ASSERT(!m_currentOperation.isNull()); auto *priv = QIOOperationPrivate::get(m_currentOperation.get()); @@ -417,7 +418,7 @@ void QRandomAccessAsyncFilePrivate::processBufferAt(qsizetype idx) } } -void QRandomAccessAsyncFilePrivate::processFlush() +void QRandomAccessAsyncFileThreadPoolBackend::processFlush() { Q_ASSERT(!m_currentOperation.isNull()); auto *priv = QIOOperationPrivate::get(m_currentOperation.get()); @@ -427,7 +428,7 @@ void QRandomAccessAsyncFilePrivate::processFlush() QBasicMutex *mutexPtr = &m_engineMutex; auto op = [engine = m_engine.get(), mutexPtr] { QMutexLocker locker(mutexPtr); - QRandomAccessAsyncFilePrivate::OperationResult result{0, QIOOperation::Error::None}; + QRandomAccessAsyncFileThreadPoolBackend::OperationResult result{0, QIOOperation::Error::None}; if (engine) { if (!engine->flush()) result.error = QIOOperation::Error::Flush; @@ -442,7 +443,7 @@ void QRandomAccessAsyncFilePrivate::processFlush() m_watcher.setFuture(f); } -void QRandomAccessAsyncFilePrivate::processOpen() +void QRandomAccessAsyncFileThreadPoolBackend::processOpen() { Q_ASSERT(!m_currentOperation.isNull()); auto *priv = QIOOperationPrivate::get(m_currentOperation.get()); @@ -457,7 +458,7 @@ void QRandomAccessAsyncFilePrivate::processOpen() m_engineMutex.unlock(); QBasicMutex *mutexPtr = &m_engineMutex; auto op = [engine = m_engine.get(), mutexPtr, mode = m_openMode] { - QRandomAccessAsyncFilePrivate::OperationResult result{0, QIOOperation::Error::None}; + QRandomAccessAsyncFileThreadPoolBackend::OperationResult result{0, QIOOperation::Error::None}; QMutexLocker locker(mutexPtr); const bool res = engine && engine->open(mode | QIODeviceBase::Unbuffered, std::nullopt); @@ -468,13 +469,13 @@ void QRandomAccessAsyncFilePrivate::processOpen() f = QtFuture::makeReadyVoidFuture().then(asyncFileThreadPool(), op); } else { f = QtFuture::makeReadyVoidFuture().then(asyncFileThreadPool(), [] { - return QRandomAccessAsyncFilePrivate::OperationResult{0, QIOOperation::Error::Open}; + return QRandomAccessAsyncFileThreadPoolBackend::OperationResult{0, QIOOperation::Error::Open}; }); } m_watcher.setFuture(f); } -void QRandomAccessAsyncFilePrivate::operationComplete() +void QRandomAccessAsyncFileThreadPoolBackend::operationComplete() { // TODO: if one of the buffers was read/written with an error, // stop processing immediately diff --git a/src/corelib/io/qwindowspipereader.cpp b/src/corelib/io/qwindowspipereader.cpp index 456a209af37..6edf4395887 100644 --- a/src/corelib/io/qwindowspipereader.cpp +++ b/src/corelib/io/qwindowspipereader.cpp @@ -14,6 +14,11 @@ using namespace Qt::StringLiterals; static const DWORD minReadBufferSize = 4096; +/*! + \class QWindowsPipeReader + \inmodule QtCore + \internal +*/ QWindowsPipeReader::QWindowsPipeReader(QObject *parent) : QObject(parent), handle(INVALID_HANDLE_VALUE), diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index f6b08099fe7..7eca3094a66 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -239,17 +239,15 @@ namespace QRangeModelDetails : std::true_type {}; - // we use std::rotate in moveRows/Columns, which requires std::swap and the - // iterators to be at least a forward iterator - template <typename It, typename = void> - struct test_rotate : std::false_type {}; - + // we use std::rotate in moveRows/Columns, which requires the values (which + // might be const if we only get a const iterator) to be swappable, and the + // iterator type to be at least a forward iterator template <typename It> - struct test_rotate<It, std::void_t<decltype(std::swap(*std::declval<It>(), - *std::declval<It>()))>> - : std::is_base_of<std::forward_iterator_tag, - typename std::iterator_traits<It>::iterator_category> - {}; + using test_rotate = std::conjunction< + std::is_swappable<decltype(*std::declval<It>())>, + std::is_base_of<std::forward_iterator_tag, + typename std::iterator_traits<It>::iterator_category> + >; template <typename C, typename = void> struct test_splice : std::false_type {}; diff --git a/src/corelib/itemmodels/qrangemodeladapter.h b/src/corelib/itemmodels/qrangemodeladapter.h index bd3342e6185..0234c402248 100644 --- a/src/corelib/itemmodels/qrangemodeladapter.h +++ b/src/corelib/itemmodels/qrangemodeladapter.h @@ -19,13 +19,11 @@ class QT_TECH_PREVIEW_API QRangeModelAdapter #ifdef Q_QDOC using range_type = Range; - using const_row_reference = typename std::iterator_traits<Range>::const_reference; - using row_reference = typename std::iterator_traits<Range>::reference; #else using range_type = QRangeModelDetails::wrapped_t<Range>; +#endif using const_row_reference = typename Impl::const_row_reference; using row_reference = typename Impl::row_reference; -#endif using range_features = typename QRangeModelDetails::range_traits<range_type>; using row_type = std::remove_reference_t<row_reference>; using row_features = QRangeModelDetails::range_traits<typename Impl::wrapped_row_type>; @@ -78,16 +76,22 @@ class QT_TECH_PREVIEW_API QRangeModelAdapter template <typename C> using if_compatible_row_range = std::enable_if_t<is_compatible_row_range<C>, bool>; template <typename Data> - static constexpr bool is_compatible_data = true; - // std::is_convertible_v<Data, decltype(*std::begin(std::declval<const_row_reference>()))>; + static constexpr bool is_compatible_data = std::is_convertible_v<Data, data_type>; template <typename Data> using if_compatible_data = std::enable_if_t<is_compatible_data<Data>, bool>; template <typename C> static constexpr bool is_compatible_data_range = is_compatible_data< + typename QRangeModelDetails::data_type< + typename QRangeModelDetails::row_traits< decltype(*std::begin(std::declval<C&>())) - >; + >::item_type + >::type + >; template <typename C> - using if_compatible_data_range = std::enable_if_t<is_compatible_data_range<C>, bool>; + using if_compatible_column_data = std::enable_if_t<is_compatible_data<C> + || is_compatible_data_range<C>, bool>; + template <typename C> + using if_compatible_column_range = std::enable_if_t<is_compatible_data_range<C>, bool>; template <typename R> using if_assignable_range = std::enable_if_t<std::is_assignable_v<range_type, R>, bool>; @@ -141,6 +145,7 @@ public: } DataReference(const DataReference &other) = default; + DataReference(DataReference &&other) = default; // reference (not std::reference_wrapper) semantics DataReference &operator=(const DataReference &other) @@ -149,29 +154,23 @@ public: return *this; } + DataReference &operator=(DataReference &&other) + { + *this = other.get(); + return *this; + } + ~DataReference() = default; DataReference &operator=(const value_type &value) { - constexpr Qt::ItemDataRole dataRole = Qt::RangeModelAdapterRole; + assign(value); + return *this; + } - if (m_index.isValid()) { - auto model = const_cast<QAbstractItemModel *>(m_index.model()); - [[maybe_unused]] bool couldWrite = false; - if constexpr (std::is_same_v<q20::remove_cvref_t<value_type>, QVariant>) - couldWrite = model->setData(m_index, value, dataRole); - else - couldWrite = model->setData(m_index, QVariant::fromValue(value), dataRole); -#ifndef QT_NO_DEBUG - if (!couldWrite) { - qWarning() << "Writing value of type" << QMetaType::fromType<value_type>().name() - << "to role" << dataRole << "at index" << m_index - << "of the model failed"; - } - } else { - qCritical("Data reference for invalid index, can't write to model"); -#endif - } + DataReference &operator=(value_type &&value) + { + assign(std::move(value)); return *this; } @@ -196,6 +195,33 @@ public: private: QModelIndex m_index; + template <typename Value> + void assign(Value &&value) + { + constexpr Qt::ItemDataRole dataRole = Qt::RangeModelAdapterRole; + + if (m_index.isValid()) { + auto model = const_cast<QAbstractItemModel *>(m_index.model()); + [[maybe_unused]] bool couldWrite = false; + if constexpr (std::is_same_v<q20::remove_cvref_t<Value>, QVariant>) { + couldWrite = model->setData(m_index, value, dataRole); + } else { + couldWrite = model->setData(m_index, + QVariant::fromValue(std::forward<Value>(value)), + dataRole); + } +#ifndef QT_NO_DEBUG + if (!couldWrite) { + qWarning() << "Writing value of type" + << QMetaType::fromType<q20::remove_cvref_t<Value>>().name() + << "to role" << dataRole << "at index" << m_index << "failed"; + } + } else { + qCritical("Data reference for invalid index, can't write to model"); +#endif + } + } + friend inline bool comparesEqual(const DataReference &lhs, const DataReference &rhs) { return lhs.m_index == rhs.m_index @@ -1020,60 +1046,55 @@ public: template <typename NewRange = range_type, if_assignable_range<NewRange> = true> void setRange(NewRange &&newRange) { - using namespace QRangeModelDetails; - - auto *impl = storage.implementation(); - const QModelIndex root = storage.root(); - const qsizetype newLastRow = qsizetype(Impl::size(refTo(newRange))) - 1; - auto *oldRange = impl->childRange(root); - const qsizetype oldLastRow = qsizetype(Impl::size(oldRange)) - 1; - - if (!root.isValid()) { - impl->beginResetModel(); - impl->deleteOwnedRows(); - } else if constexpr (is_tree<Impl>) { - if (oldLastRow > 0) { - impl->beginRemoveRows(root, 0, model()->rowCount(root) - 1); - impl->deleteRemovedRows(refTo(oldRange)); - impl->endRemoveRows(); - } - if (newLastRow > 0) - impl->beginInsertRows(root, 0, newLastRow); - } else { - Q_ASSERT_X(false, "QRangeModelAdapter::setRange", - "Internal error: The root index in a table or list must be invalid."); - } - refTo(oldRange) = std::forward<NewRange>(newRange); - if (!root.isValid()) { - impl->endResetModel(); - } else if constexpr (is_tree<Impl>) { - if (newLastRow > 0) { - Q_ASSERT(model()->hasChildren(root)); - // if it was moved, then newRange is now likely to be empty. Get - // the inserted row. - impl->setParentRow(refTo(impl->childRange(storage.root())), - pointerTo(impl->rowData(root))); - impl->endInsertRows(); - } - } - if constexpr (Impl::itemsAreQObjects) { - if (model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { - const auto begin = QRangeModelDetails::begin(refTo(oldRange)); - const auto end = QRangeModelDetails::end(refTo(oldRange)); - int rowIndex = 0; - for (auto it = begin; it != end; ++it, ++rowIndex) - impl->autoConnectPropertiesInRow(*it, rowIndex, root); - } - } + setRangeImpl(qsizetype(Impl::size(QRangeModelDetails::refTo(newRange))) - 1, + [&newRange](auto &oldRange) { + oldRange = std::forward<NewRange>(newRange); + }); } - template <typename NewRange = range_type, if_assignable_range<NewRange> = true> + template <typename NewRange = range_type, if_assignable_range<NewRange> = true, + unless_adapter<NewRange> = true> QRangeModelAdapter &operator=(NewRange &&newRange) { setRange(std::forward<NewRange>(newRange)); return *this; } + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + void setRange(std::initializer_list<Row> newRange) + { + setRangeImpl(qsizetype(newRange.size() - 1), [&newRange](auto &oldRange) { + oldRange = newRange; + }); + } + + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + QRangeModelAdapter &operator=(std::initializer_list<Row> newRange) + { + setRange(newRange); + return *this; + } + + template <typename Row, if_assignable_range<std::initializer_list<Row>> = true> + void assign(std::initializer_list<Row> newRange) + { + setRange(newRange); + } + + template <typename InputIterator, typename Sentinel, typename I = Impl, if_writable<I> = true> + void setRange(InputIterator first, Sentinel last) + { + setRangeImpl(qsizetype(std::distance(first, last) - 1), [first, last](auto &oldRange) { + oldRange.assign(first, last); + }); + } + + template <typename InputIterator, typename Sentinel, typename I = Impl, if_writable<I> = true> + void assign(InputIterator first, Sentinel last) + { + setRange(first, last); + } + // iterator API ConstRowIterator cbegin() const { @@ -1245,12 +1266,12 @@ public: decltype(auto) operator[](int row) const { return at(row); } template <typename I = Impl, if_table<I> = true, if_writable<I> = true> - decltype(auto) at(int row) + auto at(int row) { return RowReference{index(row, 0), this}; } template <typename I = Impl, if_table<I> = true, if_writable<I> = true> - decltype(auto) operator[](int row) { return at(row); } + auto operator[](int row) { return at(row); } // at/operator[int, int] for table: returns value at row/column template <typename I = Impl, unless_list<I> = true> @@ -1428,15 +1449,15 @@ public: return storage.m_model->insertColumn(before); } - template <typename D = row_type, typename I = Impl, - if_canInsertColumns<I> = true, if_compatible_data<D> = true> + template <typename D, typename I = Impl, + if_canInsertColumns<I> = true, if_compatible_column_data<D> = true> bool insertColumn(int before, D &&data) { return insertColumnImpl(before, storage.root(), std::forward<D>(data)); } template <typename C, typename I = Impl, - if_canInsertColumns<I> = true, if_compatible_data_range<C> = true> + if_canInsertColumns<I> = true, if_compatible_column_range<C> = true> bool insertColumns(int before, C &&data) { return insertColumnsImpl(before, storage.root(), std::forward<C>(data)); @@ -1503,6 +1524,65 @@ private: Q_EMIT storage.implementation()->dataChanged(topLeft, bottomRight, {}); } + void beginSetRangeImpl(Impl *impl, range_type *oldRange, qsizetype newLastRow) + { + const QModelIndex root = storage.root(); + const qsizetype oldLastRow = qsizetype(Impl::size(oldRange)) - 1; + + if (!root.isValid()) { + impl->beginResetModel(); + impl->deleteOwnedRows(); + } else if constexpr (is_tree<Impl>) { + if (oldLastRow > 0) { + impl->beginRemoveRows(root, 0, model()->rowCount(root) - 1); + impl->deleteRemovedRows(QRangeModelDetails::refTo(oldRange)); + impl->endRemoveRows(); + } + if (newLastRow > 0) + impl->beginInsertRows(root, 0, newLastRow); + } else { + Q_ASSERT_X(false, "QRangeModelAdapter::setRange", + "Internal error: The root index in a table or list must be invalid."); + } + } + + void endSetRangeImpl(Impl *impl, qsizetype newLastRow) + { + const QModelIndex root = storage.root(); + if (!root.isValid()) { + impl->endResetModel(); + } else if constexpr (is_tree<Impl>) { + if (newLastRow > 0) { + Q_ASSERT(model()->hasChildren(root)); + // if it was moved, then newRange is now likely to be empty. Get + // the inserted row. + impl->setParentRow(QRangeModelDetails::refTo(impl->childRange(root)), + QRangeModelDetails::pointerTo(impl->rowData(root))); + impl->endInsertRows(); + } + } + } + + template <typename Assigner> + void setRangeImpl(qsizetype newLastRow, Assigner &&assigner) + { + auto *impl = storage.implementation(); + auto *oldRange = impl->childRange(storage.root()); + beginSetRangeImpl(impl, oldRange, newLastRow); + assigner(QRangeModelDetails::refTo(oldRange)); + endSetRangeImpl(impl, newLastRow); + + if constexpr (Impl::itemsAreQObjects) { + if (model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) { + const auto begin = QRangeModelDetails::begin(QRangeModelDetails::refTo(oldRange)); + const auto end = QRangeModelDetails::end(QRangeModelDetails::refTo(oldRange)); + int rowIndex = 0; + for (auto it = begin; it != end; ++it, ++rowIndex) + impl->autoConnectPropertiesInRow(*it, rowIndex, storage.root()); + } + } + } + template <typename P> static auto setParentRow(P protocol, row_type &newRow, row_ptr parentRow) -> decltype(protocol.setParentRow(std::declval<row_type&>(), std::declval<row_ptr>())) diff --git a/src/corelib/itemmodels/qrangemodeladapter.qdoc b/src/corelib/itemmodels/qrangemodeladapter.qdoc index 263bff0dd0c..88872589299 100644 --- a/src/corelib/itemmodels/qrangemodeladapter.qdoc +++ b/src/corelib/itemmodels/qrangemodeladapter.qdoc @@ -277,14 +277,6 @@ */ /*! - \typedef QRangeModelAdapter::const_row_reference -*/ - -/*! - \typedef QRangeModelAdapter::row_reference -*/ - -/*! \typedef QRangeModelAdapter::range_type */ @@ -329,16 +321,39 @@ /*! \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(NewRange &&newRange) - \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(NewRange &&newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>, QRangeModelAdapter<Range, Protocol, Model>::unless_adapter<NewRange>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(NewRange &&newRange) + + Replaces the contents of the model with the rows in \a newRange, possibly + using move semantics. - Assigns \a newRange to the stored range, possibly using move semantics. This function makes the model() emit the \l{QAbstractItemModel::}{modelAboutToBeReset()} and \l{QAbstractItemModel::}{modelReset()} signals. \constraints \c Range is mutable, and \a newRange is of a type that can be - assigned to \c Range. + assigned to \c Range, but not a QRangeModelAdapter. + + \sa range(), at(), model(), assign() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(std::initializer_list<Row> newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::assign(std::initializer_list<Row> newRange) + \fn template <typename Range, typename Protocol, typename Model> template <typename Row, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<std::initializer_list<Row>>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(std::initializer_list<Row> newRange) + + Replaces the contents of the model with the rows in \a newRange. - \sa range(), at(), model() + \constraints \c Range is mutable, and \a newRange can be assigned to \c Range. + + \sa range(), at, model() +*/ + +/*! + \fn template <typename Range, typename Protocol, typename Model> template <typename InputIterator, typename Sentinel, typename I = Impl, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(InputIterator first, Sentinel last) + \fn template <typename Range, typename Protocol, typename Model> template <typename InputIterator, typename Sentinel, typename I = Impl, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> void QRangeModelAdapter<Range, Protocol, Model>::assign(InputIterator first, Sentinel last) + + Replaces the contents of the models with the rows in the range [\a first, \a last). + + \sa range(), at, model() */ /*! @@ -508,8 +523,8 @@ */ /*! - \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row) const - \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::at(int row) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const \return a constant reference to the row at \a row, as stored in \c Range. @@ -517,8 +532,8 @@ */ /*! - \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row) - \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) \return a mutable reference to the row at \a row, as stored in \c Range. @@ -574,6 +589,16 @@ */ /*! + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path) const + \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> decltype(auto) QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path) const + + \return a constant reference to the row specified by \a path, as stored in + \c Range. + + \constraints \c Range is a tree. +*/ + +/*! \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path) \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path) @@ -810,7 +835,7 @@ */ /*! - \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before, D &&data) + \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_column_data<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before, D &&data) \overload Inserts a single column constructed from \a data before the column specified @@ -838,7 +863,7 @@ */ /*! - \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumns(int before, C &&data) + \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_column_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumns(int before, C &&data) Inserts columns constructed from the elements in \a data before the column specified by \a before into all rows, and returns whether the insertion was diff --git a/src/corelib/kernel/qassociativeiterable.h b/src/corelib/kernel/qassociativeiterable.h index 39f66d45fa0..4f9bbf67bfb 100644 --- a/src/corelib/kernel/qassociativeiterable.h +++ b/src/corelib/kernel/qassociativeiterable.h @@ -156,10 +156,12 @@ inline QVariantRef<QAssociativeIterator>::operator QVariant() const if (!metaType.isValid()) return m_pointer->key(); + return [&] { QVariant v(metaType); metaAssociation.mappedAtIterator(m_pointer->constIterator(), metaType == QMetaType::fromType<QVariant>() ? &v : v.data()); return v; + }(); } template<> diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm index 687fc7e85fa..f5f1f4a8cb8 100644 --- a/src/corelib/kernel/qcore_mac.mm +++ b/src/corelib/kernel/qcore_mac.mm @@ -22,6 +22,10 @@ #include <spawn.h> #include <qdebug.h> +#include <qpoint.h> +#include <qsize.h> +#include <qrect.h> +#include <qmargins.h> #include "qendian.h" #include "qhash.h" @@ -222,6 +226,34 @@ QDebug operator<<(QDebug dbg, CFStringRef stringRef) return dbg; } +QDebug operator<<(QDebug dbg, CGPoint point) +{ + dbg << QPointF::fromCGPoint(point); + return dbg; +} + +QDebug operator<<(QDebug dbg, CGSize size) +{ + dbg << QSizeF::fromCGSize(size); + return dbg; +} + +QDebug operator<<(QDebug dbg, CGRect rect) +{ + dbg << QRectF::fromCGRect(rect); + return dbg; +} + +#if defined(Q_OS_MACOS) +QDebug operator<<(QDebug dbg, NSEdgeInsets insets) +#else +QDebug operator<<(QDebug dbg, UIEdgeInsets insets) +#endif +{ + dbg << QMargins(insets.left, insets.top, insets.right, insets.bottom); + return dbg; +} + // Prevents breaking the ODR in case we introduce support for more types // later on, and lets the user override our default QDebug operators. #define QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType) \ diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h index 2c4b4c02c55..1e57ee01e1d 100644 --- a/src/corelib/kernel/qcore_mac_p.h +++ b/src/corelib/kernel/qcore_mac_p.h @@ -78,6 +78,15 @@ kern_return_t IOObjectRelease(io_object_t object); Q_FORWARD_DECLARE_OBJC_CLASS(NSObject); Q_FORWARD_DECLARE_OBJC_CLASS(NSString); +struct CGPoint; +struct CGSize; +struct CGRect; +#if defined(Q_OS_MACOS) +struct NSEdgeInsets; +#else +struct UIEdgeInsets; +#endif + // @compatibility_alias doesn't work with categories or their methods #define QtExtras QT_MANGLE_NAMESPACE(QtExtras) @@ -225,6 +234,14 @@ Q_AUTOTEST_EXPORT void qt_mac_ensureResponsible(); #ifndef QT_NO_DEBUG_STREAM Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool); Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QCFString &string); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGPoint); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGSize); +Q_CORE_EXPORT QDebug operator<<(QDebug, CGRect); +#if defined(Q_OS_MACOS) +Q_CORE_EXPORT QDebug operator<<(QDebug, NSEdgeInsets); +#else +Q_CORE_EXPORT QDebug operator<<(QDebug, UIEdgeInsets); +#endif #endif Q_CORE_EXPORT bool qt_apple_isApplicationExtension(); diff --git a/src/corelib/kernel/qeventdispatcher_cf.mm b/src/corelib/kernel/qeventdispatcher_cf.mm index 2dddf147f85..d57c057a21a 100644 --- a/src/corelib/kernel/qeventdispatcher_cf.mm +++ b/src/corelib/kernel/qeventdispatcher_cf.mm @@ -207,6 +207,7 @@ QEventLoop *QEventDispatcherCoreFoundation::currentEventLoop() const } /*! + \internal Processes all pending events that match \a flags until there are no more events to process. Returns \c true if pending events were handled; otherwise returns \c false. diff --git a/src/corelib/kernel/qmetacontainer.h b/src/corelib/kernel/qmetacontainer.h index c9d3a6bf9c6..66047afefd4 100644 --- a/src/corelib/kernel/qmetacontainer.h +++ b/src/corelib/kernel/qmetacontainer.h @@ -971,18 +971,11 @@ public: Iterator mutableEnd(); QVariant at(qsizetype idx) const; - void set(qsizetype idx, const QVariant &value); + void setAt(qsizetype idx, const QVariant &value); void append(const QVariant &value); void prepend(const QVariant &value); void removeLast(); void removeFirst(); - -#if QT_DEPRECATED_SINCE(6, 11) - enum Position: quint8 { Unspecified, AtBegin, AtEnd }; - void addValue(const QVariant &value, Position position = Unspecified); - void removeValue(Position position = Unspecified); - QMetaType valueMetaType() const; -#endif // QT_DEPRECATED_SINCE(6, 11) }; #else using Iterable = QtMetaContainerPrivate::Sequence; diff --git a/src/corelib/kernel/qmetasequence.cpp b/src/corelib/kernel/qmetasequence.cpp index 2a3a923d5ca..018dd610146 100644 --- a/src/corelib/kernel/qmetasequence.cpp +++ b/src/corelib/kernel/qmetasequence.cpp @@ -531,39 +531,6 @@ void QMetaSequence::valueAtConstIterator(const void *iterator, void *result) con */ /*! - \enum QMetaSequence::Iterable::Position - \deprecated [6.11] Use append(), prepend(), removeFirst(), or removeLast() - - Specifies the position at which an element shall be added to or removed from - the iterable. - - \value AtBegin - Add or remove at the beginning of the iterable. - \value AtEnd - Add or remove at the end of the iterable. - \value Unspecified - Add or remove at an unspecified position in the iterable. - */ - -/*! - \fn void QMetaSequence::Iterable::addValue(const QVariant &value, Position position) - \deprecated [6.11] Use append() or prepend() - Adds \a value to the container, at \a position, if possible. - */ - -/*! - \deprecated [6.11] Use removeFirst() or removeLast() - \fn void QMetaSequence::Iterable::removeValue(Position position) - Removes a value from the container, at \a position, if possible. - */ - -/*! - \deprecated [6.11] Use QMetaSequence::valueMetaType() - \fn QMetaType QMetaSequence::Iterable::valueMetaType() const - Returns the meta type for values stored in the underlying container. - */ - -/*! \fn QVariant QMetaSequence::Iterable::at(qsizetype idx) const Returns the value at position \a idx in the container. @@ -574,13 +541,8 @@ void QMetaSequence::valueAtConstIterator(const void *iterator, void *result) con */ /*! - \fn void QMetaSequence::Iterable::set(qsizetype idx, const QVariant &value) + \fn void QMetaSequence::Iterable::setAt(qsizetype idx, const QVariant &value) Sets the element at position \a idx in the container to \a value. - - \note If the underlying container does not provide a native way to assign - an element at an index, this method will synthesize the assignment - using iterators. This behavior is deprecated and will be removed in a - future version of Qt. */ /*! diff --git a/src/corelib/kernel/qmetasequence.h b/src/corelib/kernel/qmetasequence.h index 26156e7924f..f8052476d79 100644 --- a/src/corelib/kernel/qmetasequence.h +++ b/src/corelib/kernel/qmetasequence.h @@ -196,25 +196,11 @@ public: }); } - void set(qsizetype idx, const QVariant &value) + void setAt(qsizetype idx, const QVariant &value) { const QMetaSequence meta = metaContainer(); QtPrivate::QVariantTypeCoercer coercer; - const void *dataPtr = coercer.coerce(value, meta.valueMetaType()); - if (meta.canSetValueAtIndex()) { - meta.setValueAtIndex(mutableIterable(), idx, dataPtr); - return; - } - -#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) - // We shouldn't second-guess the underlying container - QtPrivate::warnSynthesizedAccess( - "set() called on an iterable without native indexed accessors. This is slow"); - void *it = meta.begin(m_iterable.mutablePointer()); - meta.advanceIterator(it, idx); - meta.setValueAtIterator(it, dataPtr); - meta.destroyIterator(it); -#endif + meta.setValueAtIndex(mutableIterable(), idx, coercer.coerce(value, meta.valueMetaType())); } void append(const QVariant &value) @@ -261,6 +247,9 @@ public: QMetaType valueMetaType() const Q_DECL_EQ_DELETE_X("Use QMetaSequence::valueMetaType() instead."); + void set(qsizetype idx, const QVariant &value) + Q_DECL_EQ_DELETE_X("Use setAt() instead."); + QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 11) }; diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index 6767917e176..69383dafdd9 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -119,7 +119,7 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); To ensure the relevant permission backend is included with your application, please \l {QT_ANDROID_PACKAGE_SOURCE_DIR} {point the build system to your custom \c AndroidManifest.xml} - or use \l {qt_add_android_permission()}. + or use \l {qt_add_android_permission}(). The relevant permission names are described in the documentation for each permission type. diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index d538ed7b4e9..9141a8f8bad 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -402,6 +402,16 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec } /*! + \class QUntypedPropertyBinding + \inmodule QtCore + \since 6.0 + \ingroup tools + \brief Represents a type-erased property binding. + + \sa QUntypedBindable +*/ + +/*! Constructs a null QUntypedPropertyBinding. \sa isNull() @@ -409,8 +419,8 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec QUntypedPropertyBinding::QUntypedPropertyBinding() = default; /*! - \fn template<typename Functor> - QUntypedPropertyBinding(QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) + \fn template<typename Functor> QUntypedPropertyBinding( + QMetaType metaType, Functor &&f, const QPropertyBindingSourceLocation &location) \internal */ @@ -448,7 +458,6 @@ QUntypedPropertyBinding::QUntypedPropertyBinding(const QUntypedPropertyBinding & : d(other.d) { } - /*! Copy-assigns \a other to this QUntypedPropertyBinding. */ @@ -1183,7 +1192,7 @@ QString QPropertyBindingError::description() const \return \c true when the binding was successfully set. - //! \sa QUntypedPropertyBinding::valueMetaType() + \sa QUntypedPropertyBinding::valueMetaType() */ /*! @@ -1199,8 +1208,7 @@ QString QPropertyBindingError::description() const Returns the metatype of the property from which the QUntypedBindable was created. If the bindable is invalid, an invalid metatype will be returned. - \sa isValid() - //! \sa QUntypedPropertyBinding::valueMetaType() + \sa isValid(), QUntypedPropertyBinding::valueMetaType() */ /*! diff --git a/src/corelib/kernel/qsequentialiterable.h b/src/corelib/kernel/qsequentialiterable.h index 92252cb19dd..76908bdae4b 100644 --- a/src/corelib/kernel/qsequentialiterable.h +++ b/src/corelib/kernel/qsequentialiterable.h @@ -142,10 +142,13 @@ inline QVariantRef<QSequentialIterator>::operator QVariant() const if (m_pointer == nullptr) return QVariant(); const QMetaType metaType(m_pointer->metaContainer().valueMetaType()); + + return [&] { QVariant v(metaType); void *dataPtr = metaType == QMetaType::fromType<QVariant>() ? &v : v.data(); m_pointer->metaContainer().valueAtIterator(m_pointer->constIterator(), dataPtr); return v; + }(); } template<> diff --git a/src/corelib/kernel/qtranslator.cpp b/src/corelib/kernel/qtranslator.cpp index 9fdac89f775..6000edaa177 100644 --- a/src/corelib/kernel/qtranslator.cpp +++ b/src/corelib/kernel/qtranslator.cpp @@ -392,8 +392,8 @@ public: translation files may contain misleading or malicious translations. \sa QCoreApplication::installTranslator(), QCoreApplication::removeTranslator(), - QObject::tr(), QCoreApplication::translate(), {I18N Example}, - {Hello tr() Example}, {Arrow Pad Example}, {Troll Print Example} + QObject::tr(), QCoreApplication::translate(), + {Localized Clock Example}, {Arrow Pad Example}, {Troll Print Example} */ /*! diff --git a/src/corelib/platform/windows/qbstr_p.h b/src/corelib/platform/windows/qbstr_p.h index 21eecfe2df7..32b1aace76f 100644 --- a/src/corelib/platform/windows/qbstr_p.h +++ b/src/corelib/platform/windows/qbstr_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QBSTR_P_H #define QBSTR_P_H @@ -141,4 +142,4 @@ QT_END_NAMESPACE #endif // Q_OS_WIN -#endif // QCOMPTR_P_H +#endif // QBSTR_P_H diff --git a/src/corelib/platform/windows/qcomobject_p.h b/src/corelib/platform/windows/qcomobject_p.h index bd6f81d1c28..b7a9c56555d 100644 --- a/src/corelib/platform/windows/qcomobject_p.h +++ b/src/corelib/platform/windows/qcomobject_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMOBJECT_P_H #define QCOMOBJECT_P_H diff --git a/src/corelib/platform/windows/qcomptr_p.h b/src/corelib/platform/windows/qcomptr_p.h index 2a69e7b6038..e640528d3c2 100644 --- a/src/corelib/platform/windows/qcomptr_p.h +++ b/src/corelib/platform/windows/qcomptr_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMPTR_P_H #define QCOMPTR_P_H diff --git a/src/corelib/platform/windows/qcomvariant_p.h b/src/corelib/platform/windows/qcomvariant_p.h index 34ce5f179ce..cc4ad104106 100644 --- a/src/corelib/platform/windows/qcomvariant_p.h +++ b/src/corelib/platform/windows/qcomvariant_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QCOMVARIANT_P_H #define QCOMVARIANT_P_H diff --git a/src/corelib/platform/windows/qfactorycacheregistration.cpp b/src/corelib/platform/windows/qfactorycacheregistration.cpp index 6bd69c66d14..04c81b7b665 100644 --- a/src/corelib/platform/windows/qfactorycacheregistration.cpp +++ b/src/corelib/platform/windows/qfactorycacheregistration.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qfactorycacheregistration_p.h" diff --git a/src/corelib/platform/windows/qfactorycacheregistration_p.h b/src/corelib/platform/windows/qfactorycacheregistration_p.h index d0b19b995b4..f6e7d9eb064 100644 --- a/src/corelib/platform/windows/qfactorycacheregistration_p.h +++ b/src/corelib/platform/windows/qfactorycacheregistration_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QFACTORYCACHEREGISTRATION_P_H #define QFACTORYCACHEREGISTRATION_P_H diff --git a/src/corelib/platform/windows/qt_winrtbase_p.h b/src/corelib/platform/windows/qt_winrtbase_p.h index 79c2bdf6b1c..69a602a59e0 100644 --- a/src/corelib/platform/windows/qt_winrtbase_p.h +++ b/src/corelib/platform/windows/qt_winrtbase_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QT_WINRTBASE_P_H #define QT_WINRTBASE_P_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 13d74e591d5..0aeb2b07f47 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -975,6 +975,7 @@ QCborContainerPrivate *QCborContainerPrivate::detach(QCborContainerPrivate *d, q } /*! + \internal Prepare for an insertion at position \a index Detaches and ensures there are at least index entries in the array, padding diff --git a/src/corelib/serialization/qdatastream.cpp b/src/corelib/serialization/qdatastream.cpp index ae3bed5b751..fd2f7faeee5 100644 --- a/src/corelib/serialization/qdatastream.cpp +++ b/src/corelib/serialization/qdatastream.cpp @@ -552,6 +552,7 @@ void QDataStream::setByteOrder(ByteOrder bo) \value Qt_6_9 \value Qt_6_10 \value Qt_6_11 + \value Qt_6_12 \omitvalue Qt_DefaultCompiledVersion \sa setVersion(), version() diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index 04373fe9c8a..d6fcf17efcd 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -96,8 +96,9 @@ public: Qt_6_9 = Qt_6_7, Qt_6_10 = 23, Qt_6_11 = 24, - Qt_DefaultCompiledVersion = Qt_6_11 -#if QT_VERSION >= QT_VERSION_CHECK(6, 12, 0) + Qt_6_12 = Qt_6_11, + Qt_DefaultCompiledVersion = Qt_6_12 +#if QT_VERSION >= QT_VERSION_CHECK(6, 13, 0) #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion #endif }; diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp index 9cd90fa9d65..ff70289013a 100644 --- a/src/corelib/serialization/qxmlstream.cpp +++ b/src/corelib/serialization/qxmlstream.cpp @@ -3678,7 +3678,7 @@ void QXmlStreamWriter::setStopWritingOnError(bool stop) The error status is never reset. Writes happening after the error occurred may be ignored, even if the error condition is cleared. - \sa error(), errorString(), raiseError(const QString &message), + \sa error(), errorString(), raiseError() */ bool QXmlStreamWriter::hasError() const { @@ -3692,7 +3692,7 @@ bool QXmlStreamWriter::hasError() const QXmlStreamWriter::Error::None. \since 6.10 - \sa errorString(), raiseError(const QString &message), hasError() + \sa errorString(), raiseError(), hasError() */ QXmlStreamWriter::Error QXmlStreamWriter::error() const { @@ -3708,7 +3708,7 @@ QXmlStreamWriter::Error QXmlStreamWriter::error() const a null string. \since 6.10 - \sa error(), raiseError(const QString &message), hasError() + \sa error(), raiseError(), hasError() */ QString QXmlStreamWriter::errorString() const { diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index fed09aee45e..18007cacae6 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -301,6 +301,7 @@ bool operator<(LikelyPair lhs, LikelyPair rhs) } // anonymous namespace /*! + \internal Fill in blank fields of a locale ID. An ID in which some fields are zero stands for any locale that agrees with diff --git a/src/corelib/text/qstringconverter.cpp b/src/corelib/text/qstringconverter.cpp index bf6e776ee0e..896142b4837 100644 --- a/src/corelib/text/qstringconverter.cpp +++ b/src/corelib/text/qstringconverter.cpp @@ -2739,7 +2739,7 @@ QStringList QStringConverter::availableCodecs() May also provide data from residual content that was pending decoding. When there is no residual data to account for, the return's \c error - field will be set to \l {QCharConverter::FinalizeResult::Error::} + field will be set to \l {QStringConverter::FinalizeResultChar::error} {NoError}. If \a out is supplied and non-null, it must have space in which up to @@ -2793,7 +2793,7 @@ auto QStringDecoder::finalize(char16_t *out, qsizetype maxlen) -> FinalizeResult May also provide data from residual content that was pending decoding. When there is no residual data to account for, the return's \c error - field will be set to \l {QCharConverter::FinalizeResult::Error::} + field will be set to \l {QStringConverter::FinalizeResultChar::error} {NoError}. If \a out is supplied and non-null, it must have space in which up to diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 371ee524d72..d6c185cd704 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -845,7 +845,7 @@ struct UnwrapHandler using NestedType = typename QtPrivate::Future<ResultType>::type; QFutureInterface<NestedType> promise(QFutureInterfaceBase::State::Pending); - outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { + auto chain = outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { // We use the .then([](QFuture<ResultType> outerFuture) {...}) version // (where outerFuture == *outer), to propagate the exception if the // outer future has failed. @@ -883,6 +883,13 @@ struct UnwrapHandler promise.reportCanceled(); promise.reportFinished(); }); + + // Inject the promise into the chain. + // We use a fake function as a continuation, since the promise is + // managed by the outer future + chain.d.setContinuation(ContinuationWrapper(std::move([](const QFutureInterfaceBase &) {})), + promise.d, QFutureInterfaceBase::ContinuationType::Then); + return promise.future(); } }; diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index 0b88013800e..ff17560d3a1 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -42,6 +42,8 @@ template<class Function, class ResultType> class FailureHandler; #endif +struct UnwrapHandler; + #if QT_CORE_REMOVED_SINCE(6, 10) void Q_CORE_EXPORT watchContinuationImpl(const QObject *context, QtPrivate::QSlotObjectBase *slotObj, @@ -187,6 +189,8 @@ private: friend class QtPrivate::FailureHandler; #endif + friend struct QtPrivate::UnwrapHandler; + #if QT_CORE_REMOVED_SINCE(6, 10) friend Q_CORE_EXPORT void QtPrivate::watchContinuationImpl( const QObject *context, QtPrivate::QSlotObjectBase *slotObj, QFutureInterfaceBase &fi); diff --git a/src/corelib/thread/qreadwritelock.cpp b/src/corelib/thread/qreadwritelock.cpp index 96e35dcb965..2a1af2315ca 100644 --- a/src/corelib/thread/qreadwritelock.cpp +++ b/src/corelib/thread/qreadwritelock.cpp @@ -234,14 +234,14 @@ QBasicReadWriteLock::contendedTryLockForRead(QDeadlineTimer timeout, void *dd) return d->recursiveLockForRead(timeout); auto lock = qt_unique_lock(d->mutex); - if (d != d_ptr.loadRelaxed()) { + if (QReadWriteLockPrivate *dd = d_ptr.loadAcquire(); d != dd) { // d_ptr has changed: this QReadWriteLock was unlocked before we had // time to lock d->mutex. // We are holding a lock to a mutex within a QReadWriteLockPrivate // that is already released (or even is already re-used). That's ok // because the QFreeList never frees them. // Just unlock d->mutex (at the end of the scope) and retry. - d = d_ptr.loadAcquire(); + d = dd; continue; } return d->lockForRead(lock, timeout); @@ -340,11 +340,11 @@ QBasicReadWriteLock::contendedTryLockForWrite(QDeadlineTimer timeout, void *dd) return d->recursiveLockForWrite(timeout); auto lock = qt_unique_lock(d->mutex); - if (d != d_ptr.loadRelaxed()) { + if (QReadWriteLockPrivate *dd = d_ptr.loadAcquire(); d != dd) { // The mutex was unlocked before we had time to lock the mutex. // We are holding to a mutex within a QReadWriteLockPrivate that is already released // (or even is already re-used) but that's ok because the QFreeList never frees them. - d = d_ptr.loadAcquire(); + d = dd; continue; } return d->lockForWrite(lock, timeout); diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index 10882738a39..05ba3d2beae 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -2389,6 +2389,7 @@ bool operator==(QDateTimeParser::SectionNode s1, QDateTimeParser::SectionNode s2 } /*! + \internal Sets \a cal as the calendar to use. The default is Gregorian. */ diff --git a/src/corelib/tools/qeasingcurve.cpp b/src/corelib/tools/qeasingcurve.cpp index de68a0042ac..1e647a83dc0 100644 --- a/src/corelib/tools/qeasingcurve.cpp +++ b/src/corelib/tools/qeasingcurve.cpp @@ -332,15 +332,13 @@ struct TCBPoint qreal _c; qreal _b; - TCBPoint() {} - TCBPoint(QPointF point, qreal t, qreal c, qreal b) : _point(point), _t(t), _c(c), _b(b) {} - bool operator==(const TCBPoint &other) const + friend bool operator==(const TCBPoint &lhs, const TCBPoint &rhs) noexcept { - return _point == other._point && - qFuzzyCompare(_t, other._t) && - qFuzzyCompare(_c, other._c) && - qFuzzyCompare(_b, other._b); + return qFuzzyCompare(lhs._point, rhs._point) + && QtPrivate::fuzzyCompare(lhs._t, rhs._t) + && QtPrivate::fuzzyCompare(lhs._c, rhs._c) + && QtPrivate::fuzzyCompare(lhs._b, rhs._b); } }; Q_DECLARE_TYPEINFO(TCBPoint, Q_PRIMITIVE_TYPE); @@ -1381,7 +1379,7 @@ void QEasingCurve::addTCBSegment(const QPointF &nextPoint, qreal t, qreal c, qre if (!d_ptr->config) d_ptr->config = curveToFunctionObject(d_ptr->type); - d_ptr->config->_tcbPoints.append(TCBPoint(nextPoint, t, c, b)); + d_ptr->config->_tcbPoints.append(TCBPoint{nextPoint, t, c, b}); if (nextPoint == QPointF(1.0, 1.0)) { d_ptr->config->_bezierCurves = tcbToBezier(d_ptr->config->_tcbPoints); diff --git a/src/gui/accessible/qaccessiblecache.cpp b/src/gui/accessible/qaccessiblecache.cpp index 311b53aeaa3..b5dcdca6270 100644 --- a/src/gui/accessible/qaccessiblecache.cpp +++ b/src/gui/accessible/qaccessiblecache.cpp @@ -19,6 +19,7 @@ Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache"); */ static QAccessibleCache *accessibleCache = nullptr; +static bool inCacheDestructor = false; static void cleanupAccessibleCache() { @@ -32,6 +33,8 @@ QAccessibleObjectDestroyedEvent::~QAccessibleObjectDestroyedEvent() QAccessibleCache::~QAccessibleCache() { + inCacheDestructor = true; + for (QAccessible::Id id: idToInterface.keys()) deleteInterface(id); } @@ -188,10 +191,9 @@ void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj) return; } - // QObjects sends this from their destructor, but - // the object less interfaces calls deleteInterface - // directly - if (!obj && !iface->object()) { + // QObjects send this from their destructors, but the interfaces + // with no associated object call deleteInterface directly. + if (!inCacheDestructor && !obj && !iface->object()) { if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) { QAccessibleObjectDestroyedEvent event(id); QAccessible::updateAccessibility(&event); diff --git a/src/gui/doc/images/coordinatesystem-transformations.png b/src/gui/doc/images/coordinatesystem-transformations.png Binary files differdeleted file mode 100644 index 2736213c072..00000000000 --- a/src/gui/doc/images/coordinatesystem-transformations.png +++ /dev/null diff --git a/src/gui/doc/images/coordinatesystem-transformations.svg b/src/gui/doc/images/coordinatesystem-transformations.svg new file mode 100644 index 00000000000..a3bc17af3ef --- /dev/null +++ b/src/gui/doc/images/coordinatesystem-transformations.svg @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="760" height="300" + viewBox="0 0 760 300" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> + +<style> + svg .line-style { stroke: black; fill: none; stroke-width: 2 } + svg .text-style { font: 14px arial; fill: black } + svg .mono-text-style { font: 14px monospace; fill: black } + svg .heading-style { font: 14px arial; fill: black; font-weight: bold } + svg .shape-style { stroke: none; fill: black } + svg .dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: black } + + svg .window-line-style { stroke: red; fill: none; stroke-width: 2 } + svg .window-text-style { font: 14px arial; fill: red } + svg .window-shape-style { stroke: none; fill: red } + svg .window-dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: red } + + svg.dark .line-style { stroke: #f2f2f2; fill: none; stroke-width: 2 } + svg.dark .text-style { font: 14px arial; fill: #f2f2f2 } + svg.dark .mono-text-style { font: 14px monospace; fill: #f2f2f2 } + svg.dark .heading-style { font: 14px arial; fill: #f2f2f2; font-weight: bold } + svg.dark .shape-style { stroke: none; fill: #f2f2f2 } + svg.dark .dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: #f2f2f2 } + svg.dark .window-line-style { stroke: yellow; fill: none; stroke-width: 2 } + svg.dark .window-text-style { font: 14px arial; fill: yellow } + svg.dark .window-shape-style { stroke: none; fill: yellow } + svg.dark .window-dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: yellow } + + [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none; stroke-width: 2 } + [data-theme="dark"] svg .text-style { font: 14px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .mono-text-style { font: 14px monospace; fill: #f2f2f2 } + [data-theme="dark"] svg .heading-style { font: 14px arial; fill: #f2f2f2; font-weight: bold } + [data-theme="dark"] svg .shape-style { stroke: none; fill: #f2f2f2 } + [data-theme="dark"] svg .dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: #f2f2f2 } + [data-theme="dark"] svg .window-line-style { stroke: yellow; fill: none; stroke-width: 2 } + [data-theme="dark"] svg .window-text-style { font: 14px arial; fill: yellow } + [data-theme="dark"] svg .window-shape-style { stroke: none; fill: yellow } + [data-theme="dark"] svg .window-dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: yellow } + + [data-theme="light"] svg .line-style { stroke: black; fill: none; stroke-width: 2 } + [data-theme="light"] svg .text-style { font: 14px arial; fill: black } + [data-theme="light"] svg .mono-text-style { font: 14px monospace; fill: black } + [data-theme="light"] svg .heading-style { font: 14px arial; fill: black; font-weight: bold } + [data-theme="light"] svg .shape-style { stroke: none; fill: black } + [data-theme="light"] svg .dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: black } + [data-theme="light"] svg .window-line-style { stroke: red; fill: none; stroke-width: 2 } + [data-theme="light"] svg .window-text-style { font: 14px arial; fill: red } + [data-theme="light"] svg .window-shape-style { stroke: none; fill: red } + [data-theme="light"] svg .window-dash-style { stroke-dasharray: 4,3; stroke-dashoffset: 0; fill: none; + stroke: red } +</style> + +<text x="50" y="20" fill="black" font-size="14px" font-family="arial" + font-weight="bold" + class="heading-style">World coordinates</text> + +<g transform="translate(20, 30)"> + <rect x="0" y="0" width="200" height="200" stroke="black" fill="none" stroke-dasharray="4,3" stroke-dashoffset="0" + class="dash-style" /> + + <path d="M5,5 L16,10 L10,16 L5,5" class="shape-style" /> + <polyline points="5,5 20,20" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <text x="25" y="30" fill="black" font-size="14px" font-family="arial" + class="text-style">(0, 0)</text> + + <path d="M195,195 L184,190 L190,184 L195,195" class="shape-style" /> + <polyline points="195,195 180,180" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <text x="115" y="175" fill="black" font-size="14px" font-family="arial" + class="text-style">(100, 100)</text> + + <polyline points="-3,-3 3,3" stroke="black" stroke-width="2" class="line-style" /> + <polyline points="-3,3 3,-3" stroke="black" stroke-width="2" class="line-style" /> +</g> + +<g transform="translate(45,235)"> + <path d="M 0,0 c 0,25 10,35 25,35" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <path d="M 25,30 l 10,5 l -10,5 z" class="shape-style" /> + <text x="40" y="40" fill="black" font-size="14px" font-family="monospace" + class="mono-text-style">setWindow(-50, -50, 100, 100)</text> + <path d="M 290,35 c 15,0 25,-10 25,-35" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <path d="M 315,0 l 5,10 l -10,0 z" class="shape-style" /> +</g> + +<text x="300" y="20" fill="black" font-size="14px" font-family="arial" + font-weight="bold" + class="heading-style">"Window" coordinates</text> + +<g transform="translate(280, 30)"> + + <rect x="0" y="0" width="200" height="200" stroke="red" fill="none" stroke-dasharray="4,3" stroke-dashoffset="0" + class="window-dash-style" /> + + <path d="M5,5 L16,10 L10,16 L5,5" fill="red" class="window-shape-style" /> + <polyline points="5,5 20,20" stroke="red" stroke-width="2" fill="none" class="window-line-style" /> + <text x="25" y="30" fill="red" font-size="14px" font-family="arial" + class="window-text-style">(-50, -50)</text> + + <path d="M195,195 L184,190 L190,184 L195,195" fill="red" class="window-shape-style" /> + <polyline points="195,195 180,180" stroke="red" stroke-width="2" fill="none" class="window-line-style" /> + <text x="130" y="175" fill="red" font-size="14px" font-family="arial" + class="window-text-style">(50, 50)</text> + + <polyline points="97,97 103,103" stroke="red" stroke-width="2" class="window-line-style" /> + <polyline points="97,103 103,97" stroke="red" stroke-width="2" class="window-line-style" /> +</g> + +<g transform="translate(395,235)"> + <path d="M 0,0 c 0,25 10,35 25,35" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <path d="M 25,30 l 10,5 l -10,5 z" class="shape-style" /> + <text x="40" y="40" fill="black" font-size="14px" font-family="monospace" + class="mono-text-style">setViewport(45, 25, 50, 50)</text> + <path d="M 270,35 c 15,0 25,-10 25,-35" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <path d="M 295,0 l 5,10 l -10,0 z" class="shape-style" /> +</g> + +<text x="570" y="20" fill="black" font-size="14px" font-family="arial" + font-weight="bold" + class="heading-style">Device coordinates</text> + +<g transform="translate(540, 30)"> + + <rect x="0" y="0" width="200" height="200" stroke="black" stroke-width="2" fill="none" class="line-style" /> + + <path d="M5,5 L16,10 L10,16 L5,5" class="shape-style" /> + <polyline points="5,5 20,20" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <text x="25" y="30" fill="black" font-size="14px" font-family="arial" + class="text-style">(0, 0)</text> + + <path d="M195,195 L184,190 L190,184 L195,195" class="shape-style" /> + <polyline points="195,195 180,180" stroke="black" stroke-width="2" fill="none" class="line-style" /> + <text x="115" y="175" fill="black" font-size="14px" font-family="arial" + class="text-style">(100, 100)</text> + + <rect x="90" y="50" width="100" height="100" stroke="red" fill="none" stroke-dasharray="4,3" stroke-dashoffset="0" + class="window-dash-style" /> + <polyline points="137,97 143,103" stroke="red" stroke-width="2" class="window-line-style" /> + <polyline points="137,103 143,97" stroke="red" stroke-width="2" class="window-line-style" /> +</g> + +</svg> diff --git a/src/gui/doc/qtgui.qdocconf b/src/gui/doc/qtgui.qdocconf index 24d9d522735..8b7569c1296 100644 --- a/src/gui/doc/qtgui.qdocconf +++ b/src/gui/doc/qtgui.qdocconf @@ -50,7 +50,8 @@ depends += \ qtshadertools \ qttestlib \ qtplatformintegration \ - qthelp + qthelp \ + qtquickcontrols headerdirs += .. diff --git a/src/gui/doc/src/coordsys.qdoc b/src/gui/doc/src/coordsys.qdoc index 3dd064c19bc..22e14121af6 100644 --- a/src/gui/doc/src/coordsys.qdoc +++ b/src/gui/doc/src/coordsys.qdoc @@ -321,7 +321,7 @@ still transformed to the viewport using the same linear algebraic approach. - \image coordinatesystem-transformations.png {Illustration showing + \image coordinatesystem-transformations.svg {Illustration showing how coordinates are mapped using viewport, "window" and transformation matrix} diff --git a/src/gui/doc/src/external-resources.qdoc b/src/gui/doc/src/external-resources.qdoc index 0f356dd5046..14ed0817e62 100644 --- a/src/gui/doc/src/external-resources.qdoc +++ b/src/gui/doc/src/external-resources.qdoc @@ -36,6 +36,7 @@ \externalpage https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html \title Freedesktop Icon Naming Specification */ + /*! \externalpage https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout \title Icon Theme Specification - Directory Layout diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp index b7ab3dbbc46..ea19db0d20f 100644 --- a/src/gui/itemmodels/qfileinfogatherer.cpp +++ b/src/gui/itemmodels/qfileinfogatherer.cpp @@ -46,6 +46,12 @@ static QString translateDriveName(const QFileInfo &drive) } /*! + \class QFileInfoGatherer + \inmodule QtGui + \internal +*/ + +/*! Creates thread */ QFileInfoGatherer::QFileInfoGatherer(QObject *parent) diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 741b089306e..e8df40f21b2 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -490,6 +490,12 @@ static QWindowGeometrySpecification windowGeometrySpecification = Q_WINDOW_GEOME */ /*! + \class QGuiApplicationPrivate + \inmodule QtGui + \internal +*/ + +/*! Initializes the window system and constructs an application object with \a argc command line arguments in \a argv. diff --git a/src/gui/kernel/qplatformintegrationfactory.cpp b/src/gui/kernel/qplatformintegrationfactory.cpp index d0a5e8871f8..d113e86090d 100644 --- a/src/gui/kernel/qplatformintegrationfactory.cpp +++ b/src/gui/kernel/qplatformintegrationfactory.cpp @@ -24,6 +24,7 @@ QPlatformIntegration *QPlatformIntegrationFactory::create(const QString &platfor } /*! + \internal Returns the list of valid keys, i.e. the keys this factory can create styles for. diff --git a/src/gui/kernel/qplatformthemefactory.cpp b/src/gui/kernel/qplatformthemefactory.cpp index beefa1c2942..3ac462598fd 100644 --- a/src/gui/kernel/qplatformthemefactory.cpp +++ b/src/gui/kernel/qplatformthemefactory.cpp @@ -31,6 +31,7 @@ QPlatformTheme *QPlatformThemeFactory::create(const QString& key, const QString } /*! + \internal Returns the list of valid keys, i.e. the keys this factory can create styles for. diff --git a/src/gui/opengl/qopenglfunctions.cpp b/src/gui/opengl/qopenglfunctions.cpp index 6b45d26fb4c..cb927d7c296 100644 --- a/src/gui/opengl/qopenglfunctions.cpp +++ b/src/gui/opengl/qopenglfunctions.cpp @@ -192,6 +192,12 @@ QOpenGLFunctions::QOpenGLFunctions(QOpenGLContext *context) qWarning("QOpenGLFunctions created with non-current context"); } +/*! + \class QOpenGLExtensions + \inmodule QtGui + \internal +*/ + QOpenGLExtensions::QOpenGLExtensions() { } diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp index 047be5f1c3d..8e815394849 100644 --- a/src/gui/painting/qpaintengine_raster.cpp +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -2881,6 +2881,7 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs, /*! + * \internal * Returns \c true if the rectangle is completely within the current clip * state of the paint engine. */ diff --git a/src/gui/painting/qpainterpath.h b/src/gui/painting/qpainterpath.h index cc80641ded9..ab9e7fe312a 100644 --- a/src/gui/painting/qpainterpath.h +++ b/src/gui/painting/qpainterpath.h @@ -6,9 +6,11 @@ #include <QtGui/qtguiglobal.h> #include <QtGui/qtransform.h> + #include <QtCore/qglobal.h> #include <QtCore/qline.h> #include <QtCore/qlist.h> +#include <QtCore/qpoint.h> #include <QtCore/qrect.h> QT_BEGIN_NAMESPACE diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index 908051a477c..b1ea3f240f1 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -3192,6 +3192,7 @@ static inline bool is_monochrome(const QList<QRgb> &colorTable) } /*! + * \internal * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. */ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, qint64 serial_no) diff --git a/src/gui/painting/qstroker.cpp b/src/gui/painting/qstroker.cpp index 0d435c95048..08128c30a70 100644 --- a/src/gui/painting/qstroker.cpp +++ b/src/gui/painting/qstroker.cpp @@ -145,6 +145,11 @@ static inline qreal adapted_angle_on_x(const QLineF &line) return QLineF(0, 0, 1, 0).angleTo(line); } +/*! + \class QStrokerOps + \inmodule QtGui + \internal +*/ QStrokerOps::QStrokerOps() : m_elements(0) , m_curveThreshold(qt_real_to_fixed(0.25)) @@ -373,6 +378,7 @@ QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle) /*! + \internal This function is called to stroke the currently built up subpath. The subpath is cleared when the function completes. */ diff --git a/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp index fe050461260..00c3192e00a 100644 --- a/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp +++ b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp @@ -48,6 +48,7 @@ void QDBusPlatformMenuItem::setIcon(const QIcon &icon) } /*! + \internal Set a submenu under this menu item. */ void QDBusPlatformMenuItem::setMenu(QPlatformMenu *menu) diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index d41296291f6..4df55d5b89c 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -402,7 +402,7 @@ bool QFontEngine::processHheaTable() const const qreal unitsPerEm = emSquareSize().toReal(); // Bail out if values are too large for QFixed - const auto limitForQFixed = qreal(std::numeric_limits<int>::max() / 64) / fontDef.pixelSize; + const auto limitForQFixed = qreal(std::numeric_limits<int>::max() >> 6) / fontDef.pixelSize; if (ascent > limitForQFixed || descent > limitForQFixed || leading > limitForQFixed) return false; m_ascent = QFixed::fromReal(ascent * fontDef.pixelSize / unitsPerEm); @@ -470,7 +470,7 @@ bool QFontEngine::processOS2Table() const if (typoAscent == 0 && typoDescent == 0) return false; // Bail out if values are too large for QFixed - const auto limitForQFixed = qreal(std::numeric_limits<int>::max() / 64) / fontDef.pixelSize; + const auto limitForQFixed = qreal(std::numeric_limits<int>::max() >> 6) / fontDef.pixelSize; if (typoAscent > limitForQFixed || typoDescent > limitForQFixed || typoLineGap > limitForQFixed) return false; @@ -481,7 +481,7 @@ bool QFontEngine::processOS2Table() const // Some fonts may have invalid OS/2 data. We detect this and bail out. if (winAscent == 0 && winDescent == 0) return false; - const auto limitForQFixed = qreal(std::numeric_limits<int>::max() / 64) / fontDef.pixelSize; + const auto limitForQFixed = qreal(std::numeric_limits<int>::max() >> 6) / fontDef.pixelSize; if (winAscent > limitForQFixed || winDescent > limitForQFixed) return false; m_ascent = QFixed::fromReal(winAscent * fontDef.pixelSize / unitsPerEm); @@ -1059,6 +1059,7 @@ void QFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metr } /*! + \internal Returns \c true if the font table idetified by \a tag exists in the font; returns \c false otherwise. diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index 4f01d09fed1..d519cd5a5d3 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2385,6 +2385,11 @@ static QString colorValue(QColor color) return result; } +/*! + \class QTextHtmlExporter + \inmodule QtGui + \internal +*/ QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc) : doc(_doc), fragmentMarkers(false) { diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp index 227cbae2952..85a74d366ac 100644 --- a/src/gui/text/qtextdocument_p.cpp +++ b/src/gui/text/qtextdocument_p.cpp @@ -1006,6 +1006,7 @@ int QTextDocumentPrivate::undoRedo(bool undo) } /*! + \internal Appends a custom undo \a item to the undo stack. */ void QTextDocumentPrivate::appendUndoItem(QAbstractUndoItem *item) diff --git a/src/gui/text/qtexttable.cpp b/src/gui/text/qtexttable.cpp index ff8644b5302..337228ff170 100644 --- a/src/gui/text/qtexttable.cpp +++ b/src/gui/text/qtexttable.cpp @@ -394,11 +394,9 @@ void QTextTablePrivate::fragmentRemoved(QChar type, uint fragment) } /*! - /fn void QTextTablePrivate::update() const - + \internal This function is usually called when the table is "dirty". It seems to update all kind of table information. - */ void QTextTablePrivate::update() const { diff --git a/src/gui/text/windows/qwindowsfontdatabase.cpp b/src/gui/text/windows/qwindowsfontdatabase.cpp index 93af1a9600b..0c6d9a31316 100644 --- a/src/gui/text/windows/qwindowsfontdatabase.cpp +++ b/src/gui/text/windows/qwindowsfontdatabase.cpp @@ -1113,17 +1113,21 @@ void QWindowsFontDatabase::removeApplicationFonts() m_eudcFonts.clear(); } +#if QT_CONFIG(directwrite) QWindowsFontDatabase::FontHandle::FontHandle(IDWriteFontFace *face, const QString &name) : fontFace(face), faceName(name) { fontFace->AddRef(); } +#endif // !QT_NO_DIRECTWRITE QWindowsFontDatabase::FontHandle::~FontHandle() { +#if QT_CONFIG(directwrite) if (fontFace != nullptr) fontFace->Release(); +#endif // !QT_NO_DIRECTWRITE } void QWindowsFontDatabase::releaseHandle(void *handle) diff --git a/src/gui/text/windows/qwindowsfontdatabase_p.h b/src/gui/text/windows/qwindowsfontdatabase_p.h index 92e3f04f968..856a5593722 100644 --- a/src/gui/text/windows/qwindowsfontdatabase_p.h +++ b/src/gui/text/windows/qwindowsfontdatabase_p.h @@ -78,10 +78,14 @@ public: struct FontHandle { FontHandle(const QString &name) : faceName(name) {} +#if QT_CONFIG(directwrite) FontHandle(IDWriteFontFace *face, const QString &name); +#endif // !QT_NO_DIRECTWRITE ~FontHandle(); +#if QT_CONFIG(directwrite) IDWriteFontFace *fontFace = nullptr; +#endif // !QT_NO_DIRECTWRITE QString faceName; }; diff --git a/src/gui/util/qlayoutpolicy.cpp b/src/gui/util/qlayoutpolicy.cpp index 4d81a426835..0c0651c1f39 100644 --- a/src/gui/util/qlayoutpolicy.cpp +++ b/src/gui/util/qlayoutpolicy.cpp @@ -8,6 +8,11 @@ QT_BEGIN_NAMESPACE +/*! + \class QLayoutPolicy + \inmodule QtGui + \internal +*/ void QLayoutPolicy::setControlType(ControlType type) { /* diff --git a/src/network/access/qbytedatabuffer_p.h b/src/network/access/qbytedatabuffer_p.h index a119093cf7e..036b562d06a 100644 --- a/src/network/access/qbytedatabuffer_p.h +++ b/src/network/access/qbytedatabuffer_p.h @@ -280,6 +280,8 @@ public: } return false; } + + const QByteArray &last() const { return buffers.last(); } }; QT_END_NAMESPACE diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp index 2895e8335d2..c0b07ddd652 100644 --- a/src/network/access/qhttp2connection.cpp +++ b/src/network/access/qhttp2connection.cpp @@ -34,8 +34,38 @@ using namespace Http2; \sa QHttp2Connection */ -QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept - : QObject(connection), m_streamID(streamID) +/*! + \struct QHttp2Stream::Configuration + \inmodule QtNetwork + \internal + + \brief Configuration options for a QHttp2Stream. + + The Configuration struct holds options that control stream behavior. + + \sa QHttp2Connection::createStream() +*/ + +/*! + \variable QHttp2Stream::Configuration::useDownloadBuffer + + Controls whether incoming DATA frames, from QHttp2Stream::dataReceived(), + are buffered. The default is \c true. + + You may disable buffering for client-initiated streams when the + application processes DATA immediately. + + Buffering must remain enabled for pushed streams. A pushed stream can + receive DATA before the application becomes aware of them and the buffered + DATA is required to deliver the pushed response. + + \sa QHttp2Stream::downloadBuffer(), QHttp2Stream::takeDownloadBuffer(), + QHttp2Configuration::serverPushEnabled(), QHttp2Stream::dataReceived() +*/ + +QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID, + Configuration configuration) noexcept + : QObject(connection), m_streamID(streamID), m_configuration(configuration) { Q_ASSERT(connection); Q_ASSERT(streamID); // stream id 0 is reserved for connection control messages @@ -213,6 +243,12 @@ QHttp2Stream::~QHttp2Stream() noexcept { Returns the buffer containing the data received from the remote peer. */ +/*! + \fn QHttp2Stream::Configuration QHttp2Stream::configuration() const + + Returns the configuration of this stream. +*/ + void QHttp2Stream::finishWithError(Http2::Http2Error errorCode, const QString &message) { qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)", @@ -697,8 +733,14 @@ void QHttp2Stream::handleDATA(const Frame &inboundFrame) inboundFrame.dataSize()); if (endStream) transitionState(StateTransition::CloseRemote); - emit dataReceived(fragment, endStream); - m_downloadBuffer.append(std::move(fragment)); + const auto shouldBuffer = m_configuration.useDownloadBuffer && !fragment.isEmpty(); + if (shouldBuffer) { + // Only non-empty fragments get appended! + m_downloadBuffer.append(std::move(fragment)); + emit dataReceived(m_downloadBuffer.last(), endStream); + } else { + emit dataReceived(fragment, endStream); + } } if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) { @@ -885,23 +927,35 @@ QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *sock } /*! - Creates a stream on this connection. + \fn QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream() + Creates a stream on this connection, using the default QHttp2Stream::Configuration. + +//! [createStream] Automatically picks the next available stream ID and returns a pointer to the new stream, if possible. Otherwise returns an error. \sa QHttp2Connection::CreateStreamError, QHttp2Stream +//! [createStream] + \sa createStream(QHttp2Stream::Configuration) +*/ + +/*! + Creates a stream with \a configuration on this connection. + + \include qhttp2connection.cpp createStream */ -QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream() +QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> +QHttp2Connection::createStream(QHttp2Stream::Configuration configuration) { Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients if (m_nextStreamID > lastValidStreamID) return { QHttp2Connection::CreateStreamError::StreamIdsExhausted }; - return createLocalStreamInternal(); + return createLocalStreamInternal(configuration); } QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> -QHttp2Connection::createLocalStreamInternal() +QHttp2Connection::createLocalStreamInternal(QHttp2Stream::Configuration conf) { if (m_goingAway) return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY }; @@ -909,7 +963,7 @@ QHttp2Connection::createLocalStreamInternal() if (size_t(m_peerMaxConcurrentStreams) <= size_t(numActiveLocalStreams())) return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached }; - if (QHttp2Stream *ptr = createStreamInternal_impl(streamID)) { + if (QHttp2Stream *ptr = createStreamInternal_impl(streamID, conf)) { m_nextStreamID += 2; return {ptr}; } @@ -917,7 +971,8 @@ QHttp2Connection::createLocalStreamInternal() return { QHttp2Connection::CreateStreamError::UnknownError }; } -QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID) +QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID, + QHttp2Stream::Configuration conf) { Q_ASSERT(streamID > m_lastIncomingStreamID || streamID >= m_nextStreamID); @@ -930,7 +985,7 @@ QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID) if (!result.inserted) return nullptr; QPointer<QHttp2Stream> &stream = result.iterator.value(); - stream = new QHttp2Stream(this, streamID); + stream = new QHttp2Stream(this, streamID, conf); stream->m_recvWindow = streamInitialReceiveWindowSize; stream->m_sendWindow = streamInitialSendWindowSize; diff --git a/src/network/access/qhttp2connection_p.h b/src/network/access/qhttp2connection_p.h index f3f14145278..e2af7d7ab33 100644 --- a/src/network/access/qhttp2connection_p.h +++ b/src/network/access/qhttp2connection_p.h @@ -101,6 +101,11 @@ public: Q_ENUM(State) constexpr static quint8 DefaultPriority = 127; + struct Configuration + { + bool useDownloadBuffer = true; + }; + ~QHttp2Stream() noexcept; // HTTP2 things @@ -124,6 +129,8 @@ public: QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(m_downloadBuffer, {}); } void clearDownloadBuffer() { m_downloadBuffer.clear(); } + Configuration configuration() const { return m_configuration; } + Q_SIGNALS: void headersReceived(const HPack::HttpHeader &headers, bool endStream); void headersUpdated(); @@ -154,7 +161,8 @@ private Q_SLOTS: private: friend class QHttp2Connection; - QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept; + QHttp2Stream(QHttp2Connection *connection, quint32 streamID, + Configuration configuration) noexcept; [[nodiscard]] QHttp2Connection *getConnection() const { @@ -201,6 +209,8 @@ private: bool m_isReserved = false; bool m_owningByteDevice = false; + const Configuration m_configuration; + friend tst_QHttp2Connection; }; @@ -235,7 +245,12 @@ public: createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config); ~QHttp2Connection(); - [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream(); + [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream() + { + return createStream(QHttp2Stream::Configuration{}); + } + [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> + createStream(QHttp2Stream::Configuration config); QHttp2Stream *getStream(quint32 streamId) const; QHttp2Stream *promisedStream(const QUrl &streamKey) const @@ -278,8 +293,9 @@ private: friend class QHttp2Stream; [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); } - QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createLocalStreamInternal(); - QHttp2Stream *createStreamInternal_impl(quint32 streamID); + QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> + createLocalStreamInternal(QHttp2Stream::Configuration = {}); + QHttp2Stream *createStreamInternal_impl(quint32 streamID, QHttp2Stream::Configuration = {}); bool isInvalidStream(quint32 streamID) noexcept; bool streamWasResetLocally(quint32 streamID) noexcept; diff --git a/src/network/access/qnetworkaccesscache.cpp b/src/network/access/qnetworkaccesscache.cpp index 6ff70cd546d..1f2bbd321cf 100644 --- a/src/network/access/qnetworkaccesscache.cpp +++ b/src/network/access/qnetworkaccesscache.cpp @@ -176,18 +176,6 @@ void QNetworkAccessCache::updateTimer() timer.start(interval + 10, this); } -bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member) -{ - if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)), - target, member, Qt::QueuedConnection)) - return false; - - emit entryReady(node->object); - disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*))); - - return true; -} - void QNetworkAccessCache::timerEvent(QTimerEvent *) { while (firstExpiringNode && firstExpiringNode->timer.hasExpired()) { diff --git a/src/network/access/qnetworkaccesscache_p.h b/src/network/access/qnetworkaccesscache_p.h index 4d4ff325f98..5d7dc819461 100644 --- a/src/network/access/qnetworkaccesscache_p.h +++ b/src/network/access/qnetworkaccesscache_p.h @@ -69,9 +69,6 @@ public: void releaseEntry(const QByteArray &key); void removeEntry(const QByteArray &key); -signals: - void entryReady(QNetworkAccessCache::CacheableObject *); - protected: void timerEvent(QTimerEvent *) override; @@ -86,7 +83,6 @@ private: void linkEntry(const QByteArray &key); bool unlinkEntry(const QByteArray &key); void updateTimer(); - bool emitEntryReady(Node *node, QObject *target, const char *member); }; Q_DECLARE_OPERATORS_FOR_FLAGS(QNetworkAccessCache::CacheableObject::Options) diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 0905ba3e644..1b34490e358 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -330,6 +330,13 @@ QMargins QCocoaWindow::safeAreaMargins() const // merge them. auto screenRect = m_view.window.screen.frame; auto screenInsets = m_view.window.screen.safeAreaInsets; + auto screenSafeArea = QCocoaScreen::mapFromNative(NSMakeRect( + NSMinX(screenRect) + screenInsets.left, + NSMinY(screenRect) + screenInsets.bottom, // Non-flipped + NSWidth(screenRect) - screenInsets.left - screenInsets.right, + NSHeight(screenRect) - screenInsets.top - screenInsets.bottom + )); + auto screenRelativeViewBounds = QCocoaScreen::mapFromNative( [m_view.window convertRectToScreen: [m_view convertRect:m_view.bounds toView:nil]] @@ -339,20 +346,10 @@ QMargins QCocoaWindow::safeAreaMargins() const // Note that we do not want represent the area outside of the // screen as being outside of the safe area. QMarginsF screenSafeAreaMargins = { - screenInsets.left ? - qMax(0.0f, screenInsets.left - screenRelativeViewBounds.left()) - : 0.0f, - screenInsets.top ? - qMax(0.0f, screenInsets.top - screenRelativeViewBounds.top()) - : 0.0f, - screenInsets.right ? - qMax(0.0f, screenInsets.right - - (screenRect.size.width - screenRelativeViewBounds.right())) - : 0.0f, - screenInsets.bottom ? - qMax(0.0f, screenInsets.bottom - - (screenRect.size.height - screenRelativeViewBounds.bottom())) - : 0.0f + qMin(screenSafeArea.left() - screenRelativeViewBounds.left(), screenInsets.left), + qMin(screenSafeArea.top() - screenRelativeViewBounds.top(), screenInsets.top), + qMin(screenRelativeViewBounds.right() - screenSafeArea.right(), screenInsets.right), + qMin(screenRelativeViewBounds.bottom() - screenSafeArea.bottom(), screenInsets.bottom) }; return (screenSafeAreaMargins | viewSafeAreaMargins).toMargins(); diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 0236669d6fb..23bbe409caa 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -450,6 +450,26 @@ QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int hwnd = GetDesktopWindow(); const QRect screenGeometry = geometry(); windowSize = screenGeometry.size(); + // When dpi awareness is not set to PerMonitor, windows reports primary display or dummy + // DPI for all displays, so xIn and yIn and windowSize are calculated with a wrong DPI, + // so we need to recalculate them using the actual screen size we get from + // EnumDisplaySettings api. + const auto dpiAwareness = QWindowsContext::instance()->processDpiAwareness(); + if (dpiAwareness != QtWindows::DpiAwareness::PerMonitor && + dpiAwareness != QtWindows::DpiAwareness::PerMonitorVersion2) { + MONITORINFOEX info = {}; + info.cbSize = sizeof(MONITORINFOEX); + if (GetMonitorInfo(handle(), &info)) { + DEVMODE dm = {}; + dm.dmSize = sizeof(dm); + if (EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &dm)) { + qreal scale = static_cast<qreal>(dm.dmPelsWidth) / windowSize.width(); + x = static_cast<int>(static_cast<qreal>(x) * scale); + y = static_cast<int>(static_cast<qreal>(y) * scale); + windowSize = QSize(dm.dmPelsWidth, dm.dmPelsHeight); + } + } + } x += screenGeometry.x(); y += screenGeometry.y(); } diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/src/plugins/sqldrivers/.cmake.conf +++ b/src/plugins/sqldrivers/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index ff2d4bd845f..e9b90d787bc 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -596,7 +596,7 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt if (sub & SC_ComboBoxArrow) { QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); painter->setFont(d->assetFont); - painter->setPen(controlTextColor(option)); + painter->setPen(controlTextColor(option, true)); painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed)); } if (state & State_KeyboardFocusChange && hasFocus) { @@ -887,7 +887,7 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption if (isOn) { painter->setFont(d->assetFont); - painter->setPen(controlTextColor(option, QPalette::Window)); + painter->setPen(controlTextColor(option)); qreal clipWidth = 1.0; const QString str = fluentIcon(Icon::AcceptMedium); QFontMetrics fm(d->assetFont); @@ -907,7 +907,7 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption QFont f(d->assetFont); f.setPointSize(6); painter->setFont(f); - painter->setPen(controlTextColor(option, QPalette::Window)); + painter->setPen(controlTextColor(option)); painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::Dash12)); } } @@ -1214,7 +1214,7 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op case QStyle::CE_ComboBoxLabel: #if QT_CONFIG(combobox) if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { - painter->setPen(controlTextColor(option)); + painter->setPen(controlTextColor(option, true)); QStyleOptionComboBox newOption = *cb; newOption.rect.adjust(4,0,-4,0); QCommonStyle::drawControl(element, &newOption, painter, widget); @@ -2737,7 +2737,7 @@ QBrush QWindows11Style::inputFillBrush(const QStyleOption *option, const QWidget return winUI3Color(fillControlDefault); } -QColor QWindows11Style::controlTextColor(const QStyleOption *option, QPalette::ColorRole role) const +QColor QWindows11Style::controlTextColor(const QStyleOption *option, bool ignoreIsChecked) const { using namespace StyleOptionHelper; static constexpr WINUI3Color colorEnums[2][4] = { @@ -2750,12 +2750,9 @@ QColor QWindows11Style::controlTextColor(const QStyleOption *option, QPalette::C if (option->palette.isBrushSet(QPalette::Current, QPalette::ButtonText)) return option->palette.buttonText().color(); - const int colorIndex = isChecked(option) ? 1 : 0; + const int colorIndex = !ignoreIsChecked && isChecked(option) ? 1 : 0; const auto state = calcControlState(option); - const auto alpha = winUI3Color(colorEnums[colorIndex][int(state)]); - QColor col = option->palette.color(role); - col.setAlpha(alpha.alpha()); - return col; + return winUI3Color(colorEnums[colorIndex][int(state)]); } void QWindows11Style::drawLineEditFrame(QPainter *p, const QRectF &rect, const QStyleOption *o, bool isEditable) const diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h index 96c2c4136e0..9d0cdda3e33 100644 --- a/src/plugins/styles/modernwindows/qwindows11style_p.h +++ b/src/plugins/styles/modernwindows/qwindows11style_p.h @@ -104,8 +104,7 @@ private: QBrush controlFillBrush(const QStyleOption *option, ControlType controlType) const; QBrush inputFillBrush(const QStyleOption *option, const QWidget *widget) const; // ControlType::ControlAlt can be mapped to QPalette directly - QColor controlTextColor(const QStyleOption *option, - QPalette::ColorRole role = QPalette::ButtonText) const; + QColor controlTextColor(const QStyleOption *option, bool ignoreIsChecked = false) const; void drawLineEditFrame(QPainter *p, const QRectF &rect, const QStyleOption *o, bool isEditable = true) const; inline QColor winUI3Color(enum WINUI3Color col) const; diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp index 64ffba2d6f8..36b5d0f0143 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -16,6 +16,9 @@ #include <private/qapplication_p.h> #include <private/qsystemlibrary_p.h> #include <private/qwindowsthemecache_p.h> +#if QT_CONFIG(tooltip) +#include "private/qtooltip_p.h" +#endif #include "qdrawutil.h" // for now #include <qbackingstore.h> @@ -4676,7 +4679,7 @@ void QWindowsVistaStyle::polish(QWidget *widget) widget->setPalette(pal); } else #endif // QT_CONFIG(commandlinkbutton) - if (widget->inherits("QTipLabel")) { + if (qobject_cast<const QTipLabel *>(widget)) { //note that since tooltips are not reused //we do not have to care about unpolishing widget->setContentsMargins(3, 0, 4, 0); diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp index deb257be01c..d3b7d669ec7 100644 --- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp @@ -407,8 +407,13 @@ QList<QSslCertificate> systemCaCertificates() for (const QByteArray &directory : directories) { for (const auto &dirEntry : QDirListing(QFile::decodeName(directory), flags)) { // use canonical path here to not load the same certificate twice if symlinked - if (hasMatchingExtension(dirEntry.fileName())) - certFiles.insert(dirEntry.canonicalFilePath()); + if (hasMatchingExtension(dirEntry.fileName())) { + QString canonicalPath = dirEntry.canonicalFilePath(); + // skip broken symlinks to not end up adding "" to the list which will then + // just be rejected by `QSslCertificate::fromFile` + if (!canonicalPath.isEmpty()) + certFiles.insert(canonicalPath); + } } } for (const QString& file : std::as_const(certFiles)) diff --git a/src/testlib/doc/src/qt-webpages.qdoc b/src/testlib/doc/src/qt-webpages.qdoc index 611f3795ba9..b32fd4f750f 100644 --- a/src/testlib/doc/src/qt-webpages.qdoc +++ b/src/testlib/doc/src/qt-webpages.qdoc @@ -15,7 +15,3 @@ \title Googletest Mocking (gMock) Framework */ -/*! - \externalpage https://www.itk.org/Wiki/CMake_Testing_With_CTest - \title CMake/Testing With CTest -*/ diff --git a/src/tools/androidtestrunner/main.cpp b/src/tools/androidtestrunner/main.cpp index b517d85c5fb..161d95db49c 100644 --- a/src/tools/androidtestrunner/main.cpp +++ b/src/tools/androidtestrunner/main.cpp @@ -28,7 +28,16 @@ using namespace Qt::StringLiterals; -#define EXIT_ERROR -1 + +// QTest-based test processes may exit with up to 127 for normal test failures +static constexpr int HIGHEST_QTEST_EXITCODE = 127; +// Something went wrong in androidtestrunner, in general +static constexpr int EXIT_ERROR = 254; +// More specific exit codes for failures in androidtestrunner: +static constexpr int EXIT_NOEXITCODE = 253; // Failed to transfer exit code from device +static constexpr int EXIT_ANR = 252; // Android ANR error (Application Not Responding) +static constexpr int EXIT_NORESULTS = 251; // Failed to transfer result files from device + struct Options { @@ -71,6 +80,13 @@ struct TestInfo static TestInfo g_testInfo; +// QTest-based processes return 0 if all tests PASSed, or the number of FAILs up to 127. +// Other exitcodes signify abnormal termination and are system-dependent. +static bool isTestExitCodeNormal(const int ec) +{ + return (ec >= 0 && ec <= HIGHEST_QTEST_EXITCODE); +} + static bool execCommand(const QString &program, const QStringList &args, QByteArray *output = nullptr, bool verbose = false) { @@ -744,9 +760,9 @@ void printLogcatCrash(const QByteArray &logcat) } if (!crashLogcat.startsWith("********** Crash dump")) - qDebug() << "********** Crash dump: **********"; + qDebug() << "[androidtestrunner] ********** BEGIN crash dump **********"; qDebug().noquote() << crashLogcat.trimmed(); - qDebug() << "********** End crash dump **********"; + qDebug() << "[androidtestrunner] ********** END crash dump **********"; } void analyseLogcat(const QString &timeStamp, int *exitCode) @@ -781,10 +797,13 @@ void analyseLogcat(const QString &timeStamp, int *exitCode) // Check for ANRs const bool anrOccurred = logcat.contains("ANR in %1"_L1.arg(g_options.package).toUtf8()); if (anrOccurred) { - // Treat a found ANR as a test failure. - *exitCode = *exitCode < 1 ? 1 : *exitCode; - qCritical("An ANR has occurred while running the test %s. The logcat will include " - "additional logs from the system_server process.", + // Rather improbable, but if the test managed to return a non-crash exitcode then overwrite + // it to signify that something blew up. Same if we didn't manage to collect an exit code. + // Preserve all other exitcodes, they might be useful crash information from the device. + if (isTestExitCodeNormal(*exitCode) || *exitCode == EXIT_NOEXITCODE) + *exitCode = EXIT_ANR; + qCritical("[androidtestrunner] An ANR has occurred while running the test '%s';" + " consult logcat for additional logs from the system_server process", qPrintable(g_options.package)); } @@ -818,13 +837,14 @@ void analyseLogcat(const QString &timeStamp, int *exitCode) } } - // If we have a crash, attempt to print both logcat and the crash buffer which - // includes the crash stacktrace that is not included in the default logcat. - const bool testCrashed = *exitCode == EXIT_ERROR && !g_testInfo.isTestRunnerInterrupted.load(); + // If we have an unpredictable exitcode, possibly a crash, attempt to print both logcat and the + // crash buffer which includes the crash stacktrace that is not included in the default logcat. + const bool testCrashed = ( !isTestExitCodeNormal(*exitCode) + && !g_testInfo.isTestRunnerInterrupted.load()); if (testCrashed) { - qDebug() << "********** logcat dump **********"; + qDebug() << "[androidtestrunner] ********** BEGIN logcat dump **********"; qDebug().noquote() << testLogcat.join(u'\n').trimmed(); - qDebug() << "********** End logcat dump **********"; + qDebug() << "[androidtestrunner] ********** END logcat dump **********"; if (!crashLogcat.isEmpty()) printLogcatCrash(crashLogcat); @@ -839,7 +859,7 @@ static QString getCurrentTimeString() QStringList dateArgs = { "shell"_L1, "date"_L1, "+'%1'"_L1.arg(timeFormat) }; QByteArray output; if (!execAdbCommand(dateArgs, &output, false)) { - qWarning() << "Date/time adb command failed"; + qWarning() << "[androidtestrunner] ERROR in command: adb shell date"; return {}; } @@ -851,14 +871,15 @@ static int testExitCode() QByteArray exitCodeOutput; const QString exitCodeCmd = "cat files/qtest_last_exit_code 2> /dev/null"_L1; if (!execAdbCommand({ "shell"_L1, runCommandAsUserArgs(exitCodeCmd) }, &exitCodeOutput, false)) { - qCritical() << "Failed to retrieve the test exit code."; - return EXIT_ERROR; + qCritical() << "[androidtestrunner] ERROR in command: adb shell cat files/qtest_last_exit_code"; + return EXIT_NOEXITCODE; } + qDebug() << "[androidtestrunner] Test exitcode: " << exitCodeOutput; bool ok; int exitCode = exitCodeOutput.toInt(&ok); - return ok ? exitCode : EXIT_ERROR; + return ok ? exitCode : EXIT_NOEXITCODE; } static bool uninstallTestPackage() @@ -899,7 +920,7 @@ void sigHandler(int signal) // a main event loop. Since, there's no other alternative to do this, // let's do the cleanup anyway. if (!g_testInfo.isPackageInstalled.load()) - _exit(-1); + _exit(EXIT_ERROR); g_testInfo.isTestRunnerInterrupted.store(true); } @@ -1031,7 +1052,9 @@ int main(int argc, char *argv[]) if (g_options.showLogcatOutput) analyseLogcat(formattedStartTime, &exitCode); - exitCode = pullResults() ? exitCode : EXIT_ERROR; + const bool pullRes = pullResults(); + if (!pullRes && isTestExitCodeNormal(exitCode)) + exitCode = EXIT_NORESULTS; if (!uninstallTestPackage()) return EXIT_ERROR; diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake index 27ea90b89ac..07e11dd935b 100644 --- a/src/tools/configure.cmake +++ b/src/tools/configure.cmake @@ -37,7 +37,7 @@ qt_feature("qmake" PRIVATE QT_FEATURE_datestring AND QT_FEATURE_regularexpression AND QT_FEATURE_temporaryfile) qt_feature("qtwaylandscanner" PRIVATE - CONDITION TARGET Wayland::Scanner + CONDITION TARGET Wayland::Scanner AND NOT INTEGRITY AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT QNX AND NOT VXWORKS ) qt_configure_add_summary_section(NAME "Core tools") diff --git a/src/widgets/accessible/qaccessiblecolorwell.cpp b/src/widgets/accessible/qaccessiblecolorwell.cpp index ca08e511e9a..64fcd2a7fd1 100644 --- a/src/widgets/accessible/qaccessiblecolorwell.cpp +++ b/src/widgets/accessible/qaccessiblecolorwell.cpp @@ -3,6 +3,8 @@ #include "private/qaccessiblecolorwell_p.h" +#include <QtCore/qcoreapplication.h> + QT_REQUIRE_CONFIG(accessibility); #if QT_CONFIG(colordialog) @@ -14,6 +16,7 @@ class QAccessibleColorWellItem : public QAccessibleInterface { QAccessibleColorWell *m_parent; + Q_DECLARE_TR_FUNCTIONS(QAccessibleColorWellItem) public: QAccessibleColorWellItem(QAccessibleColorWell *parent); @@ -79,7 +82,7 @@ QString QAccessibleColorWellItem::text(QAccessible::Text t) const if (t == QAccessible::Name) { QRgb color = m_parent->colorWell()->rgbValues()[m_parent->indexOfChild(this)]; //: Color specified via its 3 RGB components (red, green, blue) - return QObject::tr("RGB %1, %2, %3") + return tr("RGB %1, %2, %3") .arg(QString::number(qRed(color)), QString::number(qGreen(color)), QString::number(qBlue(color))); } diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp index d51c408ab5c..ce46170bba5 100644 --- a/src/widgets/dialogs/qcolordialog.cpp +++ b/src/widgets/dialogs/qcolordialog.cpp @@ -662,7 +662,7 @@ private: int val2y(int val); void setVal(int v); - QPixmap *pix; + QPixmap pix; }; @@ -682,14 +682,12 @@ QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) :QWidget(parent) { hue = 100; val = 100; sat = 100; - pix = nullptr; // setAttribute(WA_NoErase, true); setFocusPolicy(Qt::StrongFocus); } QColorLuminancePicker::~QColorLuminancePicker() { - delete pix; } void QColorLuminancePicker::keyPressEvent(QKeyEvent *event) @@ -725,7 +723,7 @@ void QColorLuminancePicker::setVal(int v) if (val == v) return; val = qMax(0, qMin(v,255)); - delete pix; pix=nullptr; + pix = QPixmap(); repaint(); emit newHsv(hue, sat, val); } @@ -744,8 +742,7 @@ void QColorLuminancePicker::paintEvent(QPaintEvent *) QRect r(0, foff, w, height() - 2*foff); int wi = r.width() - 2; int hi = r.height() - 2; - if (!pix || pix->height() != hi || pix->width() != wi) { - delete pix; + if (pix.isNull() || pix.height() != hi || pix.width() != wi) { QImage img(wi, hi, QImage::Format_RGB32); int y; uint *pixel = (uint *) img.scanLine(0); @@ -754,10 +751,10 @@ void QColorLuminancePicker::paintEvent(QPaintEvent *) std::fill(pixel, end, QColor::fromHsv(hue, sat, y2val(y + coff)).rgb()); pixel = end; } - pix = new QPixmap(QPixmap::fromImage(img)); + pix = QPixmap::fromImage(img); } QPainter p(this); - p.drawPixmap(1, coff, *pix); + p.drawPixmap(1, coff, pix); const QPalette &g = palette(); qDrawShadePanel(&p, r, g, true); p.setPen(g.windowText().color()); @@ -773,7 +770,7 @@ void QColorLuminancePicker::setCol(int h, int s , int v) val = v; hue = h; sat = s; - delete pix; pix=nullptr; + pix = QPixmap(); repaint(); } diff --git a/src/widgets/doc/src/external-resources.qdoc b/src/widgets/doc/src/external-resources.qdoc index 96117546a29..0eccc3b19d4 100644 --- a/src/widgets/doc/src/external-resources.qdoc +++ b/src/widgets/doc/src/external-resources.qdoc @@ -1,12 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \externalpage http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/index.html - \title Apple Human Interface Guidelines -*/ - /*! \externalpage https://rk.nvg.ntnu.no/sinclair/computers/zxspectrum/zxspectrum.htm \title Sinclair Spectrum diff --git a/src/widgets/kernel/qtooltip.cpp b/src/widgets/kernel/qtooltip.cpp index 3417d93d587..97332cd7d5d 100644 --- a/src/widgets/kernel/qtooltip.cpp +++ b/src/widgets/kernel/qtooltip.cpp @@ -6,15 +6,12 @@ #include <qapplication.h> #include <qevent.h> -#include <qpointer.h> #include <qstyle.h> #include <qstyleoption.h> #include <qstylepainter.h> #if QT_CONFIG(effects) #include <private/qeffects_p.h> #endif -#include <qtextdocument.h> -#include <qdebug.h> #include <qpa/qplatformscreen.h> #include <qpa/qplatformcursor.h> #if QT_CONFIG(style_stylesheet) @@ -23,12 +20,9 @@ #include <qpa/qplatformwindow.h> #include <qpa/qplatformwindow_p.h> -#include <qlabel.h> #include <QtWidgets/private/qlabel_p.h> #include <QtGui/private/qhighdpiscaling_p.h> #include <qtooltip.h> - -#include <QtCore/qbasictimer.h> #include <QtWidgets/private/qtooltip_p.h> QT_BEGIN_NAMESPACE @@ -190,10 +184,10 @@ void QTipLabel::resizeEvent(QResizeEvent *e) void QTipLabel::mouseMoveEvent(QMouseEvent *e) { if (!rect.isNull()) { - QPoint pos = e->globalPosition().toPoint(); + QPointF pos = e->globalPosition(); if (widget) pos = widget->mapFromGlobal(pos); - if (!rect.contains(pos)) + if (!rect.contains(pos.toPoint())) hideTip(); } QLabel::mouseMoveEvent(e); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 9499c88af12..bd2b5be11aa 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -32,7 +32,7 @@ #include "private/qwidgetwindow_p.h" #include "qpainter.h" #if QT_CONFIG(tooltip) -#include "qtooltip.h" +#include "private/qtooltip_p.h" #endif #if QT_CONFIG(whatsthis) #include "qwhatsthis.h" @@ -1435,7 +1435,9 @@ void QWidgetPrivate::createTLSysExtra() if (extra->topextra->opacity != 255 && q->isWindow()) extra->topextra->window->setOpacity(qreal(extra->topextra->opacity) / qreal(255)); - const bool isTipLabel = q->inherits("QTipLabel"); +#if QT_CONFIG(tooltip) + const bool isTipLabel = qobject_cast<const QTipLabel *>(q) != nullptr; +#endif const bool isAlphaWidget = !isTipLabel && q->inherits("QAlphaWidget"); #ifdef Q_OS_WIN // Pass on native parent handle for Widget embedded into Active X. diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 90c1cfb4b86..592b70ef8ba 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -1994,12 +1994,9 @@ void QCommonStyle::drawControl(ControlElement element, const QStyleOption *opt, tr = proxy()->subElementRect(SE_TabBarTabText, opt, widget); if (!tab->icon.isNull()) { - QPixmap tabIcon = tab->icon.pixmap(tab->iconSize, QStyleHelper::getDpr(p), - (tab->state & State_Enabled) ? QIcon::Normal - : QIcon::Disabled, - (tab->state & State_Selected) ? QIcon::On - : QIcon::Off); - p->drawPixmap(iconRect.x(), iconRect.y(), tabIcon); + const auto mode = (tab->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled; + const auto state = (tab->state & State_Selected) ? QIcon::On : QIcon::Off; + tab->icon.paint(p, iconRect, Qt::AlignCenter, mode, state); } proxy()->drawItemText(p, tr, alignment, tab->palette, tab->state & State_Enabled, tab->text, diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index 25b048db65e..e0fdd56e6d8 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -35,7 +35,7 @@ #include <qabstractscrollarea.h> #include "private/qabstractscrollarea_p.h" #if QT_CONFIG(tooltip) -#include <qtooltip.h> +#include "private/qtooltip_p.h" #endif #include <qshareddata.h> #if QT_CONFIG(toolbutton) @@ -950,7 +950,7 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject * hasFont = v.extractFont(&font, &adj); #if QT_CONFIG(tooltip) - if (object && qstrcmp(object->metaObject()->className(), "QTipLabel") == 0) + if (qobject_cast<const QTipLabel *>(object) != nullptr) palette = QToolTip::palette(); #endif @@ -1495,7 +1495,7 @@ bool QRenderRule::hasModification() const static inline QObject *parentObject(const QObject *obj) { #if QT_CONFIG(tooltip) - if (qobject_cast<const QLabel *>(obj) && qstrcmp(obj->metaObject()->className(), "QTipLabel") == 0) { + if (qobject_cast<const QTipLabel *>(obj) != nullptr) { QObject *p = qvariant_cast<QObject *>(obj->property("_q_stylesheet_parent")); if (p) return p; @@ -1515,7 +1515,7 @@ public: return QStringList(); const QMetaObject *metaObject = OBJECT_PTR(node)->metaObject(); #if QT_CONFIG(tooltip) - if (qstrcmp(metaObject->className(), "QTipLabel") == 0) + if (metaObject == &QTipLabel::staticMetaObject) return QStringList("QToolTip"_L1); #endif QStringList result; @@ -1581,7 +1581,7 @@ public: return false; const QMetaObject *metaObject = OBJECT_PTR(node)->metaObject(); #if QT_CONFIG(tooltip) - if (qstrcmp(metaObject->className(), "QTipLabel") == 0) + if (metaObject == &QTipLabel::staticMetaObject) return nodeName == "QToolTip"_L1; #endif do { @@ -1747,7 +1747,7 @@ int QStyleSheetStyle::nativeFrameWidth(const QWidget *w) } #endif - if (qstrcmp(w->metaObject()->className(), "QTipLabel") == 0) + if (w->metaObject() == &QTipLabel::staticMetaObject) return base->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, w); return base->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, w); diff --git a/src/xml/doc/src/external-resources.qdoc b/src/xml/doc/src/external-resources.qdoc index 89c30a84c47..89552477ae8 100644 --- a/src/xml/doc/src/external-resources.qdoc +++ b/src/xml/doc/src/external-resources.qdoc @@ -1,17 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \externalpage http://www.w3.org/2000/xmlns/ - \title http://www.w3.org/2000/xmlns/ -*/ - -/*! - \externalpage http://www.saxproject.org/ - \title SAX2 Java interface -*/ - /*! \externalpage http://www.w3.org/TR/DOM-Level-2-Core/ \title W3C DOM Level 2 diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index ac8aece707b..7dd9340f51b 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -4,6 +4,9 @@ # Order by dependency [*], then alphabetic. [*] If bugs in part A of # our source would break tests of part B, then test A before B. + +add_subdirectory(util/testrunner) + set(run_dbus_tests OFF) if (QT_FEATURE_dbus) set(run_dbus_tests ON) diff --git a/tests/auto/cmake/mockplugins/.cmake.conf b/tests/auto/cmake/mockplugins/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/mockplugins/.cmake.conf +++ b/tests/auto/cmake/mockplugins/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf b/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf +++ b/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/cmake/test_static_resources/.cmake.conf b/tests/auto/cmake/test_static_resources/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/test_static_resources/.cmake.conf +++ b/tests/auto/cmake/test_static_resources/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp b/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp index c2c09a234c8..d134afc4323 100644 --- a/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp +++ b/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp @@ -463,9 +463,7 @@ void tst_QParallelAnimationGroup::deleteChildrenWithRunningGroup() QCOMPARE(group.state(), QAnimationGroup::Running); QCOMPARE(anim1->state(), QAnimationGroup::Running); - QTest::qWaitFor([&]{ - return group.currentLoopTime() > 0; - }, 200ms); + QVERIFY(QTest::qWaitFor([&] { return group.currentLoopTime() > 0; }, 200ms)); delete anim1; QCOMPARE(group.animationCount(), 0); diff --git a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp index 4124b723b4c..29e26f99bdd 100644 --- a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp +++ b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp @@ -220,8 +220,8 @@ API_TEST(moveRows, moveRows(0, 0, 0)) API_TEST(moveTreeRows, moveRows(QList<int>{0, 0}, 0, QList<int>{0, 0})) API_TEST(insertColumn, insertColumn(0)) -API_TEST(insertColumnWithData, insertColumn(0, {})) -API_TEST(insertColumns, insertColumns(0, std::declval<Range&>())) +API_TEST(insertColumnWithData, insertColumn(0, QList<int>{0})) +API_TEST(insertColumns, insertColumns(0, QList<int>{0})) API_TEST(removeColumn, removeColumn(0)) API_TEST(removeColumns, removeColumns(0, 0)) API_TEST(moveColumn, moveColumn(0, 0)) @@ -849,7 +849,7 @@ void tst_QRangeModelAdapter::insertColumn_API() static_assert(has_insertColumnWithData(d.tableOfNumbers)); static_assert(!has_insertColumnWithData(d.constTableOfNumbers)); - static_assert(has_insertColumnWithData(d.tableOfPointers)); + static_assert(!has_insertColumnWithData(d.tableOfPointers)); } void tst_QRangeModelAdapter::insertColumns_API() @@ -863,7 +863,7 @@ void tst_QRangeModelAdapter::insertColumns_API() static_assert(has_insertColumns(d.tableOfNumbers)); static_assert(!has_insertColumns(d.constTableOfNumbers)); - static_assert(has_insertColumns(d.tableOfPointers)); + static_assert(!has_insertColumns(d.tableOfPointers)); static_assert(!has_insertColumns(d.tableOfRowPointers)); static_assert(!has_insertColumns(d.listOfNamedRoles)); static_assert(!has_insertColumns(d.m_tree)); @@ -1025,8 +1025,18 @@ void tst_QRangeModelAdapter::modelReset() QCOMPARE(adapter[0], 3); QCOMPARE(adapter, (std::vector<int>{3, 2, 1})); + modelAboutToBeResetSpy.clear(); + modelResetSpy.clear(); std::vector<int> modifiedData = adapter; + + adapter.assign(modifiedData.begin(), modifiedData.end()); + QCOMPARE(modelResetSpy.count(), 1); + adapter.setRange(std::vector<int>{3, 2, 1}); + QCOMPARE(modelResetSpy.count(), 2); + std::vector<short> shorts = {10, 11, 12}; + adapter.assign(shorts.begin(), shorts.end()); + QCOMPARE(modelResetSpy.count(), 3); } { @@ -1533,7 +1543,7 @@ void tst_QRangeModelAdapter::tableWriteAccess() QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Not able to assign QVariant")); QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Writing value of type Object\\* to " - "role Qt::RangeModelAdapterRole at index .* of the model failed")); + "role Qt::RangeModelAdapterRole at index .* failed")); #endif adapter.at(0, 0) = new Object; QCOMPARE(dataChangedSpy.count(), 0); @@ -2620,11 +2630,35 @@ using ObjectTree = std::vector<ObjectTreeItem>; class ObjectTreeItem : public ObjectRow { public: - ObjectTreeItem(Object *item = nullptr) + ObjectTreeItem() = default; + + explicit ObjectTreeItem(Object *item) { m_objects[0] = item; } + ObjectTreeItem(const ObjectTreeItem &other) = delete; + ObjectTreeItem &operator=(const ObjectTreeItem &other) = delete; + ObjectTreeItem(ObjectTreeItem &&other) noexcept + { + m_children = std::move(other.m_children); + m_objects = std::move(other.m_objects); + other.m_objects = {}; + } + + ObjectTreeItem &operator=(ObjectTreeItem &&other) noexcept + { + m_children = std::move(other.m_children); + m_objects = std::move(other.m_objects); + other.m_objects = {}; + return *this; + } + + ~ObjectTreeItem() + { + qDeleteAll(m_objects); + } + ObjectTreeItem *parentRow() const { return m_parentRow; } void setParentRow(ObjectTreeItem *parentRow) { m_parentRow = parentRow; } const auto &childRows() const { return m_children; } @@ -2646,9 +2680,7 @@ namespace std { void tst_QRangeModelAdapter::insertAutoConnectObjects() { - ObjectTree emptyTree; - - QRangeModelAdapter adapter(emptyTree); + QRangeModelAdapter adapter(ObjectTree{}); QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged); adapter.model()->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full); @@ -2662,11 +2694,11 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() Object *newChild = new Object; auto firstRow = adapter.begin(); - (*firstRow).children() = ObjectTree{ - ObjectTreeItem(newChild), - ObjectTreeItem(), - ObjectTreeItem() - }; + { + ObjectTree children(3); + children[0] = ObjectTreeItem(newChild); + (*firstRow).children() = std::move(children); + } QCOMPARE(dataChangedSpy.count(), 0); QVERIFY(adapter.hasChildren(0)); newChild->setString("0.0"); @@ -2684,12 +2716,14 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() newChild = new Object; Object *newGrandChild = new Object; ObjectTreeItem newBranch(newChild); - newBranch.childRows() = ObjectTree{ - ObjectTreeItem(), // skip the first row to verify that we continue through nullptr - ObjectTreeItem(newGrandChild), - ObjectTreeItem() - }; - adapter.at({0, 2}) = newBranch; + { + ObjectTree children(3); + // skip the first row to verify that we continue through nullptr + children[1] = ObjectTreeItem(newGrandChild); + newBranch.childRows() = std::move(children); + } + adapter.at({0, 2}) = std::move(newBranch); + QCOMPARE(adapter.rowCount({0, 2}), 3); QCOMPARE(dataChangedSpy.count(), 1); newChild->setNumber(1); QCOMPARE(dataChangedSpy.count(), 2); diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp index 2d9d351286f..f1f88891015 100644 --- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp @@ -63,11 +63,11 @@ using namespace Qt::StringLiterals; #elif defined(Q_OS_WIN) # undef dll_VALID # define dll_VALID true -//# ifdef QT_NO_DEBUG +# if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) +# define SUFFIX "d.dll" +# else # define SUFFIX ".dll" -//# else -//# define SUFFIX "d.dll" -//# endif +# endif # define PREFIX "" #else // all other Unix diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 806b6b43161..f7d98eb15e2 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -5656,6 +5656,12 @@ void tst_QFuture::cancelChainWithContext() int onCancelCnt = 0; bool unexpectedThread = false; + // For in-other-thread case we need a semaphore to make sure that + // the needed continuation is executed. + // Fot the in-main-thread case it's guaranteed by the unwrap() + // implementation. + QSemaphore sem; + auto f = p1.future() .then(context, [&]() { if (QThread::currentThread() != thread) @@ -5666,6 +5672,8 @@ void tst_QFuture::cancelChainWithContext() if (QThread::currentThread() != thread) unexpectedThread = true; ++thenCnt; + if (inOtherThread) + sem.release(); return f; }).unwrap() .then(context, [&]{ @@ -5685,6 +5693,8 @@ void tst_QFuture::cancelChainWithContext() }); p1.finish(); + if (inOtherThread) + sem.acquire(); f.cancelChain(); p2.finish(); f.waitForFinished(); @@ -5737,6 +5747,75 @@ void tst_QFuture::cancelChainWithContext() QCOMPARE_EQ(thenCnt, 4); QCOMPARE_EQ(onCancelCnt, 0); } + // cancelling propagates through unwrap() + { + QPromise<void> p1, p2; + p1.start(); + p2.start(); + + int thenCnt = 0; + int onCancelCnt = 0; + bool unexpectedThread = false; + + // For in-other-thread case we need a semaphore to make sure that + // the first continuation is executed. + // Fot the in-main-thread case it's guaranteed by the unwrap() + // implementation. + QSemaphore sem; + + auto f = p1.future() + .then(context, [&, f2 = p2.future()]() { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + if (inOtherThread) + sem.release(); + return f2; + }).unwrap() + .onCanceled(context, [&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then([&]() { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + return QtFuture::makeReadyVoidFuture(); + }).unwrap() + .onCanceled([&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then(context, [&]{ + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + return QtFuture::makeReadyVoidFuture(); + }).unwrap() + .onCanceled([&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then(context, [&]{ + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + }); + + p1.finish(); + if (inOtherThread) + sem.acquire(); + f.cancelChain(); + p2.finish(); + f.waitForFinished(); + + QVERIFY(!unexpectedThread); + QCOMPARE_EQ(thenCnt, 1); + QCOMPARE_EQ(onCancelCnt, 3); + } } void tst_QFuture::cancelChainOnAnOverwrittenFuture() diff --git a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp index 86dfa5faffc..4c089091f8d 100644 --- a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp +++ b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp @@ -57,6 +57,7 @@ private slots: void multipleReadersLoop(); void multipleWritersLoop(); void multipleReadersWritersLoop(); + void heavyLoadLocks(); void countingTest(); void limitedReaders(); void deleteOnUnlock(); @@ -603,6 +604,111 @@ public: } }; +class HeavyLoadLockThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + const qsizetype iterations; + const int numThreads; + inline HeavyLoadLockThread(QReadWriteLock &l, qsizetype iters, int numThreads, QVector<QAtomicInt *> &counters): + testRwlock(l), + iterations(iters), + numThreads(numThreads), + counters(counters) + { } + +private: + QVector<QAtomicInt *> &counters; + QAtomicInt *getCounter(qsizetype index) + { + QReadLocker locker(&testRwlock); + /* + The index is increased monotonically, so the index + being requested should be always within or at the end of the + counters vector. + */ + Q_ASSERT(index <= counters.size()); + if (counters.size() <= index || counters[index] == nullptr) { + locker.unlock(); + QWriteLocker wlocker(&testRwlock); + if (counters.size() <= index) + counters.resize(index + 1, nullptr); + if (counters[index] == nullptr) + counters[index] = new QAtomicInt(0); + return counters[index]; + } + return counters[index]; + } + void releaseCounter(qsizetype index) + { + QWriteLocker locker(&testRwlock); + delete counters[index]; + counters[index] = nullptr; + } + +public: + void run() override + { + for (qsizetype i = 0; i < iterations; ++i) { + QAtomicInt *counter = getCounter(i); + /* + Here each counter is accessed by each thread + and increaed only once. As a result, when the + counter reaches numThreads, i.e. the fetched + value before the increment is numThreads-1, + we know all threads have accessed this counter + and we can delete it safely. + */ + int prev = counter->fetchAndAddRelaxed(1); + if (prev == numThreads - 1) { +#ifdef QT_BUILDING_UNDER_TSAN + /* + Under TSAN, deleting and freeing an object + will trigger a write operation on the memory + of the object. Since we used fetchAndAddRelaxed + to update the counter, TSAN will report a data + race when deleting the counter here. To avoid + the false positive, we simply reset the counter + to 0 here, with ordered semantics to establish + the sequence to ensure the the free-ing option + happens after all fetchAndAddRelaxed operations + in other threads. + + When not building under TSAN, deleting the counter + will not result in any data read or written to the + memory region of the counter, so no data race will + happen. + */ + counter->fetchAndStoreOrdered(0); +#endif + releaseCounter(i); + } + } + } +}; + +/* + Multiple threads racing acquiring and releasing + locks on the same indices. +*/ + +void tst_QReadWriteLock::heavyLoadLocks() +{ + constexpr qsizetype iterations = 65536 * 4; + constexpr int numThreads = 8; + QVector<QAtomicInt *> counters; + QReadWriteLock testLock; + std::array<std::unique_ptr<HeavyLoadLockThread>, numThreads> threads; + for (auto &thread : threads) + thread = std::make_unique<HeavyLoadLockThread>(testLock, iterations, numThreads, counters); + for (auto &thread : threads) + thread->start(); + for (auto &thread : threads) + thread->wait(); + QVERIFY(counters.size() == iterations); + for (qsizetype i = 0; i < iterations; ++i) + QVERIFY(counters[i] == nullptr); +} /* A writer acquires a read-lock, a reader locks diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp index 417655c31d9..22aa9d44262 100644 --- a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp +++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp @@ -21,6 +21,8 @@ class tst_QHttp2Connection : public QObject private slots: void construct(); void constructStream(); + void streamConfiguration_data(); + void streamConfiguration(); void testSETTINGSFrame(); void maxHeaderTableSize(); void testPING(); @@ -204,6 +206,59 @@ void tst_QHttp2Connection::constructStream() QCOMPARE(stream->isUploadingDATA(), false); } +void tst_QHttp2Connection::streamConfiguration_data() +{ + QTest::addColumn<bool>("useDownloadBuffer"); + + QTest::addRow("useDownloadBuffer=true") << true; + QTest::addRow("useDownloadBuffer=false") << false; +} + +void tst_QHttp2Connection::streamConfiguration() +{ + QFETCH(const bool, useDownloadBuffer); + + auto [client, server] = makeFakeConnectedSockets(); + auto *clientConnection = makeHttp2Connection(client.get(), {}, Client); + auto *serverConnection = makeHttp2Connection(server.get(), {}, Server); + + QHttp2Stream::Configuration config; + config.useDownloadBuffer = useDownloadBuffer; + + QHttp2Stream *clientStream = clientConnection->createStream(config).unwrap(); + QVERIFY(clientStream); + QCOMPARE(clientStream->configuration().useDownloadBuffer, useDownloadBuffer); + QVERIFY(waitForSettingsExchange(clientConnection, serverConnection)); + + QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream }; + QSignalSpy clientDataReceivedSpy{ clientStream, &QHttp2Stream::dataReceived }; + + HPack::HttpHeader headers = getRequiredHeaders(); + clientStream->sendHEADERS(headers, false); + + QVERIFY(newIncomingStreamSpy.wait()); + auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>(); + QVERIFY(serverStream); + + const HPack::HttpHeader responseHeaders{ { ":status", "200" } }; + serverStream->sendHEADERS(responseHeaders, false); + + const QByteArray testData = "Hello World"_ba.repeated(100); + serverStream->sendDATA(testData, true); + + QVERIFY(clientDataReceivedSpy.wait()); + QCOMPARE(clientDataReceivedSpy.count(), 1); + + const QByteArray receivedData = clientDataReceivedSpy.front().front().value<QByteArray>(); + QCOMPARE(receivedData, testData); + + if (useDownloadBuffer) { + QCOMPARE(clientStream->downloadBuffer().byteAmount(), testData.size()); + } else { + QVERIFY(clientStream->downloadBuffer().isEmpty()); + } +} + void tst_QHttp2Connection::testSETTINGSFrame() { constexpr qint32 PrefaceLength = 24; diff --git a/tests/auto/tools/rcc/data/legal/rcc_legal.cpp b/tests/auto/tools/rcc/data/legal/rcc_legal.cpp index 248ab2e3b48..96f87d192e7 100644 --- a/tests/auto/tools/rcc/data/legal/rcc_legal.cpp +++ b/tests/auto/tools/rcc/data/legal/rcc_legal.cpp @@ -3,7 +3,7 @@ ** Copyright (C) 2024 Intel Corporation. ** SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only ** -** Created by: The Resource Compiler for Qt version 6.11.0 +** Created by: The Resource Compiler for Qt version 6.12.0 ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ diff --git a/tests/auto/util/testrunner/CMakeLists.txt b/tests/auto/util/testrunner/CMakeLists.txt new file mode 100644 index 00000000000..5ca8406f854 --- /dev/null +++ b/tests/auto/util/testrunner/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +# Run the qt-testrunner test only inside the CI. +if(DEFINED ENV{COIN_UNIQUE_JOB_ID} AND NOT IOS) + qt_internal_create_test_script( + NAME tst_qt_testrunner + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tst_qt_testrunner.py" ARGS -v + WORKING_DIRECTORY "${test_working_dir}" + OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/tst_qt_testrunner_Wrapper$<CONFIG>.cmake" + ENVIRONMENT "TESTRUNNER" "" + ) +endif() diff --git a/util/testrunner/tests/qt_mock_test-log.xml b/tests/auto/util/testrunner/qt_mock_test-log.xml index 62e93bb8dcc..a164bec9f9c 100644 --- a/util/testrunner/tests/qt_mock_test-log.xml +++ b/tests/auto/util/testrunner/qt_mock_test-log.xml @@ -21,6 +21,16 @@ <Incident type="{{always_crash_result}}" file="" line="0" /> <Duration msecs="0.828272"/> </TestFunction> + + <!-- The strings like this one "{{fail_then_pass:2_result}}" + are just template strings that will be replaced by the test driver + before each test. The colon doesn't have a special meaning. + The datatags in the following tests are just "2", "5", "6". + We don't strictly need datatags because the tests don't include + specific testing for datatags. It's just that adding a couple + of datatags to this XML template, complicates it a bit and + tests somewhat that functionality as a side-effect. + --> <TestFunction name="fail_then_pass"> <Incident type="{{fail_then_pass:2_result}}" file="" line="0"> <DataTag><![CDATA[2]]></DataTag> @@ -32,5 +42,9 @@ <DataTag><![CDATA[6]]></DataTag> </Incident> </TestFunction> + <TestFunction name="fail_then_crash"> + <Incident type="{{fail_then_crash_result}}" file="" line="0" /> + <Duration msecs="0.828272"/> + </TestFunction> <Duration msecs="1904.9"/> </TestCase> diff --git a/util/testrunner/tests/qt_mock_test.py b/tests/auto/util/testrunner/qt_mock_test.py index a7adb8804af..af8fdf24509 100755 --- a/util/testrunner/tests/qt_mock_test.py +++ b/tests/auto/util/testrunner/qt_mock_test.py @@ -24,28 +24,36 @@ # Mode B: # # If invoked without any argument, it runs the tests listed in the -# variable QT_MOCK_TEST_FAIL_LIST. If variable is empty it just runs +# variable QT_MOCK_TEST_RUN_LIST. If variable is empty it just runs # the always_pass test. It also understands qtestlib's `-o outfile.xml,xml` # option for writing a mock testlog in a file. Requires environment variables: # + QT_MOCK_TEST_STATE_FILE :: See above # + QT_MOCK_TEST_XML_TEMPLATE_FILE :: may point to the template XML file # located in the same source directory. Without this variable, the # option `-o outfile.xml,xml` will be ignored. -# + QT_MOCK_TEST_FAIL_LIST :: may contain a comma-separated list of test +# + QT_MOCK_TEST_RUN_LIST :: may contain a comma-separated list of test # that should run. +# + QT_MOCK_TEST_CRASH_CLEANLY :: if set to 1, then the executable will +# crash (exit with a high exit code) +# after successfully running the given tests and writing the XML logfile. + import sys import os import traceback -from tst_testrunner import write_xml_log +from tst_qt_testrunner import write_xml_log MY_NAME = os.path.basename(sys.argv[0]) STATE_FILE = None XML_TEMPLATE = None XML_OUTPUT_FILE = None +CRASH_CLEANLY = False + +def crash(): + sys.exit(131) def put_failure(test_name): with open(STATE_FILE, "a") as f: @@ -98,6 +106,15 @@ def log_test(testcase, result, testsuite=MY_NAME.rpartition(".")[0]): print("%-7s: %s::%s()" % (result, testsuite, testcase)) +def log_xml(fail_list): + if XML_OUTPUT_FILE and XML_TEMPLATE is not None: + if XML_TEMPLATE == "": + # If the template is an empty file, then write an empty output file + with open(XML_OUTPUT_FILE, "w"): + pass + else: + write_xml_log(XML_OUTPUT_FILE, failure=fail_list) + # Return the exit code def run_test(testname): if testname == "initTestCase": @@ -107,7 +124,14 @@ def run_test(testname): elif testname == "always_fail": exit_code = 1 elif testname == "always_crash": - exit_code = 130 + exit_code = 131 + elif testname == "fail_then_crash": + previous_fails = get_failures(testname) + if previous_fails == 0: + put_failure(testname) + exit_code = 1 + else: + exit_code = 131 elif testname.startswith("fail_then_pass"): wanted_fails = int(testname.partition(":")[2]) previous_fails = get_failures(testname) @@ -139,13 +163,16 @@ def no_args_run(): for test in run_list: test_exit_code = run_test(test) if test_exit_code not in (0, 1): - sys.exit(130) # CRASH! + crash() if test_exit_code != 0: fail_list.append(test) total_result = total_result and (test_exit_code == 0) - if XML_TEMPLATE and XML_OUTPUT_FILE: - write_xml_log(XML_OUTPUT_FILE, failure=fail_list) + log_xml(fail_list) + + if CRASH_CLEANLY: + # Crash despite all going well and writing all output files cleanly. + crash() if total_result: sys.exit(0) @@ -163,13 +190,25 @@ def main(): with open(os.environ["QT_MOCK_TEST_XML_TEMPLATE_FILE"]) as f: XML_TEMPLATE = f.read() + global CRASH_CLEANLY + if ("QT_MOCK_TEST_CRASH_CLEANLY" in os.environ + and os.environ["QT_MOCK_TEST_CRASH_CLEANLY"] == "1" + ): + CRASH_CLEANLY = True + args = clean_cmdline() if len(args) == 0: no_args_run() assert False, "Unreachable!" - else: - sys.exit(run_test(args[0])) + else: # run single test function + exit_code = run_test(args[0]) + # Write "fail" in the XML log only if the specific run has failed. + if exit_code != 0: + log_xml([args[0]]) + else: + log_xml([]) + sys.exit(exit_code) # TODO write XPASS test that does exit(1) diff --git a/util/testrunner/tests/tst_testrunner.py b/tests/auto/util/testrunner/tst_qt_testrunner.py index 4a3d92e167b..1134fa0427f 100755 --- a/util/testrunner/tests/tst_testrunner.py +++ b/tests/auto/util/testrunner/tst_qt_testrunner.py @@ -14,7 +14,8 @@ from tempfile import TemporaryDirectory, mkstemp MY_NAME = os.path.basename(__file__) my_dir = os.path.dirname(__file__) -testrunner = os.path.join(my_dir, "..", "qt-testrunner.py") +testrunner = os.path.join(my_dir, "..", "..", "..", "..", + "util", "testrunner", "qt-testrunner.py") mock_test = os.path.join(my_dir, "qt_mock_test.py") xml_log_template = os.path.join(my_dir, "qt_mock_test-log.xml") @@ -26,7 +27,12 @@ import unittest def setUpModule(): global TEMPDIR - TEMPDIR = TemporaryDirectory(prefix="tst_testrunner-") + TEMPDIR = TemporaryDirectory(prefix="tst_qt_testrunner-") + + global EMPTY_FILE + EMPTY_FILE = os.path.join(TEMPDIR.name, "EMPTY") + with open(EMPTY_FILE, "w") as f: + pass filename = os.path.join(TEMPDIR.name, "file_1") print("setUpModule(): setting up temporary directory and env var" @@ -35,6 +41,7 @@ def setUpModule(): os.environ["QT_MOCK_TEST_STATE_FILE"] = filename os.environ["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = xml_log_template + os.environ["QT_TESTRUNNER_TESTING"] = "1" def tearDownModule(): print("\ntearDownModule(): Cleaning up temporary directory:", @@ -44,22 +51,32 @@ def tearDownModule(): # Helper to run a command and always capture output -def run(*args, **kwargs): +def run(args : list, **kwargs): + if args[0].endswith(".py"): + # Make sure we run python executables with the same python version. + # It also helps on Windows, that .py files are not directly executable. + args = [ sys.executable, *args ] if DEBUG: print("Running: ", args, flush=True) - proc = subprocess.run(*args, stdout=PIPE, stderr=STDOUT, **kwargs) + proc = subprocess.run(args, stdout=PIPE, stderr=STDOUT, **kwargs) if DEBUG and proc.stdout: print(proc.stdout.decode(), flush=True) return proc # Helper to run qt-testrunner.py with proper testing arguments. -def run_testrunner(xml_filename=None, testrunner_args=None, +# Always append --log-dir=TEMPDIR unless specifically told not to. +def run_testrunner(xml_filename=None, log_dir=None, + testrunner_args=None, wrapper_script=None, wrapper_args=None, qttest_args=None, env=None): args = [ testrunner ] if xml_filename: args += [ "--parse-xml-testlog", xml_filename ] + if log_dir == None: + args += [ "--log-dir", TEMPDIR.name ] + elif log_dir != "": + args += [ "--log-dir", log_dir ] if testrunner_args: args += testrunner_args @@ -75,16 +92,21 @@ def run_testrunner(xml_filename=None, testrunner_args=None, return run(args, env=env) # Write the XML_TEMPLATE to filename, replacing the templated results. -def write_xml_log(filename, failure=None): +def write_xml_log(filename, failure=None, inject_message=None): data = XML_TEMPLATE + if failure is None: + failure = [] + elif isinstance(failure, str): + failure = [ failure ] # Replace what was asked to fail with "fail" - if type(failure) in (list, tuple): - for template in failure: - data = data.replace("{{"+template+"_result}}", "fail") - elif type(failure) is str: - data = data.replace("{{"+failure+"_result}}", "fail") + for x in failure: + data = data.replace("{{" + x + "_result}}", "fail") # Replace the rest with "pass" data = re.sub(r"{{[^}]+}}", "pass", data) + # Inject possible <Message> tags inside the first <TestFunction> + if inject_message: + i = data.index("</TestFunction>") + data = data[:i] + inject_message + data[i:] with open(filename, "w") as f: f.write(data) @@ -96,6 +118,12 @@ class Test_qt_mock_test(unittest.TestCase): state_file = os.environ["QT_MOCK_TEST_STATE_FILE"] if os.path.exists(state_file): os.remove(state_file) + def assertProcessCrashed(self, proc): + if DEBUG: + print("process returncode is:", proc.returncode) + self.assertTrue(proc.returncode < 0 or + proc.returncode >= 128) + def test_always_pass(self): proc = run([mock_test, "always_pass"]) self.assertEqual(proc.returncode, 0) @@ -125,6 +153,11 @@ class Test_qt_mock_test(unittest.TestCase): self.assertEqual(proc.returncode, 1) proc = run([mock_test, "fail_then_pass:2"]) self.assertEqual(proc.returncode, 0) + def test_fail_then_crash(self): + proc = run([mock_test, "fail_then_crash"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_crash"]) + self.assertProcessCrashed(proc) def test_xml_file_is_written(self): filename = os.path.join(TEMPDIR.name, "testlog.xml") proc = run([mock_test, "-o", filename+",xml"]) @@ -132,6 +165,39 @@ class Test_qt_mock_test(unittest.TestCase): self.assertTrue(os.path.exists(filename)) self.assertGreater(os.path.getsize(filename), 0) os.remove(filename) + # Test it will write an empty XML file if template is empty + def test_empty_xml_file_is_written(self): + my_env = { + **os.environ, + "QT_MOCK_TEST_XML_TEMPLATE_FILE": EMPTY_FILE + } + filename = os.path.join(TEMPDIR.name, "testlog.xml") + proc = run([mock_test, "-o", filename+",xml"], + env=my_env) + self.assertEqual(proc.returncode, 0) + self.assertTrue(os.path.exists(filename)) + self.assertEqual(os.path.getsize(filename), 0) + os.remove(filename) + def test_crash_cleanly(self): + proc = run([mock_test], + env={ **os.environ, "QT_MOCK_TEST_CRASH_CLEANLY":"1" }) + if DEBUG: + print("returncode:", proc.returncode) + self.assertProcessCrashed(proc) + + +# Find in @path, files that start with @testname and end with @pattern, +# where @pattern is a glob-like string. +def find_test_logs(testname=None, path=None, pattern="-*[0-9].xml"): + if testname is None: + testname = os.path.basename(mock_test) + if path is None: + path = TEMPDIR.name + pattern = os.path.join(path, testname + pattern) + logfiles = glob.glob(pattern) + if DEBUG: + print(f"Test ({testname}) logfiles found: ", logfiles) + return logfiles # Test regular invocations of qt-testrunner. class Test_testrunner(unittest.TestCase): @@ -140,19 +206,17 @@ class Test_testrunner(unittest.TestCase): if os.path.exists(state_file): os.remove(state_file) # The mock_test honors only the XML output arguments, the rest are ignored. - old_logfiles = glob.glob(os.path.basename(mock_test) + "*.xml", - root_dir=TEMPDIR.name) + old_logfiles = find_test_logs(pattern="*.xml") for fname in old_logfiles: os.remove(os.path.join(TEMPDIR.name, fname)) - self.env = dict() - self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = os.environ["QT_MOCK_TEST_XML_TEMPLATE_FILE"] - self.env["QT_MOCK_TEST_STATE_FILE"] = state_file - self.testrunner_args = [ "--log-dir", TEMPDIR.name ] + self.env = dict(os.environ) + self.testrunner_args = [] def prepare_env(self, run_list=None): if run_list is not None: self.env['QT_MOCK_TEST_RUN_LIST'] = ",".join(run_list) def run2(self): return run_testrunner(testrunner_args=self.testrunner_args, env=self.env) + def test_simple_invocation(self): # All tests pass. proc = self.run2() @@ -163,11 +227,7 @@ class Test_testrunner(unittest.TestCase): self.assertEqual(proc.returncode, 0) def test_output_files_are_generated(self): proc = self.run2() - xml_output_files = glob.glob(os.path.basename(mock_test) + "-*[0-9].xml", - root_dir=TEMPDIR.name) - if DEBUG: - print("Output files found: ", - xml_output_files) + xml_output_files = find_test_logs() self.assertEqual(len(xml_output_files), 1) def test_always_fail(self): self.prepare_env(run_list=["always_fail"]) @@ -195,10 +255,38 @@ class Test_testrunner(unittest.TestCase): self.prepare_env(run_list=["initTestCase,always_pass"]) proc = self.run2() self.assertEqual(proc.returncode, 3) + def test_fail_then_crash(self): + self.prepare_env(run_list=["fail_then_crash"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + # By testing --no-extra-args, we ensure qt-testrunner works for + # tst_selftests and the other NON_XML_GENERATING_TESTS. + def test_no_extra_args_pass(self): + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_no_extra_args_fail(self): + self.prepare_env(run_list=["always_fail"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_no_extra_args_reruns_only_once_1(self): + self.prepare_env(run_list=["fail_then_pass:1"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + # The 1st rerun PASSed. + self.assertEqual(proc.returncode, 0) + def test_no_extra_args_reruns_only_once_2(self): + self.prepare_env(run_list=["fail_then_pass:2"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + # We never re-run more than once, so the exit code shows FAIL. + self.assertEqual(proc.returncode, 3) # If no XML file is found by qt-testrunner, it is usually considered a # CRASH and the whole test is re-run. Even when the return code is zero. - # It is a PASS only if the test is not capable of XML output (see no_extra_args, TODO test it). + # It is a PASS only if the test is not capable of XML output (see no_extra_args above). def test_no_xml_log_written_pass_crash(self): del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] self.prepare_env(run_list=["always_pass"]) @@ -220,11 +308,86 @@ class Test_testrunner(unittest.TestCase): proc = self.run2() self.assertEqual(proc.returncode, 3) + def test_empty_xml_crash_1(self): + self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = EMPTY_FILE + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_empty_xml_crash_2(self): + self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = EMPTY_FILE + self.prepare_env(run_list=["always_fail"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + # test qFatal should be a crash in all cases. + def test_qfatal_crash_1(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_qfatal_crash_2(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure="always_fail", inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass,always_fail"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + def test_qwarn_is_ignored_1(self): + qwarn_xml_message = """ + <Message type="qwarn" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to create RHI (backend 2)]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=qwarn_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_qwarn_is_ignored_2(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + <Message type="qwarn" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to create RHI (backend 2)]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + # If a test returns success but XML contains failures, it's a CRASH. def test_wrong_xml_log_written_1_crash(self): logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") write_xml_log(logfile, failure="always_fail") del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" self.prepare_env(run_list=["always_pass"]) proc = self.run2() self.assertEqual(proc.returncode, 3) @@ -233,6 +396,7 @@ class Test_testrunner(unittest.TestCase): logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") write_xml_log(logfile) del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" self.prepare_env(run_list=["always_fail"]) proc = self.run2() self.assertEqual(proc.returncode, 3) @@ -241,25 +405,34 @@ class Test_testrunner(unittest.TestCase): if not content: content='exec "$@"' filename = os.path.join(TEMPDIR.name, filename) - # if os.path.exists(filename): - # os.remove(filename) with open(filename, "w") as f: f.write(f'#!/bin/sh\n{content}\n') self.wrapper_script = f.name os.chmod(self.wrapper_script, 0o700) + # Test that it re-runs the full executable in case of crash, even if the + # XML file is valid and shows one specific test failing. + def test_crash_reruns_full_QTQAINFRA_5226(self): + self.env["QT_MOCK_TEST_RUN_LIST"] = "always_fail" + # Tell qt_mock_test to crash after writing a proper XML file. + self.env["QT_MOCK_TEST_CRASH_CLEANLY"] = "1" + proc = self.run2() + # Verify qt-testrunner exited with 3 which means CRASH. + self.assertEqual(proc.returncode, 3) + # Verify that a full executable re-run happened that re-runs 2 times, + # instead of individual functions that re-run 5 times. + xml_output_files = find_test_logs() + self.assertEqual(len(xml_output_files), 2) + # Test that qt-testrunner detects the correct executable name even if we # use a special wrapper script, and that it uses that in the XML log filename. + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") def test_wrapper(self): self.create_wrapper("coin_vxworks_qemu_runner.sh") proc = run_testrunner(wrapper_script=self.wrapper_script, - testrunner_args=["--log-dir",TEMPDIR.name], env=self.env) self.assertEqual(proc.returncode, 0) - xml_output_files = glob.glob(os.path.basename(mock_test) + "-*[0-9].xml", - root_dir=TEMPDIR.name) - if DEBUG: - print("XML output files found: ", xml_output_files) + xml_output_files = find_test_logs() self.assertEqual(len(xml_output_files), 1) # The "androidtestrunner" wrapper is special. It expects the QTest arguments after "--". @@ -269,48 +442,50 @@ class Test_testrunner(unittest.TestCase): self.create_wrapper("androidtestrunner", content= 'while [ "$1" != "--" ]; do shift; done; shift; exec {} "$@"'.format(mock_test)) + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") def test_androidtestrunner_with_aab(self): self.create_mock_anroidtestrunner_wrapper() - # Copied verbatim from our CI logs. The only relevant option is --aab. - androidtestrunner_args= ['--path', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup', '--adb', '/opt/android/sdk/platform-tools/adb', '--skip-install-root', '--ndk-stack', '/opt/android/android-ndk-r27c/ndk-stack', '--manifest', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/app/AndroidManifest.xml', '--make', '"/opt/cmake-3.30.5/bin/cmake" --build /home/qt/work/qt/qtdeclarative_standalone_tests --target tst_qquickpopup_make_aab', '--aab', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/tst_qquickpopup.aab', '--bundletool', '/opt/bundletool/bundletool', '--timeout', '1425'] + # Copied from our CI logs. The only relevant option is --aab. + androidtestrunner_args= [ + '--path', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup', + '--adb', '/opt/android/sdk/platform-tools/adb', '--skip-install-root', + '--ndk-stack', '/opt/android/android-ndk-r27c/ndk-stack', + '--manifest', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/app/AndroidManifest.xml', + '--make', '"/opt/cmake-3.30.5/bin/cmake" --build /home/qt/work/qt/qtdeclarative_standalone_tests --target tst_qquickpopup_make_aab', + '--aab', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/tst_qquickpopup.aab', + '--bundletool', '/opt/bundletool/bundletool', '--timeout', '1425' + ] # In COIN CI, TESTRUNNER="qt-testrunner.py --". That's why we append "--". - proc = run_testrunner(testrunner_args=["--log-dir", TEMPDIR.name, "--"], + proc = run_testrunner(testrunner_args=["--"], wrapper_script=self.wrapper_script, wrapper_args=androidtestrunner_args, env=self.env) self.assertEqual(proc.returncode, 0) - xml_output_files = glob.glob("tst_qquickpopup-*[0-9].xml", - root_dir=TEMPDIR.name) - if DEBUG: - print("XML output files found: ", xml_output_files) + xml_output_files = find_test_logs("tst_qquickpopup") self.assertEqual(len(xml_output_files), 1) # similar to above but with "--apk" + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") def test_androidtestrunner_with_apk(self): self.create_mock_anroidtestrunner_wrapper() androidtestrunner_args= ['--blah', '--apk', '/whatever/waza.apk', 'blue'] - proc = run_testrunner(testrunner_args=["--log-dir", TEMPDIR.name, "--"], + proc = run_testrunner(testrunner_args=["--"], wrapper_script=self.wrapper_script, wrapper_args=androidtestrunner_args, env=self.env) self.assertEqual(proc.returncode, 0) - xml_output_files = glob.glob("waza-*[0-9].xml", - root_dir=TEMPDIR.name) - if DEBUG: - print("XML output files found: ", xml_output_files) + xml_output_files = find_test_logs("waza") self.assertEqual(len(xml_output_files), 1) # similar to above but with neither "--apk" nor "--aab". qt-testrunner throws error. + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") def test_androidtestrunner_fail_to_detect_filename(self): self.create_mock_anroidtestrunner_wrapper() androidtestrunner_args= ['--blah', '--argh', '/whatever/waza.apk', 'waza.aab'] - proc = run_testrunner(testrunner_args=["--log-dir", TEMPDIR.name, "--"], + proc = run_testrunner(testrunner_args=["--"], wrapper_script=self.wrapper_script, wrapper_args=androidtestrunner_args, env=self.env) self.assertEqual(proc.returncode, 1) - xml_output_files = glob.glob("waza-*[0-9].xml", - root_dir=TEMPDIR.name) - if DEBUG: - print("XML output files found: ", xml_output_files) + xml_output_files = find_test_logs("waza") self.assertEqual(len(xml_output_files), 0) @@ -322,7 +497,8 @@ class Test_testrunner(unittest.TestCase): # + No failure logged. qt-testrunner should exit(0) # + The "always_pass" test has failed. qt-testrunner should exit(0). # + The "always_fail" test has failed. qt-testrunner should exit(2). -# + The "always_crash" test has failed. qt-testrunner should exit(2). +# + The "always_crash" test has failed. qt-testrunner should exit(3) +# since the re-run will crash. # + The "fail_then_pass:2" test failed. qt-testrunner should exit(0). # + The "fail_then_pass:5" test failed. qt-testrunner should exit(2). # + The "initTestCase" failed which is listed as NO_RERUN thus @@ -333,28 +509,32 @@ class Test_testrunner_with_xml_logfile(unittest.TestCase): (_handle, self.xml_file) = mkstemp( suffix=".xml", prefix="qt_mock_test-log-", dir=TEMPDIR.name) + os.close(_handle) if os.path.exists(os.environ["QT_MOCK_TEST_STATE_FILE"]): os.remove(os.environ["QT_MOCK_TEST_STATE_FILE"]) def tearDown(self): os.remove(self.xml_file) del self.xml_file + # Run testrunner specifically for the tests here, with --parse-xml-testlog. + def run3(self, testrunner_args=None): + return run_testrunner(self.xml_file, + testrunner_args=testrunner_args) def test_no_failure(self): write_xml_log(self.xml_file, failure=None) - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 0) def test_always_pass_failed(self): write_xml_log(self.xml_file, failure="always_pass") - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 0) def test_always_pass_failed_max_repeats_0(self): write_xml_log(self.xml_file, failure="always_pass") - proc = run_testrunner(self.xml_file, - testrunner_args=["--max-repeats", "0"]) + proc = self.run3(testrunner_args=["--max-repeats", "0"]) self.assertEqual(proc.returncode, 2) def test_always_fail_failed(self): write_xml_log(self.xml_file, failure="always_fail") - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 2) # Assert that one of the re-runs was in verbose mode matches = re.findall("VERBOSE RUN", @@ -362,22 +542,22 @@ class Test_testrunner_with_xml_logfile(unittest.TestCase): self.assertEqual(len(matches), 1) # Assert that the environment was altered too self.assertIn("QT_LOGGING_RULES", proc.stdout.decode()) - def test_always_crash_failed(self): + def test_always_crash_crashed(self): write_xml_log(self.xml_file, failure="always_crash") - proc = run_testrunner(self.xml_file) - self.assertEqual(proc.returncode, 2) + proc = self.run3() + self.assertEqual(proc.returncode, 3) def test_fail_then_pass_2_failed(self): write_xml_log(self.xml_file, failure="fail_then_pass:2") - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 0) def test_fail_then_pass_5_failed(self): write_xml_log(self.xml_file, failure="fail_then_pass:5") - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 2) def test_with_two_failures(self): write_xml_log(self.xml_file, failure=["always_pass", "fail_then_pass:2"]) - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 0) # Check that test output is properly interleaved with qt-testrunner's logging. matches = re.findall(r"(PASS|FAIL!).*\n.*Test process exited with code", @@ -385,7 +565,7 @@ class Test_testrunner_with_xml_logfile(unittest.TestCase): self.assertEqual(len(matches), 4) def test_initTestCase_fail_crash(self): write_xml_log(self.xml_file, failure="initTestCase") - proc = run_testrunner(self.xml_file) + proc = self.run3() self.assertEqual(proc.returncode, 3) diff --git a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp index 10a67daa02a..0f8a6da823e 100644 --- a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp +++ b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp @@ -3572,6 +3572,7 @@ void tst_QComboBox::popupPositionAfterStyleChange() QFrame *container = box.findChild<QComboBoxPrivateContainer *>(); QVERIFY(container); QVERIFY(QTest::qWaitForWindowExposed(container)); + container->resize(80, 80); // Select the last menu item, which will close the popup. This item is then expected // to be centered on top of the combobox the next time the popup opens. diff --git a/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp b/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp index 02fe3f986e9..54ba45c1256 100644 --- a/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp +++ b/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp @@ -2,8 +2,24 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> + #include <QColor> +#include <vector> + +static std::vector<QColor> all_rgb_colors() +{ + std::vector<QColor> colors; + colors.reserve(256 * 256 * 256); + for (int r = 0; r < 256; ++r) { + for (int g = 0; g < 256; ++g) { + for (int b = 0; b < 256; ++b) + colors.emplace_back(r, g, b); + } + } + return colors; +} + class tst_QColor : public QObject { @@ -12,6 +28,11 @@ class tst_QColor : public QObject private slots: void nameRgb(); void nameArgb(); + void toHsl(); + void toHsv(); + +private: + const std::vector<QColor> m_all_rgb = all_rgb_colors(); }; void tst_QColor::nameRgb() @@ -32,6 +53,22 @@ void tst_QColor::nameArgb() } } +void tst_QColor::toHsl() +{ + QBENCHMARK { + for (const QColor &c : m_all_rgb) + [[maybe_unused]] const auto r = c.toHsl(); + } +} + +void tst_QColor::toHsv() +{ + QBENCHMARK { + for (const QColor &c : m_all_rgb) + [[maybe_unused]] const auto r = c.toHsv(); + } +} + QTEST_MAIN(tst_QColor) #include "tst_qcolor.moc" diff --git a/util/testrunner/README b/util/testrunner/README index 5758e325140..cb6722ac807 100644 --- a/util/testrunner/README +++ b/util/testrunner/README @@ -15,10 +15,4 @@ It offers the following functionality The script itself has a testsuite that is simply run by invoking -qtbase/util/testrunner/tests/tst_testrunner.py - -Please *run this manually* before submitting a change to qt-testrunner and -make sure it's passing. The reason it does not run automatically during the -usual qtbase test run, is because -+ the test run should not depend on Python -+ we don't want to wrap the testrunner tests with testrunner. +qtbase/tests/auto/util/testrunner/tst_qt_testrunner.py diff --git a/util/testrunner/qt-testrunner.py b/util/testrunner/qt-testrunner.py index 41e81e83122..1573534cee9 100755 --- a/util/testrunner/qt-testrunner.py +++ b/util/testrunner/qt-testrunner.py @@ -4,10 +4,9 @@ # !!!IMPORTANT!!! If you change anything to this script, run the testsuite -# manually and make sure it still passes, as it doesn't run automatically. -# Just execute the command line as such: +# and make sure it still passes: # -# ./util/testrunner/tests/tst_testrunner.py -v [--debug] +# qtbase/tests/auto/util/testrunner/tst_qt_testrunner.py -v [--debug] # # ======== qt-testrunner ======== # @@ -15,24 +14,44 @@ # tst_whatever, and tries to iron out unpredictable test failures. # In particular: # -# + Appends output argument to it: "-o tst_whatever.xml,xml" -# + Checks the exit code. If it is zero, the script exits with zero, -# otherwise proceeds. -# + Reads the XML test log and Understands exactly which function -# of the test failed. -# + If no XML file is found or was invalid, the test executable -# probably CRASHed, so we *re-run the full test once again*. -# + If some testcases failed it executes only those individually -# until they pass, or until max-repeats times is reached. +# + Append output argument to it: "-o tst_whatever.xml,xml" and +# execute it. +# + Save the exit code. +# - If it is <0 or >=128 (see NOTE_2), mark the test run as CRASH. +# + Read the XML test log and find exactly which functions +# of the test FAILed. +# + Mark the test run as CRASH, if: +# - no XML file is found, +# - or an invalid XML file is found, +# - or the XML contains a QFatal message: <Message type="qfatal"> +# - or no test FAILures are listed in the XML but the saved +# exit code is not 0. +# + If, based on the rules above, the test run is marked as CRASH, +# then *re-run the full test once again* and start this logic over. +# If we are on the 2nd run and CRASH happens again, then exit(3). +# + Examine the saved exit code: +# if it is 0, then exit(0) (success, all tests have PASSed). +# + Otherwise, some testcases failed, so execute only those individually +# until they pass, or until max-repeats (default: 5) times is reached. # # The regular way to use is to set the environment variable TESTRUNNER to -# point to this script before invoking ctest. +# point to this script before invoking ctest. In COIN CI it is set as +# TESTRUNNER="qt-testrunner.py --" to stop it from parsing further args. # # NOTE: this script is crafted specifically for use with Qt tests and for # using it in Qt's CI. For example it detects and acts specially if test # executable is "tst_selftests" or "androidtestrunner". It also detects # env var "COIN_CTEST_RESULTSDIR" and uses it as log-dir. # +# NOTE_2: Why is qt-testrunner considering exit code outside [0,127] as CRASH? +# On Linux, Python subprocess module returns positive `returncode` +# (255 for example), even if the child does exit(-1 for example). It +# returns negative `returncode` only if the child is killed by a signal. +# Qt-testrunner wants to catch both of these cases as CRASH. +# On Windows, a crash is usually accompanied by exitcode >= 0xC0000000. +# Finally, QTest is limiting itself to exit codes in [0,127] +# so anything outside that range is abnormal, thus treated as CRASH. +# # TODO implement --dry-run. # Exit codes of this script: @@ -63,9 +82,17 @@ from pprint import pprint from typing import NamedTuple, Tuple, List, Optional # Define a custom type for returning a fail incident -class WhatFailed(NamedTuple): +class TestResult(NamedTuple): func: str tag: Optional[str] = None +class WhatFailed(NamedTuple): + qfatal_message: Optional[str] = None + failed_tests: List[TestResult] = [] + +class ReRunCrash(Exception): + pass +class BadXMLCrash(Exception): + pass # In the last test re-run, we add special verbosity arguments, in an attempt @@ -83,9 +110,11 @@ NO_RERUN_FUNCTIONS = { # not try to append "-o" to their command-line or re-run failed testcases. # Only add tests here if absolutely necessary! NON_XML_GENERATING_TESTS = { - "tst_selftests", # qtestlib's selftests are using an external test framework (Catch) that does not support -o argument - "tst_QDoc", # Some of QDoc's tests are using an external test framework (Catch) that does not support -o argument - "tst_QDoc_Catch_Generators", # Some of QDoc's tests are using an external test framework (Catch) that does not support -o argument + # These tests use an external test framework (Catch) that doesn't support + # QtTest's -o argument. + "tst_selftests", + "tst_QDoc", + "tst_QDoc_Catch_Generators", } # These are scripts that are used to wrap test execution for special platforms. # They need special handling (most times just skipping the wrapper name in argv[]). @@ -131,6 +160,9 @@ Default flags: --max-repeats 5 --passes-needed 1 " -o log_file.xml -v2 -vs. This will disable some functionality like the" " failed test repetition and the verbose output on failure. This is" " activated by default when TESTARGS is tst_selftests.") + # TODO parser.parse_args(args=sys.argv[0:cmd_index]). + # Where cmd_index is either the first positional argument, or the argument right after "--". + # This way it won't interpet arguments after the first positional arg. args = parser.parse_args() args.self_name = os.path.basename(sys.argv[0]) args.specific_extra_args = [] @@ -198,11 +230,13 @@ Default flags: --max-repeats 5 --passes-needed 1 return args -def parse_log(results_file) -> List[WhatFailed]: - """Parse the XML test log file. Return the failed testcases, if any. +def parse_log(results_file) -> WhatFailed: + """ + Parse the XML test log file. Return the failed testcases, if any, + and the first qfatal message possibly printed. Failures are considered the "fail" and "xpass" incidents. - A testcase is a function with an optional data tag.""" + """ start_timer = timeit.default_timer() try: @@ -222,10 +256,12 @@ def parse_log(results_file) -> List[WhatFailed]: root = tree.getroot() if root.tag != "TestCase": - raise AssertionError( + raise BadXMLCrash( f"The XML test log must have <TestCase> as root tag, but has: <{root.tag}>") failures = [] + qfatal_message = None + n_passes = 0 for e1 in root: if e1.tag == "TestFunction": @@ -233,23 +269,43 @@ def parse_log(results_file) -> List[WhatFailed]: if e2.tag == "Incident": if e2.attrib["type"] in ("fail", "xpass"): func = e1.attrib["name"] + datatag = None e3 = e2.find("DataTag") # every <Incident> might have a <DataTag> if e3 is not None: - failures.append(WhatFailed(func, tag=e3.text)) - else: - failures.append(WhatFailed(func)) + datatag = e3.text + failures.append(TestResult(func, datatag)) else: n_passes += 1 + # Use iter() here to _recursively_ search root for <Message>, + # as we don't trust that messages are always at the same depth. + for message_tag in root.iter(tag="Message"): + messagetype = message_tag.get("type") + if messagetype == "qfatal": + message_desc = message_tag.find("Description") + if message_desc is not None: + qfatal_message = message_desc.text + else: + qfatal_message = "--EMPTY QFATAL--" + L.warning("qFatal message ('%s') found in the XML, treating this run as a CRASH!", + qfatal_message) + break + end_timer = timeit.default_timer() t = end_timer - start_timer L.info(f"Parsed XML file {results_file} in {t:.3f} seconds") L.info(f"Found {n_passes} passes and {len(failures)} failures") - return failures + return WhatFailed(qfatal_message, failures) def run_test(arg_list: List[str], **kwargs): + if (os.environ.get("QT_TESTRUNNER_TESTING", "0") == "1" + and os.name == "nt" + and arg_list[0].endswith(".py") + ): + # For executing qt_mock_test.py under the same Python interpreter when testing. + arg_list = [ sys.executable ] + arg_list L.debug("Running test command line: %s", arg_list) proc = subprocess.run(arg_list, **kwargs) L.info("Test process exited with code: %d", proc.returncode) @@ -257,6 +313,11 @@ def run_test(arg_list: List[str], **kwargs): return proc def unique_filename(test_basename: str) -> str: + + # Hidden env var for testing, enforcing a predictable, non-unique filename. + if os.environ.get("QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"): + return f"{test_basename}" + timestamp = round(time.time() * 1000) return f"{test_basename}-{timestamp}" @@ -291,18 +352,19 @@ def run_full_test(test_basename, testargs: List[str], output_dir: str, def rerun_failed_testcase(test_basename, testargs: List[str], output_dir: str, - what_failed: WhatFailed, + testcase: TestResult, max_repeats, passes_needed, dryrun=False, timeout=None) -> bool: """Run a specific function:tag of a test, until it passes enough times, or until max_repeats is reached. Return True if it passes eventually, False if it fails. + Raise ReRunCrash Exception if it crashes. """ assert passes_needed <= max_repeats - failed_arg = what_failed.func - if what_failed.tag: - failed_arg += ":" + what_failed.tag + failed_arg = testcase.func + if testcase.tag: + failed_arg += ":" + testcase.tag n_passes = 0 @@ -325,6 +387,19 @@ def rerun_failed_testcase(test_basename, testargs: List[str], output_dir: str, proc = run_test(testargs + output_args + VERBOSE_ARGS + [failed_arg], timeout=timeout, env={**os.environ, **VERBOSE_ENV}) + # There are platforms that run tests wrapped with some test-runner + # script, that can possibly fail to extract a process exit code. + # Because of these cases, we *also* parse the XML file and signify + # CRASH in case of QFATAL/empty/corrupt result. + what_failed = parse_log(f"{pathname_stem}.xml") + if what_failed.qfatal_message: + raise ReRunCrash(f"CRASH! returncode:{proc.returncode} " + f"QFATAL:'{what_failed.qfatal_message}'") + if proc.returncode < 0 or proc.returncode >= 128: + raise ReRunCrash(f"CRASH! returncode:{proc.returncode}") + if proc.returncode == 0 and len(what_failed.failed_tests) > 0: + raise ReRunCrash("CRASH! returncode:0 but failures were found: " + + what_failed.failed_tests) if proc.returncode == 0: n_passes += 1 if n_passes == passes_needed: @@ -354,20 +429,22 @@ def main(): try: results_file = None - failed_functions = [] + what_failed = WhatFailed() if args.parse_xml_testlog: # do not run test, just parse file - failed_functions = parse_log(args.parse_xml_testlog) + what_failed = parse_log(args.parse_xml_testlog) # Pretend the test returned correct exit code - retcode = len(failed_functions) + retcode = len(what_failed.failed_tests) else: # normal invocation, run test (retcode, results_file) = \ run_full_test(args.test_basename, args.testargs, args.log_dir, args.no_extra_args, args.dry_run, args.timeout, args.specific_extra_args) if results_file: - failed_functions = parse_log(results_file) + what_failed = parse_log(results_file) + + failed_functions = what_failed.failed_tests - if retcode < 0: + if retcode < 0 or retcode >= 128 or what_failed.qfatal_message: L.warning("CRASH detected, re-running the whole executable") continue if retcode == 0: @@ -392,6 +469,8 @@ def main(): assert len(failed_functions) > 0 and retcode != 0 break # all is fine, goto re-running individual failed testcases + except AssertionError: + raise except Exception as e: L.error("exception:%s %s", type(e).__name__, e) L.error("The test executable probably crashed, see above for details") @@ -402,13 +481,15 @@ def main(): L.info("Some tests failed, will re-run at most %d times.\n", args.max_repeats) - for what_failed in failed_functions: + for test_result in failed_functions: try: ret = rerun_failed_testcase(args.test_basename, args.testargs, args.log_dir, - what_failed, args.max_repeats, args.passes_needed, + test_result, args.max_repeats, args.passes_needed, dryrun=args.dry_run, timeout=args.timeout) + except AssertionError: + raise except Exception as e: - L.error("exception:%s %s", type(e).__name__, e) + L.error("exception:%s", e) L.error("The testcase re-run probably crashed, giving up") sys.exit(3) # Test re-run CRASH |
