diff options
Diffstat (limited to 'tests')
60 files changed, 1892 insertions, 999 deletions
diff --git a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt index d0ef37d817f..e9578d7246a 100644 --- a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt +++ b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt @@ -1,3 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(${RunCMake_TEST} LANGUAGES CXX) -include(${RunCMake_TEST}.cmake) + +# To allow including the gen files in other subdirectory projects. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(${SBOM_INCLUDE_FILE}.cmake) diff --git a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake index 3103e651093..81e83ccead3 100644 --- a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake +++ b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake @@ -1,27 +1,68 @@ include(QtRunCMake) -function(run_cmake_and_build case) +function(run_cmake_and_build case format_case) + set(include_file "${case}") + set(case "${format_case}-${case}") + # Set common build directory for configure and build set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build) set(options "-DQt6_DIR=${Qt6_DIR}" "-DCMAKE_INSTALL_PREFIX=${RunCMake_TEST_BINARY_DIR}/installed" + "-DSBOM_INCLUDE_FILE=${include_file}" + "-DFORMAT_CASE=${format_case}" ) + if(format_case STREQUAL "spdx23") + list(APPEND options + -DQT_GENERATE_SBOM=ON + -DQT_SBOM_GENERATE_SPDX_V2=ON + -DQT_SBOM_GENERATE_CYDX_V1_6=OFF + ) + elseif(format_case STREQUAL "cydx16") + list(APPEND options + -DQT_GENERATE_SBOM=ON + -DQT_SBOM_GENERATE_SPDX_V2=OFF + -DQT_SBOM_GENERATE_CYDX_V1_6=ON + ) + elseif(format_case STREQUAL "all") + list(APPEND options + -DQT_GENERATE_SBOM=ON + -DQT_SBOM_GENERATE_SPDX_V2=ON + -DQT_SBOM_GENERATE_CYDX_V1_6=ON + ) + elseif(format_case STREQUAL "none") + list(APPEND options + -DQT_GENERATE_SBOM=OFF + ) + endif() + + # Check CI environment variables for SBOM options to ensure we only enabled checks that + # require additional dependencies on machines that actually have them. + # Also allow force enabling all checks via QT_SBOM_FORCE_ALL_CHECKS env var. set(maybe_sbom_env_args "$ENV{SBOM_COMMON_ARGS}") + set(force_all_checks "$ENV{QT_SBOM_FORCE_ALL_CHECKS}") - if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON") + if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON" + OR force_all_checks) list(APPEND options "-DQT_INTERNAL_SBOM_DEFAULT_CHECKS=ON") endif() - if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON") + if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON" + OR force_all_checks) list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT=ON") endif() - if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON") + if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON" + OR force_all_checks) list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON") endif() + if(maybe_sbom_env_args MATCHES "QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON" + OR force_all_checks) + list(APPEND options "-DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON") + endif() + # Need to pass the python interpreter paths, to avoid sbom2doc not found errors. # This mirrors what coin/instructions/prepare_building_env.yaml does. set(maybe_python3_path "$ENV{PYTHON3_PATH}") @@ -42,9 +83,17 @@ function(run_cmake_and_build case) # fine. set(RunCMake_TEST_OUTPUT_MERGE 1) run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .) + + # Check the sbom files are present after installation. + set(RunCMake-check-file "check.cmake") run_cmake_command(${case}-install ${CMAKE_COMMAND} --install .) + unset(RunCMake-check-file) endfunction() -run_cmake_and_build(minimal) -run_cmake_and_build(full) -run_cmake_and_build(versions) +set(format_cases spdx23 cydx16 all none) +foreach(format_case IN LISTS format_cases) + run_cmake_and_build(minimal "${format_case}") + run_cmake_and_build(full "${format_case}") + run_cmake_and_build(versions "${format_case}") +endforeach() + diff --git a/tests/auto/cmake/RunCMake/Sbom/check.cmake b/tests/auto/cmake/RunCMake/Sbom/check.cmake new file mode 100644 index 00000000000..9e143e35ab2 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/check.cmake @@ -0,0 +1,63 @@ +function(check_exists file) + if(NOT EXISTS "${file}") + get_filename_component(file_dir "${file}" DIRECTORY) + file(GLOB dir_contents "${file_dir}/*") + string(APPEND RunCMake_TEST_FAILED "${file} does not exist\n. " + "Contents of directory ${file_dir}:\n ${dir_contents}\n") + endif() + set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) +endfunction() + +function(check_not_exists file) + if(EXISTS "${file}") + get_filename_component(file_dir "${file}" DIRECTORY) + file(GLOB dir_contents "${file_dir}/*") + string(APPEND RunCMake_TEST_FAILED "${file} exists\n. " + "Contents of directory ${file_dir}:\n ${dir_contents}\n") + endif() + set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) +endfunction() + +# Check that the correct option values are used for the project root sbom. +set(root_result_file "${RunCMake_TEST_BINARY_DIR}/result.cmake") +if(EXISTS "${root_result_file}") + include("${root_result_file}") + + if(NOT "${ORIGINAL_QT_GENERATE_SBOM}" STREQUAL "${RESULT_QT_GENERATE_SBOM}") + string(APPEND RunCMake_TEST_FAILED + "QT_GENERATE_SBOM is ${RESULT_QT_GENERATE_SBOM}, expected ${ORIGINAL_QT_GENERATE_SBOM} \n") + endif() + + if(NOT "${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2}" STREQUAL "${RESULT_QT_SBOM_GENERATE_SPDX_V2}") + string(APPEND RunCMake_TEST_FAILED + "QT_SBOM_GENERATE_SPDX_V2 is ${RESULT_QT_SBOM_GENERATE_SPDX_V2}, " + "expected ${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2} \n") + endif() + + if(NOT "${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6}" STREQUAL "${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}") + string(APPEND RunCMake_TEST_FAILED + "QT_SBOM_GENERATE_CYDX_V1_6 is ${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}, " + "expected ${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6} \n") + endif() +endif() + + +# Glob for all result.cmake files recursively in the root of the test binary dir, and run checks +# for each of them. +file(GLOB_RECURSE result_files + "${RunCMake_TEST_BINARY_DIR}/**/result.cmake" +) + +# Confirm that the all subproject sbom files are installed, including the root one. +foreach(result_file IN LISTS result_files) + include("${result_file}") + + foreach(sbom_doc IN LISTS SBOM_DOCUMENTS) + check_exists("${sbom_doc}") + endforeach() + + foreach(sbom_doc IN LISTS NO_SBOM_DOCUMENTS) + check_not_exists("${sbom_doc}") + endforeach() +endforeach() + diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake new file mode 100644 index 00000000000..a892fee2082 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake @@ -0,0 +1,67 @@ +if(NOT SBOM_PROJECT_NAME) + set(SBOM_PROJECT_NAME "${PROJECT_NAME}") +endif() +# Convert to lower case, otherwise on case-sensitive filesystems the generated +# filenames may not match the expected ones. +string(TOLOWER "${SBOM_PROJECT_NAME}" SBOM_PROJECT_NAME) + +if(NOT SBOM_VERSION) + set(SBOM_VERSION "1.0.0") +endif() + +if(NOT SBOM_INSTALL_DIR) + set(SBOM_INSTALL_DIR "sbom") +endif() + +set(sbom_document_base_name "${SBOM_PROJECT_NAME}-${SBOM_VERSION}") +set(sbom_install_dir "${CMAKE_BINARY_DIR}/installed/${SBOM_INSTALL_DIR}") + +set(spdx_file "${sbom_install_dir}/${sbom_document_base_name}.spdx") +set(spdx_json_file "${sbom_install_dir}/${sbom_document_base_name}.spdx.json") +set(cydx_file "${sbom_install_dir}/${sbom_document_base_name}.cdx.json") + +set(sbom_documents "") +set(no_sbom_documents "") + +if(FORMAT_CASE STREQUAL "spdx23" OR FORMAT_CASE STREQUAL "all") + if(QT_SBOM_GENERATE_SPDX_V2) + list(APPEND sbom_documents "${spdx_file}") + else() + list(APPEND no_sbom_documents "${spdx_file}") + endif() + + if(QT_SBOM_GENERATE_SPDX_V2_JSON) + list(APPEND sbom_documents "${spdx_json_file}") + else() + list(APPEND no_sbom_documents "${spdx_json_file}") + endif() +endif() + +if(FORMAT_CASE STREQUAL "cydx16" OR FORMAT_CASE STREQUAL "all") + if(QT_SBOM_GENERATE_CYDX_V1_6) + list(APPEND sbom_documents "${cydx_file}") + else() + list(APPEND no_sbom_documents "${cydx_file}") + endif() +endif() + +if(FORMAT_CASE STREQUAL "none") + set(no_sbom_documents ${spdx_file} ${spdx_json_file} ${cydx_file}) + set(sbom_documents "") +endif() + +# These values will be used by the check.cmake script after installation. +file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/result.cmake" + CONTENT + " +set(SBOM_DOCUMENTS \"${sbom_documents}\") +set(NO_SBOM_DOCUMENTS \"${no_sbom_documents}\") +set(ORIGINAL_QT_GENERATE_SBOM \"${original_QT_GENERATE_SBOM}\") +set(ORIGINAL_QT_SBOM_GENERATE_SPDX_V2 \"${original_QT_SBOM_GENERATE_SPDX_V2}\") +set(ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6 \"${original_QT_SBOM_GENERATE_CYDX_V1_6}\") +set(RESULT_QT_GENERATE_SBOM \"${QT_GENERATE_SBOM}\") +set(RESULT_QT_SBOM_GENERATE_SPDX_V2 \"${QT_SBOM_GENERATE_SPDX_V2}\") +set(RESULT_QT_SBOM_GENERATE_CYDX_V1_6 \"${QT_SBOM_GENERATE_CYDX_V1_6}\") +" +) diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake new file mode 100644 index 00000000000..11ec50d76d6 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake @@ -0,0 +1,12 @@ +# Record the sbom option values before they might be modified by an sbom_setup call, due to +# missing python dependencies. +set(original_QT_GENERATE_SBOM "${QT_GENERATE_SBOM}") +set(original_QT_SBOM_GENERATE_SPDX_V2 "${QT_SBOM_GENERATE_SPDX_V2}") +set(original_QT_SBOM_GENERATE_CYDX_V1_6 "${QT_SBOM_GENERATE_CYDX_V1_6}") + +# Explicitly set these because none case only has QT_GENERATE_SBOM passed as OFF. +# In this case, the defaults for the formats is to remain ON. +if(NOT QT_GENERATE_SBOM) + set(original_QT_SBOM_GENERATE_SPDX_V2 ON) + set(original_QT_SBOM_GENERATE_CYDX_V1_6 ON) +endif() diff --git a/tests/auto/cmake/RunCMake/Sbom/full.cmake b/tests/auto/cmake/RunCMake/Sbom/full.cmake index 7241b8e77a8..b5ac77c3ccc 100644 --- a/tests/auto/cmake/RunCMake/Sbom/full.cmake +++ b/tests/auto/cmake/RunCMake/Sbom/full.cmake @@ -1,23 +1,30 @@ # Needed to make the sbom functions available. find_package(Qt6 REQUIRED Core) +include(CommonResultGenIntro) + _qt_internal_setup_sbom( GENERATE_SBOM_DEFAULT "TRUE" ) set(IS_FULL_BUILD "TRUE") +# These are used by common_result_gen.cmake. +set(SBOM_VERSION "2.0.0") +set(SBOM_INSTALL_DIR "sbom_full") +set(SBOM_PROJECT_NAME "${PROJECT_NAME}ProjectFull") + _qt_internal_sbom_begin_project( - SBOM_PROJECT_NAME "${PROJECT_NAME}Project" + SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}" SUPPLIER "QtProjectTest" SUPPLIER_URL "https://qt-project.org/SbomTest" LICENSE_EXPRESSION "LGPL-3.0-only" COPYRIGHTS "2025 The Qt Company Ltd." INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" - INSTALL_SBOM_DIR "sbom" + INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}" DOWNLOAD_LOCATION "https://download.qt.io/sbom" CPE "cpe:2.3:a:qt:qtprojecttest:1.0.0:*:*:*:*:*:*:*" - VERSION "1.0.0" + VERSION "${SBOM_VERSION}" DOCUMENT_CREATOR_TOOL "Test Build System Tool" LICENSE_DIR_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/custom_licenses" ) @@ -26,3 +33,7 @@ include(common_targets.cmake) _qt_internal_sbom_end_project() +include(CommonResultGen) + +# Also create separate sboms for some sibling projects under this subdir. +add_subdirectory(subprojects) diff --git a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake index 4422314b2d5..27dfc0a3b12 100644 --- a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake +++ b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake @@ -1,17 +1,26 @@ # Needed to make the sbom functions available. find_package(Qt6 REQUIRED Core) +include(CommonResultGenIntro) + _qt_internal_setup_sbom( GENERATE_SBOM_DEFAULT "TRUE" ) +# This is used by common_result_gen.cmake. +set(SBOM_VERSION "1.0.0") + _qt_internal_sbom_begin_project( SUPPLIER "QtProjectTest" SUPPLIER_URL "https://qt-project.org/SbomTest" - VERSION "1.0.0" + VERSION "${SBOM_VERSION}" ) include(common_targets.cmake) _qt_internal_sbom_end_project() +include(CommonResultGen) + +# Also create separate sboms for some sibling projects under this subdir. +add_subdirectory(subprojects) diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt new file mode 100644 index 00000000000..0d9e12534df --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt @@ -0,0 +1,5 @@ +# These subprojects look up the same system library dependency separately in each subdirectory +# scope to ensure that we simulate the same scenario as in a top-level qt5.git build, so that +# we try to reuse the same system library entity in multiple sboms. +add_subdirectory(subproj1) +add_subdirectory(subproj2) diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt new file mode 100644 index 00000000000..a59908d8889 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt @@ -0,0 +1,51 @@ +project(subproj1) + +# This is used by common_result_gen.cmake. +set(SBOM_VERSION "1.0.1") +set(SBOM_PROJECT_NAME "${PROJECT_NAME}") + +_qt_internal_sbom_begin_project( + SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}" + INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}" + SUPPLIER "QtProjectTest" + SUPPLIER_URL "https://qt-project.org/SbomTest" + VERSION "${SBOM_VERSION}" +) + +add_library(subproj1_helper STATIC) +target_sources(subproj1_helper PRIVATE subproj1_helper.cpp) +target_link_libraries(subproj1_helper) +install(TARGETS subproj1_helper + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +_qt_internal_add_sbom(subproj1_helper + TYPE "LIBRARY" + RUNTIME_PATH bin + ARCHIVE_PATH lib + LIBRARY_PATH lib +) + + +# This will actually refer to the root project Threads, that gets brought in via Qt6::Core +# dependency. +find_package(Threads) +if(TARGET Threads::Threads) + _qt_internal_add_sbom(Threads::Threads + TYPE SYSTEM_LIBRARY + ) + target_link_libraries(subproj1_helper PRIVATE Threads::Threads) +endif() + +# Create another IMPORTED target that is meant to simulate a system library, so we don't refer to +# a target that exists in the root scope. +add_library(FancySystemLib IMPORTED INTERFACE) +_qt_internal_add_sbom(FancySystemLib + TYPE SYSTEM_LIBRARY +) +target_link_libraries(subproj1_helper PRIVATE FancySystemLib) + +_qt_internal_sbom_end_project() + +include(CommonResultGen) diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp new file mode 100644 index 00000000000..f6b17d4b9f8 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp @@ -0,0 +1,4 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +int func() { return 0; }; diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt new file mode 100644 index 00000000000..0a408ddf47f --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt @@ -0,0 +1,46 @@ +project(subproj2) + +# This is used by common_result_gen.cmake. +set(SBOM_VERSION "1.0.2") +set(SBOM_PROJECT_NAME "${PROJECT_NAME}") + +_qt_internal_sbom_begin_project( + SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}" + INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}" + SUPPLIER "QtProjectTest" + SUPPLIER_URL "https://qt-project.org/SbomTest" + VERSION "${SBOM_VERSION}" +) + +add_library(subproj2_helper STATIC) +target_sources(subproj2_helper PRIVATE subproj2_helper.cpp) +target_link_libraries(subproj2_helper) +install(TARGETS subproj2_helper + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +_qt_internal_add_sbom(subproj2_helper + TYPE "LIBRARY" + RUNTIME_PATH bin + ARCHIVE_PATH lib + LIBRARY_PATH lib +) + +find_package(Threads) +if(TARGET Threads::Threads) + _qt_internal_add_sbom(Threads::Threads + TYPE SYSTEM_LIBRARY + ) + target_link_libraries(subproj2_helper PRIVATE Threads::Threads) +endif() + +add_library(FancySystemLib IMPORTED INTERFACE) +_qt_internal_add_sbom(FancySystemLib + TYPE SYSTEM_LIBRARY +) +target_link_libraries(subproj2_helper PRIVATE FancySystemLib) + +_qt_internal_sbom_end_project() + +include(CommonResultGen) diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp new file mode 100644 index 00000000000..f6b17d4b9f8 --- /dev/null +++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp @@ -0,0 +1,4 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +int func() { return 0; }; diff --git a/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp b/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp index fe509e558c6..72175f8e04c 100644 --- a/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp +++ b/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp @@ -347,7 +347,7 @@ void tst_QCheckedInt::division() // This causes an internal compiler error on MSVC, so skipping it there // Integrity's compiler says this code isn't constexpr. -#if (!defined(Q_CC_MSVC) || Q_CC_MSVC > 1944) && !defined(Q_CC_GHS) +#if (!defined(Q_CC_MSVC) || Q_CC_MSVC > 1950) && !defined(Q_CC_GHS) template <typename T> constexpr bool checkedIntTypeProperties() diff --git a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp index bbe355061eb..0474ad974cb 100644 --- a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp +++ b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp @@ -17,15 +17,26 @@ class tst_QGetPutEnv : public QObject { Q_OBJECT private slots: + void init(); + void getSetCheck(); void encoding(); void intValue_data(); void intValue(); + +private: + QByteArray uniqueEnvVarName; }; +void tst_QGetPutEnv::init() +{ + QUuid uuid = QUuid::createUuid(); + uniqueEnvVarName = "QT_TEST_ENV_VAR_" + uuid.toByteArray(QUuid::Id128); +} + void tst_QGetPutEnv::getSetCheck() { - const char varName[] = "should_not_exist"; + const char *varName = uniqueEnvVarName.constData(); bool ok; @@ -117,13 +128,14 @@ void tst_QGetPutEnv::encoding() // The LATIN SMALL LETTER A WITH ACUTE is NFC for NFD: // U+0061 U+0301 LATIN SMALL LETTER A + COMBINING ACUTE ACCENT - const char varName[] = "should_not_exist"; + const char *varName = uniqueEnvVarName.constData(); + static const wchar_t rawvalue[] = { 'a', 0x00E1, 0x03B1, 0x0430, 0 }; QString value = QString::fromWCharArray(rawvalue); #if defined(Q_OS_WIN) - const wchar_t wvarName[] = L"should_not_exist"; - _wputenv_s(wvarName, rawvalue); + std::wstring wvarName = QString::fromUtf8(varName).toStdWString(); + _wputenv_s(wvarName.data(), rawvalue); #else // confirm the locale is UTF-8 if (value.toLocal8Bit() != "a\xc3\xa1\xce\xb1\xd0\xb0") @@ -203,7 +215,7 @@ void tst_QGetPutEnv::intValue_data() void tst_QGetPutEnv::intValue() { const int maxlen = (sizeof(qint64) * CHAR_BIT + 2) / 3; - const char varName[] = "should_not_exist"; + const char *varName = uniqueEnvVarName.constData(); QFETCH(QByteArray, value); QFETCH(qint64, expected); diff --git a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp index 0b65673c393..bbcc8c5e5e1 100644 --- a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp +++ b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp @@ -464,7 +464,7 @@ Q_COREAPP_STARTUP_FUNCTION(myStartupFunc) void tst_QGlobal::qCoreAppStartupFunction() { - QCOMPARE(qStartupFunctionValue, 0); + qStartupFunctionValue = 0; int argc = 1; char *argv[] = { const_cast<char*>(QTest::currentAppName()) }; QCoreApplication app(argc, argv); diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp index d2fee5124db..34c0fd11bed 100644 --- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp +++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp @@ -1011,7 +1011,7 @@ SUB_OVERFLOW_UNSIGNED_TYPE_TEST(ulong, ULONG_MAX) #if defined(QT_HAS_128_BIT_MULTIPLICATION) // Compiling this causes an ICE in MSVC, so skipping it -#if !defined(Q_CC_MSVC) || Q_CC_MSVC > 1944 +#if !defined(Q_CC_MSVC) || Q_CC_MSVC > 1950 SIGNED_TYPE_TEST(qlonglong, LLONG_MIN, LLONG_MAX) UNSIGNED_TYPE_TEST(qulonglong, ULLONG_MAX) #endif diff --git a/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp index a32045bbbb1..f9a77e05de9 100644 --- a/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp +++ b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp @@ -304,11 +304,9 @@ void tst_QRandomGenerator::generate32_data() QTest::newRow("fixed") << (RandomValue32 & RandomDataMask); QTest::newRow("global") << 0U; #ifdef QT_BUILD_INTERNAL - if (qHasHwrng()) - QTest::newRow("hwrng") << uint(UseSystemRNG); - QTest::newRow("system") << uint(UseSystemRNG | SkipHWRNG); + QTest::newRow("system") << uint(UseSystemRNG); # ifdef HAVE_FALLBACK_ENGINE - QTest::newRow("system-fallback") << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG); + QTest::newRow("system-fallback") << uint(UseSystemRNG | SkipSystemRNG); # endif #endif } @@ -553,7 +551,7 @@ void tst_QRandomGenerator::bounded() QCOMPARE(ivalue, int(expected)); // confirm only the bound now - setRNGControl(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + setRNGControl(control & (SkipSystemRNG|UseSystemRNG)); value = rng.bounded(sup); QVERIFY(value < sup); @@ -685,7 +683,7 @@ void tst_QRandomGenerator::bounded64() QCOMPARE(ivalue, int(expected)); // confirm only the bound now - setRNGControl(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + setRNGControl(control & (SkipSystemRNG|UseSystemRNG)); value = rng.bounded(sup); QVERIFY(value < sup); @@ -810,11 +808,9 @@ void tst_QRandomGenerator::stdUniformIntDistribution_data() auto newRow = [&](quint32 max) { #ifdef QT_BUILD_INTERNAL - if (qHasHwrng()) - QTest::addRow("hwrng:%u", max) << uint(UseSystemRNG) << max; - QTest::addRow("system:%u", max) << uint(UseSystemRNG | SkipHWRNG) << max; + QTest::addRow("system:%u", max) << uint(UseSystemRNG) << max; # ifdef HAVE_FALLBACK_ENGINE - QTest::addRow("system-fallback:%u", max) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << max; + QTest::addRow("system-fallback:%u", max) << uint(UseSystemRNG | SkipSystemRNG) << max; # endif #endif QTest::addRow("global:%u", max) << 0U << max; @@ -925,11 +921,9 @@ void tst_QRandomGenerator::stdUniformRealDistribution_data() auto newRow = [&](double min, double sup) { #ifdef QT_BUILD_INTERNAL - if (qHasHwrng()) - QTest::addRow("hwrng:%g-%g", min, sup) << uint(UseSystemRNG) << min << sup; - QTest::addRow("system:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG) << min << sup; + QTest::addRow("system:%g-%g", min, sup) << uint(UseSystemRNG) << min << sup; # ifdef HAVE_FALLBACK_ENGINE - QTest::addRow("system-fallback:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << min << sup; + QTest::addRow("system-fallback:%g-%g", min, sup) << uint(UseSystemRNG | SkipSystemRNG) << min << sup; # endif #endif QTest::addRow("global:%g-%g", min, sup) << 0U << min << sup; @@ -948,7 +942,7 @@ void tst_QRandomGenerator::stdUniformRealDistribution() QFETCH(uint, control); QFETCH(double, min); QFETCH(double, sup); - RandomGenerator rng(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + RandomGenerator rng(control & (SkipSystemRNG|UseSystemRNG)); { QRandomGenerator rd; diff --git a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp index e0f677e1511..7603d84623b 100644 --- a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp +++ b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp @@ -1623,6 +1623,8 @@ void tst_QDebug::threadSafety() const #ifdef Q_OS_WASM QSKIP("threadSafety does not run on wasm"); #else + s_messages = {}; + MessageHandlerSetter mhs(threadSafeMessageHandler); const int numThreads = 10; QThreadPool::globalInstance()->setMaxThreadCount(numThreads); diff --git a/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp b/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp index 0fd62d40f62..9aa99a1b653 100644 --- a/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp +++ b/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp @@ -186,6 +186,12 @@ private slots: Q_ASSERT(!qApp); // Rules should not require an app to resolve + static bool calledOnce = false; + if (calledOnce) + QSKIP("QLoggingRegistry_environment can only run once"); + + calledOnce = true; + qputenv("QT_LOGGING_RULES", "qt.foo.bar=true"); QLoggingCategory qtEnabledByLoggingRule("qt.foo.bar"); QCOMPARE(qtEnabledByLoggingRule.isDebugEnabled(), true); diff --git a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp index 5453237cadb..0d06951fb1e 100644 --- a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp +++ b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp @@ -183,7 +183,6 @@ private slots: void testIniParsing_data(); void testIniParsing(); - void testEscapes(); void testEscapedKeys_data(); void testEscapedKeys(); void testUnescapedKeys_data(); @@ -192,6 +191,11 @@ private slots: void testEscapedStringList(); void testUnescapedStringList_data(); void testUnescapedStringList(); + void testEscapedVariant_data(); + void testEscapedVariant(); + void testBadEscape(); + void testBadEscape_data(); + void testNormalizedKey_data(); void testNormalizedKey(); void testVariantTypes_data() { populateWithFormats(); } @@ -2836,48 +2840,6 @@ QString escapeWeirdChars(const QString &s) } #ifdef QT_BUILD_INTERNAL -void tst_QSettings::testEscapes() -{ - QSettings settings(QSettings::UserScope, "software.org", "KillerAPP"); - -#define testVariant(val, escStr, func) \ - { \ - QVariant v(val); \ - QString s = QSettingsPrivate::variantToString(v); \ - QCOMPARE(s, escStr); \ - QCOMPARE(QVariant(QSettingsPrivate::stringToVariant(escStr)), v); \ - QVERIFY((val) == v.func()); \ - } - -#define testBadEscape(escStr, vStr) \ - { \ - QVariant v = QSettingsPrivate::stringToVariant(QString(escStr)); \ - QCOMPARE(v.toString(), QString(vStr)); \ - } - - // streaming qvariant into a string - testVariant(QString("Hello World!"), QString("Hello World!"), toString); - testVariant(QString("Hello, World!"), QString("Hello, World!"), toString); - testVariant(QString("@Hello World!"), QString("@@Hello World!"), toString); - testVariant(QString("@@Hello World!"), QString("@@@Hello World!"), toString); - testVariant(QVariant(100), QString("100"), toString); - testVariant(QStringList() << "ene" << "due" << "rike", QString::fromLatin1("@Variant(\x0\x0\x0\xb\x0\x0\x0\x3\x0\x0\x0\x6\x0\x65\x0n\x0\x65\x0\x0\x0\x6\x0\x64\x0u\x0\x65\x0\x0\x0\x8\x0r\x0i\x0k\x0\x65)", 50), toStringList); - testVariant(QRect(1, 2, 3, 4), QString("@Rect(1 2 3 4)"), toRect); - testVariant(QSize(5, 6), QString("@Size(5 6)"), toSize); - testVariant(QPoint(7, 8), QString("@Point(7 8)"), toPoint); - - testBadEscape("", ""); - testBadEscape("@", "@"); - testBadEscape("@@", "@"); - testBadEscape("@@@", "@@"); - testBadEscape(" ", " "); - testBadEscape("@Rect", "@Rect"); - testBadEscape("@Rect(", "@Rect("); - testBadEscape("@Rect()", "@Rect()"); - testBadEscape("@Rect)", "@Rect)"); - testBadEscape("@Rect(1 2 3)", "@Rect(1 2 3)"); - testBadEscape("@@Rect(1 2 3)", "@Rect(1 2 3)"); -} void tst_QSettings::testEscapedKeys_data() { @@ -3062,6 +3024,71 @@ void tst_QSettings::testUnescapedStringList() QCOMPARE(iniUnescapedStringList(reescStrList), plainStrList); } +void tst_QSettings::testEscapedVariant_data() +{ + QTest::addColumn<QVariant>("val"); + QTest::addColumn<QString>("escStr"); + + // streaming qvariant into a string + + QTest::newRow("Hello World!") << QVariant{u"Hello World!"_s} << u"Hello World!"_s; + QTest::newRow("Hello, World!") << QVariant{u"Hello, World!"_s} << u"Hello, World!"_s; + QTest::newRow("@Hello World!") << QVariant{u"@Hello World!"_s} << u"@@Hello World!"_s; + QTest::newRow("@@Hello World!") << QVariant{u"@@Hello World!"_s} << u"@@@Hello World!"_s; + QTest::newRow("int-100") << QVariant{100} << u"100"_s; + QTest::newRow("qrect") << QVariant{QRect(1, 2, 3, 4)} << u"@Rect(1 2 3 4)"_s; + QTest::newRow("qsize") << QVariant{QSize(5, 6)} << u"@Size(5 6)"_s; + QTest::newRow("qpoint") << QVariant{QPoint(7, 8)} << u"@Point(7 8)"_s; + + auto expected = QString::fromLatin1("@Variant(\x0\x0\x0\xb\x0\x0\x0\x3\x0\x0\x0\x6\x0\x65\x0n" + "\x0\x65\x0\x0\x0\x6\x0\x64\x0u\x0\x65\x0\x0\x0\x8\x0r\x0" + "i\x0k\x0\x65)", 50); + QTest::newRow("stringlist") << QVariant{QStringList{u"ene"_s, u"due"_s, u"rike"_s}} << expected; +} + +void tst_QSettings::testEscapedVariant() +{ + QFETCH(QVariant, val); + QFETCH(QString, escStr); + + QSettings settings(QSettings::UserScope, "example.org", "KillerAPP"); + + QString variantAsString = QSettingsPrivate::variantToString(val); + QCOMPARE(variantAsString, escStr); + + QVariant stringAsVariant = QSettingsPrivate::stringToVariant(escStr); + QCOMPARE(stringAsVariant, val); +} + +void tst_QSettings::testBadEscape_data() +{ + QTest::addColumn<QString>("escStr"); + QTest::addColumn<QString>("variantStr"); + + QTest::newRow("empty") << u""_s << u""_s; + QTest::newRow("space") << u" "_s << u" "_s; + QTest::newRow("@") << u"@"_s << u"@"_s; + QTest::newRow("@@") << u"@@"_s << u"@"_s; + QTest::newRow("@@@") << u"@@@"_s << u"@@"_s; + QTest::newRow("@Rect") << u"@Rect"_s << u"@Rect"_s; + QTest::newRow("@Rect(") << u"@Rect("_s << u"@Rect("_s; + QTest::newRow("@Rect()") << u"@Rect()"_s << u"@Rect()"_s; + QTest::newRow("@Rect)") << u"@Rect)"_s << u"@Rect)"_s; + + QTest::newRow("@Rect(1 2 3)") << u"@Rect(1 2 3)"_s << u"@Rect(1 2 3)"_s; + QTest::newRow("@@Rect(1 2 3)") << u"@@Rect(1 2 3)"_s << u"@Rect(1 2 3)"_s; +} + +void tst_QSettings::testBadEscape() +{ + QFETCH(QString, escStr); + QFETCH(QString, variantStr); + + QSettings settings(QSettings::UserScope, "example.org", "KillerAPP"); + + QVariant v = QSettingsPrivate::stringToVariant(escStr); + QCOMPARE(v.toString(), variantStr); +} #endif void tst_QSettings::testCaseSensitivity() diff --git a/tests/auto/corelib/itemmodels/qrangemodel/data.h b/tests/auto/corelib/itemmodels/qrangemodel/data.h index 0df5b981fd6..c49c7da3d42 100644 --- a/tests/auto/corelib/itemmodels/qrangemodel/data.h +++ b/tests/auto/corelib/itemmodels/qrangemodel/data.h @@ -113,20 +113,36 @@ struct QRangeModel::ItemAccess<ItemAccessType> class Object : public QObject { Q_OBJECT - Q_PROPERTY(QString string READ string WRITE setString) - Q_PROPERTY(int number READ number WRITE setNumber) + Q_PROPERTY(QString string READ string WRITE setString NOTIFY stringChanged) + Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged) public: using QObject::QObject; QString string() const { return m_string; } - void setString(const QString &string) { m_string = string; } + void setString(const QString &string) + { + if (m_string == string) + return; + m_string = string; + Q_EMIT stringChanged(); + } int number() const { return m_number; } - void setNumber(int number) { m_number = number; } + void setNumber(int number) + { + if (m_number == number) + return; + m_number = number; + Q_EMIT numberChanged(m_number); + } + +Q_SIGNALS: + void stringChanged(); + void numberChanged(int number); private: // note: default values need to be convertible to each other QString m_string = "1234"; - int m_number = 42; + int m_number = -1; }; // a class that can be both and requires disambiguation diff --git a/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp b/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp index 6c57a870b27..09522e48084 100644 --- a/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp +++ b/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp @@ -10,6 +10,8 @@ #include <QtCore/qstringlistmodel.h> #include <QtTest/qsignalspy.h> +#include <QtGui/qcolor.h> + #if QT_CONFIG(itemmodeltester) #include <QtTest/qabstractitemmodeltester.h> #endif @@ -34,6 +36,8 @@ private slots: void overrideRoleNames(); void setRoleNames(); void defaultRoleNames(); + void autoConnectPolicy_data(); + void autoConnectPolicy(); void dimensions_data() { createTestData(); } void dimensions(); @@ -730,6 +734,232 @@ void tst_QRangeModel::defaultRoleNames() }(); } +class MultiRoleObject : public Object +{ +public: + template <typename Signal> + bool isConnected(Signal &&signal) const + { + return isSignalConnected(QMetaMethod::fromSignal(signal)); + } +}; + +template <> +struct QRangeModel::RowOptions<MultiRoleObject> +{ + static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem; +}; + +void tst_QRangeModel::autoConnectPolicy_data() +{ + QTest::addColumn<QRangeModel::AutoConnectPolicy>("policy"); + + QTest::addRow("Full") << QRangeModel::AutoConnectPolicy::Full; + QTest::addRow("OnRead") << QRangeModel::AutoConnectPolicy::OnRead; +} + +void tst_QRangeModel::autoConnectPolicy() +{ + QFETCH(const QRangeModel::AutoConnectPolicy, policy); + + [policy]{ + QList<MultiRoleObject *> objectList = { + new MultiRoleObject, + new MultiRoleObject, + new MultiRoleObject, + }; + QRangeModel model(&objectList); + model.setAutoConnectPolicy(policy); + QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); + + int emissions = 0; + objectList[0]->setString("String 0"); + if (policy == QRangeModel::AutoConnectPolicy::OnRead) { + QCOMPARE(dataChangedSpy.size(), emissions); + } else { + QCOMPARE(dataChangedSpy.size(), ++emissions); + QCOMPARE(dataChangedSpy.at(0).at(0), model.index(0, 0)); + QCOMPARE(dataChangedSpy.at(0).at(1), model.index(0, 0)); + QCOMPARE(dataChangedSpy.at(0).at(2), QVariant::fromValue(QList<int>{Qt::UserRole})); + } + + if (policy == QRangeModel::AutoConnectPolicy::OnRead) { + QVERIFY(!objectList.at(1)->isConnected(&Object::stringChanged)); + QVERIFY(!objectList.at(1)->isConnected(&Object::numberChanged)); + model.data(model.index(1, 0), Qt::UserRole + 1); + QVERIFY(!objectList.at(1)->isConnected(&Object::stringChanged)); + QVERIFY(objectList.at(1)->isConnected(&Object::numberChanged)); + model.itemData(model.index(1, 0)); + QVERIFY(objectList.at(1)->isConnected(&Object::stringChanged)); + } + + objectList[1]->setNumber(42); + QCOMPARE(dataChangedSpy.size(), ++emissions); + QCOMPARE(dataChangedSpy.at(emissions - 1).at(0), model.index(1, 0)); + QCOMPARE(dataChangedSpy.at(emissions - 1).at(1), model.index(1, 0)); + QCOMPARE(dataChangedSpy.at(emissions - 1).at(2), QVariant::fromValue(QList<int>{Qt::UserRole + 1})); + + QVERIFY(model.insertRow(0)); + QCOMPARE(objectList.at(1)->isConnected(&Object::numberChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + QCOMPARE(objectList.at(1)->isConnected(&Object::stringChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + }(); + + [policy]{ + QList<QList<MultiRoleObject *>> objectTable = { + {new MultiRoleObject, new MultiRoleObject}, + {new MultiRoleObject, new MultiRoleObject}, + }; + QRangeModel model(&objectTable); + connect(&model, &QRangeModel::rowsInserted, + &model, [&objectTable](const QModelIndex &, int first, int last){ + while (first <= last) { + objectTable[first][0] = new MultiRoleObject; + objectTable[first][1] = new MultiRoleObject; + ++first; + } + }); + connect(&model, &QRangeModel::columnsInserted, + &model, [&objectTable](const QModelIndex &, int first, int last){ + for (auto &row : objectTable) { + for (int column = first; column <= last; ++column) + row[column] = new MultiRoleObject; + } + }); + model.setAutoConnectPolicy(policy); + QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); + + objectTable[0][1]->setString("String 0/1"); + QCOMPARE(dataChangedSpy.size(), policy == QRangeModel::AutoConnectPolicy::Full ? 1 : 0); + + model.insertRows(1, 2); + for (const auto &row : std::as_const(objectTable)) { + for (const auto &object : row) { + QCOMPARE(object->isConnected(&Object::numberChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + QCOMPARE(object->isConnected(&Object::stringChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + } + } + + model.insertColumn(0); + for (const auto &row : std::as_const(objectTable)) { + for (const auto &object : row) { + QCOMPARE(object->isConnected(&Object::numberChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + QCOMPARE(object->isConnected(&Object::stringChanged), + policy == QRangeModel::AutoConnectPolicy::Full); + } + } + }(); + + [policy]{ + QList<std::tuple<MultiRoleObject *, MultiRoleObject *>> objectTable = { + {new MultiRoleObject, new MultiRoleObject}, + {new MultiRoleObject, new MultiRoleObject}, + }; + QRangeModel model(&objectTable); + model.setAutoConnectPolicy(policy); + QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); + + if (policy == QRangeModel::AutoConnectPolicy::OnRead) { + for (int row = 0; row < model.rowCount(); ++row) { + for (int column = 0; column < model.columnCount(); ++column) + model.itemData(model.index(row, column)); + } + } + + auto *topRight = get<1>(objectTable[0]); + auto *bottomLeft = get<0>(objectTable[1]); + topRight->setNumber(52); + QCOMPARE(dataChangedSpy.size(), 1); + + QVERIFY(bottomLeft->isConnected(&Object::numberChanged)); + QVERIFY(bottomLeft->isConnected(&Object::stringChanged)); + QVERIFY(model.removeRows(1, 1)); + bottomLeft->setNumber(52); // this will lazily break the connection + QVERIFY(!bottomLeft->isConnected(&Object::numberChanged)); + QVERIFY(bottomLeft->isConnected(&Object::stringChanged)); + QCOMPARE(dataChangedSpy.size(), 1); + bottomLeft->setNumber(53); // this should not crash + bottomLeft->setString("No update"); + QVERIFY(!bottomLeft->isConnected(&Object::stringChanged)); + + const QModelIndex index = model.index(0, 0); + dataChangedSpy.clear(); + QVERIFY(model.setData(index, "string", Qt::UserRole)); + QCOMPARE(dataChangedSpy.count(), 1); + QCOMPARE(dataChangedSpy.back().at(2), + QVariant::fromValue(QList<int>{Qt::UserRole})); + // this will right now emit dataChanged three times: + QVERIFY(model.setItemData(index, QMap<int, QVariant>{ + {Qt::UserRole, QVariant("string")}, + {Qt::UserRole + 1, QVariant(42)}, + })); + QCOMPARE(dataChangedSpy.count(), 2); + QCOMPARE(dataChangedSpy.back().at(2), + QVariant::fromValue(QList<int>{Qt::UserRole, Qt::UserRole + 1})); + QVERIFY(model.setData(index, 42, Qt::UserRole + 1)); + QCOMPARE(dataChangedSpy.count(), 3); + QCOMPARE(dataChangedSpy.back().at(2), + QVariant::fromValue(QList<int>{Qt::UserRole + 1})); + }(); + + [policy]{ + using Tree = QList<MultiRoleObject *>; + struct ObjectTreeProtocol + { + const MultiRoleObject *parentRow(const MultiRoleObject &row) const + { + return static_cast<MultiRoleObject *>(row.parent()); + } + void setParentRow(MultiRoleObject &row, MultiRoleObject *parent) + { + row.setParent(parent); + } + const Tree &childRows(const MultiRoleObject &row) const { + // don't do that at home... + return *reinterpret_cast<const Tree *>(&row.children()); + } + Tree &childRows(const MultiRoleObject &) { + empty = {}; + return empty; + } + Tree empty; + }; + Tree tree { + new MultiRoleObject, + new MultiRoleObject, + new MultiRoleObject, + }; + tree[0]->setObjectName("root 0"); + tree[1]->setObjectName("root 1"); + tree[2]->setObjectName("root 2"); + auto *child01 = new MultiRoleObject; + child01->setObjectName("child 0/1"); + child01->setParent(tree[0]); + (new MultiRoleObject)->setParent(tree[1]); + (new MultiRoleObject)->setParent(tree[2]); + + QRangeModel model(std::ref(tree), ObjectTreeProtocol{}); + QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); + model.setAutoConnectPolicy(policy); + + const QModelIndex root0 = model.index(0, 0); + const QModelIndex child01Index = model.index(0, 0, root0); + if (policy == QRangeModel::AutoConnectPolicy::OnRead) + QVERIFY(model.data(child01Index, Qt::UserRole + 1).isValid()); + + child01->setNumber(42); + QCOMPARE(dataChangedSpy.size(), 1); + + QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), child01Index); + QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), child01Index); + QCOMPARE(dataChangedSpy.at(0).at(2), QVariant::fromValue(QList{Qt::UserRole + 1})); + }(); +} + void tst_QRangeModel::dimensions() { QFETCH(Factory, factory); diff --git a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp index 96bbd60ab83..9a5e2a5f89b 100644 --- a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp +++ b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp @@ -326,7 +326,7 @@ void tst_QChronoTimer::remainingTimeDuringActivation() if (!singleShot) { // do it again - see QTBUG-46940 - remainingTime = std::chrono::milliseconds::min(); + remainingTime = std::chrono::nanoseconds::min(); QVERIFY(timeoutSpy.wait()); QCOMPARE_LE(remainingTime, timeout); QCOMPARE_GT(remainingTime, 0ns); diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index ff54a5a6fc4..696bcdc07d7 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -2640,29 +2640,27 @@ void tst_QMetaObject::keysToValue() QCOMPARE(QLatin1String(mf.valueToKeys(3)), QLatin1String("MyFlag1|MyFlag2")); // Test flags with extra '|' - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, ends with '|'.+"_s)); + static const QRegularExpression endsWithBar( + R"(QMetaEnum::keysToValue: malformed keys string, ends with '\|'.+)"_L1); + QTest::ignoreMessage(QtWarningMsg, endsWithBar); QCOMPARE(mf.keysToValue64("MyFlag1|MyFlag2|"), std::nullopt); - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, ends with '|'.+"_s)); + QTest::ignoreMessage(QtWarningMsg, endsWithBar); QCOMPARE(mf.keysToValue("MyFlag1|MyFlag2|", &ok), -1); QCOMPARE(ok, false); - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, starts with '|'.+"_s)); + static const QRegularExpression startsWithBar( + R"(QMetaEnum::keysToValue: malformed keys string, starts with '\|'.+)"_L1); + QTest::ignoreMessage(QtWarningMsg, startsWithBar); QCOMPARE(mf.keysToValue64("|MyFlag1|MyFlag2|"), std::nullopt); - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression(u"QMetaEnum::keysToValue: malformed keys string, starts with '|'.+"_s)); + QTest::ignoreMessage(QtWarningMsg, startsWithBar); QCOMPARE(mf.keysToValue("|MyFlag1|MyFlag2|", &ok), -1); QCOMPARE(ok, false); - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression( - u"QMetaEnum::keysToValue: malformed keys string, has two consecutive '|'.+"_s)); + static const QRegularExpression doubleBar( + R"(QMetaEnum::keysToValue: malformed keys string, has two consecutive '\|'.+)"_L1); + QTest::ignoreMessage(QtWarningMsg, doubleBar); QCOMPARE(mf.keysToValue64("MyFlag1||MyFlag2"), std::nullopt); - QTest::ignoreMessage(QtWarningMsg, - QRegularExpression( - u"QMetaEnum::keysToValue: malformed keys string, has two consecutive '|'.+"_s)); + QTest::ignoreMessage(QtWarningMsg, doubleBar); QCOMPARE(mf.keysToValue("MyFlag1||MyFlag2", &ok), -1); QCOMPARE(ok, false); diff --git a/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp b/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp index bde70e32233..10769165ec4 100644 --- a/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp +++ b/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp @@ -307,6 +307,13 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(EnumFlagsTester::TestFlags) void tst_QMetaProperty::readAndWriteWithLazyRegistration() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("lazy registration only runs once per type per process"); + return; + } + executedOnce = true; + QVERIFY(!QMetaType::fromName("CustomReadObject*").isValid()); QVERIFY(!QMetaType::fromName("CustomWriteObject*").isValid()); diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index 683025fd409..3cf16367777 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -528,11 +528,25 @@ void tst_QMetaType::qMetaTypeId() QCOMPARE(::qMetaTypeId<qint8>(), QMetaType::fromType<qint8>().id()); } +class QPropObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList<QVariant> prop READ prop WRITE setProp) + +public: + QPropObject() { propList << 42 << "Hello"; } + + QList<QVariant> prop() const { return propList; } + void setProp(const QList<QVariant> &list) { propList = list; } + QList<QVariant> propList; +}; + void tst_QMetaType::properties() { qRegisterMetaType<QList<QVariant> >("QList<QVariant>"); + QPropObject sut; - QVariant v = property("prop"); + QVariant v = sut.property("prop"); QCOMPARE(v.typeName(), "QVariantList"); @@ -542,8 +556,8 @@ void tst_QMetaType::properties() values << 43 << "world"; - QVERIFY(setProperty("prop", values)); - v = property("prop"); + QVERIFY(sut.setProperty("prop", values)); + v = sut.property("prop"); QCOMPARE(v.toList().size(), 4); } @@ -1450,6 +1464,11 @@ void tst_QMetaType::defaultConstructTrivial_QTBUG_109594() void tst_QMetaType::typedConstruct() { + static bool calledOnce = false; + if (calledOnce) + QSKIP("tst_QMetaType::typedConstruct can only run once"); + calledOnce = true; + auto testMetaObjectWriteOnGadget = [](QVariant &gadget, const QList<GadgetPropertyType> &properties) { auto metaObject = QMetaType(gadget.userType()).metaObject(); diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h index 6f218bb0651..e9a177356e6 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h @@ -25,7 +25,6 @@ struct MessageHandlerCustom : public MessageHandler class tst_QMetaType: public QObject { Q_OBJECT - Q_PROPERTY(QList<QVariant> prop READ prop WRITE setProp) public: struct GadgetPropertyType { @@ -34,14 +33,8 @@ public: QVariant testData; }; - tst_QMetaType() { propList << 42 << "Hello"; } - - QList<QVariant> prop() const { return propList; } - void setProp(const QList<QVariant> &list) { propList = list; } - private: void registerGadget(const char * name, const QList<GadgetPropertyType> &gadgetProperties); - QList<QVariant> propList; private slots: #if QT_CONFIG(thread) diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp index 661c1f6072b..93f23259745 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp @@ -148,6 +148,11 @@ void registerCustomTypeConversions() void tst_QMetaType::convertCustomType_data() { + static bool calledOnce = false; + if (calledOnce) + QSKIP("convertCustomType can only run once"); + calledOnce = true; + customTypeNotYetConvertible(); registerCustomTypeConversions(); @@ -363,6 +368,11 @@ void tst_QMetaType::compareCustomEqualOnlyType() void tst_QMetaType::customDebugStream() { + static bool calledOnce = false; + if (calledOnce) + QSKIP("customDebugStream can only run once"); + calledOnce = true; + MessageHandlerCustom handler(::qMetaTypeId<CustomDebugStreamableType>()); QVariant v1 = QVariant::fromValue(CustomDebugStreamableType()); handler.expectedMessage = "QVariant(CustomDebugStreamableType, string-content)"; diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index afb0bf0169a..6d9f7d12dcb 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -8736,6 +8736,9 @@ QT_END_NAMESPACE void tst_QObject::emitToDestroyedClass() { using namespace EmitToDestroyedClass; + assertionCallCount = 0; + wouldHaveAssertedCount = 0; + std::unique_ptr ptr = std::make_unique<Derived>(); QObject::connect(ptr.get(), &Base::theSignal, ptr.get(), &Derived::doNothing); QCOMPARE(assertionCallCount, 0); diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp index 81316b061d0..98c184872f9 100644 --- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp +++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp @@ -1202,10 +1202,10 @@ void tst_QTimer::singleShotDestructionBeforeEventDispatcher() // would be deleted by the QObject destructor, which is too late to // unregister the timer. - auto thr = QThread::create([] { + std::unique_ptr<QThread> thr{QThread::create([] { QObject o; QTimer::singleShot(300s, &o, [] {}); - }); + })}; thr->start(); thr->wait(); } diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index e713901101c..721d3d3af98 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -432,6 +432,9 @@ private slots: void get_QTransform() { get_impl(QTransform{1, 2, 3, 4, 5, 6, 7, 8, 9}); } // too large void get_NonDefaultConstructible(); + void reference(); + void pointer(); + private: using StdVariant = std::variant<std::monostate, // list here all the types with which we instantiate getIf_impl: @@ -6213,6 +6216,73 @@ void tst_QVariant::get_NonDefaultConstructible() get_impl(NonDefaultConstructible{42}); } +struct QVariantWrapper +{ +public: + static constexpr bool canNoexceptConvertToQVariant + = std::is_nothrow_copy_constructible_v<QVariant>; + static constexpr bool canNoexceptAssignQVariant + = std::is_nothrow_copy_assignable_v<QVariant>; + + QVariantWrapper(QVariant *content = nullptr) noexcept : m_content(content) {} + + QVariant content() const noexcept(canNoexceptConvertToQVariant) { return *m_content; } + void setContent(const QVariant &content) noexcept(canNoexceptAssignQVariant) + { + *m_content = content; + } + +private: + QVariant *m_content = nullptr; +}; + +QT_BEGIN_NAMESPACE +template<> +QVariant::ConstReference<QVariantWrapper>::operator QVariant() const + noexcept(QVariantWrapper::canNoexceptConvertToQVariant) +{ + return m_referred.content(); +} + +template<> +QVariant::Reference<QVariantWrapper> &QVariant::Reference<QVariantWrapper>::operator=( + const QVariant &content) noexcept(QVariantWrapper::canNoexceptAssignQVariant) +{ + m_referred.setContent(content); + return *this; +} +QT_END_NAMESPACE + +void tst_QVariant::reference() +{ + QVariant content(5); + + QVariant::ConstReference<QVariantWrapper> constRef(&content); + QCOMPARE(QVariant(constRef), QVariant(5)); + + QVariant::Reference<QVariantWrapper> ref(&content); + QCOMPARE(QVariant(ref), QVariant(5)); + + ref = QVariant(12); + QCOMPARE(QVariant(ref), QVariant(12)); + QCOMPARE(content, QVariant(12)); +} + +void tst_QVariant::pointer() +{ + QVariant content(5); + + QVariant::ConstPointer<QVariantWrapper> constPtr(&content); + QCOMPARE(*constPtr, QVariant(5)); + + QVariant::Pointer<QVariantWrapper> ptr(&content); + QCOMPARE(*ptr, QVariant(5)); + + *ptr = QVariant(12); + QCOMPARE(*ptr, QVariant(12)); + QCOMPARE(content, QVariant(12)); +} + template <typename T> T mutate(const T &t) { return t + t; } template <> diff --git a/tests/auto/corelib/platform/android/tst_android.cpp b/tests/auto/corelib/platform/android/tst_android.cpp index 3db9629d4a3..3665f100a61 100644 --- a/tests/auto/corelib/platform/android/tst_android.cpp +++ b/tests/auto/corelib/platform/android/tst_android.cpp @@ -348,7 +348,8 @@ void tst_Android::testFullScreenDimensions() QVERIFY(display.isValid()); QSize appSize; - if (QNativeInterface::QAndroidApplication::sdkVersion() >= __ANDROID_API_R__) { + const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion(); + if (sdkVersion >= __ANDROID_API_R__) { using namespace QtJniTypes; auto windowMetrics = windowManager.callMethod<WindowMetrics>("getCurrentWindowMetrics"); auto bounds = windowMetrics.callMethod<Rect>("getBounds"); @@ -383,7 +384,6 @@ void tst_Android::testFullScreenDimensions() const auto appContext = activity.callMethod<QtJniTypes::Context>("getApplicationContext"); const auto appInfo = appContext.callMethod<QtJniTypes::ApplicationInfo>("getApplicationInfo"); const int targetSdkVersion = appInfo.getField<jint>("targetSdkVersion"); - const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion(); if (sdkVersion >= __ANDROID_API_V__ && targetSdkVersion >= __ANDROID_API_V__) { expectedWidth = appSize.width(); @@ -440,6 +440,11 @@ void tst_Android::testFullScreenDimensions() QTRY_COMPARE(screen->availableGeometry().height(), realSize.getField<jint>("y")); QTRY_COMPARE(screen->geometry().width(), realSize.getField<jint>("x")); + // TODO needs fix to work in local and CI on same fashion + const bool runsOnCI = qgetenv("QTEST_ENVIRONMENT").split(' ').contains("ci"); + if ((sdkVersion > __ANDROID_API_V__) && runsOnCI) + QEXPECT_FAIL("", "Fails on Android 16 (QTBUG-141712).", Continue); + QTRY_COMPARE(screen->geometry().height(), realSize.getField<jint>("y")); widget.showNormal(); } diff --git a/tests/auto/corelib/plugin/CMakeLists.txt b/tests/auto/corelib/plugin/CMakeLists.txt index 5518231ace2..82fa9d18749 100644 --- a/tests/auto/corelib/plugin/CMakeLists.txt +++ b/tests/auto/corelib/plugin/CMakeLists.txt @@ -7,7 +7,9 @@ endif() add_subdirectory(quuid) if(QT_FEATURE_library) add_subdirectory(qpluginloader) - add_subdirectory(qlibrary) + if(QT_FEATURE_private_tests) + add_subdirectory(qlibrary) + endif() endif() if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_library) add_subdirectory(qplugin) diff --git a/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt b/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt index fc452f37f53..62dd4a06c17 100644 --- a/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt +++ b/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt @@ -13,7 +13,7 @@ qt_internal_add_test(tst_qlibrary SOURCES ../tst_qlibrary.cpp TESTDATA ${test_data} - LIBRARIES mylib mylib2 + LIBRARIES mylib mylib2 Qt::CorePrivate ) add_dependencies(tst_qlibrary mylib mylib2) diff --git a/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp b/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp index 5ae49ff1707..d708f6def1c 100644 --- a/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp +++ b/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp @@ -6,6 +6,7 @@ #include <qdir.h> #include <qlibrary.h> #include <QtCore/QRegularExpression> +#include <QtCore/private/qlibrary_p.h> // Helper macros to let us know if some suffixes and prefixes are valid @@ -162,7 +163,12 @@ void tst_QLibrary::cleanup() }; for (const auto &entry : libs) { - do {} while (QLibrary(entry.name, entry.version).unload()); + bool unloaded = false; + do { + QLibrary lib(entry.name, entry.version); + auto libPrivate = QLibraryPrivate::get(&lib); + unloaded = libPrivate->unload(); + } while (unloaded); } } diff --git a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp index 37e1f744f34..763ec71e277 100644 --- a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp +++ b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp @@ -80,6 +80,14 @@ QSingleton<SingletonObject> IncrementThread::singleton; void tst_QThreadOnce::sameThread_data() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + + SingletonObject::runCount = 0; QTest::addColumn<int>("expectedValue"); @@ -105,6 +113,14 @@ void tst_QThreadOnce::sameThread() void tst_QThreadOnce::multipleThreads() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + + #if defined(Q_OS_VXWORKS) const int NumberOfThreads = 20; #else @@ -136,6 +152,13 @@ void tst_QThreadOnce::multipleThreads() void tst_QThreadOnce::nesting() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + int variable = 0; Q_ONCE { Q_ONCE { @@ -148,6 +171,14 @@ void tst_QThreadOnce::nesting() static void reentrant(int control, int &counter) { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + + Q_ONCE { if (counter) reentrant(--control, counter); @@ -159,6 +190,13 @@ static void reentrant(int control, int &counter) void tst_QThreadOnce::reentering() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + const int WantedRecursions = 5; int count = 0; SingletonObject::runCount = 0; @@ -181,6 +219,14 @@ static void exception_helper(int &val) #ifndef QT_NO_EXCEPTIONS void tst_QThreadOnce::exception() { + static bool executedOnce = false; + if (executedOnce) { + QSKIP("tst_QThreadOnce can only run once"); + return; + } + executedOnce = true; + + int count = 0; try { diff --git a/tests/auto/corelib/time/CMakeLists.txt b/tests/auto/corelib/time/CMakeLists.txt index 1fb95e9245c..29e882429f4 100644 --- a/tests/auto/corelib/time/CMakeLists.txt +++ b/tests/auto/corelib/time/CMakeLists.txt @@ -8,6 +8,7 @@ if(QT_FEATURE_datetimeparser) add_subdirectory(qdatetimeparser) endif() add_subdirectory(qtime) -if(QT_FEATURE_timezone) - add_subdirectory(qtimezone) +add_subdirectory(qtimezone) +if(QT_FEATURE_timezone AND QT_FEATURE_developer_build) + add_subdirectory(qtimezonebackend) endif() diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp index fde06d2edd9..1e9aeeef799 100644 --- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp +++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp @@ -1262,8 +1262,7 @@ void tst_QDateTime::toString_strformat() QCOMPARE(testDateTime.toString(u"yyyy-MM-dd hh:mm:ss tt"), u"2013-01-01 01:02:03 +0000"_s); QCOMPARE(testDateTime.toString(u"yyyy-MM-dd hh:mm:ss ttt"), u"2013-01-01 01:02:03 +00:00"_s); -#if QT_CONFIG(icu) && !defined(Q_STL_DINKUMWARE) - // The Dinkum (VxWorks) exception may just be because it has an old ICU. +#if QT_CONFIG(icu) // Hopefully other timezone backends shall eventually agree with this: const QString longForm = u"2013-01-01 01:02:03 Coordinated Universal Time"_s; #else diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 66fbac97977..d1d86a4802f 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -3,7 +3,9 @@ #include <QTest> #include <qtimezone.h> -#include <private/qtimezoneprivate_p.h> +#if QT_CONFIG(timezone) +# include <private/qtimezoneprivate_p.h> +#endif #include <private/qcomparisontesthelper_p.h> #include <qlocale.h> @@ -51,33 +53,21 @@ private Q_SLOTS: void checkOffset(); void stressTest(); void windowsId(); - void isValidId_data(); - void isValidId(); void serialize(); void malformed(); - // Backend tests + // Backend tests (see also ../qtimezonebackend/) void utcTest(); - void icuTest(); - void tzTest(); - void macTest(); void darwinTypes(); - void winTest(); void localeSpecificDisplayName_data(); void localeSpecificDisplayName(); - void roundtripDisplayNames_data(); - void roundtripDisplayNames(); + // Compatibility with std::chrono::tzdb: void stdCompatibility_data(); void stdCompatibility(); -#endif // timezone backends +#endif // timezone backends exist private: - void printTimeZone(const QTimeZone &tz); #if QT_CONFIG(timezone) -# if defined(QT_BUILD_INTERNAL) - // Generic tests of privates, called by implementation-specific private tests: - void testCetPrivate(const QTimeZonePrivate &tzp); - void testEpochTranPrivate(const QTimeZonePrivate &tzp); -# endif // QT_BUILD_INTERNAL + void printTimeZone(const QTimeZone &tz); // Where tzdb contains a link between zones in different territories, CLDR // doesn't treat those as aliases for one another. For details see "Links in // the tz database" at: @@ -102,6 +92,7 @@ private: static constexpr bool debug = false; }; +#if QT_CONFIG(timezone) void tst_QTimeZone::printTimeZone(const QTimeZone &tz) { QDateTime now = QDateTime::currentDateTime(); @@ -167,9 +158,11 @@ void tst_QTimeZone::printTimeZone(const QTimeZone &tz) qDebug() << "Transition before 1 Jun = " << tz.previousTransition(jun).atUtc; qDebug() << ""; } +#endif // feature timezone void tst_QTimeZone::createTest() { +#if QT_CONFIG(timezone) const QTimeZone tz("Pacific/Auckland"); if constexpr (debug) @@ -265,13 +258,16 @@ void tst_QTimeZone::createTest() QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset); } } +#else + QSKIP("Test depends on backends, enabled by feature timezone"); +#endif // feature timezone } void tst_QTimeZone::nullTest() { QTimeZone nullTz1; QTimeZone nullTz2; - QTimeZone utc("UTC"); + QTimeZone utc(QTimeZone::UTC); // Validity tests QCOMPARE(nullTz1.isValid(), false); @@ -290,6 +286,7 @@ void tst_QTimeZone::nullTest() utc = nullTz1; QCOMPARE(utc.isValid(), false); +#if QT_CONFIG(timezone) QCOMPARE(nullTz1.id(), QByteArray()); QCOMPARE(nullTz1.territory(), QLocale::AnyTerritory); QCOMPARE(nullTz1.comment(), QString()); @@ -335,6 +332,7 @@ void tst_QTimeZone::nullTest() QCOMPARE(data.offsetFromUtc, invalidOffset); QCOMPARE(data.standardTimeOffset, invalidOffset); QCOMPARE(data.daylightTimeOffset, invalidOffset); +#endif // feature timezone } void tst_QTimeZone::assign() @@ -487,7 +485,7 @@ void tst_QTimeZone::offset() void tst_QTimeZone::dataStreamTest() { -#ifndef QT_NO_DATASTREAM +#if QT_CONFIG(timezone) && !defined(QT_NO_DATASTREAM) // Test the OffsetFromUtc backend serialization. First with a custom timezone: QTimeZone tz1("QST"_ba, 23456, u"Qt Standard Time"_s, u"QST"_s, QLocale::Norway, u"Qt Testing"_s); @@ -546,7 +544,7 @@ void tst_QTimeZone::dataStreamTest() QCOMPARE(ds.status(), QDataStream::Ok); } QCOMPARE(tz2.id(), tz1.id()); -#endif +#endif // feature timezone and enabled datastream } #if QT_CONFIG(timezone) @@ -791,7 +789,7 @@ void tst_QTimeZone::hasAlternativeName() void tst_QTimeZone::specificTransition_data() { -#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) +#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__) QSKIP("libstdc++'s C++20 misreads the IANA DB for Moscow's transitions (among others)."); #endif #if defined Q_OS_ANDROID && !QT_CONFIG(timezone_tzdb) @@ -1159,120 +1157,6 @@ void tst_QTimeZone::windowsId() } } -void tst_QTimeZone::isValidId_data() -{ -#ifdef QT_BUILD_INTERNAL - QTest::addColumn<QByteArray>("input"); - QTest::addColumn<bool>("valid"); - - // a-z, A-Z, 0-9, '.', '-', '_' are valid chars - // Can't start with '-' - // Parts separated by '/', each part min 1 and max of 14 chars - // (Android has parts with lengths up to 17, so tolerates this as a special case.) -#define TESTSET(name, section, valid) \ - QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \ - QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \ - QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid - - // a-z, A-Z, 0-9, '.', '-', '_' are valid chars - // Can't start with '-' - // Parts separated by '/', each part min 1 and max of 14 chars - TESTSET("empty", "", false); - TESTSET("minimal", "m", true); -#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) - TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this - TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this. -#else - TESTSET("maximal", "12345678901234", true); - TESTSET("maximal twice", "12345678901234/12345678901234", true); - TESTSET("too long", "123456789012345", false); - TESTSET("too-long/maximal", "123456789012345/12345678901234", false); - TESTSET("maximal/too-long", "12345678901234/123456789012345", false); -#endif - - TESTSET("bad hyphen", "-hyphen", false); - TESTSET("good hyphen", "hy-phen", true); - - TESTSET("valid char _", "_", true); - TESTSET("valid char .", ".", true); - TESTSET("valid char :", ":", true); - TESTSET("valid char +", "+", true); - TESTSET("valid char A", "A", true); - TESTSET("valid char Z", "Z", true); - TESTSET("valid char a", "a", true); - TESTSET("valid char z", "z", true); - TESTSET("valid char 0", "0", true); - TESTSET("valid char 9", "9", true); - - TESTSET("valid pair az", "az", true); - TESTSET("valid pair AZ", "AZ", true); - TESTSET("valid pair 09", "09", true); - TESTSET("valid pair .z", ".z", true); - TESTSET("valid pair _z", "_z", true); - TESTSET("invalid pair -z", "-z", false); - - TESTSET("valid triple a/z", "a/z", true); - TESTSET("valid triple a.z", "a.z", true); - TESTSET("valid triple a-z", "a-z", true); - TESTSET("valid triple a_z", "a_z", true); - TESTSET("invalid triple a z", "a z", false); - TESTSET("invalid triple a\\z", "a\\z", false); - TESTSET("invalid triple a,z", "a,z", false); - - TESTSET("invalid space", " ", false); - TESTSET("invalid char ^", "^", false); - TESTSET("invalid char \"", "\"", false); - TESTSET("invalid char $", "$", false); - TESTSET("invalid char %", "%", false); - TESTSET("invalid char &", "&", false); - TESTSET("invalid char (", "(", false); - TESTSET("invalid char )", ")", false); - TESTSET("invalid char =", "=", false); - TESTSET("invalid char -", "-", false); - TESTSET("invalid char ?", "?", false); - TESTSET("invalid char ß", "ß", false); - TESTSET("invalid char \\x01", "\x01", false); - TESTSET("invalid char ' '", " ", false); - -#undef TESTSET - - QTest::newRow("az alone") << QByteArray("az") << true; - QTest::newRow("AZ alone") << QByteArray("AZ") << true; - QTest::newRow("09 alone") << QByteArray("09") << true; - QTest::newRow("a/z alone") << QByteArray("a/z") << true; - QTest::newRow("a.z alone") << QByteArray("a.z") << true; - QTest::newRow("a-z alone") << QByteArray("a-z") << true; - QTest::newRow("a_z alone") << QByteArray("a_z") << true; - QTest::newRow(".z alone") << QByteArray(".z") << true; - QTest::newRow("_z alone") << QByteArray("_z") << true; - QTest::newRow("a z alone") << QByteArray("a z") << false; - QTest::newRow("a\\z alone") << QByteArray("a\\z") << false; - QTest::newRow("a,z alone") << QByteArray("a,z") << false; - QTest::newRow("/z alone") << QByteArray("/z") << false; - QTest::newRow("-z alone") << QByteArray("-z") << false; -#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) - QTest::newRow("long alone") << QByteArray("12345678901234567") << true; - QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false; -#else - QTest::newRow("long alone") << QByteArray("12345678901234") << true; - QTest::newRow("over-long alone") << QByteArray("123456789012345") << false; -#endif - -#else - QSKIP("This test requires a Qt -developer-build."); -#endif // QT_BUILD_INTERNAL -} - -void tst_QTimeZone::isValidId() -{ -#ifdef QT_BUILD_INTERNAL - QFETCH(QByteArray, input); - QFETCH(bool, valid); - - QCOMPARE(QTimeZonePrivate::isValidId(input), valid); -#endif -} - void tst_QTimeZone::serialize() { int parts = 0; @@ -1365,7 +1249,7 @@ void tst_QTimeZone::utcTest() tz = QTimeZone(15 * 3600); // no IANA ID, so uses minimal id, skipping :00 minutes QVERIFY(tz.isValid()); - QCOMPARE(tz.id(), "UTC+15"); + QCOMPARE(tz.id(), "UTC+15:00"); QCOMPARE(tz.offsetFromUtc(now), 15 * 3600); QCOMPARE(tz.standardTimeOffset(now), 15 * 3600); QCOMPARE(tz.daylightTimeOffset(now), 0); @@ -1402,314 +1286,6 @@ void tst_QTimeZone::utcTest() QCOMPARE(tz.daylightTimeOffset(now), 0); } -// Relies on local variable names: zone tzp and locale enUS. -#define ZONE_DNAME_CHECK(type, name, val) \ - QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val); - -void tst_QTimeZone::icuTest() -{ -#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && !defined(Q_OS_UNIX) - // Known datetimes - qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - - // Test default constructor - QIcuTimeZonePrivate tzpd; - QVERIFY(tzpd.isValid()); - - // Test invalid is not available: - QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon")); - // and construction gives an invalid result: - QIcuTimeZonePrivate tzpi("Gondwana/Erewhon"); - QCOMPARE(tzpi.isValid(), false); - - // Test named constructor - QIcuTimeZonePrivate tzp("Europe/Berlin"); - QVERIFY(tzp.isValid()); - - // Only test names in debug mode, names used can vary by ICU version installed - if constexpr (debug) { - // Test display names by type - QLocale enUS("en_US"); - ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time"); - ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00"); - ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00"); - ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time"); - ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00"); - ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00"); - // ICU C api does not support Generic Time yet, C++ api does - ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time"); - ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00"); - ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00"); - - // Test Abbreviations - QCOMPARE(tzp.abbreviation(std), u"CET"); - QCOMPARE(tzp.abbreviation(dst), u"CEST"); - } - - testCetPrivate(tzp); - if (QTest::currentTestFailed()) - return; - testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto")); -#endif // ICU not on Unix, without tzdb -} - -void tst_QTimeZone::tzTest() -{ -#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX \ - && !QT_CONFIG(timezone_tzdb) && !defined Q_OS_DARWIN && !defined Q_OS_ANDROID - const auto UTC = QTimeZone::UTC; - // Known datetimes - qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); - qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); - - // Test default constructor - QTzTimeZonePrivate tzpd; - QVERIFY(tzpd.isValid()); - - // Test invalid constructor - QTzTimeZonePrivate tzpi("Gondwana/Erewhon"); - QVERIFY(!tzpi.isValid()); - - // Test named constructor - QTzTimeZonePrivate tzp("Europe/Berlin"); - QVERIFY(tzp.isValid()); - - // Test POSIX-format value for $TZ: - QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"); - QVERIFY(tzposix.isValid()); - QVERIFY(tzposix.hasDaylightTime()); - - // Cope with stray space at start of value (QTBUG-135109): - QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3"); - QVERIFY(syd.isValid()); - QVERIFY(syd.hasDaylightTime()); - - // RHEL has been seen with this as Africa/Casablanca's POSIX rule: - QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25"); - const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst - QVERIFY(permaDst.isValid()); - QVERIFY(permaDst.hasDaylightTime()); - QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch())); - QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch())); - // Note that the final /25 could be misunderstood as putting a fall-back at - // 1am on the next year's Jan 1st; check we don't do that: - QVERIFY(permaDst.isDaylightTime( - QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch())); - // It shouldn't have any transitions. QTimeZone::hasTransitions() only says - // whether the backend supports them, so ask for transitions in a wide - // enough interval that one would show up, if there are any: - QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(), - QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch() - ).isEmpty()); - - QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule - QVERIFY(tzBrazil.isValid()); - QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800); - - // Test display names by type, either ICU or abbreviation only - QLocale enUS(u"en_US"); - // Only test names in debug mode, names used can vary by ICU version installed - if constexpr (debug) { -#if QT_CONFIG(icu) - ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); - ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); - ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); - ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); - ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); - ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); - // ICU C api does not support Generic Time yet, C++ api does - ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time"); - ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00"); - ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); -#else - ZONE_DNAME_CHECK(StandardTime, LongName, "CET"); - ZONE_DNAME_CHECK(StandardTime, ShortName, "CET"); - ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET"); - ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST"); - ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST"); - ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST"); - ZONE_DNAME_CHECK(GenericTime, LongName, "CET"); - ZONE_DNAME_CHECK(GenericTime, ShortName, "CET"); - ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET"); -#endif // icu - - // Test Abbreviations - QCOMPARE(tzp.abbreviation(std), u"CET"); - QCOMPARE(tzp.abbreviation(dst), u"CEST"); - } - - testCetPrivate(tzp); - if (QTest::currentTestFailed()) - return; - testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto")); - if (QTest::currentTestFailed()) - return; - - // Test first and last transition rule - // Warning: This could vary depending on age of TZ file! - - // Test low date uses first rule found - constexpr qint64 ancient = -Q_INT64_C(9999999999999); - // Note: Depending on the OS in question, the database may be carrying the - // Local Mean Time. which for Berlin is 0:53:28 - QTimeZonePrivate::Data dat = tzp.data(ancient); - QCOMPARE(dat.atMSecsSinceEpoch, ancient); - QCOMPARE(dat.daylightTimeOffset, 0); - if (dat.abbreviation == u"LMT") { - QCOMPARE(dat.standardTimeOffset, 3208); - } else { - QCOMPARE(dat.standardTimeOffset, 3600); - - constexpr qint64 invalidTime = std::numeric_limits<qint64>::min(); - constexpr int invalidOffset = std::numeric_limits<int>::min(); - // Test previous to low value is invalid - dat = tzp.previousTransition(ancient); - QCOMPARE(dat.atMSecsSinceEpoch, invalidTime); - QCOMPARE(dat.standardTimeOffset, invalidOffset); - QCOMPARE(dat.daylightTimeOffset, invalidOffset); - } - - dat = tzp.nextTransition(ancient); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, - QTimeZone::fromSecondsAheadOfUtc(3600)), - QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), - QTimeZone::fromSecondsAheadOfUtc(3600))); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); - - // Date-times late enough to exercise POSIX rules: - qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(); - qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch(); - // Relevant last Sundays in October and March: - QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday); - QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday); - QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday); - - dat = tzp.data(stdHi); - QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0)); - QCOMPARE(dat.offsetFromUtc, 3600); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); - - dat = tzp.data(dstHi); - QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0)); - QCOMPARE(dat.offsetFromUtc, 7200); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 3600); - - dat = tzp.previousTransition(stdHi); - QCOMPARE(dat.abbreviation, u"CET"); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); - QCOMPARE(dat.offsetFromUtc, 3600); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); - - dat = tzp.previousTransition(dstHi); - QCOMPARE(dat.abbreviation, u"CEST"); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); - QCOMPARE(dat.offsetFromUtc, 7200); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 3600); - - dat = tzp.nextTransition(stdHi); - QCOMPARE(dat.abbreviation, u"CEST"); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); - QCOMPARE(dat.offsetFromUtc, 7200); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 3600); - - dat = tzp.nextTransition(dstHi); - QCOMPARE(dat.abbreviation, u"CET"); - QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, - QTimeZone::fromSecondsAheadOfUtc(3600)), - QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); - QCOMPARE(dat.offsetFromUtc, 3600); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); - - // Test TZ timezone vs UTC timezone for non-whole-hour negative offset: - QTzTimeZonePrivate tztz1("America/Caracas"); - QUtcTimeZonePrivate tzutc1("UTC-04:30"); - QVERIFY(tztz1.isValid()); - QVERIFY(tzutc1.isValid()); - QTzTimeZonePrivate::Data datatz1 = tztz1.data(std); - QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std); - QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc); - - // Test TZ timezone vs UTC timezone for non-whole-hour positive offset: - QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name - QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name - // Can't assign QtzTZP, so use a reference; prefer new name. - QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c; - QUtcTimeZonePrivate tzutc2("UTC+05:30"); - QVERIFY2(tztz2.isValid(), tztz2.id().constData()); - QVERIFY(tzutc2.isValid()); - QTzTimeZonePrivate::Data datatz2 = tztz2.data(std); - QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std); - QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc); - - // Test a timezone with an abbreviation that isn't all letters: - QTzTimeZonePrivate tzBarnaul("Asia/Barnaul"); - if (tzBarnaul.isValid()) { - QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07"); - - // first full day of the new rule (tzdata2016b) - QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC); - QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07"); - } -#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !timezone_tzdb && !Q_OS_DARWIN && !Q_OS_ANDROID -} - -void tst_QTimeZone::macTest() -{ -#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb) - // Known datetimes - qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - - // Test default constructor - QMacTimeZonePrivate tzpd; - QVERIFY(tzpd.isValid()); - - // Test invalid constructor - QMacTimeZonePrivate tzpi("Gondwana/Erewhon"); - QCOMPARE(tzpi.isValid(), false); - - // Test named constructor - QMacTimeZonePrivate tzp("Europe/Berlin"); - QVERIFY(tzp.isValid()); - - // Only test names in debug mode, names used can vary by version - if constexpr (debug) { - // Test display names by type - QLocale enUS(u"en_US"); - ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); - ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); - ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); - ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); - ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); - ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); - // ICU C api does not support Generic Time yet, C++ api does - ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time"); - ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time"); - ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); - - // Test Abbreviations - QCOMPARE(tzp.abbreviation(std), u"CET"); - QCOMPARE(tzp.abbreviation(dst), u"CEST"); - } - - testCetPrivate(tzp); - if (QTest::currentTestFailed()) - return; - testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto")); -#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN without tzdb -} - void tst_QTimeZone::darwinTypes() { #ifndef Q_OS_DARWIN @@ -1720,59 +1296,6 @@ void tst_QTimeZone::darwinTypes() #endif } -void tst_QTimeZone::winTest() -{ -#if defined(QT_BUILD_INTERNAL) && defined(USING_WIN_TZ) - // Known datetimes - qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); - - // Test default constructor - QWinTimeZonePrivate tzpd; - if constexpr (debug) - qDebug() << "System ID = " << tzpd.id() - << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale()) - << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale()); - QVERIFY(tzpd.isValid()); - - // Test invalid constructor - QWinTimeZonePrivate tzpi("Gondwana/Erewhon"); - QCOMPARE(tzpi.isValid(), false); - - // Test named constructor - QWinTimeZonePrivate tzp("Europe/Berlin"); - QVERIFY(tzp.isValid()); - - // Only test names in debug mode, names used can vary by version - if constexpr (debug) { - // Test display names by type - QLocale enUS(u"en_US"); - ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time"); - ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time"); - ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); - ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time"); - ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time"); - ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); - ZONE_DNAME_CHECK(GenericTime, LongName, - "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); - ZONE_DNAME_CHECK(GenericTime, ShortName, - "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); - ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); - - // Test Abbreviations - QCOMPARE(tzp.abbreviation(std), u"CET"); - QCOMPARE(tzp.abbreviation(dst), u"CEST"); - } - - testCetPrivate(tzp); - if (QTest::currentTestFailed()) - return; - testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto")); -#endif // QT_BUILD_INTERNAL && USING_WIN_TZ -} - -#undef ZONE_DNAME_CHECK - void tst_QTimeZone::localeSpecificDisplayName_data() { QTest::addColumn<QByteArray>("zoneName"); @@ -1849,285 +1372,6 @@ void tst_QTimeZone::localeSpecificDisplayName() #endif } -void tst_QTimeZone::roundtripDisplayNames_data() -{ -#ifdef QT_BUILD_INTERNAL - QTest::addColumn<QTimeZone>("zone"); - QTest::addColumn<QLocale>("locale"); - QTest::addColumn<QTimeZone::TimeType>("type"); - - constexpr QTimeZone::TimeType types[] = { - QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime - }; - const auto typeName = [](QTimeZone::TimeType type) { - switch (type) { - case QTimeZone::GenericTime: return "Gen"; - case QTimeZone::StandardTime: return "Std"; - case QTimeZone::DaylightTime: return "DST"; - } - Q_UNREACHABLE_RETURN("Unrecognised"); - }; - const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba); -#ifdef EXHAUSTIVE_ZONE_DISPLAY - const QList<QByteArray> idList = allList; -#else - const QList<QByteArray> idList = { - "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba, - "America/Caracas"_ba, "America/Indiana/Tell_City"_ba, "America/Managua"_ba, - "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba, - "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba, - "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba, - "Brazil/Acre"_ba, "CST6CDT"_ba, "Canada/Atlantic"_ba, - "Chile/EasterIsland"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba, - "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba, - "Europe/Prague"_ba, "Europe/Vatican"_ba, - "Indian/Comoro"_ba, "Mexico/BajaSur"_ba, - "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba, - "US/Aleutian"_ba, - "UTC"_ba, - // Those named overtly in tst_QDateTime - special cases first: - "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba, - "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba, - // ... then ordinary names in alphabetic order: - "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba, - "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba, - "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba, - "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba, - "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba, - "Europe/Rome"_ba, - "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba, - "Vulcan/ShiKahr"_ba // Invalid: also worth testing. - }; - // Some valid zones in that list may be absent from the platform's - // availableTimeZoneIds(), yet in fact work when used as it's asked to - // instantiate them (e.g. Etc/Universal on macOS). This can give them a - // displayName() that we fail to decode, without timezone_locale, due to - // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we - // have to filter on membership of allList when creating rows. -#endif // Exhaustive - const QLocale fr(QLocale::French, QLocale::France); - const QLocale hi(QLocale::Hindi, QLocale::India); - for (const QByteArray &id : idList) { - if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id)) - continue; - QTimeZone zone = QTimeZone(id); - if (!zone.isValid()) - continue; - for (const auto type : types) { - QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type)) - << zone << fr << type; - QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type)) - << zone << hi << type; - } - } -#else - QSKIP("Test needs access to internal APIs"); -#endif -} - -void tst_QTimeZone::roundtripDisplayNames() -{ -#ifdef QT_BUILD_INTERNAL - QFETCH(const QTimeZone, zone); - QFETCH(const QLocale, locale); - QFETCH(const QTimeZone::TimeType, type); - static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC); - static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC); - const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan; - - // Some zones exercise region format. - const QString name = zone.displayName(type, QTimeZone::LongName, locale); - if (!name.isEmpty()) { - const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type); - const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs() - ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch; - const QString extended = name + "some spurious cruft"_L1; - auto match = - QTimeZonePrivate::findLongNamePrefix(extended, locale, when); - if (!match) - match = QTimeZonePrivate::findLongNamePrefix(extended, locale); - if (!match) - match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale); - if (!match) - match = QTimeZonePrivate::findLongUtcPrefix(extended); - auto report = qScopeGuard([=]() { - qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC) - << "via" << name; - }); - QCOMPARE(match.nameLength, name.size()); - report.dismiss(); -#if 0 - if (match.ianaId != zone.id()) { - const QTimeZone found = QTimeZone(match.ianaId); - if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when) - != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) { - // For DST, some zones haven't done it in ages, so tran may be ancient. - // Meanwhile, match.ianaId is typically the canonical zone for a metazone. - // That, in turn, may not have been doing DST when zone was. - // So we can't rely on a match, but can report the mismatches. - qDebug() << "Long name" << name << "on" - << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when) - << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC) - << "got" << match.ianaId << "on" - << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when); - // There are also some absurdly over-generic names, that lead to - // ambiguities, e.g. "heure : West" - } - } -#endif // Debug code - } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */ - qDebug("Empty display name"); - } -#else - Q_ASSERT(!"Should be skipped when building data table"); -#endif -} - -#ifdef QT_BUILD_INTERNAL -// Test each private produces the same basic results for CET -void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp) -{ - // Known datetimes - const auto UTC = QTimeZone::UTC; - const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600); - qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); - qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); - qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); - - QCOMPARE(tzp.offsetFromUtc(std), 3600); - QCOMPARE(tzp.offsetFromUtc(dst), 7200); - - QCOMPARE(tzp.standardTimeOffset(std), 3600); - QCOMPARE(tzp.standardTimeOffset(dst), 3600); - - QCOMPARE(tzp.daylightTimeOffset(std), 0); - QCOMPARE(tzp.daylightTimeOffset(dst), 3600); - - QCOMPARE(tzp.hasDaylightTime(), true); - QCOMPARE(tzp.isDaylightTime(std), false); - QCOMPARE(tzp.isDaylightTime(dst), true); - - QTimeZonePrivate::Data dat = tzp.data(std); - QCOMPARE(dat.atMSecsSinceEpoch, std); - QCOMPARE(dat.offsetFromUtc, 3600); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); - QCOMPARE(dat.abbreviation, tzp.abbreviation(std)); - - dat = tzp.data(dst); - QCOMPARE(dat.atMSecsSinceEpoch, dst); - QCOMPARE(dat.offsetFromUtc, 7200); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 3600); - QCOMPARE(dat.abbreviation, tzp.abbreviation(dst)); - - // Only test transitions if host system supports them - if (tzp.hasTransitions()) { - QTimeZonePrivate::Data tran = tzp.nextTransition(std); - // 2012-03-25 02:00 CET, +1 -> +2 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); - QCOMPARE(tran.offsetFromUtc, 7200); - QCOMPARE(tran.standardTimeOffset, 3600); - QCOMPARE(tran.daylightTimeOffset, 3600); - - tran = tzp.nextTransition(dst); - // 2012-10-28 03:00 CEST, +2 -> +1 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2012, 10, 28), QTime(3, 0), - QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); - QCOMPARE(tran.offsetFromUtc, 3600); - QCOMPARE(tran.standardTimeOffset, 3600); - QCOMPARE(tran.daylightTimeOffset, 0); - - tran = tzp.previousTransition(std); - // 2011-10-30 03:00 CEST, +2 -> +1 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2011, 10, 30), QTime(3, 0), - QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); - QCOMPARE(tran.offsetFromUtc, 3600); - QCOMPARE(tran.standardTimeOffset, 3600); - QCOMPARE(tran.daylightTimeOffset, 0); - - tran = tzp.previousTransition(dst); - // 2012-03-25 02:00 CET, +1 -> +2 (again) - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), - QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); - QCOMPARE(tran.offsetFromUtc, 7200); - QCOMPARE(tran.standardTimeOffset, 3600); - QCOMPARE(tran.daylightTimeOffset, 3600); - - QTimeZonePrivate::DataList expected; - // 2011-03-27 02:00 CET, +1 -> +2 - tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0), - eastOneHour).toMSecsSinceEpoch(); - tran.offsetFromUtc = 7200; - tran.standardTimeOffset = 3600; - tran.daylightTimeOffset = 3600; - expected << tran; - // 2011-10-30 03:00 CEST, +2 -> +1 - tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0), - QTimeZone::fromSecondsAheadOfUtc(2 * 3600) - ).toMSecsSinceEpoch(); - tran.offsetFromUtc = 3600; - tran.standardTimeOffset = 3600; - tran.daylightTimeOffset = 0; - expected << tran; - QTimeZonePrivate::DataList result = tzp.transitions(prev, std); - QCOMPARE(result.size(), expected.size()); - for (int i = 0; i < expected.size(); ++i) { - QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour), - QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour)); - QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc); - QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset); - QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset); - } - } -} - -// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT) -void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp) -{ - if (!tzp.hasTransitions()) - return; // test only viable for transitions - - const auto UTC = QTimeZone::UTC; - const auto hour = std::chrono::hours{1}; - QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch - // 1970-04-26 02:00 EST, -5 -> -4 - const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), - QTimeZone::fromDurationAheadOfUtc(-5 * hour)); - const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC); -#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th. - QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time()); -#else - QCOMPARE(found, after); -#endif - QCOMPARE(tran.offsetFromUtc, -4 * 3600); - QCOMPARE(tran.standardTimeOffset, -5 * 3600); - QCOMPARE(tran.daylightTimeOffset, 3600); - - // Pre-epoch time-zones might not be supported at all: - tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch()); - if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs() - // Toronto *did* have a transition before 1970 (DST since 1918): - && tran.atMSecsSinceEpoch < 0) { - // ... but, if they are, we should be able to search back to them: - tran = tzp.previousTransition(0); // i.e. last before epoch - // 1969-10-26 02:00 EDT, -4 -> -5 - QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), - QDateTime(QDate(1969, 10, 26), QTime(2, 0), - QTimeZone::fromDurationAheadOfUtc(-4 * hour))); - QCOMPARE(tran.offsetFromUtc, -5 * 3600); - QCOMPARE(tran.standardTimeOffset, -5 * 3600); - QCOMPARE(tran.daylightTimeOffset, 0); - } else { - // Do not use QSKIP(): that would discard the rest of this sub-test's caller. - qDebug() << "No support for pre-epoch time-zone transitions"; - } -} -#endif // QT_BUILD_INTERNAL - #if __cpp_lib_chrono >= 201907L Q_DECLARE_METATYPE(const std::chrono::time_zone *); #endif diff --git a/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt new file mode 100644 index 00000000000..6bead8b6549 --- /dev/null +++ b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qtimezonebackend Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qtimezonebackend LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qtimezonebackend + SOURCES + tst_qtimezonebackend.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + QT_NO_FOREACH + QT_NO_KEYWORDS + LIBRARIES + Qt::CorePrivate +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(tst_qtimezonebackend CONDITION QT_FEATURE_icu + LIBRARIES + ICU::i18n ICU::uc ICU::data +) diff --git a/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp new file mode 100644 index 00000000000..71519ccd9fc --- /dev/null +++ b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp @@ -0,0 +1,784 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> +#include <qtimezone.h> +#include <private/qtimezoneprivate_p.h> + +#include <qlocale.h> +#include <qscopeguard.h> + +#if defined(Q_OS_WIN) && !QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) +# define USING_WIN_TZ +#endif + +// Enable to test exhaustively - this is slow and expensive. +// It is also quite likely to trip over problems that we can't necessarily fix. +// #define EXHAUSTIVE_ZONE_DISPLAY + +using namespace Qt::StringLiterals; + +class tst_QTimeZoneBackend : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + // Tests of QTZP functionality: + void isValidId_data(); + void isValidId(); + void roundtripDisplayNames_data(); + void roundtripDisplayNames(); + // Tests of specific backends (see also ../qtimezone/): + void icuTest(); + void tzTest(); + void macTest(); + void winTest(); + +private: + // Generic tests of privates, called by implementation-specific private tests: + void testCetPrivate(const QTimeZonePrivate &tzp); + void testEpochTranPrivate(const QTimeZonePrivate &tzp); + // Set to true to print debug output, test Display Names and run long stress tests + static constexpr bool debug = false; +}; + +void tst_QTimeZoneBackend::isValidId_data() +{ + QTest::addColumn<QByteArray>("input"); + QTest::addColumn<bool>("valid"); + + // a-z, A-Z, 0-9, '.', '-', '_' are valid chars + // Can't start with '-' + // Parts separated by '/', each part min 1 and max of 14 chars + // (Android has parts with lengths up to 17, so tolerates this as a special case.) +#define TESTSET(name, section, valid) \ + QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \ + QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \ + QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid + + // a-z, A-Z, 0-9, '.', '-', '_' are valid chars + // Can't start with '-' + // Parts separated by '/', each part min 1 and max of 14 chars + TESTSET("empty", "", false); + TESTSET("minimal", "m", true); +#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) + TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this + TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this. +#else + TESTSET("maximal", "12345678901234", true); + TESTSET("maximal twice", "12345678901234/12345678901234", true); + TESTSET("too long", "123456789012345", false); + TESTSET("too-long/maximal", "123456789012345/12345678901234", false); + TESTSET("maximal/too-long", "12345678901234/123456789012345", false); +#endif + + TESTSET("bad hyphen", "-hyphen", false); + TESTSET("good hyphen", "hy-phen", true); + + TESTSET("valid char _", "_", true); + TESTSET("valid char .", ".", true); + TESTSET("valid char :", ":", true); + TESTSET("valid char +", "+", true); + TESTSET("valid char A", "A", true); + TESTSET("valid char Z", "Z", true); + TESTSET("valid char a", "a", true); + TESTSET("valid char z", "z", true); + TESTSET("valid char 0", "0", true); + TESTSET("valid char 9", "9", true); + + TESTSET("valid pair az", "az", true); + TESTSET("valid pair AZ", "AZ", true); + TESTSET("valid pair 09", "09", true); + TESTSET("valid pair .z", ".z", true); + TESTSET("valid pair _z", "_z", true); + TESTSET("invalid pair -z", "-z", false); + + TESTSET("valid triple a/z", "a/z", true); + TESTSET("valid triple a.z", "a.z", true); + TESTSET("valid triple a-z", "a-z", true); + TESTSET("valid triple a_z", "a_z", true); + TESTSET("invalid triple a z", "a z", false); + TESTSET("invalid triple a\\z", "a\\z", false); + TESTSET("invalid triple a,z", "a,z", false); + + TESTSET("invalid space", " ", false); + TESTSET("invalid char ^", "^", false); + TESTSET("invalid char \"", "\"", false); + TESTSET("invalid char $", "$", false); + TESTSET("invalid char %", "%", false); + TESTSET("invalid char &", "&", false); + TESTSET("invalid char (", "(", false); + TESTSET("invalid char )", ")", false); + TESTSET("invalid char =", "=", false); + TESTSET("invalid char -", "-", false); + TESTSET("invalid char ?", "?", false); + TESTSET("invalid char ß", "ß", false); + TESTSET("invalid char \\x01", "\x01", false); + TESTSET("invalid char ' '", " ", false); + +#undef TESTSET + + QTest::newRow("az alone") << QByteArray("az") << true; + QTest::newRow("AZ alone") << QByteArray("AZ") << true; + QTest::newRow("09 alone") << QByteArray("09") << true; + QTest::newRow("a/z alone") << QByteArray("a/z") << true; + QTest::newRow("a.z alone") << QByteArray("a.z") << true; + QTest::newRow("a-z alone") << QByteArray("a-z") << true; + QTest::newRow("a_z alone") << QByteArray("a_z") << true; + QTest::newRow(".z alone") << QByteArray(".z") << true; + QTest::newRow("_z alone") << QByteArray("_z") << true; + QTest::newRow("a z alone") << QByteArray("a z") << false; + QTest::newRow("a\\z alone") << QByteArray("a\\z") << false; + QTest::newRow("a,z alone") << QByteArray("a,z") << false; + QTest::newRow("/z alone") << QByteArray("/z") << false; + QTest::newRow("-z alone") << QByteArray("-z") << false; +#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb) + QTest::newRow("long alone") << QByteArray("12345678901234567") << true; + QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false; +#else + QTest::newRow("long alone") << QByteArray("12345678901234") << true; + QTest::newRow("over-long alone") << QByteArray("123456789012345") << false; +#endif +} + +void tst_QTimeZoneBackend::isValidId() +{ + QFETCH(QByteArray, input); + QFETCH(bool, valid); + + QCOMPARE(QTimeZonePrivate::isValidId(input), valid); +} + +void tst_QTimeZoneBackend::roundtripDisplayNames_data() +{ + QTest::addColumn<QTimeZone>("zone"); + QTest::addColumn<QLocale>("locale"); + QTest::addColumn<QTimeZone::TimeType>("type"); + + constexpr QTimeZone::TimeType types[] = { + QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime + }; + const auto typeName = [](QTimeZone::TimeType type) { + switch (type) { + case QTimeZone::GenericTime: return "Gen"; + case QTimeZone::StandardTime: return "Std"; + case QTimeZone::DaylightTime: return "DST"; + } + Q_UNREACHABLE_RETURN("Unrecognised"); + }; + const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba); +#ifdef EXHAUSTIVE_ZONE_DISPLAY + const QList<QByteArray> idList = allList; +#else + const QList<QByteArray> idList = { + "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba, + "America/Caracas"_ba, "America/Coyhaique"_ba, + "America/Indiana/Tell_City"_ba, "America/Managua"_ba, + "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba, + "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba, + "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba, + "Brazil/Acre"_ba, "Canada/Atlantic"_ba, "Chile/EasterIsland"_ba, + "CST6CDT"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba, + "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba, + "Europe/Prague"_ba, "Europe/Vatican"_ba, + "Indian/Comoro"_ba, "Mexico/BajaSur"_ba, + "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba, + "US/Aleutian"_ba, + "UTC"_ba, + // Those named overtly in tst_QDateTime - special cases first: + "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba, + "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba, + // ... then ordinary names in alphabetic order: + "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba, + "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba, + "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba, + "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba, + "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba, + "Europe/Rome"_ba, + "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba, + "Vulcan/ShiKahr"_ba // Invalid: also worth testing. + }; + // Some valid zones in that list may be absent from the platform's + // availableTimeZoneIds(), yet in fact work when used as it's asked to + // instantiate them (e.g. Etc/Universal on macOS). This can give them a + // displayName() that we fail to decode, without timezone_locale, due to + // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we + // have to filter on membership of allList when creating rows. +#endif // Exhaustive + const QLocale fr(QLocale::French, QLocale::France); + const QLocale hi(QLocale::Hindi, QLocale::India); + for (const QByteArray &id : idList) { + if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id)) + continue; + QTimeZone zone = QTimeZone(id); + if (!zone.isValid()) + continue; + for (const auto type : types) { + QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type)) + << zone << fr << type; + QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type)) + << zone << hi << type; + } + } +} + +void tst_QTimeZoneBackend::roundtripDisplayNames() +{ + QFETCH(const QTimeZone, zone); + QFETCH(const QLocale, locale); + QFETCH(const QTimeZone::TimeType, type); + static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC); + static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC); + const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan; + + // Some zones exercise region format. + const QString name = zone.displayName(type, QTimeZone::LongName, locale); + if (!name.isEmpty()) { + const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type); + const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs() + ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch; + const QString extended = name + "some spurious cruft"_L1; + auto match = + QTimeZonePrivate::findLongNamePrefix(extended, locale, when); + if (!match) + match = QTimeZonePrivate::findLongNamePrefix(extended, locale); + if (!match) + match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale); + if (!match) + match = QTimeZonePrivate::findLongUtcPrefix(extended); + auto report = qScopeGuard([=]() { + qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC) + << "via" << name; + }); + QCOMPARE(match.nameLength, name.size()); + report.dismiss(); +#if 0 + if (match.ianaId != zone.id()) { + const QTimeZone found = QTimeZone(match.ianaId); + if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when) + != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) { + // For DST, some zones haven't done it in ages, so tran may be ancient. + // Meanwhile, match.ianaId is typically the canonical zone for a metazone. + // That, in turn, may not have been doing DST when zone was. + // So we can't rely on a match, but can report the mismatches. + qDebug() << "Long name" << name << "on" + << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when) + << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC) + << "got" << match.ianaId << "on" + << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when); + // There are also some absurdly over-generic names, that lead to + // ambiguities, e.g. "heure : West" + } + } +#endif // Debug code + } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */ + qDebug("Empty display name"); + } +} + +// Relies on local variable names: zone tzp and locale enUS. +#define ZONE_DNAME_CHECK(type, name, val) \ + QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val); + +void tst_QTimeZoneBackend::icuTest() +{ +#if QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && (defined(Q_OS_VXWORKS) || !defined(Q_OS_UNIX)) + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + + // Test default constructor + QIcuTimeZonePrivate tzpd; + QVERIFY(tzpd.isValid()); + + // Test invalid is not available: + QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon")); + // and construction gives an invalid result: + QIcuTimeZonePrivate tzpi("Gondwana/Erewhon"); + QCOMPARE(tzpi.isValid(), false); + + // Test named constructor + QIcuTimeZonePrivate tzp("Europe/Berlin"); + QVERIFY(tzp.isValid()); + + // Only test names in debug mode, names used can vary by ICU version installed + if constexpr (debug) { + // Test display names by type + QLocale enUS("en_US"); + ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00"); + // ICU C api does not support Generic Time yet, C++ api does + ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00"); + + // Test Abbreviations + QCOMPARE(tzp.abbreviation(std), u"CET"); + QCOMPARE(tzp.abbreviation(dst), u"CEST"); + } + + testCetPrivate(tzp); + if (QTest::currentTestFailed()) + return; + testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto")); +#endif // ICU without tzdb, on VxWorks or not on Unix +} + +void tst_QTimeZoneBackend::tzTest() +{ +#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \ + || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS)) + const auto UTC = QTimeZone::UTC; + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + + // Test default constructor + QTzTimeZonePrivate tzpd; + QVERIFY(tzpd.isValid()); + + // Test invalid constructor + QTzTimeZonePrivate tzpi("Gondwana/Erewhon"); + QVERIFY(!tzpi.isValid()); + + // Test named constructor + QTzTimeZonePrivate tzp("Europe/Berlin"); + QVERIFY(tzp.isValid()); + + // Test POSIX-format value for $TZ: + QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"); + QVERIFY(tzposix.isValid()); + QVERIFY(tzposix.hasDaylightTime()); + + // Cope with stray space at start of value (QTBUG-135109): + QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3"); + QVERIFY(syd.isValid()); + QVERIFY(syd.hasDaylightTime()); + + // RHEL has been seen with this as Africa/Casablanca's POSIX rule: + QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25"); + const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst + QVERIFY(permaDst.isValid()); + QVERIFY(permaDst.hasDaylightTime()); + QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch())); + QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch())); + // Note that the final /25 could be misunderstood as putting a fall-back at + // 1am on the next year's Jan 1st; check we don't do that: + QVERIFY(permaDst.isDaylightTime( + QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch())); + // It shouldn't have any transitions. QTimeZone::hasTransitions() only says + // whether the backend supports them, so ask for transitions in a wide + // enough interval that one would show up, if there are any: + QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(), + QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch() + ).isEmpty()); + + QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule + QVERIFY(tzBrazil.isValid()); + QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800); + + // Test display names by type, either ICU or abbreviation only + QLocale enUS(u"en_US"); + // Only test names in debug mode, names used can vary by ICU version installed + if constexpr (debug) { +#if QT_CONFIG(icu) + ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); + // ICU C api does not support Generic Time yet, C++ api does + ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); +#else + ZONE_DNAME_CHECK(StandardTime, LongName, "CET"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "CET"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST"); + ZONE_DNAME_CHECK(GenericTime, LongName, "CET"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "CET"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET"); +#endif // icu + + // Test Abbreviations + QCOMPARE(tzp.abbreviation(std), u"CET"); + QCOMPARE(tzp.abbreviation(dst), u"CEST"); + } + + testCetPrivate(tzp); + if (QTest::currentTestFailed()) + return; + testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto")); + if (QTest::currentTestFailed()) + return; + + // Test first and last transition rule + // Warning: This could vary depending on age of TZ file! + + // Test low date uses first rule found + constexpr qint64 ancient = -Q_INT64_C(9999999999999); + // Note: Depending on the OS in question, the database may be carrying the + // Local Mean Time. which for Berlin is 0:53:28 + QTimeZonePrivate::Data dat = tzp.data(ancient); + QCOMPARE(dat.atMSecsSinceEpoch, ancient); + QCOMPARE(dat.daylightTimeOffset, 0); + if (dat.abbreviation == u"LMT") { + QCOMPARE(dat.standardTimeOffset, 3208); + } else { + QCOMPARE(dat.standardTimeOffset, 3600); + + constexpr qint64 invalidTime = std::numeric_limits<qint64>::min(); + constexpr int invalidOffset = std::numeric_limits<int>::min(); + // Test previous to low value is invalid + dat = tzp.previousTransition(ancient); + QCOMPARE(dat.atMSecsSinceEpoch, invalidTime); + QCOMPARE(dat.standardTimeOffset, invalidOffset); + QCOMPARE(dat.daylightTimeOffset, invalidOffset); + } + + dat = tzp.nextTransition(ancient); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, + QTimeZone::fromSecondsAheadOfUtc(3600)), + QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32), + QTimeZone::fromSecondsAheadOfUtc(3600))); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + + // Date-times late enough to exercise POSIX rules: + qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(); + qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch(); + // Relevant last Sundays in October and March: + QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday); + QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday); + QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday); + + dat = tzp.data(stdHi); + QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0)); + QCOMPARE(dat.offsetFromUtc, 3600); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + + dat = tzp.data(dstHi); + QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0)); + QCOMPARE(dat.offsetFromUtc, 7200); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 3600); + + dat = tzp.previousTransition(stdHi); + QCOMPARE(dat.abbreviation, u"CET"); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); + QCOMPARE(dat.offsetFromUtc, 3600); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + + dat = tzp.previousTransition(dstHi); + QCOMPARE(dat.abbreviation, u"CEST"); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); + QCOMPARE(dat.offsetFromUtc, 7200); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 3600); + + dat = tzp.nextTransition(stdHi); + QCOMPARE(dat.abbreviation, u"CEST"); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600))); + QCOMPARE(dat.offsetFromUtc, 7200); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 3600); + + dat = tzp.nextTransition(dstHi); + QCOMPARE(dat.abbreviation, u"CET"); + QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, + QTimeZone::fromSecondsAheadOfUtc(3600)), + QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200))); + QCOMPARE(dat.offsetFromUtc, 3600); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + + // Test TZ timezone vs UTC timezone for non-whole-hour negative offset: + QTzTimeZonePrivate tztz1("America/Caracas"); + QUtcTimeZonePrivate tzutc1("UTC-04:30"); + QVERIFY(tztz1.isValid()); + QVERIFY(tzutc1.isValid()); + QTzTimeZonePrivate::Data datatz1 = tztz1.data(std); + QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std); + QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc); + + // Test TZ timezone vs UTC timezone for non-whole-hour positive offset: + QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name + QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name + // Can't assign QtzTZP, so use a reference; prefer new name. + QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c; + QUtcTimeZonePrivate tzutc2("UTC+05:30"); + QVERIFY2(tztz2.isValid(), tztz2.id().constData()); + QVERIFY(tzutc2.isValid()); + QTzTimeZonePrivate::Data datatz2 = tztz2.data(std); + QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std); + QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc); + + // Test a timezone with an abbreviation that isn't all letters: + QTzTimeZonePrivate tzBarnaul("Asia/Barnaul"); + if (tzBarnaul.isValid()) { + QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07"); + + // first full day of the new rule (tzdata2016b) + QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC); + QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07"); + } +#endif // Unix && !(timezone_tzdb || Darwin || Android || VxWorks) +} + +void tst_QTimeZoneBackend::macTest() +{ +#if defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb) + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + + // Test default constructor + QMacTimeZonePrivate tzpd; + QVERIFY(tzpd.isValid()); + + // Test invalid constructor + QMacTimeZonePrivate tzpi("Gondwana/Erewhon"); + QCOMPARE(tzpi.isValid(), false); + + // Test named constructor + QMacTimeZonePrivate tzp("Europe/Berlin"); + QVERIFY(tzp.isValid()); + + // Only test names in debug mode, names used can vary by version + if constexpr (debug) { + // Test display names by type + QLocale enUS(u"en_US"); + ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); + // ICU C api does not support Generic Time yet, C++ api does + ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time"); + ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); + + // Test Abbreviations + QCOMPARE(tzp.abbreviation(std), u"CET"); + QCOMPARE(tzp.abbreviation(dst), u"CEST"); + } + + testCetPrivate(tzp); + if (QTest::currentTestFailed()) + return; + testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto")); +#endif // Q_OS_DARWIN without tzdb +} + +void tst_QTimeZoneBackend::winTest() +{ +#if defined(USING_WIN_TZ) + // Known datetimes + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); + + // Test default constructor + QWinTimeZonePrivate tzpd; + if constexpr (debug) + qDebug() << "System ID = " << tzpd.id() + << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale()) + << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale()); + QVERIFY(tzpd.isValid()); + + // Test invalid constructor + QWinTimeZonePrivate tzpi("Gondwana/Erewhon"); + QCOMPARE(tzpi.isValid(), false); + + // Test named constructor + QWinTimeZonePrivate tzp("Europe/Berlin"); + QVERIFY(tzp.isValid()); + + // Only test names in debug mode, names used can vary by version + if constexpr (debug) { + // Test display names by type + QLocale enUS(u"en_US"); + ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time"); + ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time"); + ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00"); + ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time"); + ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time"); + ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00"); + ZONE_DNAME_CHECK(GenericTime, LongName, + "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); + ZONE_DNAME_CHECK(GenericTime, ShortName, + "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"); + ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00"); + + // Test Abbreviations + QCOMPARE(tzp.abbreviation(std), u"CET"); + QCOMPARE(tzp.abbreviation(dst), u"CEST"); + } + + testCetPrivate(tzp); + if (QTest::currentTestFailed()) + return; + testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto")); +#endif // TZ backend +} + +#undef ZONE_DNAME_CHECK + +// Test each private produces the same basic results for CET +void tst_QTimeZoneBackend::testCetPrivate(const QTimeZonePrivate &tzp) +{ + // Known datetimes + const auto UTC = QTimeZone::UTC; + const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600); + qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch(); + + QCOMPARE(tzp.offsetFromUtc(std), 3600); + QCOMPARE(tzp.offsetFromUtc(dst), 7200); + + QCOMPARE(tzp.standardTimeOffset(std), 3600); + QCOMPARE(tzp.standardTimeOffset(dst), 3600); + + QCOMPARE(tzp.daylightTimeOffset(std), 0); + QCOMPARE(tzp.daylightTimeOffset(dst), 3600); + + QCOMPARE(tzp.hasDaylightTime(), true); + QCOMPARE(tzp.isDaylightTime(std), false); + QCOMPARE(tzp.isDaylightTime(dst), true); + + QTimeZonePrivate::Data dat = tzp.data(std); + QCOMPARE(dat.atMSecsSinceEpoch, std); + QCOMPARE(dat.offsetFromUtc, 3600); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 0); + QCOMPARE(dat.abbreviation, tzp.abbreviation(std)); + + dat = tzp.data(dst); + QCOMPARE(dat.atMSecsSinceEpoch, dst); + QCOMPARE(dat.offsetFromUtc, 7200); + QCOMPARE(dat.standardTimeOffset, 3600); + QCOMPARE(dat.daylightTimeOffset, 3600); + QCOMPARE(dat.abbreviation, tzp.abbreviation(dst)); + + // Only test transitions if host system supports them + if (tzp.hasTransitions()) { + QTimeZonePrivate::Data tran = tzp.nextTransition(std); + // 2012-03-25 02:00 CET, +1 -> +2 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); + QCOMPARE(tran.offsetFromUtc, 7200); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + tran = tzp.nextTransition(dst); + // 2012-10-28 03:00 CEST, +2 -> +1 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 10, 28), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); + QCOMPARE(tran.offsetFromUtc, 3600); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + + tran = tzp.previousTransition(std); + // 2011-10-30 03:00 CEST, +2 -> +1 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2011, 10, 30), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(2 * 3600))); + QCOMPARE(tran.offsetFromUtc, 3600); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + + tran = tzp.previousTransition(dst); + // 2012-03-25 02:00 CET, +1 -> +2 (again) + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour)); + QCOMPARE(tran.offsetFromUtc, 7200); + QCOMPARE(tran.standardTimeOffset, 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + QTimeZonePrivate::DataList expected; + // 2011-03-27 02:00 CET, +1 -> +2 + tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0), + eastOneHour).toMSecsSinceEpoch(); + tran.offsetFromUtc = 7200; + tran.standardTimeOffset = 3600; + tran.daylightTimeOffset = 3600; + expected << tran; + // 2011-10-30 03:00 CEST, +2 -> +1 + tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0), + QTimeZone::fromSecondsAheadOfUtc(2 * 3600) + ).toMSecsSinceEpoch(); + tran.offsetFromUtc = 3600; + tran.standardTimeOffset = 3600; + tran.daylightTimeOffset = 0; + expected << tran; + QTimeZonePrivate::DataList result = tzp.transitions(prev, std); + QCOMPARE(result.size(), expected.size()); + for (int i = 0; i < expected.size(); ++i) { + QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour), + QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour)); + QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc); + QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset); + QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset); + } + } +} + +// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT) +void tst_QTimeZoneBackend::testEpochTranPrivate(const QTimeZonePrivate &tzp) +{ + if (!tzp.hasTransitions()) + return; // test only viable for transitions + + const auto UTC = QTimeZone::UTC; + const auto hour = std::chrono::hours{1}; + QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch + // 1970-04-26 02:00 EST, -5 -> -4 + const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0), + QTimeZone::fromDurationAheadOfUtc(-5 * hour)); + const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC); +#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th. + QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time()); +#else + QCOMPARE(found, after); +#endif + QCOMPARE(tran.offsetFromUtc, -4 * 3600); + QCOMPARE(tran.standardTimeOffset, -5 * 3600); + QCOMPARE(tran.daylightTimeOffset, 3600); + + // Pre-epoch time-zones might not be supported at all: + tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch()); + if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs() + // Toronto *did* have a transition before 1970 (DST since 1918): + && tran.atMSecsSinceEpoch < 0) { + // ... but, if they are, we should be able to search back to them: + tran = tzp.previousTransition(0); // i.e. last before epoch + // 1969-10-26 02:00 EDT, -4 -> -5 + QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC), + QDateTime(QDate(1969, 10, 26), QTime(2, 0), + QTimeZone::fromDurationAheadOfUtc(-4 * hour))); + QCOMPARE(tran.offsetFromUtc, -5 * 3600); + QCOMPARE(tran.standardTimeOffset, -5 * 3600); + QCOMPARE(tran.daylightTimeOffset, 0); + } else { + // Do not use QSKIP(): that would discard the rest of this sub-test's caller. + qDebug() << "No support for pre-epoch time-zone transitions"; + } +} + +QTEST_APPLESS_MAIN(tst_QTimeZoneBackend) +#include "tst_qtimezonebackend.moc" diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 6ff905cb1cb..452b7dcef88 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -729,11 +729,12 @@ void tst_Http2::earlyError() : QHttpNetworkConnection::ConnectionTypeHTTP2; QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr, connectionType); +#if QT_CONFIG(ssl) QSslConfiguration config = QSslConfiguration::defaultConfiguration(); config.setAllowedNextProtocols({"h2"}); connection.setSslConfiguration(config); connection.ignoreSslErrors(); - +#endif // SETUP manually setup the QHttpNetworkRequest QHttpNetworkRequest req; req.setSsl(true); @@ -809,11 +810,12 @@ void tst_Http2::abortReply() : QHttpNetworkConnection::ConnectionTypeHTTP2; QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr, connectionType); +#if QT_CONFIG(ssl) QSslConfiguration config = QSslConfiguration::defaultConfiguration(); config.setAllowedNextProtocols({"h2"}); connection.setSslConfiguration(config); connection.ignoreSslErrors(); - +#endif // SETUP manually setup the QHttpNetworkRequest QHttpNetworkRequest req; req.setSsl(true); diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index fdc2dde7921..c2b03b70d9b 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -10290,7 +10290,7 @@ void tst_QNetworkReply::requestWithTimeout() QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred); QCOMPARE(waitForFinish(reply), int(Failure)); QCOMPARE(spy.size(), 1); - QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError); + QCOMPARE(reply->error(), QNetworkReply::TimeoutError); } #endif diff --git a/tests/auto/other/languagechange/tst_languagechange.cpp b/tests/auto/other/languagechange/tst_languagechange.cpp index 8f99730a192..ab9244c8561 100644 --- a/tests/auto/other/languagechange/tst_languagechange.cpp +++ b/tests/auto/other/languagechange/tst_languagechange.cpp @@ -172,10 +172,10 @@ void tst_languageChange::retranslatability_data() << "QFileDialog::Back" << "QFileDialog::Create New Folder" << "QFileDialog::Detail View" - << "QFileDialog::Files of type:" + << "QFileDialog::Files of &type:" << "QFileDialog::Forward" << "QFileDialog::List View" - << "QFileDialog::Look in:" + << "QFileDialog::&Look in:" << "QFileDialog::Open" << "QFileDialog::Parent Directory" << "QFileDialog::Show " diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index 2e92cdffada..305f48c95ee 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -1793,6 +1793,15 @@ void tst_QAccessibility::spinBoxTest() QAccessibleTextInterface *textIface = interface->textInterface(); QVERIFY(textIface); + QVERIFY(!spinBox->isReadOnly()); + QVERIFY(interface->state().editable); + QVERIFY(!interface->state().readOnly); + + spinBox->setReadOnly(true); + QVERIFY(spinBox->isReadOnly()); + QVERIFY(!interface->state().editable); + QVERIFY(interface->state().readOnly); + QTestAccessibility::clearEvents(); } @@ -2985,6 +2994,9 @@ void tst_QAccessibility::listTest() listView->setModelColumn(1); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); listView->resize(400,400); + listView->setAccessibleName(QLatin1String("list view's accessible name")); + listView->setToolTip(QLatin1String("This list view will be used to test accessibility")); + listView->setWhatsThis(QLatin1String("What's this list")); listView->show(); QVERIFY(QTest::qWaitForWindowExposed(listView)); @@ -2992,6 +3004,10 @@ void tst_QAccessibility::listTest() QCOMPARE(verifyHierarchy(iface), 0); QCOMPARE((int)iface->role(), (int)QAccessible::List); + QCOMPARE(iface->text(QAccessible::Name), QLatin1String("list view's accessible name")); + QCOMPARE(iface->text(QAccessible::Description), QLatin1String("This list view will be used to test accessibility")); + QCOMPARE(iface->text(QAccessible::Help), QLatin1String("What's this list")); + QCOMPARE(iface->text(QAccessible::Value), QString()); QCOMPARE(iface->childCount(), 3); { diff --git a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp index 5f54471bc0f..50971e27964 100644 --- a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp +++ b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp @@ -233,6 +233,10 @@ void tst_QFiledialog::directoryEnteredSignal() sidebar->setCurrentIndex(secondItem); QTest::keyPress(sidebar->viewport(), Qt::Key_Return); QCOMPARE(spyDirectoryEntered.size(), 1); + // ensure signal isn't emitted again when clicking on the already active item + QTest::mouseClick(sidebar->viewport(), Qt::LeftButton, {}, + sidebar->visualRect(secondItem).center()); + QCOMPARE(spyDirectoryEntered.size(), 1); spyDirectoryEntered.clear(); // lookInCombo @@ -815,9 +819,9 @@ void tst_QFiledialog::labelText() QFileDialog fd; QDialogButtonBox buttonBox; QPushButton *cancelButton = buttonBox.addButton(QDialogButtonBox::Cancel); - QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("Look in:")); + QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("&Look in:")); QCOMPARE(fd.labelText(QFileDialog::FileName), QString("File &name:")); - QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of type:")); + QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of &type:")); QCOMPARE(fd.labelText(QFileDialog::Accept), QString("&Open")); ///### see task 241462 QCOMPARE(fd.labelText(QFileDialog::Reject), cancelButton->text()); diff --git a/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp b/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp index cdbf2011a4a..4f37e2490fd 100644 --- a/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp +++ b/tests/auto/widgets/dialogs/qsidebar/tst_qsidebar.cpp @@ -53,9 +53,9 @@ void tst_QSidebar::selectUrls() QSidebar qsidebar; qsidebar.setModelAndUrls(&fsmodel, urls); - QSignalSpy spy(&qsidebar, SIGNAL(goToUrl(QUrl))); + QSignalSpy spy(&qsidebar, &QSidebar::goToUrl); qsidebar.selectUrl(urls.at(0)); - QCOMPARE(spy.size(), 0); + QCOMPARE(spy.size(), 1); } void tst_QSidebar::addUrls() @@ -160,15 +160,19 @@ void tst_QSidebar::addUrls() void tst_QSidebar::goToUrl() { QList<QUrl> urls; + const QUrl tempUrl = QUrl::fromLocalFile(QDir::temp().absolutePath()); urls << QUrl::fromLocalFile(QDir::rootPath()) - << QUrl::fromLocalFile(QDir::temp().absolutePath()); + << tempUrl; QFileSystemModel fsmodel; fsmodel.setIconProvider(&defaultIconProvider); QSidebar qsidebar; qsidebar.setModelAndUrls(&fsmodel, urls); qsidebar.show(); - QSignalSpy spy(&qsidebar, SIGNAL(goToUrl(QUrl))); + // switch to a different entry first + qsidebar.selectUrl(tempUrl); + + QSignalSpy spy(&qsidebar, &QSidebar::goToUrl); QTest::mousePress(qsidebar.viewport(), Qt::LeftButton, {}, qsidebar.visualRect(qsidebar.model()->index(0, 0)).center()); QCOMPARE(spy.size(), 1); diff --git a/tests/auto/widgets/graphicsview/qgraphicspixmapitem/tst_qgraphicspixmapitem.cpp b/tests/auto/widgets/graphicsview/qgraphicspixmapitem/tst_qgraphicspixmapitem.cpp index fd8f8bd37d8..b8eab0a94fa 100644 --- a/tests/auto/widgets/graphicsview/qgraphicspixmapitem/tst_qgraphicspixmapitem.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicspixmapitem/tst_qgraphicspixmapitem.cpp @@ -128,9 +128,6 @@ void tst_QGraphicsPixmapItem::contains_data() // public bool contains(QPointF const& point) const void tst_QGraphicsPixmapItem::contains() { - if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) - QSKIP("Wayland: This fails. Figure out why."); - QFETCH(QPixmap, pixmap); QFETCH(QPointF, point); QFETCH(bool, contains); diff --git a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt index 717daf4bcd2..a4f598f3b74 100644 --- a/tests/auto/widgets/kernel/qapplication/CMakeLists.txt +++ b/tests/auto/widgets/kernel/qapplication/CMakeLists.txt @@ -21,9 +21,10 @@ qt_internal_add_executable(apphelper_widgets Qt::Widgets ) set_target_properties(apphelper_widgets PROPERTIES OUTPUT_NAME apphelper) -set_target_properties(tst_qapplication PROPERTIES - QT_ANDROID_EXTRA_LIBS ${CMAKE_CURRENT_BINARY_DIR}/libmodal_helper_${ANDROID_ABI}.so -) + +set_property(TARGET tst_qapplication PROPERTY QT_ANDROID_EXTRA_LIBS + ${CMAKE_CURRENT_BINARY_DIR}/libdesktopsettingsaware_helper_${ANDROID_ABI}.so + ${CMAKE_CURRENT_BINARY_DIR}/libmodal_helper_${ANDROID_ABI}.so) if(QT_FEATURE_library) qt_internal_add_cmake_library(apphelper_widgets_plugin diff --git a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp index 6859f22c044..10a67daa02a 100644 --- a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp +++ b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp @@ -845,12 +845,12 @@ void tst_QComboBox::virtualAutocompletion() QKeyEvent kp3(QEvent::KeyPress, Qt::Key_R, {}, "r"); QKeyEvent kr3(QEvent::KeyRelease, Qt::Key_R, {}, "r"); - QTest::qWait(QApplication::keyboardInputInterval()); QApplication::sendEvent(testWidget, &kp3); + QTest::qWait(QApplication::keyboardInputInterval()); QApplication::sendEvent(testWidget, &kr3); QTRY_COMPARE(testWidget->currentIndex(), 3); - QTest::qWait(QApplication::keyboardInputInterval()); + QTest::qWait(2 * QApplication::keyboardInputInterval()); testWidget->view()->setKeyboardSearchFlags(Qt::MatchContains | Qt::MatchWrap); QApplication::sendEvent(testWidget, &kp3); QApplication::sendEvent(testWidget, &kr3); @@ -2247,10 +2247,11 @@ void tst_QComboBox::ignoreWheelEvents() QFETCH(bool, allowWheelScrolling); + AllowWheelScrollStyle style(allowWheelScrolling); WheelEventTestWidget widget; QComboBox *comboBox = new QComboBox(&widget); comboBox->addItems({ "0", "1" }); - comboBox->setStyle(new AllowWheelScrollStyle(allowWheelScrolling)); + comboBox->setStyle(&style); widget.show(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index 8ace9592141..ad9db868235 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -6,7 +6,6 @@ if(UIKIT) return() endif() -add_subdirectory(assets) add_subdirectory(corelib) add_subdirectory(filetest) # diaglib is broken in dev due to missing diff --git a/tests/manual/assets/CMakeLists.txt b/tests/manual/assets/CMakeLists.txt deleted file mode 100644 index 03643aa8537..00000000000 --- a/tests/manual/assets/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -add_subdirectory(downloader) diff --git a/tests/manual/assets/assets.pro b/tests/manual/assets/assets.pro deleted file mode 100644 index 43f09ba46e6..00000000000 --- a/tests/manual/assets/assets.pro +++ /dev/null @@ -1,3 +0,0 @@ -TEMPLATE=subdirs - -SUBDIRS = downloader diff --git a/tests/manual/assets/downloader/CMakeLists.txt b/tests/manual/assets/downloader/CMakeLists.txt deleted file mode 100644 index b95161ac02d..00000000000 --- a/tests/manual/assets/downloader/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -##################################################################### -## tst_manual_downloader Binary: -##################################################################### - -qt_internal_add_manual_test(tst_manual_downloader - GUI - SOURCES - main.cpp - LIBRARIES - Qt::ExamplesAssetDownloaderPrivate - Qt::Widgets -) diff --git a/tests/manual/assets/downloader/downloader.pro b/tests/manual/assets/downloader/downloader.pro deleted file mode 100644 index 53976c538f0..00000000000 --- a/tests/manual/assets/downloader/downloader.pro +++ /dev/null @@ -1,5 +0,0 @@ -QT += examples_asset_downloader-private widgets - -TARGET = tst_manual_downloader - -SOURCES += main.cpp diff --git a/tests/manual/assets/downloader/main.cpp b/tests/manual/assets/downloader/main.cpp deleted file mode 100644 index d9d711cfce5..00000000000 --- a/tests/manual/assets/downloader/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#include <QtExamplesAssetDownloader/assetdownloader.h> - -#include <QApplication> -#include <QMessageBox> -#include <QProgressDialog> - -using namespace Assets::Downloader; - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - app.setOrganizationName("QtProject"); - app.setApplicationName("Asset Downloader"); - - QProgressDialog progress; - progress.setAutoClose(false); - progress.setRange(0, 0); - QObject::connect(&progress, &QProgressDialog::canceled, &app, &QApplication::quit); - - AssetDownloader downloader; - downloader.setJsonFileName("car-configurator-assets-v1.json"); - downloader.setZipFileName("car-configurator-assets-v1.zip"); - downloader.setDownloadBase(QUrl("https://download.qt.io/learning/examples/")); - - QObject::connect(&downloader, &AssetDownloader::started, - &progress, &QProgressDialog::show); - QObject::connect(&downloader, &AssetDownloader::progressChanged, &progress, [&progress]( - int progressValue, int progressMaximum, const QString &progressText) { - progress.setLabelText(progressText); - progress.setMaximum(progressMaximum); - progress.setValue(progressValue); - }); - QObject::connect(&downloader, &AssetDownloader::finished, &progress, [&](bool success) { - progress.reset(); - progress.hide(); - if (success) - QMessageBox::information(nullptr, "Asset Downloader", "Download Finished Successfully."); - else - QMessageBox::warning(nullptr, "Asset Downloader", "Download Finished with an Error."); - }); - - downloader.start(); - - return app.exec(); -} diff --git a/tests/manual/corelib/itemmodels/qrangemodel/main.cpp b/tests/manual/corelib/itemmodels/qrangemodel/main.cpp index 0b1ede8df02..6cb87a4182a 100644 --- a/tests/manual/corelib/itemmodels/qrangemodel/main.cpp +++ b/tests/manual/corelib/itemmodels/qrangemodel/main.cpp @@ -170,7 +170,7 @@ class Object : public QObject Q_PROPERTY(QString display READ display WRITE setDisplay NOTIFY displayChanged) public: - Object(int x) + Object(int x, QObject *parent = nullptr) : m_display(QString::number(x)) {} QString display() const { return m_display; } @@ -196,6 +196,13 @@ class ModelFactory : public QObject std::vector<int> numbers = {1, 2, 3, 4, 5}; QList<QString> strings = {u"one"_s, u"two"_s, u"three"_s}; std::array<int, 1000000> largeArray = {}; + std::array<Object *, 10000> objects = {}; + + void updateAllObjects() + { + for (auto *object : objects) + object->setDisplay(QTime::currentTime().toString()); + } public slots: QRangeModel *makeNumbers() @@ -357,7 +364,7 @@ public slots: // std::make_unique<QString>("C"), // }; // return new QRangeModel(std::move(data)); - return nullptr; + return new QRangeModel(QList{"Nothing to see here"}); } QRangeModel *makeUniqueRows() @@ -410,7 +417,34 @@ public slots: return new QRangeModel(std::ref(europe)); } + + QRangeModel *makeAutoConnectedObjects() + { + QRangeModel *model = new QRangeModel(std::ref(objects)); + QTimer *updater = new QTimer(model); + connect(updater, &QTimer::timeout, this, &ModelFactory::updateAllObjects); + + for (int i = 0; i < objects.size(); ++i) + objects[i] = new Object(i, model); + updater->start(1000); + model->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::OnRead); + return model; + } + + QRangeModel *makeAutoConnectedConstObjects() + { + QRangeModel *model = new QRangeModel(&std::as_const(objects)); + QTimer *updater = new QTimer(model); + connect(updater, &QTimer::timeout, this, &ModelFactory::updateAllObjects); + + for (int i = 0; i < objects.size(); ++i) + objects[i] = new Object(i, model); + updater->start(1000); + model->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full); + return model; + } }; + struct QMetaMethodEnumerator { struct iterator @@ -498,14 +532,12 @@ public: QComboBox *modelPicker = new QComboBox; connect(modelPicker, &QComboBox::currentIndexChanged, this, &MainWindow::modelChanged); - // this will implicitly run modelChanged() - modelPicker->setModel(new QRangeModel(QMetaMethodEnumerator::fromType<ModelFactory>(), - modelPicker)); - modelPicker->setModelColumn(1); QToolBar *toolBar = addToolBar(tr("Model Operations")); toolBar->addWidget(modelPicker); + toolBar->addSeparator(); + QAction *addAction = toolBar->addAction(tr("Add"), this, &MainWindow::onAdd); addAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ListAdd)); QAction *removeAction = toolBar->addAction(tr("Remove"), this, &MainWindow::onRemove); @@ -518,6 +550,38 @@ public: indentAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::FormatIndentMore)); QAction *dedentAction = toolBar->addAction(tr("Move out"), this, &MainWindow::onOut); dedentAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::FormatIndentLess)); + + toolBar->addSeparator(); + + QAction *fullAutoConnectAction = new QAction(tr("Full")); + fullAutoConnectAction->setCheckable(true); + fullAutoConnectAction->setData(QVariant::fromValue(QRangeModel::AutoConnectPolicy::Full)); + QAction *onReadAutoConnectAction = new QAction(tr("On Read")); + onReadAutoConnectAction->setCheckable(true); + onReadAutoConnectAction->setData(QVariant::fromValue(QRangeModel::AutoConnectPolicy::OnRead)); + + connectionOptions = new QActionGroup(this); + connectionOptions->setExclusive(true); + connectionOptions->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional); + connectionOptions->addAction(fullAutoConnectAction); + connectionOptions->addAction(onReadAutoConnectAction); + + toolBar->addAction(fullAutoConnectAction); + toolBar->addAction(onReadAutoConnectAction); + + connect(connectionOptions, &QActionGroup::triggered, this, [this](){ + QAction *checkedAction = connectionOptions->checkedAction(); + const auto policy = checkedAction ? checkedAction->data().value<QRangeModel::AutoConnectPolicy>() + : QRangeModel::AutoConnectPolicy::None; + + qDebug() << "Switching to policy" << policy; + model->setAutoConnectPolicy(policy); + }); + + // this will implicitly run modelChanged() and update the UI + modelPicker->setModel(new QRangeModel(QMetaMethodEnumerator::fromType<ModelFactory>(), + modelPicker)); + modelPicker->setModelColumn(1); } private: @@ -547,6 +611,10 @@ private: qDebug() << "root object in context" << quickWidget->engine()->contextForObject(quickWidget->rootObject())->objectForName("root"); qDebug() << "list object in context" << quickWidget->engine()->contextForObject(quickWidget->rootObject())->objectForName("list"); #endif + for (auto *action : connectionOptions->actions()) { + action->setChecked(action->data().value<QRangeModel::AutoConnectPolicy>() + == model->autoConnectPolicy()); + } } delete oldModel; @@ -625,6 +693,7 @@ private: #ifdef QUICK_UI QQuickWidget *quickWidget; #endif + QActionGroup *connectionOptions; }; int main(int argc, char *argv[]) diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 47f2ae9bd0f..e28eb5316a8 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -2,7 +2,6 @@ TEMPLATE=subdirs QT_FOR_CONFIG += network-private gui-private SUBDIRS = \ -assets \ filetest \ embeddedintoforeignwindow \ foreignwindows \ diff --git a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp index 1fd6e0a0c4f..ff3e67e3fc2 100644 --- a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp +++ b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp @@ -3,6 +3,7 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/private/qwasmsuspendresumecontrol_p.h> +#include <QtCore/qdebug.h> #include <qtwasmtestlib.h> using namespace emscripten; @@ -20,6 +21,7 @@ private slots: void reuseTimer(); void cancelTimer(); void deleteTimer(); + void suspendExclusive(); }; // Verify that a single timer fires @@ -138,6 +140,49 @@ void WasmSuspendResumeControlTest::deleteTimer() QWASMSUCCESS(); } +// Verify that an exclusive suspend resumes for the exclusive event only +void WasmSuspendResumeControlTest::suspendExclusive() +{ + QWasmSuspendResumeControl suspendResume; + + // (re) implement a native timer - this gives us a unique event handler + // index which we can suspend exclusively on. + bool exclusiveTimerFired = false; + auto exclusiveTimerHandler = [&exclusiveTimerFired](emscripten::val) { + exclusiveTimerFired = true; + }; + uint32_t exlusiveTimerHandlerIndex = suspendResume.registerEventHandler(std::move(exclusiveTimerHandler)); + + std::chrono::milliseconds exclusiveTimerTimeout = timerTimeout * 4; + double timoutValue = static_cast<double>(exclusiveTimerTimeout.count()); + val jsHandler = suspendResume.jsEventHandlerAt(exlusiveTimerHandlerIndex); + val::global("window").call<double>("setTimeout", jsHandler, timoutValue); + + // Schedule suppressedTimer to fire before the exclusive timer. Expected + // behavior is that it doesn't. + bool suppressedTimerFired = false; + QWasmTimer suppressedTimer(&suspendResume, [&suppressedTimerFired](){ + suppressedTimerFired = true; + }); + suppressedTimer.setTimeout(timerTimeout); + + // Suspend exclusively for the exclusive timer, and verify that + // the correct timers fired. + suspendResume.suspendExclusive(exlusiveTimerHandlerIndex); + suspendResume.sendPendingEvents(); // <- also clears exclusive mode + if (!exclusiveTimerFired) + QWASMFAIL("Exclusive timer did not fire"); + if (suppressedTimerFired) + QWASMFAIL("Suppressed timer did fire"); + + // Send (all) events, this should give is the suppressed timer + suspendResume.sendPendingEvents(); + if (!suppressedTimerFired) + QWASMFAIL("Suppressed timer did not fire"); + + QWASMSUCCESS(); +} + int main(int argc, char **argv) { auto testObject = std::make_shared<WasmSuspendResumeControlTest>(); diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp index ec03c7209a4..1e49847c97f 100644 --- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp +++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp @@ -127,7 +127,7 @@ void passTest() EMSCRIPTEN_BINDINGS(qtwebtestrunner) { emscripten::function("cleanupTestCase", &cleanupTestCase); emscripten::function("getTestFunctions", &getTestFunctions); - emscripten::function("runTestFunction", &runTestFunction); + emscripten::function("runTestFunction", &runTestFunction, emscripten::async()); emscripten::function("qtWasmFail", &failTest); emscripten::function("qtWasmPass", &passTest); } |
