kde/kf6-kio: rebase to 6.28.0 (obsolete previewjob guard dropped; redox_qtnetwork_compat/ added dir preserved)

This commit is contained in:
2026-08-01 00:43:36 +03:00
parent 75d273bff0
commit 7b58282eb0
729 changed files with 206664 additions and 150175 deletions
Binary file not shown.
@@ -7,7 +7,11 @@ e14e17e0d75b448cfce0af487e4a6426b2bf2f1a
175466c7ae839ac738afcad1fd46c8d442eef706
d3a9783383873c1228bd34f52132d64d2624b784
7154aa45ea27959253d9dc5d9dbbea14bdaac015
#clang-tidy
# clang-tidy
8ce18b21c8f68423d23540dc642cd931d8d39a72
1cfd57e7f4ee12c4462773eefe2ed6ad99d58c50
02e2f37ac2809e5eb4376da2efd7df0cbfa3c172
# typo fixes
d3f1af35cbffbd5eada6aa4e5ecdb48fcd860b14
# code reorganization
3ec7789f713baa33f380556fdf6908fb1f28715b
@@ -5,7 +5,10 @@ include:
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/cppcheck.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
+33 -33
View File
@@ -1,39 +1,39 @@
Dependencies:
- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS', 'Android']
'require':
- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS', 'Android']
'require':
'frameworks/extra-cmake-modules': '@same'
'frameworks/ki18n' : '@same'
'frameworks/kguiaddons' : '@same'
'frameworks/kitemviews' : '@same'
'frameworks/kcoreaddons' : '@same'
'frameworks/kwidgetsaddons' : '@same'
'frameworks/kservice' : '@same'
'frameworks/karchive' : '@same'
'frameworks/solid' : '@same'
'frameworks/kjobwidgets' : '@same'
'frameworks/kiconthemes' : '@same'
'frameworks/kwindowsystem' : '@same'
'frameworks/kcompletion' : '@same'
'frameworks/kbookmarks' : '@same'
'frameworks/knotifications' : '@same'
'frameworks/kconfig' : '@same'
'frameworks/kcolorscheme' : '@same'
'frameworks/ki18n': '@same'
'frameworks/kguiaddons': '@same'
'frameworks/kitemviews': '@same'
'frameworks/kcoreaddons': '@same'
'frameworks/kwidgetsaddons': '@same'
'frameworks/kservice': '@same'
'frameworks/karchive': '@same'
'frameworks/solid': '@same'
'frameworks/kjobwidgets': '@same'
'frameworks/kiconthemes': '@same'
'frameworks/kwindowsystem': '@same'
'frameworks/kcompletion': '@same'
'frameworks/kbookmarks': '@same'
'frameworks/knotifications': '@same'
'frameworks/kconfig': '@same'
'frameworks/kcolorscheme': '@same'
- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS']
'require':
'frameworks/kcrash' : '@same'
'frameworks/kdoctools' : '@same'
- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS']
'require':
'frameworks/kcrash': '@same'
'frameworks/kdoctools': '@same'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kauth': '@same'
'frameworks/kdbusaddons' : '@same'
'frameworks/kwallet' : '@same'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@same'
'frameworks/kwallet': '@same'
Options:
test-before-installing: True
require-passing-tests-on: [ 'Linux', 'FreeBSD' ]
cmake-options: -DUSE_FTPD_WSGIDAV_UNITTEST=ON
cppcheck-arguments: '--enable=warning,style,performance --suppress-xml=cppcheck-suppressions.xml'
cppcheck-ignore-files:
- autotests
test-before-installing: True
enable-lsan: True
require-passing-tests-on: ['@all']
cmake-options: -DUSE_FTPD_WSGIDAV_UNITTEST=ON
cppcheck-arguments: '--enable=warning,style,performance --suppress-xml=cppcheck-suppressions.xml'
cppcheck-ignore-files:
- autotests
@@ -0,0 +1 @@
6.28.0
+41 -56
View File
@@ -1,12 +1,12 @@
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.10.0") # handled by release scripts
set(KF_DEP_VERSION "6.10.0") # handled by release scripts
set(KF_VERSION "6.28.0") # handled by release scripts
set(KF_DEP_VERSION "6.28.0") # handled by release scripts
project(KIO VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 6.10.0 NO_MODULE)
find_package(ECM 6.28.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
@@ -22,11 +22,11 @@ include(ECMGenerateExportHeader)
include(ECMMarkAsTest)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMAddQch)
include(ECMMarkNonGuiExecutable)
include(ECMQtDeclareLoggingCategory)
include(ECMSourceVersionControl)
include(ECMDeprecationSettings)
include(ECMGenerateQDoc)
ecm_setup_version(
PROJECT
@@ -38,6 +38,7 @@ ecm_setup_version(
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
option(KIOCORE_ONLY "Only compile KIOCore, not KIOWidgets or anything that depends on it. This will disable support for cookies and passwordhandling (prompting and storing)." OFF)
option(KIOGUI_ONLY "Build only KIOCore + KIOGui." OFF)
# Enable state assertion by default on Jenkins and from-git builds.
# This option should eventually be dropped and always be enabled.
@@ -49,15 +50,11 @@ option(KIO_ASSERT_WORKER_STATES
"Used to control whether worker state assertions are enabled. When not enabled only warnings are generated."
${ASSERT_WORKER_STATES_DEFAULT})
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON)
add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer")
option(USE_FTPD_WSGIDAV_UNITTEST "Use ftpd Ruby Gem (for ftptest) and wsgidav Python module (for webdavtest) in unittests. Note that this is a temporary option that will be removed when distros add those packages in their repos." OFF)
find_package(KF6Archive ${KF_DEP_VERSION} REQUIRED)
find_package(KF6Config ${KF_DEP_VERSION} REQUIRED)
find_package(KF6CoreAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF6I18n ${KF_DEP_VERSION} REQUIRED)
@@ -69,32 +66,27 @@ if (NOT ANDROID)
endif()
if (NOT KIOCORE_ONLY)
find_package(KF6Bookmarks ${KF_DEP_VERSION} REQUIRED)
find_package(KF6ColorScheme ${KF_DEP_VERSION} REQUIRED)
find_package(KF6Completion ${KF_DEP_VERSION} REQUIRED)
find_package(KF6GuiAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF6IconThemes ${KF_DEP_VERSION} REQUIRED)
find_package(KF6ItemViews ${KF_DEP_VERSION} REQUIRED)
find_package(KF6JobWidgets ${KF_DEP_VERSION} REQUIRED)
find_package(KF6WidgetsAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF6WindowSystem ${KF_DEP_VERSION} REQUIRED)
find_package(KF6WindowSystem ${KF_DEP_VERSION} REQUIRED)
if (NOT KIOGUI_ONLY)
find_package(KF6Bookmarks ${KF_DEP_VERSION} REQUIRED)
find_package(KF6ColorScheme ${KF_DEP_VERSION} REQUIRED)
find_package(KF6Completion ${KF_DEP_VERSION} REQUIRED)
find_package(KF6GuiAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF6IconThemes ${KF_DEP_VERSION} REQUIRED)
find_package(KF6ItemViews ${KF_DEP_VERSION} REQUIRED)
find_package(KF6JobWidgets ${KF_DEP_VERSION} REQUIRED)
find_package(KF6WidgetsAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF6KDED ${KF_DEP_VERSION})
set_package_properties(KF6KDED PROPERTIES DESCRIPTION "Daemon providing session services"
TYPE RUNTIME
PURPOSE "KDED is used at runtime for proxy management and cookie storage"
)
find_package(KF6KDED ${KF_DEP_VERSION})
set_package_properties(KF6KDED PROPERTIES DESCRIPTION "Daemon providing session services"
TYPE RUNTIME
PURPOSE "KDED is used at runtime for proxy management and cookie storage")
find_package(SwitcherooControl)
set_package_properties(SwitcherooControl PROPERTIES
TYPE RUNTIME
PURPOSE "Hybrid GPU support"
)
endif()
if (UNIX AND NOT ANDROID)
find_package(KF6Auth ${KF_DEP_VERSION} REQUIRED)
find_package(SwitcherooControl)
set_package_properties(SwitcherooControl PROPERTIES
TYPE RUNTIME
PURPOSE "Hybrid GPU support")
endif()
endif()
# tell what is missing without doctools
@@ -103,8 +95,12 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION "Provides tools to gen
PURPOSE "Required to build help KIO worker and documentation"
)
set(REQUIRED_QT_VERSION 6.6.0)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets Concurrent Xml Network)
set(REQUIRED_QT_VERSION 6.9.0)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets Network Concurrent Xml Test)
if (Qt6Gui_VERSION VERSION_GREATER_EQUAL "6.10.0")
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
endif()
# shall we use DBus?
# enabled per default on Linux & BSD systems
@@ -122,10 +118,6 @@ if(USE_DBUS)
set(DBUS_LIB Qt6::DBus)
endif()
if (Qt6Core_VERSION VERSION_LESS 6.7.0)
find_package(Qt6Core5Compat ${REQUIRED_QT_VERSION} REQUIRED) # QTextCodec
endif()
if (NOT APPLE AND NOT WIN32 AND NOT ANDROID AND NOT HAIKU)
option(WITH_X11 "Build with X11 integration" ON)
set(HAVE_X11 ${WITH_X11})
@@ -143,18 +135,18 @@ set_package_properties(ACL PROPERTIES DESCRIPTION "LibACL"
PURPOSE "Support for manipulating access control lists")
# Used by KMountPoint
if (CMAKE_SYSTEM_NAME MATCHES "Linux" AND NOT REDOX)
find_package(LibMount)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
find_package(LibMount REQUIRED)
set(HAVE_LIB_MOUNT ${LibMount_FOUND})
endif()
ecm_set_disabled_deprecation_versions(
QT 6.8.0
KF 6.8.0
QT 6.11.0
KF 6.27.0
)
add_definitions(-DTRANSLATION_DOMAIN=\"kio6\")
#ki18n_install(po)
ki18n_install(po)
if (KF6DocTools_FOUND)
kdoctools_install(po)
@@ -164,12 +156,16 @@ if (KF6DocTools_FOUND)
add_subdirectory(docs)
endif()
if(BUILD_TESTING)
add_definitions(-DBUILD_TESTING)
endif()
include(CheckLibraryExists)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(autotests)
if (NOT KIOCORE_ONLY)
if (NOT KIOCORE_ONLY AND NOT KIOGUI_ONLY)
add_subdirectory(tests)
endif()
endif()
@@ -179,22 +175,11 @@ add_subdirectory(templates)
# create a Config.cmake and a ConfigVersion.cmake file and install them
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6KIO")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KF6KIO_QCH
FILE KF6KIOQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF6KIOQchTargets.cmake\")")
endif()
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KF6KIOConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KF6KIOConfig.cmake"
PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
@@ -6,43 +6,57 @@ include(CMakeFindDependencyMacro)
find_dependency(KF6CoreAddons "@KF_DEP_VERSION@")
find_dependency(KF6Config "@KF_DEP_VERSION@")
if (NOT @BUILD_SHARED_LIBS@)
find_dependency(KF6I18n "@KF_DEP_VERSION@")
if (NOT ANDROID)
find_dependency(KF6Crash "@KF_DEP_VERSION@")
endif()
if (@LibMount_FOUND@)
find_dependency(LibMount)
endif()
find_dependency(Qt6Concurrent "@REQUIRED_QT_VERSION@")
find_dependency(Qt6Xml "@REQUIRED_QT_VERSION@")
endif()
if (NOT @KIOCORE_ONLY@)
find_dependency(KF6Bookmarks "@KF_DEP_VERSION@")
find_dependency(KF6Completion "@KF_DEP_VERSION@")
find_dependency(KF6ItemViews "@KF_DEP_VERSION@")
find_dependency(KF6JobWidgets "@KF_DEP_VERSION@")
find_dependency(KF6Solid "@KF_DEP_VERSION@")
find_dependency(KF6WidgetsAddons "@KF_DEP_VERSION@")
find_dependency(KF6WindowSystem "@KF_DEP_VERSION@")
find_dependency(KF6Service "@KF_DEP_VERSION@")
if (NOT @BUILD_SHARED_LIBS@)
find_dependency(KF6GuiAddons "@KF_DEP_VERSION@")
find_dependency(KF6IconThemes "@KF_DEP_VERSION@")
if (NOT @KIOGUI_ONLY@)
find_dependency(KF6Bookmarks "@KF_DEP_VERSION@")
find_dependency(KF6Completion "@KF_DEP_VERSION@")
find_dependency(KF6ItemViews "@KF_DEP_VERSION@")
find_dependency(KF6JobWidgets "@KF_DEP_VERSION@")
find_dependency(KF6Solid "@KF_DEP_VERSION@")
find_dependency(KF6WidgetsAddons "@KF_DEP_VERSION@")
find_dependency(KF6Service "@KF_DEP_VERSION@")
if (@HAVE_QTDBUS@)
find_dependency(KF6DBusAddons "@KF_DEP_VERSION@")
if (NOT @BUILD_SHARED_LIBS@)
find_dependency(KF6GuiAddons "@KF_DEP_VERSION@")
find_dependency(KF6IconThemes "@KF_DEP_VERSION@")
find_dependency(KF6ColorScheme "@KF_DEP_VERSION@")
if (@HAVE_QTDBUS@)
find_dependency(KF6DBusAddons "@KF_DEP_VERSION@")
endif()
endif()
if (NOT ANDROID)
find_dependency(KF6Crash "@KF_DEP_VERSION@")
endif()
if (@LibMount_FOUND@)
find_dependency(LibMount)
else() # KIOGUI_ONLY
if (NOT @BUILD_SHARED_LIBS@)
find_dependency(KF6Solid "@KF_DEP_VERSION@")
find_dependency(KF6Service "@KF_DEP_VERSION@")
endif()
endif()
else()
else() # KIOCORE_ONLY
if (NOT @BUILD_SHARED_LIBS@)
find_dependency(KF6Service "@KF_DEP_VERSION@")
endif()
endif()
find_dependency(Qt6Concurrent "@REQUIRED_QT_VERSION@")
find_dependency(Qt6Xml "@REQUIRED_QT_VERSION@")
find_dependency(Qt6Network "@REQUIRED_QT_VERSION@")
if (@HAVE_QTDBUS@)
find_dependency(Qt6DBus "@REQUIRED_QT_VERSION@")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/KF6KIOTargets.cmake")
@PACKAGE_INCLUDE_QCHTARGETS@
@@ -11,6 +11,14 @@ add_subdirectory(http)
find_package(Qt6Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
set(ECM_TEST_NAME_PREFIX "kiocore-")
ecm_add_test(
metadatatest.cpp
LINK_LIBRARIES KF6::KIOCore Qt6::Test
)
set(kacltest_SRC)
if (ACL_FOUND)
set (kacltest_SRC kacltest.cpp)
endif()
@@ -21,36 +29,47 @@ ecm_add_tests(
kmountpointtest.cpp
upurltest.cpp
dataprotocoltest.cpp
jobtest.cpp
jobremotetest.cpp
kfileitemtest.cpp
kprotocolinfotest.cpp
globaltest.cpp
mimetypefinderjobtest.cpp
mkpathjobtest.cpp
threadtest.cpp
workercanceltest.cpp
udsentrytest.cpp
deletejobtest.cpp
deleteortrashjobtest.cpp
urlutiltest.cpp
batchrenamejobtest.cpp
ksambasharetest.cpp
krecentdocumenttest.cpp
filefiltertest.cpp
NAME_PREFIX "kiocore-"
LINK_LIBRARIES KF6::KIOCore KF6::I18n KF6::ConfigCore KF6::Service Qt6::Test Qt6::Network Qt6::Xml
)
target_link_libraries(threadtest Qt6::Concurrent)
target_link_libraries(deleteortrashjobtest KF6::KIOWidgets)
# disable some tests that fail on Windows ATM
if (NOT WIN32)
ecm_add_tests(
jobtest.cpp
kfileitemtest.cpp
mkpathjobtest.cpp
deletejobtest.cpp
deleteortrashjobtest.cpp
krecentdocumenttest.cpp
LINK_LIBRARIES KF6::KIOCore KF6::I18n KF6::ConfigCore KF6::Service Qt6::Test Qt6::Network Qt6::Xml
)
target_link_libraries(deleteortrashjobtest KF6::KIOWidgets)
if(TARGET Qt6::DBus)
target_link_libraries(deletejobtest Qt6::DBus)
endif()
if(ACL_FOUND)
target_link_libraries(jobtest ${ACL_LIBS}) # copyFilePreservesAcl()
endif()
endif()
ecm_add_test(
connectionbackendtest.cpp
../src/core/connectionbackend.cpp
../src/core/kiocoreconnectiondebug.cpp
TEST_NAME connectionbackendtest
NAME_PREFIX "kiocore-"
LINK_LIBRARIES KF6::KIOCore Qt6::Test Qt6::Network KF6::I18n
)
@@ -65,7 +84,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND USE_FTPD_WSGIDAV_UNITTEST)
add_definitions(-DRubyExe_EXECUTABLE="${RubyExe_EXECUTABLE}")
ecm_add_tests(
ftptest.cpp
NAME_PREFIX "kiocore-"
LINK_LIBRARIES KF6::KIOCore KF6::I18n Qt6::Test Qt6::Network
)
@@ -76,126 +94,150 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND USE_FTPD_WSGIDAV_UNITTEST)
URL "https://pypi.org/project/WsgiDAV/")
ecm_add_tests(
webdavtest.cpp
NAME_PREFIX "kiocore-"
LINK_LIBRARIES KF6::KIOCore Qt6::Test Qt6::Network
)
endif()
if(UNIX)
ecm_add_tests(
privilegejobtest.cpp
NAME_PREFIX "kiocore-"
LINK_LIBRARIES KF6::KIOCore KF6::I18n Qt6::Test Qt6::Network
)
endif()
if (TARGET KF6::KIOGui)
set(ECM_TEST_NAME_PREFIX "kiogui-")
ecm_add_tests(
favicontest.cpp
openurljobtest.cpp
kemailclientlauncherjobtest.cpp
NAME_PREFIX "kiogui-"
filepreviewjobtest.cpp
LINK_LIBRARIES KF6::KIOCore KF6::KIOGui KF6::WindowSystem Qt6::Test
)
foreach(_kprocessrunnerTest applicationlauncherjob commandlauncherjob kterminallauncherjob)
foreach(_systemd "" "SCOPE" "SERVICE")
set(_scope 0)
set(_service 0)
set(_forking 0)
if(_systemd STREQUAL "SERVICE")
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_servicetest)
set(_service 1)
elseif(_systemd STREQUAL "SCOPE")
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_scopetest)
set(_scope 1)
else()
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_forkingtest)
set(_forking 1)
endif()
add_executable(${KPR_TEST_TARGET} ${_kprocessrunnerTest}test.cpp)
target_link_libraries(${KPR_TEST_TARGET} KF6::WindowSystem KF6::KIOCore KF6::KIOGui Qt6::Test)
ecm_mark_as_test(${KPR_TEST_TARGET})
add_test(NAME ${KPR_TEST_TARGET} COMMAND ${KPR_TEST_TARGET})
set_tests_properties(${KPR_TEST_TARGET} PROPERTIES ENVIRONMENT
"_KDE_APPLICATIONS_AS_SERVICE=${_service};
_KDE_APPLICATIONS_AS_SCOPE=${_scope};
_KDE_APPLICATIONS_AS_FORKING=${_forking};"
)
endforeach()
endforeach()
target_link_libraries(favicontest Qt6::Concurrent)
# disable some tests that fail on Windows ATM
if (NOT WIN32)
foreach(_kprocessrunnerTest applicationlauncherjob commandlauncherjob kterminallauncherjob)
foreach(_systemd "" "SCOPE" "SERVICE")
set(_scope 0)
set(_service 0)
set(_forking 0)
if(_systemd STREQUAL "SERVICE")
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_servicetest)
set(_service 1)
elseif(_systemd STREQUAL "SCOPE")
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_scopetest)
set(_scope 1)
else()
set(KPR_TEST_TARGET ${_kprocessrunnerTest}_forkingtest)
set(_forking 1)
endif()
add_executable(${KPR_TEST_TARGET} ${_kprocessrunnerTest}test.cpp)
target_link_libraries(${KPR_TEST_TARGET} KF6::WindowSystem KF6::KIOCore KF6::KIOGui Qt6::Test)
ecm_mark_as_test(${KPR_TEST_TARGET})
set(KPR_TEST_NAME "${ECM_TEST_NAME_PREFIX}${KPR_TEST_TARGET}")
add_test(NAME ${KPR_TEST_NAME} COMMAND ${KPR_TEST_TARGET})
set_tests_properties(${KPR_TEST_NAME} PROPERTIES ENVIRONMENT
"_KDE_APPLICATIONS_AS_SERVICE=${_service};
_KDE_APPLICATIONS_AS_SCOPE=${_scope};
_KDE_APPLICATIONS_AS_FORKING=${_forking};"
)
endforeach()
endforeach()
endif()
endif()
if (NOT ANDROID)
if (TARGET KF6::KIOWidgets)
set(ECM_TEST_NAME_PREFIX "kiowidgets-")
ecm_add_tests(
clipboardupdatertest.cpp
dropjobtest.cpp
kdynamicjobtrackernowidgetstest.cpp
desktopexecparsertest.cpp
kdirlistertest.cpp
kdirmodeltest.cpp
kfileitemactionstest.cpp
fileundomanagertest.cpp
kurlcompletiontest.cpp
${jobguitest_SRC}
pastetest.cpp
kurifiltersearchprovideractionstest.cpp
NAME_PREFIX "kiowidgets-"
ENVIRONMENT QT_QPA_PLATFORM=offscreen
LINK_LIBRARIES KF6::KIOCore KF6::KIOWidgets Qt6::Test ${DBUS_LIB}
)
set(_libexec_dir_kf "${KDKE_INSTALL_FULL_LIBEXECDIR_KF}")
target_compile_definitions(desktopexecparsertest PRIVATE "KDE_INSTALL_FULL_LIBEXECDIR_KF=${_libexec_dir_kf}")
# disable some tests that fail on Windows ATM
if (NOT WIN32)
ecm_add_tests(
desktopexecparsertest.cpp # desktopexecparsertest needs a classic unix shell & terminal
dropjobtest.cpp
fileundomanagertest.cpp
kdirmodeltest.cpp
kdynamicjobtrackernowidgetstest.cpp
kurlcompletiontest.cpp
ENVIRONMENT QT_QPA_PLATFORM=offscreen
LINK_LIBRARIES KF6::KIOCore KF6::KIOWidgets Qt6::Test ${DBUS_LIB}
)
set(_libexec_dir_kf "${KDKE_INSTALL_FULL_LIBEXECDIR_KF}")
target_compile_definitions(desktopexecparsertest PRIVATE "KDE_INSTALL_FULL_LIBEXECDIR_KF=${_libexec_dir_kf}")
endif()
set(kurifiltertest_libs KF6::KIOCore KF6::KIOWidgets Qt6::Test ${DBUS_LIB})
# disable some tests that fail on Windows ATM
if (NOT WIN32)
set(kurifiltertest_libs KF6::KIOCore KF6::KIOWidgets Qt6::Test ${DBUS_LIB})
set(URIFILTER_COLON_TEST kiowidgets-kurifiltertest-colon-separator)
ecm_add_test(kurifiltertest.cpp
LINK_LIBRARIES ${kurifiltertest_libs}
TEST_NAME ${URIFILTER_COLON_TEST}
)
target_compile_definitions(${URIFILTER_COLON_TEST} PUBLIC "-DWEBSHORTCUT_SEPARATOR=':'")
set(URIFILTER_COLON_TEST kurifiltertest-colon-separator)
ecm_add_test(kurifiltertest.cpp
LINK_LIBRARIES ${kurifiltertest_libs}
ENVIRONMENT QT_QPA_PLATFORM=offscreen
TEST_NAME ${URIFILTER_COLON_TEST}
)
target_compile_definitions(${URIFILTER_COLON_TEST} PUBLIC "-DWEBSHORTCUT_SEPARATOR=':'")
set(URIFILTER_SPACE_TEST kiowidgets-kurifiltertest-space-separator)
ecm_add_test(kurifiltertest.cpp
LINK_LIBRARIES ${kurifiltertest_libs}
TEST_NAME ${URIFILTER_SPACE_TEST}
)
target_compile_definitions(${URIFILTER_SPACE_TEST} PUBLIC "-DWEBSHORTCUT_SEPARATOR=' '")
set(URIFILTER_SPACE_TEST kurifiltertest-space-separator)
ecm_add_test(kurifiltertest.cpp
LINK_LIBRARIES ${kurifiltertest_libs}
ENVIRONMENT QT_QPA_PLATFORM=offscreen
TEST_NAME ${URIFILTER_SPACE_TEST}
)
target_compile_definitions(${URIFILTER_SPACE_TEST} PUBLIC "-DWEBSHORTCUT_SEPARATOR=' '")
# Same as kurlcompletiontest, but with immediate return, and results posted by thread later
ecm_add_test(
kurlcompletiontest.cpp
TEST_NAME kurlcompletiontest-nowait
NAME_PREFIX "kiowidgets-"
LINK_LIBRARIES KF6::KIOCore KF6::KIOWidgets Qt6::Test
)
set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT")
set_tests_properties(kiowidgets-kurifiltertest-space-separator PROPERTIES RUN_SERIAL TRUE)
# Same as kurlcompletiontest, but with immediate return, and results posted by thread later
ecm_add_test(
kurlcompletiontest.cpp
TEST_NAME kurlcompletiontest-nowait
ENVIRONMENT QT_QPA_PLATFORM=offscreen
LINK_LIBRARIES KF6::KIOCore KF6::KIOWidgets Qt6::Test
)
set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT")
endif()
endif()
if (TARGET KF6::KIOFileWidgets)
set(ECM_TEST_NAME_PREFIX "kiofilewidgets-")
include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets)
ecm_add_tests(
kurlnavigatortest.cpp
kurlcomboboxtest.cpp
kdiroperatortest.cpp
kfilewidgettest.cpp
kfilecustomdialogtest.cpp
knewfilemenutest.cpp
kfilecopytomenutest.cpp
kfileplacesmodeltest.cpp
kfileplacesviewtest.cpp
kurlrequestertest.cpp
kfilefiltercombotest.cpp
NAME_PREFIX "kiofilewidgets-"
ENVIRONMENT QT_QPA_PLATFORM=offscreen
LINK_LIBRARIES KF6::KIOFileWidgets KF6::KIOWidgets KF6::Bookmarks Qt6::Test KF6::I18n KF6::WindowSystem
)
# disable some tests that fail on Windows ATM
if (NOT WIN32)
ecm_add_tests(
kfilecopytomenutest.cpp
kfilewidgettest.cpp
knewfilemenutest.cpp
kfileplacesmodeltest.cpp
kfileplacesviewtest.cpp
kdirlistertest.cpp
ENVIRONMENT QT_QPA_PLATFORM=offscreen
LINK_LIBRARIES KF6::KIOFileWidgets KF6::KIOWidgets KF6::Bookmarks Qt6::Test KF6::I18n KF6::WindowSystem
)
set_tests_properties(kiofilewidgets-kfileplacesmodeltest PROPERTIES RUN_SERIAL TRUE)
set_tests_properties(kiofilewidgets-kfileplacesviewtest PROPERTIES RUN_SERIAL TRUE)
endif()
# TODO: fix symbol exports for windows -> 'KSambaShare::KSambaShare': inconsistent dll linkage
if (NOT WIN32)
ecm_add_test(
@@ -208,9 +250,6 @@ ecm_add_test(
)
endif()
set_tests_properties(kiofilewidgets-kfileplacesmodeltest PROPERTIES RUN_SERIAL TRUE)
set_tests_properties(kiofilewidgets-kfileplacesviewtest PROPERTIES RUN_SERIAL TRUE)
set_tests_properties(kiowidgets-kurifiltertest-space-separator PROPERTIES RUN_SERIAL TRUE)
endif()
endif()
@@ -110,7 +110,13 @@ private Q_SLOTS:
job->setUiDelegate(nullptr);
QSignalSpy spy(job, &KIO::BatchRenameJob::fileRenamed);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(spy.count(), oldFilenames.count());
QStringList result;
for (const auto &signal : spy) {
// collects the resulting filenames
result.append(signal[1].toUrl().fileName());
}
QCOMPARE(result, newFilenames);
QCOMPARE(job->processedAmount(KJob::Items), oldFilenames.count());
QCOMPARE(job->totalAmount(KJob::Items), oldFilenames.count());
QVERIFY(!checkFileExistence(oldFilenames));
@@ -129,8 +129,8 @@ void ClipboardUpdaterTest::testPasteAfterDeleteFile()
createTestDirectory(pasteDir, NoSymlink);
KIO::Job *job = KIO::paste(clipboard->mimeData(), QUrl::fromLocalFile(pasteDir), KIO::DefaultFlags);
QVERIFY(job);
QVERIFY(!job->exec());
QCOMPARE(job->error(), int(KIO::ERR_NO_CONTENT));
QVERIFY(job->exec());
QCOMPARE(job->error(), 0);
}
void ClipboardUpdaterTest::testPasteAfterDeleteFiles()
@@ -152,8 +152,8 @@ void ClipboardUpdaterTest::testPasteAfterDeleteFiles()
createTestDirectory(pasteDir, NoSymlink);
KIO::Job *job = KIO::paste(clipboard->mimeData(), QUrl::fromLocalFile(pasteDir), KIO::DefaultFlags);
QVERIFY(job);
QVERIFY(!job->exec());
QCOMPARE(job->error(), int(KIO::ERR_NO_CONTENT));
QVERIFY(job->exec());
QCOMPARE(job->error(), 0);
}
#include "moc_clipboardupdatertest.cpp"
@@ -314,6 +314,9 @@ void CommandLauncherJobTest::runExecutableInLocalPath()
QVERIFY(job->exec());
QTRY_VERIFY2(QFileInfo::exists(destPath), qPrintable(destPath));
// Wait for KProcessRunner to be deleted
QTRY_COMPARE(KProcessRunner::instanceCount(), 0);
}
#include "moc_commandlauncherjobtest.cpp"
@@ -14,7 +14,9 @@ class ConnectionBackendTest : public QObject
private Q_SLOTS:
void testJumboPackets()
{
#if defined(Q_OS_FREEBSD)
QSKIP("TODO testJumboPackets doesn't pass FIXME");
#endif
KIO::ConnectionBackend server;
KIO::ConnectionBackend clientConnection;
@@ -30,7 +32,7 @@ private Q_SLOTS:
spy = std::make_unique<QSignalSpy>(&clientConnection, &KIO::ConnectionBackend::commandReceived);
constexpr auto cmd = 64; // completely arbitrary value we don't actually care about the command in this test
const auto data = randomByteArray(clientConnection.StandardBufferSize * 4L);
serverConnection->sendCommand(cmd, data);
QVERIFY(serverConnection->sendCommand(cmd, data));
spy->wait();
QVERIFY(!spy->isEmpty());
@@ -99,13 +99,13 @@ public:
private:
// -- testcase related members
QString mime_type_expected; // expected MIME type
/** contains all attributes and values the testcase has to set */
/*! contains all attributes and values the testcase has to set */
KIO::MetaData attributes_expected;
/** contains the content as it is expected to be returned */
/*! contains the content as it is expected to be returned */
QByteArray content_expected;
public:
/**
/*!
* sets the MIME type that this testcase is expected to return
*/
void setExpectedMimeType(const QString &mime_type)
@@ -113,7 +113,7 @@ public:
mime_type_expected = mime_type;
}
/**
/*!
* sets all attribute-value pairs the testcase must deliver.
*/
void setExpectedAttributes(const KIO::MetaData &attres)
@@ -121,7 +121,7 @@ public:
attributes_expected = attres;
}
/**
/*!
* sets content as expected to be delivered by the testcase.
*/
void setExpectedContent(const QByteArray &content)
@@ -7,13 +7,26 @@
#include "deletejobtest.h"
#include <QDir>
#include <QDirIterator>
#include <QElapsedTimer>
#include <QFile>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QTest>
#include <QTimer>
#include <QUrl>
#include <kio/deletejob.h>
#include <kio/simplejob.h>
#include <unistd.h>
#if defined(WITH_QTDBUS)
#include <QDBusConnection>
#include <KDirNotify>
#endif
QTEST_MAIN(DeleteJobTest)
@@ -48,7 +61,7 @@ void DeleteJobTest::deleteFileTestCase()
QVERIFY(spy.isValid());
QVERIFY(spy.wait(100000));
QCOMPARE(job->error(), KJOB_NO_ERROR);
QVERIFY(!tempFile.exists());
QVERIFY(!QFile::exists(path));
}
}
@@ -81,6 +94,69 @@ void DeleteJobTest::deleteDirectoryTestCase()
}
}
void DeleteJobTest::deletePartialFailureNotifiesRemovals()
{
#if !defined(WITH_QTDBUS)
QSKIP("Removal notifications are delivered over D-Bus, which is unavailable in this build");
#else
if (geteuid() == 0) {
QSKIP("Root ignores the permission bits this test relies on");
}
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());
const QString base = tempDir.path();
// A file that can be deleted.
const QString deletable = base + QLatin1String("/possible.txt");
QFile deletableFile(deletable);
QVERIFY(deletableFile.open(QIODevice::WriteOnly));
deletableFile.close();
// An empty directory whose parent denies write access, so removing it fails
// with a permission error. The failure happens in the directory phase, which
// runs after all files have already been deleted, so possible.txt is gone by
// the time the job errors out regardless of the order entries were stated in.
const QString locked = base + QLatin1String("/locked");
const QString undeletableDir = locked + QLatin1String("/sub");
QVERIFY(QDir().mkpath(undeletableDir));
QVERIFY(QFile::setPermissions(locked, QFileDevice::ReadOwner | QFileDevice::ExeOwner));
org::kde::KDirNotify notifyListener(QString(), QString(), QDBusConnection::sessionBus(), this);
QSignalSpy removedSpy(&notifyListener, &org::kde::KDirNotify::FilesRemoved);
QVERIFY(removedSpy.isValid());
const QList<QUrl> urls{QUrl::fromLocalFile(deletable), QUrl::fromLocalFile(undeletableDir)};
KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
QSignalSpy resultSpy(job, &KJob::result);
QVERIFY(resultSpy.wait(100000));
// The job fails overall, but the file was really removed from storage while
// the protected directory was left in place.
QVERIFY(job->error() != KJOB_NO_ERROR);
QVERIFY(!QFile::exists(deletable));
QVERIFY(QDir(undeletableDir).exists());
// The removal of the file is announced, so dir listers stop showing it.
QVERIFY(removedSpy.count() > 0 || removedSpy.wait(5000));
bool deletableReported = false;
for (const auto &emission : std::as_const(removedSpy)) {
const QStringList removed = emission.at(0).toStringList();
for (const QString &url : removed) {
if (QUrl(url) == QUrl::fromLocalFile(deletable)) {
deletableReported = true;
}
}
}
QVERIFY(deletableReported);
// Let QTemporaryDir clean up.
QFile::setPermissions(locked, QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
#endif
}
void DeleteJobTest::createEmptyTestFiles(const QStringList &fileNames, const QString &path) const
{
QStringListIterator iterator(fileNames);
@@ -93,4 +169,79 @@ void DeleteJobTest::createEmptyTestFiles(const QStringList &fileNames, const QSt
QCOMPARE(QDir(path).entryList(QDir::Files).count(), fileNames.size());
}
static int countFiles(const QString &dir)
{
int n = 0;
QDirIterator it(dir, QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
++n;
}
return n;
}
void DeleteJobTest::killedRecursiveDeletionStopsEarly()
{
// A cancelled recursive deletion must stop promptly instead of emptying the whole tree.
// kio_file's deleteRecursive() now polls wasKilled(). Without that poll the worker deletes
// everything before noticing the kill. rmdir() + the "recurse" metadata sends the deletion
// straight to the worker (KIO::del would enumerate the tree first, so a cancel there would not
// exercise deleteRecursive()). Build a tree large enough that the deletion is still running
// when we cancel it.
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());
const QString root = tempDir.path() + QStringLiteral("/tree");
QVERIFY(QDir().mkpath(root));
int created = 0;
for (int d = 0; d < 50; ++d) {
const QString sub = root + QStringLiteral("/sub%1").arg(d);
QVERIFY(QDir().mkpath(sub));
for (int f = 0; f < 300; ++f) {
QFile file(sub + QStringLiteral("/f%1").arg(f));
QVERIFY(file.open(QIODevice::WriteOnly));
++created;
}
}
KIO::SimpleJob *job = KIO::rmdir(QUrl::fromLocalFile(root));
job->setUiDelegate(nullptr);
job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true"));
// Cancel once the worker has actually started removing files (so the kill lands inside
// deleteRecursive(), not before it begins).
QTimer pollTimer;
bool killed = false;
connect(&pollTimer, &QTimer::timeout, job, [&] {
if (!killed && countFiles(root) < created) {
killed = true;
pollTimer.stop();
job->kill(KJob::EmitResult);
}
});
pollTimer.start(2);
QSignalSpy spy(job, &KJob::result);
// kill(EmitResult) makes the job report almost immediately. A timeout here means the deletion
// never started (so it was never cancelled) or the job did not finish at all.
QVERIFY(spy.wait(10000));
QVERIFY(killed); // the deletion really started, otherwise the test proves nothing
// kill(EmitResult) makes the job report at once, but the in-process worker keeps running its
// deleteRecursive() until it returns (it is reaped asynchronously). Wait for the file count to
// settle so we observe the final state: with the fix the worker bailed out and files remain.
// Without it the worker keeps deleting in the background until the whole tree is gone.
int remaining = countFiles(root);
int previous = -1;
QElapsedTimer settle;
settle.start();
while (remaining != previous && settle.elapsed() < 30000) {
previous = remaining;
QTest::qWait(200);
remaining = countFiles(root);
}
QVERIFY2(remaining > 0, qPrintable(QStringLiteral("expected files to remain after cancel, %1 of %2 left").arg(remaining).arg(created)));
}
#include "moc_deletejobtest.cpp"
@@ -17,6 +17,8 @@ private Q_SLOTS:
void deleteFileTestCase();
void deleteDirectoryTestCase_data() const;
void deleteDirectoryTestCase();
void deletePartialFailureNotifiesRemovals();
void killedRecursiveDeletionStopsEarly();
private:
void createEmptyTestFiles(const QStringList &fileNames, const QString &path) const;
@@ -89,6 +89,11 @@ private Q_SLOTS:
m_trashDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/Trash");
QDir(m_trashDir).removeRecursively();
QDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)).mkpath(".");
m_tempDir = QTemporaryDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/dropjobtest"));
m_nonWritableTempDir = QTemporaryDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/dropjobtest_ro"));
QVERIFY(m_tempDir.isValid());
QVERIFY(m_nonWritableTempDir.isValid());
QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner | QFile::ReadUser | QFile::ExeOwner | QFile::ExeUser));
@@ -199,7 +204,7 @@ private Q_SLOTS:
const QUrl destUrl = QUrl::fromLocalFile(dest);
m_mimeData.setUrls(QList<QUrl>{QUrl::fromLocalFile(srcFile)});
QDropEvent dropEvent(QPoint(10, 10), dropAction, &m_mimeData, Qt::LeftButton, modifiers);
KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo | KIO::NoPrivilegeExecution);
KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
JobSpy jobSpy(job);
QSignalSpy copyJobSpy(job, &KIO::DropJob::copyJobStarted);
QSignalSpy itemCreatedSpy(job, &KIO::DropJob::itemCreated);
@@ -368,6 +373,36 @@ private Q_SLOTS:
QCOMPARE(job->error(), KIO::ERR_DROP_ON_ITSELF);
}
// Test this only on Linux due to using xdg filepath
#ifdef Q_OS_LINUX
void shouldDropFromXdgTrashToTrash() // #497390
{
// Given a file in the trash
const QString xdgtrash = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/Trash/files/srcfile");
// We utilize the file from previous test, but create the file if it doesn't exist
if (!QFile::exists(xdgtrash)) {
QVERIFY(QFileInfo(m_srcFile).isWritable());
KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(m_srcFile), QUrl(QStringLiteral("trash:/")));
QSignalSpy copyingDoneSpy(copyJob, &KIO::CopyJob::copyingDone);
QVERIFY(copyJob->exec());
const QUrl trashUrl = copyingDoneSpy.at(0).at(2).value<QUrl>();
QVERIFY(trashUrl.isValid());
QVERIFY(!QFile::exists(m_srcFile));
}
// When dropping the trashed file in the trash
m_mimeData.setUrls(QList<QUrl>{QUrl::fromLocalFile(xdgtrash)});
QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
KIO::DropJob *job = KIO::drop(&dropEvent, QUrl(QStringLiteral("trash:/")), KIO::HideProgressInfo);
QSignalSpy copyJobSpy(job, &KIO::DropJob::copyJobStarted);
QSignalSpy spy(job, &KIO::DropJob::itemCreated);
// Then an error should be reported and no files action should occur
QVERIFY(!job->exec());
QCOMPARE(job->error(), KIO::ERR_DROP_ON_ITSELF);
}
#endif
void shouldDropToDirectoryWithPopup_data()
{
QTest::addColumn<QString>("dest"); // empty for a temp dir
@@ -405,7 +440,7 @@ private Q_SLOTS:
// When dropping the source file onto the directory
QUrl destUrl = QUrl::fromLocalFile(dest);
QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction | Qt::MoveAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
JobSpy jobSpy(job);
qRegisterMetaType<KFileItemListProperties>();
@@ -417,14 +452,15 @@ private Q_SLOTS:
QVERIFY(spyShow.wait());
QTRY_VERIFY(findPopup());
QMenu *popup = findPopup();
QCOMPARE(int(popupDropActions(popup)), int(offeredActions));
// And when selecting action number <triggerActionNumber>
QAction *action = popup->actions().at(triggerActionNumber);
QVERIFY(action);
QCOMPARE(int(action->data().value<Qt::DropAction>()), int(expectedDropAction));
const QRect actionGeom = popup->actionGeometry(action);
QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center());
action->trigger();
popup->hide();
// Then the job should finish, and the chosen action should happen.
QVERIFY(jobSpy.waitForResult());
@@ -464,12 +500,22 @@ private Q_SLOTS:
const QList<QAction *> actions = popup->actions();
QVERIFY(actions.contains(&appAction1));
QVERIFY(actions.contains(&appAction2));
QVERIFY(actions.at(actions.indexOf(&appAction1) - 1)->isSeparator());
QVERIFY(actions.at(actions.indexOf(&appAction2) + 1)->isSeparator());
// there is at least one separator after action2
int sepIndex = actions.indexOf(&appAction2) + 1;
while (sepIndex < actions.length()) {
if (actions.at(sepIndex)->isSeparator()) {
break;
}
++sepIndex;
}
QVERIFY(sepIndex < actions.length());
// And when selecting action appAction1
const QRect actionGeom = popup->actionGeometry(&appAction1);
QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center());
appAction1.trigger();
popup->hide();
// Then the menu should hide and the job terminate (without doing any copying)
QVERIFY(jobSpy.waitForResult());
@@ -478,6 +524,24 @@ private Q_SLOTS:
QVERIFY(!QFile::exists(destFile));
}
void testInvalidUrl()
{
QTemporaryDir tempDestDir;
QVERIFY(tempDestDir.isValid());
const QUrl destUrl = QUrl::fromLocalFile(tempDestDir.path());
QMimeData mimeData;
mimeData.setUrls({QUrl("not a url")});
QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &mimeData, Qt::LeftButton, Qt::NoModifier);
KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
JobSpy jobSpy(job);
QVERIFY(jobSpy.waitForResult());
QCOMPARE(jobSpy.error(), KIO::ERR_NO_CONTENT);
}
private:
static QMenu *findPopup()
{
@@ -8,6 +8,7 @@
#include <KIO/FavIconRequestJob>
#include <KIO/TransferJob>
#include <QDir>
#include <QElapsedTimer>
#include <QLoggingCategory>
#include <QStandardPaths>
#include <QTest>
@@ -224,6 +225,14 @@ void FavIconTest::tooBigFaviconShouldAbort()
job->setIconUrl(QUrl("http://download.kde.org/Attic/4.13.2/src/kcalc-4.13.2.tar.xz"));
QVERIFY(willDownload(job));
QVERIFY(!job->exec());
#if defined(Q_OS_WINDOWS)
if (job->error() == int(KIO::ERR_CANNOT_CONNECT)) {
// some hosts may not be reachable by the VM
QVERIFY(job->errorString().startsWith(QStringLiteral("Could not connect to host")));
return;
}
#endif
QCOMPARE(job->error(), int(KIO::ERR_WORKER_DEFINED));
QCOMPARE(job->errorString(), QStringLiteral("Icon file too big, download aborted"));
}
@@ -271,6 +280,10 @@ static QString getAltIconUrl()
void FavIconTest::concurrentRequestsShouldWork()
{
#ifdef Q_OS_WIN
QSKIP("Skipping test on Windows, QSaveFile fails to handle concurrent accesses");
#endif
const int numThreads = 3;
QThreadPool tp;
tp.setMaxThreadCount(numThreads);
@@ -0,0 +1,76 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2026 Meven Car <meven@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "filepreviewjobtest.h"
#include "../src/gui/filepreviewjob.h"
#include <KFileItem>
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QTest>
#include <QTimer>
QTEST_GUILESS_MAIN(FilePreviewJobTest)
using namespace KIO;
void FilePreviewJobTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
}
void FilePreviewJobTest::testTimeoutTimerStoppedOnFinish()
{
// Regression test: FilePreviewJob's timeout timer used to keep running after
// the job had finished. If the job's deferred deletion was stalled behind a
// nested event loop (e.g. an open context menu via QMenu::exec), the timer
// would later fire on the already-finished job and crash in KJob::kill().
QTemporaryDir thumbRoot;
QVERIFY(thumbRoot.isValid());
QTemporaryFile file;
QVERIFY(file.open());
file.write("test");
file.close();
const KFileItem item(QUrl::fromLocalFile(file.fileName()));
PreviewOptions options;
options.size = QSize(128, 128);
PreviewSetupData setupData;
setupData.thumbRoot = thumbRoot.path();
// Leave pluginByMimeTable empty on purpose: with no thumbnailer plugin the
// job finishes quickly, which is all we need to exercise the start()->finish
// lifecycle of the timeout timer (no thumbnail worker required).
auto *job = new FilePreviewJob(item, FilePreviewJob::UnknownDeviceId, options, setupData);
job->setAutoDelete(false);
QSignalSpy resultSpy(job, &KJob::result);
job->start();
QVERIFY(resultSpy.wait());
QCOMPARE(resultSpy.count(), 1);
// The job finished well within the 5s timeout interval, so the only reason
// the single-shot timer can be inactive is that finishing stopped it.
QVERIFY(job->m_timeoutTimer);
QVERIFY(!job->m_timeoutTimer->isActive());
// It must also not fire afterwards: spinning the event loop must not produce
// a second result (the old code re-entered emitResult() via slotTimeout()).
QTest::qWait(50);
QCOMPARE(resultSpy.count(), 1);
delete job;
}
@@ -0,0 +1,22 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2026 Meven Car <meven@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef FILEPREVIEWJOBTEST_H
#define FILEPREVIEWJOBTEST_H
#include <QObject>
class FilePreviewJobTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testTimeoutTimerStoppedOnFinish();
};
#endif
@@ -38,7 +38,6 @@
#include <KUrlMimeData>
#include <cerrno>
#include <time.h>
#ifdef Q_OS_WIN
#include <sys/utime.h>
#else
@@ -117,7 +116,7 @@ static void createTestSymlink(const QString &path)
if (!ok) {
qFatal("couldn't create symlink: %s", strerror(errno));
}
QVERIFY(QT_LSTAT(QFile::encodeName(path).constData(), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(path).constData(), &buf), 0);
QVERIFY(Utils::isLinkMask(buf.st_mode));
} else {
QVERIFY(Utils::isLinkMask(buf.st_mode));
@@ -263,6 +262,14 @@ void FileUndoManagerTest::doUndo()
eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish
}
void FileUndoManagerTest::doRedo()
{
QEventLoop eventLoop;
connect(FileUndoManager::self(), &FileUndoManager::undoJobFinished, &eventLoop, &QEventLoop::quit);
FileUndoManager::self()->redo();
eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish
}
void FileUndoManagerTest::testCopyFiles()
{
// Initially inspired from JobTest::copyFileToSamePartition()
@@ -275,8 +282,12 @@ void FileUndoManagerTest::testCopyFiles()
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
QVERIFY(spyUndoAvailable.isValid());
QSignalSpy spyTextChanged(FileUndoManager::self(), &FileUndoManager::undoTextChanged);
QVERIFY(spyTextChanged.isValid());
QSignalSpy spyUndoTextChanged(FileUndoManager::self(), &FileUndoManager::undoTextChanged);
QVERIFY(spyUndoTextChanged.isValid());
QSignalSpy spyRedoAvailable(FileUndoManager::self(), &FileUndoManager::redoAvailable);
QVERIFY(spyRedoAvailable.isValid());
QSignalSpy spyRedoTextChanged(FileUndoManager::self(), &FileUndoManager::redoTextChanged);
QVERIFY(spyRedoTextChanged.isValid());
QVERIFY2(job->exec(), qPrintable(job->errorString()));
@@ -289,25 +300,57 @@ void FileUndoManagerTest::testCopyFiles()
// might have to wait for dbus signal here... but this is currently disabled.
// QTest::qWait( 20 );
QVERIFY(FileUndoManager::self()->isUndoAvailable());
QVERIFY(!FileUndoManager::self()->isRedoAvailable());
QCOMPARE(spyUndoAvailable.count(), 1);
QCOMPARE(spyTextChanged.count(), 1);
QCOMPARE(spyRedoAvailable.count(), 0);
QCOMPARE(spyUndoTextChanged.count(), 1);
QCOMPARE(spyRedoTextChanged.count(), 0);
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm
FileUndoManager::self()->undo();
auto *lastMock = m_uiInterface->askUserMockInterface();
QCOMPARE(lastMock->m_askUserDeleteCalled, 1);
QVERIFY(QFile::exists(destFile())); // nothing happened yet
// OK, now do it
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doUndo();
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well
QCOMPARE(spyTextChanged.count(), 2);
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
QVERIFY(FileUndoManager::self()->isRedoAvailable());
QCOMPARE(spyUndoAvailable.count(), 2);
QCOMPARE(spyRedoAvailable.count(), 1);
QCOMPARE(spyUndoTextChanged.count(), 2);
QCOMPARE(spyRedoTextChanged.count(), 1);
// Check that undo worked
QVERIFY(!QFile::exists(destFile()));
#ifndef Q_OS_WIN
QVERIFY(!QFile::exists(destLink()));
QVERIFY(!QFileInfo(destLink()).isSymLink());
#endif
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doRedo();
QVERIFY(FileUndoManager::self()->isUndoAvailable());
QVERIFY(!FileUndoManager::self()->isRedoAvailable());
QCOMPARE(spyUndoAvailable.count(), 3);
QCOMPARE(spyRedoAvailable.count(), 2);
QCOMPARE(spyUndoTextChanged.count(), 3);
QCOMPARE(spyRedoTextChanged.count(), 2);
QVERIFY(QFile::exists(destFile()));
#ifndef Q_OS_WIN
QVERIFY(QFileInfo(destLink()).isSymLink());
#endif
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doUndo();
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
QVERIFY(FileUndoManager::self()->isRedoAvailable());
QCOMPARE(spyUndoAvailable.count(), 4);
QCOMPARE(spyRedoAvailable.count(), 3);
QCOMPARE(spyUndoTextChanged.count(), 4);
QCOMPARE(spyRedoTextChanged.count(), 3);
// Check that undo worked
QVERIFY(!QFile::exists(destFile()));
@@ -344,6 +387,25 @@ void FileUndoManagerTest::testMoveFiles()
QVERIFY(QFileInfo(srcLink()).isSymLink());
QVERIFY(!QFileInfo(destLink()).isSymLink());
#endif
doRedo();
QVERIFY(!QFile::exists(srcFile())); // the source moved
QVERIFY(QFile::exists(destFile()));
#ifndef Q_OS_WIN
QVERIFY(!QFileInfo(srcLink()).isSymLink());
// Don't use QFile::exists, it's a broken symlink...
QVERIFY(QFileInfo(destLink()).isSymLink());
#endif
doUndo();
QVERIFY(QFile::exists(srcFile())); // the source is back
QVERIFY(!QFile::exists(destFile()));
#ifndef Q_OS_WIN
QVERIFY(QFileInfo(srcLink()).isSymLink());
QVERIFY(!QFileInfo(destLink()).isSymLink());
#endif
}
void FileUndoManagerTest::testCopyDirectory()
@@ -365,6 +427,16 @@ void FileUndoManagerTest::testCopyDirectory()
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
doRedo();
checkTestDirectory(srcSubDir()); // src untouched
checkTestDirectory(destSubDir());
doUndo();
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
}
void FileUndoManagerTest::testCopyEmptyDirectory()
@@ -385,6 +457,16 @@ void FileUndoManagerTest::testCopyEmptyDirectory()
QVERIFY(QFileInfo(src).isDir()); // untouched
QVERIFY(!QFile::exists(destEmptyDir));
doRedo();
QVERIFY(QFileInfo(src).isDir()); // untouched
QVERIFY(QFileInfo(destEmptyDir).isDir());
doUndo();
QVERIFY(QFileInfo(src).isDir()); // untouched
QVERIFY(!QFile::exists(destEmptyDir));
}
void FileUndoManagerTest::testMoveDirectory()
@@ -406,6 +488,16 @@ void FileUndoManagerTest::testMoveDirectory()
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
doRedo();
QVERIFY(!QFile::exists(srcSubDir()));
checkTestDirectory(destSubDir());
doUndo();
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
}
void FileUndoManagerTest::testRenameFile()
@@ -416,6 +508,8 @@ void FileUndoManagerTest::testRenameFile()
lst.append(oldUrl);
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
QVERIFY(spyUndoAvailable.isValid());
QSignalSpy spyRedoAvailable(FileUndoManager::self(), &FileUndoManager::redoAvailable);
QVERIFY(spyRedoAvailable.isValid());
KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job);
@@ -425,11 +519,28 @@ void FileUndoManagerTest::testRenameFile()
QVERIFY(!QFile::exists(srcFile()));
QVERIFY(QFileInfo(newUrl.toLocalFile()).isFile());
QCOMPARE(spyUndoAvailable.count(), 1);
QCOMPARE(spyRedoAvailable.count(), 1);
doUndo();
QVERIFY(QFile::exists(srcFile()));
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isFile());
QCOMPARE(spyUndoAvailable.count(), 2);
QCOMPARE(spyRedoAvailable.count(), 2);
doRedo();
QVERIFY(!QFile::exists(srcFile()));
QVERIFY(QFileInfo(newUrl.toLocalFile()).isFile());
QCOMPARE(spyUndoAvailable.count(), 3);
QCOMPARE(spyRedoAvailable.count(), 3);
doUndo();
QVERIFY(QFile::exists(srcFile()));
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isFile());
QCOMPARE(spyUndoAvailable.count(), 4);
QCOMPARE(spyRedoAvailable.count(), 4);
}
void FileUndoManagerTest::testRenameDir()
@@ -451,6 +562,16 @@ void FileUndoManagerTest::testRenameDir()
QVERIFY(QFile::exists(srcSubDir()));
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isDir());
doRedo();
QVERIFY(!QFile::exists(srcSubDir()));
QVERIFY(QFileInfo(newUrl.toLocalFile()).isDir());
doUndo();
QVERIFY(QFile::exists(srcSubDir()));
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isDir());
}
void FileUndoManagerTest::testCreateSymlink()
@@ -478,6 +599,15 @@ void FileUndoManagerTest::testCreateSymlink()
doUndo();
QVERIFY(!QFile::exists(path));
doRedo();
QVERIFY(QFile::exists(path));
QVERIFY(QFileInfo(path).isSymLink());
doUndo();
QVERIFY(!QFile::exists(path));
}
void FileUndoManagerTest::testCreateDir()
@@ -494,12 +624,18 @@ void FileUndoManagerTest::testCreateDir()
QVERIFY(QFileInfo(path).isDir());
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm
FileUndoManager::self()->undo();
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
QVERIFY(QFile::exists(path)); // nothing happened yet
m_uiInterface->setNextReplyToConfirmDeletion(true);
doUndo();
QVERIFY(!QFile::exists(path));
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doRedo();
QVERIFY(QFile::exists(path));
QVERIFY(QFileInfo(path).isDir());
// OK, now do it
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doUndo();
@@ -525,8 +661,23 @@ void FileUndoManagerTest::testMkpath()
doUndo();
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
QVERIFY(FileUndoManager::self()->isRedoAvailable());
QVERIFY(!QFile::exists(path));
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doRedo();
QVERIFY(FileUndoManager::self()->isUndoAvailable());
QVERIFY(!FileUndoManager::self()->isRedoAvailable());
QVERIFY(QFileInfo(path).isDir());
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion(true);
doUndo();
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
QVERIFY(FileUndoManager::self()->isRedoAvailable());
QVERIFY(!QFile::exists(path));
}
@@ -565,7 +716,29 @@ void FileUndoManagerTest::testTrashFiles()
QVERIFY(QFileInfo(srcLink()).isSymLink());
#endif
QVERIFY(QFile::exists(srcSubDir()));
// We can't check that the trash is empty; other partitions might have their own trash
doRedo();
// Check that things got removed
QVERIFY(!QFile::exists(srcFile()));
#ifndef Q_OS_WIN
QVERIFY(!QFileInfo(srcLink()).isSymLink());
#endif
QVERIFY(!QFile::exists(srcSubDir()));
// check trash?
// Let's just check that it's not empty. kio_trash has its own unit tests anyway.
QVERIFY(cfg.hasGroup(QStringLiteral("Status")));
QCOMPARE(cfg.group(QStringLiteral("Status")).readEntry("Empty", true), false);
doUndo();
QVERIFY(QFile::exists(srcFile()));
#ifndef Q_OS_WIN
QVERIFY(QFileInfo(srcLink()).isSymLink());
#endif
QVERIFY(QFile::exists(srcSubDir()));
// We can't check that the trash is empty; other partitions might have their own trash
}
@@ -645,6 +818,16 @@ void FileUndoManagerTest::testModifyFileBeforeUndo()
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
doRedo();
checkTestDirectory(srcSubDir()); // src untouched
checkTestDirectory(destSubDir());
doUndo();
checkTestDirectory(srcSubDir());
QVERIFY(!QFile::exists(destSubDir()));
}
void FileUndoManagerTest::testPasteClipboardUndo()
@@ -676,6 +859,16 @@ void FileUndoManagerTest::testPasteClipboardUndo()
doUndo();
clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData());
QCOMPARE(clipboardUrls, urls);
// Check if the clipboard was updated after redo operation
doRedo();
clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData());
QCOMPARE(clipboardUrls, urls2);
// Check if the clipboard was updated after undo operation
doUndo();
clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData());
QCOMPARE(clipboardUrls, urls);
}
void FileUndoManagerTest::testBatchRename()
@@ -711,6 +904,24 @@ void FileUndoManagerTest::testBatchRename()
QVERIFY(QFile::exists(srcList.at(0).path()));
QVERIFY(QFile::exists(srcList.at(1).path()));
QVERIFY(QFile::exists(srcList.at(2).path()));
doRedo();
QVERIFY(QFile::exists(createUrl("newfile001.txt").path()));
QVERIFY(QFile::exists(createUrl("newfile002.mkv").path()));
QVERIFY(QFile::exists(createUrl("newfile003.cpp").path()));
QVERIFY(!QFile::exists(srcList.at(0).path()));
QVERIFY(!QFile::exists(srcList.at(1).path()));
QVERIFY(!QFile::exists(srcList.at(2).path()));
doUndo();
QVERIFY(!QFile::exists(createUrl("newfile###.txt").path()));
QVERIFY(!QFile::exists(createUrl("newfile###.mkv").path()));
QVERIFY(!QFile::exists(createUrl("newfile###.cpp").path()));
QVERIFY(QFile::exists(srcList.at(0).path()));
QVERIFY(QFile::exists(srcList.at(1).path()));
QVERIFY(QFile::exists(srcList.at(2).path()));
}
void FileUndoManagerTest::testUndoCopyOfDeletedFile()
@@ -740,7 +951,7 @@ void FileUndoManagerTest::testUndoCopyOfDeletedFile()
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
QVERIFY(spyUndoAvailable.isValid());
doUndo();
QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well
QCOMPARE(spyUndoAvailable.count(), 1);
QVERIFY(!spyUndoAvailable.at(0).at(0).toBool());
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
}
@@ -44,6 +44,7 @@ private Q_SLOTS:
private:
void doUndo();
void doRedo();
TestUiInterface *m_uiInterface;
};
@@ -48,3 +48,7 @@ target_link_libraries(customheadertest PRIVATE Qt::Network Qt::Test KF6::KIOCore
add_executable(responsecodetest responsecodetest.cpp)
target_link_libraries(responsecodetest PRIVATE Qt::Network Qt::Test KF6::KIOCore)
add_executable(contentlengthtest contentlengthtest.cpp)
target_link_libraries(contentlengthtest PRIVATE Qt::Network Qt::Test KF6::KIOCore)
@@ -5,6 +5,7 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <KIO/MimetypeJob>
#include <KIO/TransferJob>
#include <QSignalSpy>
@@ -16,6 +17,8 @@ class AuthTest : public QObject
private Q_SLOTS:
void testGet();
void testGet_data();
void testMimeType();
void testMimeType_data();
};
void AuthTest::testGet_data()
@@ -24,8 +27,8 @@ void AuthTest::testGet_data()
QTest::addColumn<QString>("expectedMimeType");
QTest::addColumn<QByteArray>("expectedData");
QTest::addRow("html") << "http://localhost:5000/auth/test"
<< "text/html" << QByteArray("Hello");
// Error page will also be text/html, use something else.
QTest::addRow("markdown") << "http://localhost:5000/auth/test" << "text/markdown" << QByteArray("# Hello");
}
void AuthTest::testGet()
@@ -50,6 +53,26 @@ void AuthTest::testGet()
QCOMPARE(job->error(), KJob::NoError);
}
void AuthTest::testMimeType_data()
{
QTest::addColumn<QString>("url");
QTest::addColumn<QString>("expectedMimeType");
QTest::addRow("markdown") << "http://localhost:5000/auth/test" << "text/markdown";
}
void AuthTest::testMimeType()
{
QFETCH(QString, url);
QFETCH(QString, expectedMimeType);
// KIO::MimeTypeFinderJob does a GET request,
// only KIO::mimetype does a HEAD which could break with auth.
auto *job = KIO::mimetype(QUrl(url), KIO::HideProgressInfo);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->mimetype(), expectedMimeType);
}
QTEST_GUILESS_MAIN(AuthTest)
#include "authtest.moc"
@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2025 Robby Stephenson <robby@periapsis.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <KIO/TransferJob>
#include <QSignalSpy>
#include <QTest>
class ContentLengthTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRequest();
void testRequest_data();
};
void ContentLengthTest::testRequest_data()
{
QTest::addColumn<QString>("requestType");
QTest::addColumn<QString>("url");
QTest::addColumn<QString>("expectedCode");
QTest::addRow("get") << "GET" << "http://localhost:5000/content-length" << "200";
QTest::addRow("post") << "POST" << "http://localhost:5000/content-length" << "200";
}
void ContentLengthTest::testRequest()
{
QFETCH(QString, requestType);
QFETCH(QString, url);
QFETCH(QString, expectedCode);
KIO::TransferJob *job = nullptr;
if (requestType == "GET") {
job = KIO::get(QUrl(url));
} else if (requestType == "POST") {
job = KIO::http_post(QUrl(url), QByteArray());
}
QVERIFY(job);
QSignalSpy spy(job, &KJob::finished);
spy.wait();
QVERIFY(spy.size());
const QString actualCode = job->queryMetaData("responsecode");
QCOMPARE(actualCode, expectedCode);
QCOMPARE(job->error(), KJob::NoError);
}
QTEST_GUILESS_MAIN(ContentLengthTest)
#include "contentlengthtest.moc"
@@ -203,7 +203,9 @@ def verify_password(username, password):
@app.route('/auth/test')
@auth.login_required
def index():
return "Hello"
# Authentication required error page will also be text/html, use another mimetype.
resp = Response("# Hello", mimetype='text/markdown')
return resp
@app.route("/useragent/enforce")
def useragent_enforce():
@@ -284,3 +286,18 @@ def headers_pizza():
return "🤌"
else:
return Response("", status=400)
@app.route("/content-length", methods = ['GET'])
def contentlength_get():
if 'Content-Length' in request.headers:
return Response("Unexpected Content-Length in GET request", status=400)
else:
return "No Content-Length header in GET request."
@app.route("/content-length", methods = ['POST'])
def contentlength_post():
if 'Content-Length' in request.headers:
content_length = request.content_length
return f"Content-Length: {content_length} bytes in POST request"
else:
return Response("Content-Length header not found in POST request.", status=400)
@@ -125,7 +125,7 @@ void JobRemoteTest::putAndGet()
m_result = -1;
m_dataReqCount = 0;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
m_result = -1;
@@ -190,7 +190,7 @@ void JobRemoteTest::openFileWriting()
enterLoop();
QEXPECT_FAIL("", "Needs fixing in kio_file", Abort);
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
KIO::StoredTransferJob *getJob = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
getJob->setUiDelegate(nullptr);
@@ -271,7 +271,7 @@ void JobRemoteTest::openFileReading()
connect(putJob, &KJob::result, this, &JobRemoteTest::slotResult);
m_result = -1;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
m_rwCount = 4;
m_data = QByteArray();
@@ -292,7 +292,7 @@ void JobRemoteTest::openFileReading()
m_closeSignalCalled = false;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
QVERIFY(m_closeSignalCalled); // close signal called.
qDebug() << "resulting m_data: " << QString(m_data);
QCOMPARE(m_data, QByteArray("test5test4test3test2test1"));
@@ -366,7 +366,7 @@ void JobRemoteTest::openFileRead0Bytes()
connect(putJob, &KJob::result, this, &JobRemoteTest::slotResult);
m_result = -1;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
m_data = QByteArray();
@@ -388,7 +388,7 @@ void JobRemoteTest::openFileRead0Bytes()
enterLoop();
// Previously reading 0 bytes would cause both data() and error() being emitted...
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
QVERIFY(m_closeSignalCalled); // close signal called.
}
@@ -428,7 +428,7 @@ void JobRemoteTest::openFileTruncating()
connect(putJob, &KJob::result, this, &JobRemoteTest::slotResult);
m_result = -1;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
m_truncatedFile.setFileName(u.toLocalFile());
QVERIFY(m_truncatedFile.exists());
@@ -447,7 +447,7 @@ void JobRemoteTest::openFileTruncating()
m_closeSignalCalled = false;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
QVERIFY(m_closeSignalCalled); // close signal called.
}
@@ -6,10 +6,20 @@
*/
#include "jobtest.h"
#include "../src/core/config-stat-unix.h"
#include "mockcoredelegateextensions.h"
#include <config-kiocore.h> // HAVE_POSIX_ACL
#if HAVE_POSIX_ACL
#include <cstring>
#include <sys/acl.h>
#endif
#include "kio/job.h"
#include "kiotesthelper.h" // createTestFile etc.
#include "worker_p.h"
#include "workerbase.h"
#include "workerfactory.h"
#include <kio/chmodjob.h>
#include <kio/copyjob.h>
#include <kio/deletejob.h>
@@ -17,6 +27,7 @@
#include <kio/filecopyjob.h>
#include <kio/listjob.h>
#include <kio/mimetypejob.h>
#include <kio/mkdirjob.h>
#include <kio/statjob.h>
#include <kio/storedtransferjob.h>
#include <kmountpoint.h>
@@ -26,6 +37,7 @@
#include <KLocalizedString>
#include <QBuffer>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
@@ -34,6 +46,7 @@
#include <QHash>
#include <QPointer>
#include <QProcess>
#include <QScopeGuard>
#include <QSignalSpy>
#include <QTemporaryFile>
#include <QTest>
@@ -45,6 +58,8 @@
#include <unistd.h> // for readlink
#endif
using namespace Qt::StringLiterals;
QTEST_MAIN(JobTest)
// The code comes partly from kdebase/kioslave/trash/testtrash.cpp
@@ -89,7 +104,7 @@ void JobTest::initTestCase()
}
}
/*****
/*!***
* Set platform xattr related commands.
* Linux commands: setfattr, getfattr
* BSD commands: setextattr, getextattr
@@ -197,7 +212,7 @@ void JobTest::put()
m_result = -1;
m_dataReqCount = 0;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
@@ -222,7 +237,7 @@ void JobTest::putPermissionKept()
m_result = -1;
m_dataReqCount = 0;
enterLoop();
QVERIFY(m_result == 0); // no error
QCOMPARE(m_result, 0); // no error
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
@@ -514,7 +529,7 @@ static QHash<QString, QString> getSampleXattrs()
bool JobTest::checkXattrFsSupport(const QString &dir)
{
const QString writeTest = dir + "/fsXattrTestFile";
const QString writeTest = dir + "fsXattrTestFile";
createTestFile(writeTest);
bool ret = setXattr(writeTest);
QFile::remove(writeTest);
@@ -676,6 +691,7 @@ void JobTest::copyLocalFile(const QString &src, const QString &dest)
job->setUiDelegate(nullptr);
job->setUiDelegateExtension(nullptr);
QVERIFY(!job->exec());
QCOMPARE(job->error(), (int)KIO::Error::ERR_FILE_ALREADY_EXIST);
// Clean up
QFile::remove(src);
@@ -743,6 +759,7 @@ void JobTest::copyLocalDirectory(const QString &src, const QString &_dest, int f
job->setUiDelegate(nullptr);
job->setUiDelegateExtension(nullptr);
QVERIFY(!job->exec());
QCOMPARE(job->error(), (int)KIO::ERR_DIR_ALREADY_EXIST);
}
#ifndef Q_OS_WIN
@@ -760,7 +777,7 @@ static QString linkTarget(const QString &path)
static void copyLocalSymlink(const QString &src, const QString &dest, const QString &expectedLinkTarget)
{
QT_STATBUF buf;
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(src).constData(), &buf), 0);
QUrl u = QUrl::fromLocalFile(src);
QUrl d = QUrl::fromLocalFile(dest);
@@ -769,7 +786,7 @@ static void copyLocalSymlink(const QString &src, const QString &dest, const QStr
job->setUiDelegate(nullptr);
job->setUiDelegateExtension(nullptr);
QVERIFY2(job->exec(), qPrintable(QString::number(job->error())));
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); // dest exists
QCOMPARE(QT_LSTAT(QFile::encodeName(dest).constData(), &buf), 0); // dest exists
QCOMPARE(linkTarget(dest), expectedLinkTarget);
// cleanup
@@ -789,6 +806,73 @@ void JobTest::copyFileToSamePartition()
copyLocalFile(filePath, dest);
}
void JobTest::copyFilePreservesAcl()
{
#if !HAVE_POSIX_ACL
QSKIP("POSIX ACL support not compiled in");
#elif defined(Q_OS_FREEBSD)
QSKIP("The test is not adapted for FreeBSD yet");
#else
const QString homeDir = homeTmpDir();
const QString src = homeDir + "fileWithAcl";
const QString dest = homeDir + "fileWithAcl_copied";
createTestFile(src);
// Remove both files however we leave this function: createTestFile() and the copy jobs run
// before QVERIFY/QCOMPARE checks that return early on failure, and listRecursive() lists
// homeTmpDir() against a fixed reference. These files exist only when the filesystem supports
// ACLs, so they cannot be part of that reference and must not be left behind.
auto cleanup = qScopeGuard([&] {
QFile::remove(src);
QFile::remove(dest);
});
const QByteArray srcEnc = QFile::encodeName(src);
const QByteArray destEnc = QFile::encodeName(dest);
// A named-user entry makes this an *extended* ACL (stored as the system.posix_acl_access
// xattr); a full-rights mask avoids acl_to_text "#effective" annotations so a plain string
// comparison suffices.
acl_t want = acl_from_text("u::rw-,u:0:rwx,g::r--,m::rwx,o::r--");
QVERIFY(want);
if (acl_valid(want) != 0 || acl_set_file(srcEnc.constData(), ACL_TYPE_ACCESS, want) != 0) {
acl_free(want);
QSKIP("Filesystem under the test home does not support POSIX ACLs");
}
acl_free(want);
auto aclText = [](const QByteArray &path) {
acl_t acl = acl_get_file(path.constData(), ACL_TYPE_ACCESS);
char *text = acl ? acl_to_text(acl, nullptr) : nullptr;
const QByteArray result = text ? QByteArray(text) : QByteArray();
if (text) {
acl_free(text);
}
if (acl) {
acl_free(acl);
}
return result;
};
const QByteArray srcAcl = aclText(srcEnc);
QVERIFY(!srcAcl.isEmpty());
const QUrl u = QUrl::fromLocalFile(src);
const QUrl d = QUrl::fromLocalFile(dest);
// file_copy preserving the source mode (-1) goes through copy()'s ACL-preservation path.
auto *fileCopy = KIO::file_copy(u, d, -1, KIO::HideProgressInfo);
fileCopy->setUiDelegate(nullptr);
QVERIFY2(fileCopy->exec(), qPrintable(fileCopy->errorString()));
QCOMPARE(aclText(destEnc), srcAcl);
QFile::remove(dest);
// And the high-level CopyJob path (what users actually trigger).
auto *copyJob = KIO::copy(u, d, KIO::HideProgressInfo);
copyJob->setUiDelegate(nullptr);
copyJob->setUiDelegateExtension(nullptr);
QVERIFY2(copyJob->exec(), qPrintable(copyJob->errorString()));
QCOMPARE(aclText(destEnc), srcAcl);
#endif
}
void JobTest::copyDirectoryToSamePartition()
{
// qDebug();
@@ -810,6 +894,32 @@ void JobTest::copyDirectoryToExistingDirectory()
copyLocalDirectory(src, dest, AlreadyExists);
}
void JobTest::copyDirectoryWithOverwriteCreatesNewSubdirs()
{
// Regression test: with the Overwrite flag, CopyJob tags every directory mkdir with
// overwrite=true; the file worker must still create directories that don't exist yet.
const QString src = homeTmpDir() + "dirOverwriteSrc";
const QString dest = homeTmpDir() + "dirOverwriteDest";
createTestDirectory(src);
QVERIFY(QDir().mkpath(src + "/subdir"));
createTestFile(src + "/subdir/subfile");
KIO::Job *job = KIO::copyAs(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(nullptr);
job->setUiDelegateExtension(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QVERIFY(QFileInfo(dest).isDir());
QVERIFY(QFileInfo(dest + "/testfile").isFile());
QVERIFY(QFileInfo(dest + "/subdir").isDir());
QVERIFY(QFileInfo(dest + "/subdir/subfile").isFile());
// Leave the shared home dir as we found it, so the later listRecursive()/directorySize() tests
// still see exactly the files they expect.
QVERIFY(QDir(src).removeRecursively());
QVERIFY(QDir(dest).removeRecursively());
}
void JobTest::copyDirectoryToExistingSymlinkedDirectory()
{
// qDebug();
@@ -920,6 +1030,36 @@ void JobTest::testCopyFilePermissionsToSamePartition()
#endif
}
void JobTest::testMkdirPermissionsAndOwnership()
{
#if defined(Q_OS_UNIX)
QT_STATBUF buff;
// A new directory created with explicit permissions has exactly those bits.
const QString permDir = homeTmpDir() + "mkdirWithPerms";
KIO::MkdirJob *permJob = KIO::mkdir(QUrl::fromLocalFile(permDir), 0750);
permJob->setUiDelegate(nullptr);
QVERIFY2(permJob->exec(), qPrintable(permJob->errorString()));
QCOMPARE(QT_LSTAT(QFile::encodeName(permDir).constData(), &buff), 0);
QCOMPARE(buff.st_mode & 0777, mode_t(0750));
QVERIFY(QDir(permDir).removeRecursively());
// A new directory created with an explicit owner gets it. A normal user can
// only set its own uid and gid, so use those, which still exercises the
// ownership code path and proves it does not fail or change the owner.
const QString ownDir = homeTmpDir() + "mkdirWithOwnership";
KIO::MkdirJob *ownJob = KIO::mkdir(QUrl::fromLocalFile(ownDir), 0700);
ownJob->setOwnership(getuid(), getgid());
ownJob->setUiDelegate(nullptr);
QVERIFY2(ownJob->exec(), qPrintable(ownJob->errorString()));
QCOMPARE(QT_LSTAT(QFile::encodeName(ownDir).constData(), &buff), 0);
QCOMPARE(buff.st_uid, getuid());
QCOMPARE(buff.st_gid, getgid());
QCOMPARE(buff.st_mode & 0777, mode_t(0700));
QVERIFY(QDir(ownDir).removeRecursively());
#endif
}
void JobTest::copyDirectoryToOtherPartition()
{
// qDebug();
@@ -1090,7 +1230,7 @@ void JobTest::moveLocalFile(const QString &src, const QString &dest)
static void moveLocalSymlink(const QString &src, const QString &dest)
{
QT_STATBUF buf;
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(src).constData(), &buf), 0);
QUrl u = QUrl::fromLocalFile(src);
QUrl d = QUrl::fromLocalFile(dest);
@@ -1099,7 +1239,7 @@ static void moveLocalSymlink(const QString &src, const QString &dest)
job->setUiDelegate(nullptr);
job->setUiDelegateExtension(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(dest).constData(), &buf), 0);
QVERIFY(!QFile::exists(src)); // not there anymore
// move it back with KIO::move()
@@ -1108,7 +1248,7 @@ static void moveLocalSymlink(const QString &src, const QString &dest)
job->setUiDelegateExtension(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) != 0); // doesn't exist anymore
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); // it's back
QCOMPARE(QT_LSTAT(QFile::encodeName(src).constData(), &buf), 0); // it's back
}
void JobTest::moveLocalDirectory(const QString &src, const QString &dest)
@@ -1332,7 +1472,7 @@ void JobTest::moveDirectoryToReadonlyFilesystem()
QVERIFY(QDir(dst_dir).removeRecursively());
});
KIO::CopyJob *job = KIO::move(sources, dst, KIO::HideProgressInfo | KIO::NoPrivilegeExecution);
KIO::CopyJob *job = KIO::move(sources, dst, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
QVERIFY(!job->exec());
QCOMPARE(job->error(), expectedErrorCode);
@@ -1393,6 +1533,96 @@ void JobTest::listRecursive()
QCOMPARE(joinedNames.toLatin1(), ref_names);
}
static QByteArray expectedListRecursiveOutputExcludeDots()
{
return QByteArray(
"dirFromHome,dirFromHome/testfile,"
"dirFromHome/testlink," // exists on Windows too, see createTestDirectory
"dirFromHome_copied,"
"dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile,"
"dirFromHome_copied/dirFromHome/testlink,"
"dirFromHome_copied/testfile,"
"dirFromHome_copied/testlink,"
#ifndef Q_OS_WIN
"dirFromHome_link,"
#endif
"fileFromHome");
}
void JobTest::listRecursiveExcludeDots()
{
// Note: many other tests must have been run before since we rely on the files they created
const QString src = homeTmpDir();
m_names.clear();
KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src),
KIO::HideProgressInfo,
KIO::ListJob::ListFlag::ExcludeDotAndDotDot | KIO::ListJob::ListFlag::ExcludeHidden);
job->setUiDelegate(nullptr);
connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
m_names.sort();
const QByteArray ref_names = expectedListRecursiveOutputExcludeDots();
const QString joinedNames = m_names.join(QLatin1Char(','));
if (joinedNames.toLatin1() != ref_names) {
qDebug("%s", qPrintable(joinedNames));
qDebug("%s", ref_names.data());
}
QCOMPARE(joinedNames.toLatin1(), ref_names);
}
void JobTest::listRecursiveExcludeDotsHiddenToggle()
{
// Note: many other tests must have been run before since we rely on the files they created
// create a hidden file in dirFromHome
const auto hiddenFileName = homeTmpDir() + "/.hidden-file";
createTestFile(hiddenFileName);
const QString src = homeTmpDir();
{
m_names.clear();
KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src),
KIO::HideProgressInfo,
KIO::ListJob::ListFlag::ExcludeDotAndDotDot | KIO::ListJob::ListFlag::IncludeHidden);
job->setUiDelegate(nullptr);
connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
m_names.sort();
const QByteArray ref_names = ".hidden-file," + expectedListRecursiveOutputExcludeDots();
const QString joinedNames = m_names.join(QLatin1Char(','));
if (joinedNames.toLatin1() != ref_names) {
qDebug("%s", qPrintable(joinedNames));
qDebug("%s", ref_names.data());
}
QCOMPARE(joinedNames.toLatin1(), ref_names);
}
{
m_names.clear();
KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src),
KIO::HideProgressInfo,
KIO::ListJob::ListFlag::ExcludeDotAndDotDot | KIO::ListJob::ListFlag::ExcludeHidden);
job->setUiDelegate(nullptr);
connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
m_names.sort();
const QByteArray ref_names = expectedListRecursiveOutputExcludeDots();
const QString joinedNames = m_names.join(QLatin1Char(','));
if (joinedNames.toLatin1() != ref_names) {
qDebug("%s", qPrintable(joinedNames));
qDebug("%s", ref_names.data());
}
QCOMPARE(joinedNames.toLatin1(), ref_names);
}
QFile::remove(hiddenFileName);
}
void JobTest::multipleListRecursive()
{
// Note: listRecursive() must have been run first
@@ -1507,6 +1737,7 @@ void JobTest::directorySizeError()
KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist")));
job->setUiDelegate(nullptr);
QVERIFY(!job->exec());
QCOMPARE(job->error(), (int)KIO::Error::ERR_DOES_NOT_EXIST);
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
@@ -1542,6 +1773,7 @@ void JobTest::getInvalidUrl()
job->setUiDelegate(nullptr);
QVERIFY(!job->exec()); // it should fail :)
QCOMPARE(job->error(), (int)KIO::ERR_MALFORMED_URL);
}
void JobTest::slotMimetype(KIO::Job *job, const QString &type)
@@ -1734,6 +1966,7 @@ void JobTest::rmdirNotEmpty()
createTestDirectory(dir + "/subdir");
KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir));
QVERIFY(!job->exec());
QCOMPARE(job->error(), (int)KIO::ERR_CANNOT_RMDIR);
QVERIFY(QFile::exists(dir));
}
@@ -2933,4 +3166,115 @@ void JobTest::cancelCopyAndCleanDest()
QTRY_VERIFY2(!QFile::exists(destToCheck), qPrintable(destToCheck));
}
void JobTest::renameFileWithNoUDSACCESS() // #510567
{
// This mock worker is needed to emulate very specific copyjob case
class Factory : public KIO::WorkerFactory
{
public:
using KIO::WorkerFactory::WorkerFactory;
std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override
{
class CopyWorker : public KIO::WorkerBase
{
public:
CopyWorker(const QByteArray &pool, const QByteArray &app)
: WorkerBase(QByteArrayLiteral("kio-test"), pool, app)
{
}
Q_REQUIRED_RESULT KIO::WorkerResult stat(const QUrl &url) override
{
// Create fake entries
auto fakeEntry = [](QString name, int size) {
KIO::UDSEntry entry;
entry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size);
return entry;
};
if (url.toString() == u"kio-test://foo@bar/baz/text.txt"_s) {
statEntry(fakeEntry(u"text.txt"_s, 1000));
return KIO::WorkerResult::pass();
}
if (url.toString() == u"kio-test://foo@bar/baz/text_two.txt"_s) {
statEntry(fakeEntry(u"text_two.txt"_s, 1000));
return KIO::WorkerResult::pass();
}
return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, u"Unsupported URL: %1"_s.arg(url.toString()));
}
Q_REQUIRED_RESULT KIO::WorkerResult
copy(QUrl const & /*start*/, QUrl const & /*dest*/, int /* permissions */, KIO::JobFlags /* flags*/) override
{
return KIO::WorkerResult::pass();
}
Q_REQUIRED_RESULT KIO::WorkerResult del(QUrl const & /*url */, bool /* b */) override
{
return KIO::WorkerResult::pass();
}
};
return std::unique_ptr<KIO::WorkerBase>(new CopyWorker(pool, app));
}
};
auto factory = std::make_shared<Factory>();
KIO::Worker::setTestWorkerFactory(factory);
QUrl testUrl(u"kio-test://foo@bar/baz/text.txt"_s);
QUrl testUrl2(u"kio-test://foo@bar/baz/text_two.txt"_s);
KIO::StatJob *statJob = KIO::stat(testUrl, KIO::HideProgressInfo);
QVERIFY(statJob);
QVERIFY2(statJob->exec(), qPrintable(statJob->errorString()));
const KIO::UDSEntry &entry = statJob->statResult();
QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE));
QVERIFY(!entry.contains(KIO::UDSEntry::UDS_ACCESS));
// If we check for UDS_ACCESS in copyjob, this will fail
KIO::CopyJob *copyJob = KIO::copy(testUrl, testUrl2, KIO::HideProgressInfo);
QVERIFY(copyJob);
QVERIFY2(copyJob->exec(), qPrintable(copyJob->errorString()));
}
void JobTest::statWithNanosecondTimeOffset()
{
QUrl testFilePath = QUrl::fromLocalFile(homeTmpDir() + "testfile");
createTestFile(testFilePath.toLocalFile());
KIO::StatJob *statJob = KIO::stat(testFilePath, KIO::StatJob::StatSide::SourceSide, KIO::StatBasic | KIO::StatTimeNsOffset, KIO::HideProgressInfo);
QVERIFY(statJob);
QVERIFY2(statJob->exec(), qPrintable(statJob->errorString()));
const KIO::UDSEntry &entry = statJob->statResult();
QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME));
QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME_NS_OFFSET));
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME));
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME_NS_OFFSET));
QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME));
QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME_NS_OFFSET));
const auto modificationTimeInSec = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
auto modificationDateTime = QDateTime::fromSecsSinceEpoch(modificationTimeInSec);
modificationDateTime = modificationDateTime.addMSecs(entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME_NS_OFFSET) / 1000000);
const auto accessTimeInSec = entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME);
auto accessDateTime = QDateTime::fromSecsSinceEpoch(accessTimeInSec);
accessDateTime = accessDateTime.addMSecs(entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME_NS_OFFSET) / 1000000);
const auto fileInfo = QFileInfo(testFilePath.toLocalFile());
QCOMPARE(modificationDateTime, fileInfo.lastModified());
QCOMPARE(accessDateTime, fileInfo.lastRead());
#if HAVE_STATX
struct statx buff;
auto result = statx(AT_FDCWD, QFile::encodeName(testFilePath.toLocalFile()).data(), AT_SYMLINK_NOFOLLOW, STATX_ATIME | STATX_MTIME | STATX_BTIME, &buff);
QVERIFY(result == 0);
QCOMPARE(buff.stx_mtime.tv_sec, entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME));
QCOMPARE(buff.stx_mtime.tv_nsec, entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME_NS_OFFSET));
QCOMPARE(buff.stx_atime.tv_sec, entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME));
QCOMPARE(buff.stx_atime.tv_nsec, entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME_NS_OFFSET));
QCOMPARE(buff.stx_btime.tv_sec, entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME));
QCOMPARE(buff.stx_btime.tv_nsec, entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME_NS_OFFSET));
#endif
}
#include "moc_jobtest.cpp"
@@ -47,9 +47,12 @@ private Q_SLOTS:
void storedPutIODeviceSlowDeviceBigChunk();
void asyncStoredPutReadyReadAfterFinish();
void copyFileToSamePartition();
void copyFilePreservesAcl();
void testCopyFilePermissionsToSamePartition();
void testMkdirPermissionsAndOwnership();
void copyDirectoryToSamePartition();
void copyDirectoryToExistingDirectory();
void copyDirectoryWithOverwriteCreatesNewSubdirs();
void copyDirectoryToExistingSymlinkedDirectory();
void copyFileToOtherPartition();
void copyDirectoryToOtherPartition();
@@ -60,6 +63,8 @@ private Q_SLOTS:
void suspendFileCopy();
void suspendCopy();
void listRecursive();
void listRecursiveExcludeDots();
void listRecursiveExcludeDotsHiddenToggle();
void multipleListRecursive();
void listFile();
void killJob();
@@ -130,6 +135,9 @@ private Q_SLOTS:
void cancelCopyAndCleanDest();
void cancelCopyAndCleanDest_data();
void renameFileWithNoUDSACCESS();
void statWithNanosecondTimeOffset();
// Remote tests
// void copyFileToSystem();
@@ -1,6 +1,7 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2025 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@@ -9,10 +10,14 @@
#include "jobuidelegatefactory.h"
#include "kiotesthelper.h"
#include "worker_p.h"
#include "workerbase.h"
#include "workerfactory.h"
#include <kio/copyjob.h>
#include <kio/deletejob.h>
#include <kio/jobuidelegateextension.h>
#include <kio/simplejob.h>
#include <kio/udsentry.h>
#include <kprotocolinfo.h>
#ifdef WITH_QTDBUS
@@ -22,9 +27,10 @@
#include <KDirWatch>
#include <QDebug>
#include <QTemporaryFile>
#include <QTest>
using namespace Qt::StringLiterals;
QTEST_MAIN(KDirListerTest)
GlobalInits::GlobalInits()
@@ -45,7 +51,7 @@ void KDirListerTest::initTestCase()
// To avoid failing on broken locally defined MIME types
QStandardPaths::setTestModeEnabled(true);
m_tempDir.reset(new QTemporaryDir(tmpDirTemplate()));
m_tempDir.reset(new QTemporaryDir(homeTmpDir()));
// No message dialogs
KIO::setDefaultJobUiDelegateExtension(nullptr);
@@ -364,7 +370,7 @@ void KDirListerTest::testNewItemsInSymlink() // #213799
const int origItemCount = m_items.count();
QCOMPARE(fileCount(), origItemCount);
const QString path = tempPath();
QTemporaryFile tempFile(tmpDirTemplate() + QStringLiteral("_normal_file"));
QTemporaryFile tempFile(homeTmpDir() + QStringLiteral("_normal_file"));
QVERIFY(tempFile.open());
const QString symPath = tempFile.fileName() + "_link";
tempFile.close();
@@ -839,7 +845,7 @@ void KDirListerTest::testConcurrentListingAndStop()
MyDirLister dirLister2;
// Use a new tempdir for this test, so that we don't use the cache at all.
QTemporaryDir tempDir(tmpDirTemplate());
QTemporaryDir tempDir(homeTmpDir());
const QString path = tempDir.path() + '/';
createTestFile(path + "file_1");
createTestFile(path + "file_2");
@@ -1031,7 +1037,7 @@ void KDirListerTest::testBug211472()
{
m_items.clear();
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
const QString path = newDir.path() + "/newsubdir/";
QDir().mkdir(path);
MyDirLister dirLister;
@@ -1202,7 +1208,7 @@ void KDirListerTest::testListEmptyDirFromCache() // #278431
{
m_items.clear();
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
const QUrl url = QUrl::fromLocalFile(newDir.path());
// List and watch an empty dir
@@ -1238,7 +1244,7 @@ void KDirListerTest::testWatchingAfterCopyJob() // #331582
{
m_items.clear();
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
const QString path = newDir.path() + '/';
// List and watch an empty dir
@@ -1290,7 +1296,7 @@ void KDirListerTest::testRemoveWatchedDirectory()
{
m_items.clear();
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
const QString path = newDir.path() + '/';
// List and watch an empty dir
@@ -1331,7 +1337,7 @@ void KDirListerTest::testRemoveWatchedDirectory()
void KDirListerTest::testDirPermissionChange()
{
QTemporaryDir tempDir(tmpDirTemplate());
QTemporaryDir tempDir(homeTmpDir());
const QString path = tempDir.path() + '/';
const QString subdir = path + QLatin1String("subdir");
@@ -1476,7 +1482,7 @@ void KDirListerTest::testRenameDirectory() // #401552
void KDirListerTest::testRequestMimeType()
{
// Use a new tempdir and lister instance for this test, so that we don't use any cache at all.
QTemporaryDir tempDir(tmpDirTemplate());
QTemporaryDir tempDir(homeTmpDir());
QString path = tempDir.path() + QLatin1Char('/');
createTestFile(path + "/file_1");
@@ -1528,17 +1534,18 @@ void KDirListerTest::testMimeFilter_data()
QTest::addColumn<QStringList>("mimeTypes");
QTest::addColumn<QStringList>("filteredFiles");
const QStringList files = {"bla.txt", "main.cpp", "main.c", "image.jpeg"};
const QStringList files = {"bla.txt", "main.cpp", "main.c", "image.jpeg", "picture.png"};
QTest::newRow("single_file_exact_mimetype") << files << QStringList{"text/x-c++src"} << QStringList{"main.cpp"};
QTest::newRow("inherited_mimetype") << files << QStringList{"text/plain"} << QStringList{"bla.txt", "main.cpp", "main.c"};
QTest::newRow("no_match") << files << QStringList{"audio/flac"} << QStringList{};
QTest::newRow("glob") << files << QStringList{"image/*"} << QStringList{"image.jpeg", "picture.png"};
}
void KDirListerTest::testMimeFilter()
{
// Use a new tempdir and lister instance for this test, so that we don't use any cache at all.
QTemporaryDir tempDir(tmpDirTemplate());
QTemporaryDir tempDir(homeTmpDir());
QString path = tempDir.path() + '/';
QFETCH(QStringList, files);
@@ -1578,16 +1585,17 @@ void KDirListerTest::testDeleteCurrentDir()
QCOMPARE(m_dirLister.spyClearDir.count(), 0);
QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1);
const KFileItem deletedItem = m_dirLister.spyItemsDeleted.at(0).at(0).value<KFileItemList>().at(0);
QUrl currentDirUrl = QUrl::fromLocalFile(tempPath()).adjusted(QUrl::StripTrailingSlash);
QCOMPARE(currentDirUrl, deletedItem.url());
// there can be duplicated delete events
QVERIFY(m_dirLister.spyItemsDeleted.count() >= 1 && m_dirLister.spyItemsDeleted.count() <= 2);
const QUrl currentDirUrl = QUrl::fromLocalFile(tempPath()).adjusted(QUrl::StripTrailingSlash);
for (const auto &deletedItem : m_dirLister.spyItemsDeleted) {
QCOMPARE(currentDirUrl, deletedItem.at(0).value<KFileItemList>().at(0).url());
}
}
void KDirListerTest::testForgetDir()
{
QTemporaryDir tempDir(tmpDirTemplate());
QTemporaryDir tempDir(homeTmpDir());
QString path = tempDir.path();
createTestFile(path + "/file_1");
@@ -1657,7 +1665,7 @@ void KDirListerTest::waitUntilAfter(const QDateTime &ctime)
// and stop watching a directory because a separate lister left a directory open in another lister
void KDirListerTest::testBug386763()
{
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
const QString path = newDir.path() + "/newsubdir/";
const QString otherpath = newDir.path() + "/othersubdir/";
@@ -1680,7 +1688,7 @@ void KDirListerTest::testBug386763()
// Create a file in 'newsubdir' while still opened in dirLister2
// bug was that the watch on newsubdir when dirLister left this dir
// eventhough dirLister2 is still listing it
// even though dirLister2 is still listing it
QCOMPARE(dirLister2.spyCompleted.count(), 1);
createTestFile(path + "newFile-1");
@@ -1696,7 +1704,7 @@ void KDirListerTest::testBug386763()
void KDirListerTest::testCacheEviction()
{
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
MyDirLister dirLister;
dirLister.openUrl(QUrl::fromLocalFile(newDir.path()));
@@ -1720,9 +1728,10 @@ void KDirListerTest::testCacheEviction()
QVERIFY(KDirWatch::self()->contains(newDir.path() + QString("dir_1")));
}
void KDirListerTest::testUnreadableParentDirectory() {
void KDirListerTest::testUnreadableParentDirectory()
{
#ifdef WITH_QTDBUS
QTemporaryDir newDir(tmpDirTemplate());
QTemporaryDir newDir(homeTmpDir());
MyDirLister dirLister;
const QString hiddenPath = newDir.path() + QString("/hidden");
@@ -1767,4 +1776,113 @@ void KDirListerTest::testUnreadableParentDirectory() {
#endif
}
void KDirListerTest::testPathWithSquareBrackets()
{
#if QT_VERSION == QT_VERSION_CHECK(6, 9, 0)
QSKIP("This test is expected to fail on Qt 6.9.0");
#endif
QTemporaryDir newDir(homeTmpDir());
QFile file(newDir.filePath("[test].txt"));
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
file.close();
MyDirLister dirLister;
dirLister.openUrl(QUrl::fromLocalFile(newDir.path()));
QVERIFY(dirLister.spyCompleted.wait(500));
QVERIFY(dirLister.isFinished());
m_refreshedItems.clear();
connect(&dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems);
QSignalSpy spyRefreshItems(&dirLister, &KCoreDirLister::refreshItems);
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
file.write(QByteArray("foo"));
file.close();
QVERIFY(spyRefreshItems.wait(1000));
QCOMPARE(m_refreshedItems.count(), 1);
QCOMPARE(m_refreshedItems.at(0).first.url(), QUrl::fromLocalFile(file.fileName()));
}
void KDirListerTest::testSFTPRedirect()
{
// This mock worker is needed to emulate very specific redirection case.
class Factory : public KIO::WorkerFactory
{
public:
using KIO::WorkerFactory::WorkerFactory;
std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override
{
class RedirectWorker : public KIO::WorkerBase
{
public:
RedirectWorker(const QByteArray &pool, const QByteArray &app)
: WorkerBase(QByteArrayLiteral("kio-test"), pool, app)
{
}
// This emulates the behavior sftp:// protocol does, when connecting to
// sftp://user@host -> it redirects to sftp://user@host/home/user
Q_REQUIRED_RESULT KIO::WorkerResult listDir(const QUrl &url) override
{
if (url.toString() == u"kio-test://foo@bar"_s) {
QUrl redir(url);
redir.setPath(u"/home/foo"_s);
redirection(redir);
// It is important to return a pass() here, otherwise the DirItem will not be marked complete and
// consequently isn't inserted into the cache.
return KIO::WorkerResult::pass();
}
if (url.toString() == u"kio-test://foo@bar/home/foo"_s) {
// Create fake entries
auto fakeEntry = [](QString name, int size) {
KIO::UDSEntry entry;
entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size);
entry.fastInsert(KIO::UDSEntry::UDS_USER, QStringLiteral("user1"));
entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QStringLiteral("group1"));
entry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 123456);
entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, 12345);
entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, 2);
entry.fastInsert(KIO::UDSEntry::UDS_INODE, 56);
return entry;
};
listEntry(fakeEntry(QStringLiteral("filename1.json"), 10));
listEntry(fakeEntry(QStringLiteral("filename2.txt"), 1000));
listEntry(fakeEntry(QStringLiteral("."), 1));
return KIO::WorkerResult::pass();
}
return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("Unsupported URL: %1").arg(url.toString()));
}
};
return std::unique_ptr<KIO::WorkerBase>(new RedirectWorker(pool, app));
}
};
auto factory = std::make_shared<Factory>();
KIO::Worker::setTestWorkerFactory(factory);
QUrl testUrl(u"kio-test://foo@bar"_s);
MyDirLister dirLister;
// Mimic what dolphin does: upon redirection we open the redirected url. This is needless but exercises specific code paths.
connect(&dirLister, &KCoreDirLister::redirection, this, [&dirLister](const QUrl &oldUrl, const QUrl &newUrl) {
Q_UNUSED(oldUrl);
dirLister.openUrl(newUrl);
});
dirLister.openUrl(testUrl);
QVERIFY(dirLister.spyCompleted.wait(500));
// Make sure we have the items listed properly on the first time.
QCOMPARE(dirLister.items().count(), 2);
// This should not crash!
dirLister.openUrl(testUrl);
QVERIFY(dirLister.spyCompleted.wait(500));
// This should not list any items: We have already done it in the previous iteration.
// If this lists items, the view (for example in Dolphin) will have the items duplicated.
QCOMPARE(dirLister.items().count(), 0);
}
#include "moc_kdirlistertest.cpp"
@@ -1,6 +1,7 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2025 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@@ -12,8 +13,9 @@
#include <QObject>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <kdirlister.h>
#include <kio/workerbase.h>
#include <memory>
Q_DECLARE_METATYPE(KFileItemList)
@@ -111,6 +113,8 @@ private Q_SLOTS:
void testBug386763();
void testCacheEviction();
void testUnreadableParentDirectory();
void testPathWithSquareBrackets();
void testSFTPRedirect();
void testDeleteCurrentDir(); // must be just before last!
void testForgetDir(); // must be last!
@@ -19,6 +19,7 @@
#include <kprotocolinfo.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QMimeData>
#include <QSignalSpy>
#include <QUrl>
@@ -255,7 +256,7 @@ void KDirModelTest::testIndex()
QCOMPARE(m_fileInDirIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel));
// QCOMPARE(m_fileInDirIndex.row(), 0); // ordering isn't guaranteed
QCOMPARE(m_fileInDirIndex.column(), 0);
QVERIFY(m_fileInDirIndex.parent() == m_dirIndex);
QCOMPARE(m_fileInDirIndex.parent(), m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex));
// Index of subdir/subsubdir/testfile
@@ -263,7 +264,7 @@ void KDirModelTest::testIndex()
QCOMPARE(m_fileInSubdirIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel));
QCOMPARE(m_fileInSubdirIndex.row(), 0); // we can check it because it's the only file there
QCOMPARE(m_fileInSubdirIndex.column(), 0);
QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex);
QCOMPARE(m_fileInSubdirIndex.parent().parent(), m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex));
// Test sibling() by going from subdir/testfile to subdir/subsubdir
@@ -271,7 +272,7 @@ void KDirModelTest::testIndex()
QVERIFY(subsubdirIndex.isValid());
QModelIndex sibling1 = m_dirModel->sibling(subsubdirIndex.row(), 0, m_fileInDirIndex);
QVERIFY(sibling1.isValid());
QVERIFY(sibling1 == subsubdirIndex);
QCOMPARE(sibling1, subsubdirIndex);
QVERIFY(m_dirModel->hasChildren(subsubdirIndex));
// Invalid sibling call
@@ -280,7 +281,7 @@ void KDirModelTest::testIndex()
// Test index() with a valid parent (dir).
QModelIndex index2 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), subsubdirIndex);
QVERIFY(index2.isValid());
QVERIFY(index2 == m_fileInSubdirIndex);
QCOMPARE(index2, m_fileInSubdirIndex);
// Test index() with a non-parent (file).
QModelIndex index3 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), m_fileIndex);
@@ -389,7 +390,7 @@ void KDirModelTest::testData()
void KDirModelTest::testIcon()
{
/**
/*!
* Create test data only used in testIcon()
* PATH/subdirwithcustomicon
* PATH/subdirwithcustomicon/.directory
@@ -503,7 +504,7 @@ void KDirModelTest::testIcon()
// Test the custom icon is correctly loaded for the directory
const QIcon icon = m_dirModel->data(dirWithIconIndex, Qt::DecorationRole).value<QIcon>();
QCOMPARE(m_dirModel->itemForIndex(dirWithIconIndex).iconName(), exampleIconPath);
QVERIFY(icon.pixmap(comparedSize).toImage() == expectedIconImage);
QCOMPARE(icon.pixmap(comparedSize).toImage(), expectedIconImage);
// Test "unknown" icon will be used when the icon path refers to an invalid icon
const QIcon icon2 = m_dirModel->data(dirWithInvalidIconIndex, Qt::DecorationRole).value<QIcon>();
@@ -926,6 +927,19 @@ void KDirModelTest::testExpandToUrl()
}
}
void KDirModelTest::testExpandToUrlBelowFile()
{
// Expanding to a URL whose path goes "into" an existing *file*, e.g. the user
// types "toplevelfile_1/foo" in a file dialog, must not assert (it used to).
const QString path = m_tempDir->path() + '/';
// The file itself is known to the model.
QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")).isValid());
const QUrl url = QUrl::fromLocalFile(path + "toplevelfile_1/foo");
m_dirModel->expandToUrl(url); // should not assert
QVERIFY(!m_dirModel->indexForUrl(url).isValid());
}
void KDirModelTest::slotExpand(const QModelIndex &index)
{
QVERIFY(index.isValid());
@@ -31,7 +31,7 @@ private Q_SLOTS:
void testIndexForItem();
void testData();
/**
/*!
* Test if the icon is valid if "Icon" is specified in the desktop file, and can fall back to "unknown"
* if KDirModel fails to load the icon.
*/
@@ -46,6 +46,7 @@ private Q_SLOTS:
void testChmodDirectory();
void testExpandToUrl_data();
void testExpandToUrl();
void testExpandToUrlBelowFile();
void testFilter();
void testFilterPatterns();
void testMimeFilter();
@@ -14,7 +14,7 @@
#include <QTreeView>
#include <kdiroperator.h>
/**
/*!
* Unit test for KDirOperator
*/
class KDirOperatorTest : public QObject
@@ -61,7 +61,7 @@ private Q_SLOTS:
delete dirOp;
}
/**
/*!
* testBug187066 does the following:
*
* 1. Open a KDirOperator in kdelibs/kfile
@@ -254,10 +254,10 @@ private Q_SLOTS:
QVERIFY(dirOp.selectedItems().isEmpty());
}
/**
/*!
* If one copies the location of a file and then paste that into the location bar,
* the directory browser should show the directory of the file instead of showing an error.
* @see https://bugs.kde.org/459900
* \sa https://bugs.kde.org/459900
*/
void test_bug459900()
{
@@ -7,14 +7,16 @@
#include "kfileitemactionstest.h"
#include <KDesktopFileAction>
#include <kfileitemactions.h>
#include <kfileitemlistproperties.h>
#include <QMenu>
#include <QPointer>
#include <QStandardPaths>
#include <QTest>
/**
/*!
* In KDE 4.x, calling KFileItemActions::setParentWidget(QWidget *widget) would
* result in 'widget' not only being the parent of any dialogs created by,
* KFileItemActions, but also of the actions. Nevertheless, the destructor of
@@ -39,8 +41,10 @@ void KFileItemActionsTest::testSetParentWidget()
fileItemActions.setItemListProperties(properties);
// Create the "Open With" actions and add them to a menu.
// Skip Plugins to avoid system plugins (e.g. compressfileitemaction) starting
// async KIO jobs that would be reported as leaks by LeakSanitizer.
QMenu menu;
fileItemActions.addActionsTo(&menu);
fileItemActions.addActionsTo(&menu, KFileItemActions::MenuActionSource::Services);
// Delete the widget. In KDE 4.x, this would also delete the "Open With" actions
// because they were children of the widget. We would then get a crash in the
@@ -86,6 +90,35 @@ void KFileItemActionsTest::testTopLevelServiceMenuActions()
}
}
void KFileItemActionsTest::testCreateServiceMenuActions()
{
#ifdef Q_OS_WIN
QSKIP("Service menu discovery does not work on Windows");
#endif
QStandardPaths::setTestModeEnabled(true);
qputenv("XDG_DATA_DIRS", QFINDTESTDATA("servicemenu_protocol_mime_test_data").toUtf8());
KFileItemActions fileItemActions;
const QList<QAction *> actions = fileItemActions.createServiceMenuActions();
// All enabled service menu actions are returned regardless of MIME type or protocol
QCOMPARE(actions.count(), 4);
// Each action carries its actionsKey as objectName and a KDesktopFileAction in data
for (const QAction *action : actions) {
QVERIFY(!action->objectName().isEmpty());
QVERIFY(!action->text().isEmpty());
const auto desktopAction = action->data().value<KDesktopFileAction>();
QCOMPARE(desktopAction.actionsKey(), action->objectName());
}
// Calling again should delete old actions and return new instances
QPointer<QAction> firstAction = actions.first();
const QList<QAction *> freshActions = fileItemActions.createServiceMenuActions();
QVERIFY(firstAction.isNull());
QCOMPARE(freshActions.count(), actions.count());
}
QTEST_MAIN(KFileItemActionsTest)
#include "moc_kfileitemactionstest.cpp"
@@ -17,6 +17,7 @@ class KFileItemActionsTest : public QObject
private Q_SLOTS:
void testSetParentWidget();
void testTopLevelServiceMenuActions();
void testCreateServiceMenuActions();
};
#endif
@@ -71,12 +71,7 @@ void KFileItemTest::testPermissionsString()
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeUser | QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther));
QFile folderFile(tempDir.path() + "/afolder");
KFileItem folderItem(QUrl::fromLocalFile(folderFile.fileName()), QString(), KFileItem::Unknown);
#ifdef Q_OS_FREEBSD
// QDir::mkdir does not apply permissions correctly on FreeBSD
QCOMPARE(folderItem.permissionsString(), QStringLiteral("drwx------")); // 700
#else
QCOMPARE(folderItem.permissionsString(), QStringLiteral("drwxr-xr-x")); // 755
#endif
QVERIFY(folderItem.isReadable());
QCOMPARE(folderItem.getStatusBarInfo(), CaseInsensitiveStringCompareHelper(QStringLiteral("afolder (Folder)")));
@@ -94,7 +89,7 @@ void KFileItemTest::testPermissionsString()
CaseInsensitiveStringCompareHelper(QStringLiteral("asymlink (Empty document, Link to %1/afile)").arg(tempDir.path())));
#ifdef Q_OS_UNIX
// changing home temporarly
// changing home temporarily
auto home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
setenv("HOME", tempDir.path().toLatin1(), 1);
@@ -106,7 +101,7 @@ void KFileItemTest::testPermissionsString()
#ifdef Q_OS_UNIX
// relative Symlink to a file
QString relativeSymlink = tempDir.path() + "/afolder/relative-symlink";
QVERIFY(::symlink("../afile", relativeSymlink.toLatin1()) == 0);
QCOMPARE(::symlink("../afile", relativeSymlink.toLatin1()), 0);
QUrl relativeSymlinkUrl = QUrl::fromLocalFile(relativeSymlink);
KFileItem relativeSymlinkItem(relativeSymlinkUrl, QString(), KFileItem::Unknown);
QCOMPARE((uint)relativeSymlinkItem.permissions(), (uint)0604);
@@ -214,18 +209,18 @@ void KFileItemTest::testDetach()
KFileItem fileItem(QUrl::fromLocalFile(QStringLiteral("/one")), QString(), KFileItem::Unknown);
QCOMPARE(fileItem.name(), QStringLiteral("one"));
KFileItem fileItem2(fileItem);
QVERIFY(fileItem == fileItem2);
QVERIFY(fileItem.d == fileItem2.d);
QCOMPARE(fileItem, fileItem2);
QCOMPARE(fileItem.d, fileItem2.d);
fileItem2.setName(QStringLiteral("two"));
QCOMPARE(fileItem2.name(), QStringLiteral("two"));
QCOMPARE(fileItem.name(), QStringLiteral("one")); // it detached
QVERIFY(fileItem == fileItem2);
QCOMPARE(fileItem, fileItem2);
QVERIFY(fileItem.d != fileItem2.d);
fileItem = fileItem2;
QCOMPARE(fileItem.name(), QStringLiteral("two"));
QVERIFY(fileItem == fileItem2);
QVERIFY(fileItem.d == fileItem2.d);
QCOMPARE(fileItem, fileItem2);
QCOMPARE(fileItem.d, fileItem2.d);
QVERIFY(!(fileItem != fileItem2));
}
@@ -452,7 +447,7 @@ void KFileItemTest::testCmp()
KFileItem fileItem(QUrl::fromLocalFile(file.fileName()));
fileItem.setDelayedMimeTypes(true);
KFileItem fileItem2(QUrl::fromLocalFile(file.fileName()));
QVERIFY(fileItem == fileItem2); // created independently, but still 'equal'
QCOMPARE(fileItem, fileItem2); // created independently, but still 'equal'
QVERIFY(fileItem.d != fileItem2.d);
QVERIFY(!(fileItem != fileItem2));
QVERIFY(fileItem.cmp(fileItem2));
@@ -470,7 +465,7 @@ void KFileItemTest::testCmpAndInit()
QVERIFY(dirItem.cmp(dirItem2));
QVERIFY(dirItem2.isDir());
QVERIFY(dirItem.cmp(dirItem2));
QVERIFY(dirItem == dirItem2);
QCOMPARE(dirItem, dirItem2);
QVERIFY(dirItem.d != dirItem2.d);
QVERIFY(!(dirItem != dirItem2));
@@ -480,7 +475,7 @@ void KFileItemTest::testCmpAndInit()
QVERIFY(dirItem3.cmp(dirItem));
QVERIFY(dirItem3.isDir());
QVERIFY(dirItem3.cmp(dirItem));
QVERIFY(dirItem == dirItem3);
QCOMPARE(dirItem, dirItem3);
QVERIFY(dirItem.d != dirItem3.d);
QVERIFY(!(dirItem != dirItem3));
}
@@ -703,6 +698,19 @@ void KFileItemTest::testEncodeFileName()
QCOMPARE(KIO::encodeFileName(text), expectedFileName);
}
void KFileItemTest::testSquareBracketsInFileName()
{
#if QT_VERSION == QT_VERSION_CHECK(6, 9, 0)
QSKIP("This test is expected to fail on Qt 6.9.0");
#endif
QString dir = QStringLiteral("/tmp[%]");
QString file = QStringLiteral("[%].txt");
KIO::UDSEntry entry;
entry.fastInsert(KIO::UDSEntry::UDS_NAME, file);
KFileItem item(entry, QUrl::fromLocalFile(dir), true, true);
QCOMPARE(item.url(), QUrl::fromLocalFile(dir + QStringLiteral("/") + file));
}
void KFileItemTest::testListProperties_data()
{
QTest::addColumn<QString>("itemDescriptions");
@@ -954,8 +962,8 @@ void KFileItemTest::testIsReadable()
QCOMPARE(fileItem.isReadable(), readable);
QVERIFY(file.remove());
// still cached thanks to the cached internal udsentry
QCOMPARE(fileItem.isReadable(), readable);
// not existing, can't be readable
QCOMPARE(fileItem.isReadable(), false);
}
void KFileItemTest::testIsWritable_data()
@@ -985,8 +993,8 @@ void KFileItemTest::testIsWritable()
QCOMPARE(fileItem.isWritable(), writable);
QVERIFY(file.remove());
// still cached thanks to the cached internal udsentry
QCOMPARE(fileItem.isWritable(), writable);
// not existing, can't be writable
QCOMPARE(fileItem.isWritable(), false);
}
void KFileItemTest::testIsExecutable_data()
@@ -1016,8 +1024,8 @@ void KFileItemTest::testIsExecutable()
QCOMPARE(fileItem.isExecutable(), executable);
QVERIFY(file.remove());
// still cached thanks to the cached internal udsentry
QCOMPARE(fileItem.isExecutable(), executable);
// not existing, can't be executable
QCOMPARE(fileItem.isExecutable(), false);
}
// Restore permissions so that the QTemporaryDir cleanup can happen (taken from tst_qsavefile.cpp)
@@ -54,6 +54,8 @@ private Q_SLOTS:
void testEncodeFileName_data();
void testEncodeFileName();
void testSquareBracketsInFileName();
// KFileItemListProperties tests
void testListProperties_data();
void testListProperties();
@@ -28,6 +28,8 @@
Q_DECLARE_METATYPE(KFilePlacesModel::GroupType)
using namespace Qt::StringLiterals;
// Avoid QHash randomization so that the order of the devices is stable
static void seedInit()
{
@@ -75,6 +77,8 @@ private Q_SLOTS:
void testPlaceGroupHiddenSignal();
void testPlaceGroupHiddenRole();
void testSupportedSchemes();
void testPlaceCollapsedUrl_data();
void testPlaceCollapsedUrl();
private:
QStringList placesUrls(KFilePlacesModel *model = nullptr) const;
@@ -103,6 +107,7 @@ static QString bookmarksFile()
void KFilePlacesModelTest::initTestCase()
{
qunsetenv("LANG");
QVERIFY(m_tmpHome.isValid());
qputenv("HOME", m_tmpHome.path().toUtf8()); // use a empty home dir
@@ -1332,6 +1337,43 @@ void KFilePlacesModelTest::testSupportedSchemes()
QCOMPARE(placesUrls(), initialListOfUrls());
}
void KFilePlacesModelTest::testPlaceCollapsedUrl_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QString>("result");
QTest::newRow("home") << QUrl::fromLocalFile(m_tmpHome.path()) << u"Home"_s;
QTest::newRow("home/foo") << QUrl::fromLocalFile(m_tmpHome.path() + "/foo"_L1) << u"Home/foo"_s;
// Leaves trailing slash intact.
QTest::newRow("home/foo/") << QUrl::fromLocalFile(m_tmpHome.path() + "/foo/"_L1) << u"Home/foo/"_s;
QTest::newRow("trash root 1") << QUrl(u"trash:/"_s) << u"Trash"_s; // one slash.
// Not considered a valid URL by closestItem.
QTest::newRow("trash root 2") << QUrl(u"trash://"_s) << QString(); // two slashes.
QTest::newRow("trash root 3") << QUrl(u"trash:///"_s) << u"Trash"_s; // three slashes.
QTest::newRow("trash/foo 1") << QUrl(u"trash:/foo"_s) << u"Trash/foo"_s;
// Not considered a valid URL by closestItem.
QTest::newRow("trash/foo 2") << QUrl(u"trash://foo"_s) << QString();
QTest::newRow("trash/foo 3") << QUrl(u"trash:///foo"_s) << u"Trash/foo"_s;
QTest::newRow("trash/foo/bar") << QUrl(u"trash:///foo/bar"_s) << u"Trash/foo/bar"_s;
QTest::newRow("foreign") << QUrl(u"file:///foreign"_s) << u"/foreign"_s;
QTest::newRow("foreign/foo") << QUrl(u"file:///foreign/foo"_s) << u"/foreign/foo"_s;
QTest::newRow("empty place name") << QUrl(u"file:///media/nfs/foo"_s) << QString();
QTest::newRow("non-existing place") << QUrl::fromLocalFile(u"file:///tmp"_s) << QString();
}
void KFilePlacesModelTest::testPlaceCollapsedUrl()
{
QFETCH(QUrl, url);
QFETCH(QString, result);
QCOMPARE(m_places->placeCollapsedUrl(url), result);
}
QTEST_MAIN(KFilePlacesModelTest)
#include "kfileplacesmodeltest.moc"
@@ -13,7 +13,6 @@
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTest>
#include <QUrl>
#include "../utils_p.h"
#include "kiotesthelper.h" // createTestFile
@@ -32,6 +31,7 @@
#include <QLineEdit>
#include <QList>
#include <QMimeData>
#include <QPushButton>
#include <QStringList>
#include <QStringLiteral>
#include <QUrl>
@@ -51,7 +51,7 @@ static QWidget *findLocationLabel(QWidget *parent)
return nullptr;
}
/**
/*!
* Unit test for KFileWidget
*/
class KFileWidgetTest : public QObject
@@ -85,6 +85,12 @@ private Q_SLOTS:
void testTokenize();
void testTokenizeForSave_data();
void testTokenizeForSave();
void testThumbnailPreviewSetting();
void testReplaceLocationEditFilename_data();
void testReplaceLocationEditFilename();
void testSaveDirectoryCurrentDirSelectable();
void testSaveDirectoryCurrentDirSelectableSubDir();
};
void KFileWidgetTest::initTestCase()
@@ -445,6 +451,11 @@ void KFileWidgetTest::testExtensionForSave_data()
// If the file literally exists, then no new extension will be appended.
QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README");
// test bug 382437
const QString octetStreamfilter = QStringLiteral("application/octet-stream");
QTest::newRow("octetstream.noext") << "some" << octetStreamfilter << QStringLiteral("some") << QStringLiteral("some");
QTest::newRow("octetstream.ext") << "some.txt" << octetStreamfilter << QStringLiteral("some.txt") << QStringLiteral("some.txt");
}
void KFileWidgetTest::testExtensionForSave()
@@ -578,31 +589,25 @@ void KFileWidgetTest::testDropFile()
fileWidget.setMode(KFile::File);
fileWidget.show();
QMimeData *mimeData = new QMimeData();
std::unique_ptr<QMimeData> mimeData(new QMimeData());
mimeData->setUrls(QList<QUrl>() << fileUrl);
KDirLister *dirLister = fileWidget.dirOperator()->dirLister();
QSignalSpy spy(dirLister, qOverload<>(&KCoreDirLister::completed));
QAbstractItemView *view = fileWidget.dirOperator()->view();
QVERIFY(view);
QDragEnterEvent event1(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
QDragEnterEvent event1(QPoint(), Qt::DropAction::MoveAction, mimeData.get(), Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
QVERIFY(qApp->sendEvent(view->viewport(), &event1));
// Fake drop
QDropEvent event(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
QDropEvent event(QPoint(), Qt::DropAction::MoveAction, mimeData.get(), Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
QVERIFY(qApp->sendEvent(view->viewport(), &event));
if (!dir.isEmpty()) {
// once we drop a file the dirlister scans the dir
// wait for the completed signal from the dirlister
QVERIFY(spy.wait());
}
// Verify the expected populated name.
// Verify the expected populated name. Dropping a file selects it through the
// view, which only populates the location edit once the directory listing has
// finished, so wait for the location edit to catch up rather than asserting
// synchronously.
QCOMPARE(fileWidget.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl);
QCOMPARE(fileWidget.locationEdit()->currentText(), expectedCurrentText);
QTRY_COMPARE(fileWidget.locationEdit()->currentText(), expectedCurrentText);
// QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles()
// which calls KFileWidget::selectedUrls().
@@ -639,7 +644,7 @@ void KFileWidgetTest::testCreateNestedNewFolders()
const QString name = QStringLiteral("folder%1").arg(i);
lineEdit->setText(name);
// simulate the time the user will take to type the new folder name
QTest::qWait(1000);
QTest::qWait(300);
dialog->accept();
@@ -668,12 +673,14 @@ void KFileWidgetTest::testTokenize_data()
// file, the whole name will not be escaped.
QTest::newRow("quote-single-file") << QStringList{"test\"quote"} << QString("test\\\"quote");
QTest::newRow("single-file-with-two-quotes") << QStringList{"\"test\".txt"} << QString{"\\\"test\\\".txt"};
// Escape multiple files. Files should also be wrapped in ""
// Note that we are also testing quote at the end of the name
QTest::newRow("quote-multi-file") << QStringList{"test\"quote", "test2-quote\"", "test"} << QString("\"test\\\"quote\" \"test2-quote\\\"\" \"test\"");
// Ok, enough with quotes... lets do some backslashes
// Backslash literals in file names - Unix only case
#ifndef Q_OS_WIN
QTest::newRow("backslash-single-file") << QStringList{"test\\backslash"} << QString("test\\\\backslash");
QTest::newRow("backslash-multi-file") << QStringList{"test\\back\\slash", "test"} << QString("\"test\\\\back\\\\slash\" \"test\"");
@@ -683,6 +690,7 @@ void KFileWidgetTest::testTokenize_data()
QTest::newRow("double-backslash-end") << QStringList{"test\\\\"} << QString("test\\\\\\\\");
QTest::newRow("single-backslash-end") << QStringList{"some thing", "test\\"} << QString("\"some thing\" \"test\\\\\"");
#endif
QTest::newRow("sharp") << QStringList{"some#thing"} << QString("some#thing");
@@ -788,6 +796,7 @@ void KFileWidgetTest::testTokenizeForSave_data()
// All quotes in names should be escaped, however since this is a single
// file, the whole name will not be escaped.
QTest::newRow("save-quote") << QString{"test\"quote"} << QString() << QString("test\\\"quote");
QTest::newRow("save-file-with-quotes") << QString{"\"test\".txt"} << QString() << QString{"\\\"test\\\".txt"};
// Ok, enough with quotes... lets do some backslashes
// Backslash literals in file names - Unix only case
@@ -875,6 +884,188 @@ void KFileWidgetTest::testTokenizeForSave()
QCOMPARE(urls[0], fileUrl);
}
// BUG: 501743
void KFileWidgetTest::testThumbnailPreviewSetting()
{
QTemporaryDir tempDir;
QUrl path = QUrl::fromLocalFile(tempDir.path());
auto getAction = [](KFileWidget *fw) {
QAction *previewAction = nullptr;
for (auto action : fw->actions()) {
if (action->text() == "Show Preview") {
previewAction = action;
break;
}
}
return previewAction;
};
// Set up
KFileWidget fwSetup(path);
fwSetup.setOperationMode(KFileWidget::Saving);
fwSetup.setMode(KFile::File);
QAction *previewAction = getAction(&fwSetup);
QVERIFY(previewAction);
previewAction->setChecked(true);
QCOMPARE(previewAction->isChecked(), true);
fwSetup.cancelButton()->click();
// Check preview settings are true, then save them as false
KFileWidget fwPreviewTrue(path);
fwPreviewTrue.setOperationMode(KFileWidget::Saving);
fwPreviewTrue.setMode(KFile::File);
previewAction = getAction(&fwPreviewTrue);
QVERIFY(previewAction);
QCOMPARE(previewAction->isChecked(), true);
previewAction->setChecked(false);
QCOMPARE(previewAction->isChecked(), false);
fwPreviewTrue.cancelButton()->click();
// Check preview settings are saved as false
KFileWidget fwPreviewFalse(path);
fwPreviewFalse.setOperationMode(KFileWidget::Saving);
fwPreviewFalse.setMode(KFile::File);
previewAction = getAction(&fwPreviewFalse);
QVERIFY(previewAction);
QCOMPARE(previewAction->isChecked(), true);
fwPreviewFalse.cancelButton()->click();
}
struct LocationTestItem {
bool dir;
QString name;
};
void KFileWidgetTest::testReplaceLocationEditFilename_data()
{
QTest::addColumn<LocationTestItem>("initialItem");
QTest::addColumn<LocationTestItem>("selectedItem");
QTest::addColumn<QString>("lineEditTextResult");
QTest::addColumn<bool>("overrideModifiedText");
QTest::newRow("replace-dir-with-dir") << LocationTestItem(true, "folder1") << LocationTestItem(true, "folder2") << "" << false;
QTest::newRow("replace-dir-with-file") << LocationTestItem(true, "folder1") << LocationTestItem(false, "file1") << "file1" << true;
QTest::newRow("replace-file-with-file") << LocationTestItem(false, "file1") << LocationTestItem(false, "file2") << "file2" << true;
QTest::newRow("replace-file-with-dir") << LocationTestItem(false, "file1") << LocationTestItem(true, "folder1") << "file1" << false;
}
// BUG: 502794
// Test that we don't override file names with folder names
void KFileWidgetTest::testReplaceLocationEditFilename()
{
QFETCH(LocationTestItem, initialItem);
QFETCH(LocationTestItem, selectedItem);
QFETCH(QString, lineEditTextResult);
QFETCH(bool, overrideModifiedText);
// Setup - Create folders/files in temp dir
QTemporaryDir tempDir;
const QString tempDirPath = tempDir.path();
QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath);
QUrl replacedUrl = QUrl::fromLocalFile(tempDirPath + QLatin1Char('/') + initialItem.name);
QUrl selectedUrl = QUrl::fromLocalFile(tempDirPath + QLatin1Char('/') + selectedItem.name);
auto createTestItem = [tempDirUrl](LocationTestItem item, const QUrl &url) {
if (item.dir) {
QDir(tempDirUrl.toLocalFile()).mkdir(url.toLocalFile());
QVERIFY(QDir(url.toLocalFile()).exists());
} else {
QFile file(url.toLocalFile());
if (!file.open(QIODevice::WriteOnly)) {
qFatal("Couldn't create %s", qPrintable(url.toLocalFile()));
}
file.write(QByteArray("Test file"));
file.close();
QVERIFY(file.exists());
}
};
createTestItem(initialItem, replacedUrl);
createTestItem(selectedItem, selectedUrl);
// Open the filewidget in tempdir
KFileWidget fw(tempDirUrl);
fw.setOperationMode(KFileWidget::Saving);
// Highlight the item, then another
auto highlightItem = [&fw](QUrl url) {
KFileItem fileItem(url);
QSignalSpy fileHighlightedSpy(fw.dirOperator(), &KDirOperator::fileHighlighted);
fw.dirOperator()->highlightFile(fileItem);
QVERIFY(fileHighlightedSpy.count());
};
highlightItem(replacedUrl);
highlightItem(selectedUrl);
// Compare that we have the wanted result when selecting items
QCOMPARE(fw.locationEdit()->lineEdit()->text(), lineEditTextResult);
// Make sure we don't overwrite any text user has modified in some cases
const QString modifiedText("New Filename.txt");
fw.locationEdit()->setEditText(modifiedText);
highlightItem(selectedUrl);
if (overrideModifiedText) {
QCOMPARE(fw.locationEdit()->lineEdit()->text(), lineEditTextResult);
} else {
QCOMPARE(fw.locationEdit()->lineEdit()->text(), modifiedText);
}
}
void KFileWidgetTest::testSaveDirectoryCurrentDirSelectable()
{
// GIVEN
QTemporaryDir tempDir;
const QUrl baseUrl = QUrl::fromLocalFile(tempDir.path());
KFileWidget fw(baseUrl);
fw.setOperationMode(KFileWidget::OperationMode::Saving);
fw.setMode(KFile::Directory);
QSignalSpy acceptedSpy(&fw, &KFileWidget::accepted);
QCOMPARE(fw.locationEdit()->currentText(), QString());
QVERIFY(fw.okButton()->isEnabled());
// WHEN
fw.slotOk(); // okButton is not hooked by default to slotOk
fw.accept(); // Accept the popup
// THEN
QCOMPARE(acceptedSpy.count(), 1);
QCOMPARE(fw.locationEdit()->currentText(), QString());
QCOMPARE(fw.selectedUrl().adjusted(QUrl::StripTrailingSlash), baseUrl);
}
void KFileWidgetTest::testSaveDirectoryCurrentDirSelectableSubDir()
{
// GIVEN
QTemporaryDir tempDir;
const QUrl baseUrl = QUrl::fromLocalFile(tempDir.path());
auto subDirPath = tempDir.filePath(QStringLiteral("subdir"));
QVERIFY(QDir().mkdir(subDirPath));
const QUrl subDirUrl = QUrl::fromLocalFile(subDirPath);
KFileWidget fw(baseUrl);
fw.setOperationMode(KFileWidget::OperationMode::Saving);
fw.setMode(KFile::Directory);
QSignalSpy acceptedSpy(&fw, &KFileWidget::accepted);
QSignalSpy urlEnteredSpy(fw.dirOperator(), &KDirOperator::urlEntered);
// WHEN
QVERIFY(fw.okButton()->isEnabled());
fw.dirOperator()->highlightFile(KFileItem{subDirUrl});
QCOMPARE(fw.locationEdit()->currentText(), subDirUrl.fileName());
fw.slotOk(); // Accept the selected Dir
fw.accept(); // Accept the popup
// THEN
QCOMPARE(acceptedSpy.count(), 1);
QCOMPARE(fw.selectedUrl(), subDirUrl);
}
QTEST_MAIN(KFileWidgetTest)
#include "kfilewidgettest.moc"
@@ -26,10 +26,6 @@
#include "kioglobal_p.h"
// to be used in QTemporaryDir constructor, will ensure absolute path temporary directory
QString tmpDirTemplate()
{
return QDir::tempPath() + QStringLiteral("/kiotests_XXXXXX");
}
QString homeTmpDir()
{
@@ -84,7 +80,7 @@ static void createTestSymlink(const QString &path, const QByteArray &target = "/
qFatal("couldn't create symlink: %s", strerror(errno));
}
QT_STATBUF buf;
QVERIFY(QT_LSTAT(QFile::encodeName(path), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(path), &buf), 0);
QVERIFY(Utils::isLinkMask(buf.st_mode));
// qDebug( "symlink %s created", qPrintable( path ) );
QVERIFY(QFileInfo(path).isSymLink());
@@ -98,7 +94,7 @@ void createTestPipe(const QString &path)
qFatal("couldn't create named pipe: %s", strerror(errno));
}
QT_STATBUF buf;
QVERIFY(QT_LSTAT(QFile::encodeName(path), &buf) == 0);
QCOMPARE(QT_LSTAT(QFile::encodeName(path), &buf), 0);
QVERIFY(S_ISFIFO(buf.st_mode));
#else
// to not change the filecount everywhere in the tests
@@ -19,6 +19,11 @@ void KMountPointTest::initTestCase()
void KMountPointTest::testCurrentMountPoints()
{
// on Windows we just create pseudo mount points for drives, that will fail the tests below
#ifdef Q_OS_WIN
QSKIP("Skipping test on Windows, KMountPoint only has pseudo mounts for drive letters");
#endif
const KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName);
if (mountPoints.isEmpty()) { // can happen in chroot jails
QSKIP("mtab is empty");
@@ -39,6 +44,8 @@ void KMountPointTest::testCurrentMountPoints()
if (!mountPoint->realDeviceName().isEmpty() && !mountWithDevice) {
mountWithDevice = mountPoint;
}
QVERIFY(mountPoints.findByPath(mountPoint->mountPoint()) != nullptr);
}
if (!mountWithDevice) {
@@ -73,6 +80,8 @@ void KMountPointTest::testCurrentMountPoints()
} else {
QCOMPARE(homeMountPoint->mountPoint(), QDir(QStringLiteral("/home")).canonicalPath());
}
QCOMPARE(homeMountPoint->deviceId(), homeStatBuff.st_dev);
} else {
qDebug() << "/home doesn't seem to exist, skipping test";
}
@@ -136,6 +145,11 @@ void KMountPointTest::testCurrentMountPointOptions()
void KMountPointTest::testPossibleMountPoints()
{
// on Windows we just create pseudo mount points for drives, that will fail the tests below
#ifdef Q_OS_WIN
QSKIP("Skipping test on Windows, KMountPoint only has pseudo mounts for drive letters");
#endif
const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedRealDeviceName | KMountPoint::NeedMountOptions);
if (mountPoints.isEmpty()) { // can happen in chroot jails
QSKIP("fstab is empty");
@@ -7,7 +7,12 @@
#include <QTest>
#include <KCollapsibleGroupBox>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KIO/StoredTransferJob>
#include <KMessageWidget>
#include <KSharedConfig>
#include <KShell>
#include <QDialog>
#include <QLineEdit>
@@ -16,6 +21,9 @@
#include <knewfilemenu.h>
#include <kpropertiesdialog.h>
#include <QFileInfo>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QSignalSpy>
#include <QStandardPaths>
@@ -69,15 +77,30 @@ private Q_SLOTS:
// The files in template dirs are traversed only once
QFile templ(m_xdgConfigDir + "/test-templates/test-text.desktop");
QVERIFY(templ.open(QIODevice::WriteOnly));
const QByteArray contents =
const QByteArray contents1 =
"[Desktop Entry]\n"
"Name=Custom...\n"
"Type=Link\n"
"URL=TestTextFile.txt\n"
"Icon=text-plain\n";
templ.write(contents);
templ.write(contents1);
templ.close();
QFile templText(m_xdgConfigDir + "/test-templates/TestTextFile.txt");
QVERIFY(templText.open(QIODevice::WriteOnly));
templText.close();
QFile notExist(m_xdgConfigDir + "/test-templates/not-existing.desktop");
QVERIFY(notExist.open(QIODevice::WriteOnly));
const QByteArray contents2 =
"[Desktop Entry]\n"
"Name=Doesn't exist\n"
"Type=Link\n"
"URL=NonExisting.txt\n"
"Icon=text-plain\n";
notExist.write(contents2);
notExist.close();
QDir folderTemplate(templatesLoc + "/my-folder");
QVERIFY(folderTemplate.mkdir(folderTemplate.path()));
@@ -93,6 +116,28 @@ private Q_SLOTS:
QDir(m_xdgConfigDir + "/test-templates").removeRecursively();
}
void openActionText(KNewFileMenu *menu, const QString actionText)
{
QAction *action = menu;
QVERIFY(action);
QAction *textAct = nullptr;
const QList<QAction *> actionsList = action->menu()->actions();
for (QAction *act : actionsList) {
if (act->text().contains(actionText)) {
textAct = act;
}
}
QVERIFY(textAct != nullptr);
if (!textAct) {
for (const QAction *act : actionsList) {
qDebug() << act << act->text() << act->data();
}
const QString err = QLatin1String("action with text \"") + actionText + QLatin1String("\" not found.");
QVERIFY2(textAct, qPrintable(err));
}
textAct->trigger();
}
// Ensure that we can use storedPut() with a qrc file as input
// similar to JobTest::storedPutIODeviceFile, but with a qrc file as input
// (and here because jobtest doesn't link to KIO::FileWidgets, which has the qrc)
@@ -209,24 +254,7 @@ private Q_SLOTS:
menu.setSelectDirWhenAlreadyExist(true);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
QAction *action = &menu;
QVERIFY(action);
QAction *textAct = nullptr;
const QList<QAction *> actionsList = action->menu()->actions();
for (QAction *act : actionsList) {
if (act->text().contains(actionText)) {
textAct = act;
}
}
QVERIFY(textAct != nullptr);
if (!textAct) {
for (const QAction *act : actionsList) {
qDebug() << act << act->text() << act->data();
}
const QString err = QLatin1String("action with text \"") + actionText + QLatin1String("\" not found.");
QVERIFY2(textAct, qPrintable(err));
}
textAct->trigger();
openActionText(&menu, actionText);
QDialog *dialog;
// QTRY_ because a NameFinderJob could be running and the dialog will be shown when
@@ -256,7 +284,7 @@ private Q_SLOTS:
lineEdit->setText(typedFilename);
}
QUrl emittedUrl;
QSignalSpy spy(&menu, &KNewFileMenu::fileCreated);
QSignalSpy fileCreatedSpy(&menu, &KNewFileMenu::fileCreated);
QSignalSpy folderSpy(&menu, &KNewFileMenu::directoryCreated);
// expectedFilename is empty in the "Folder already exists" case, the button won't
@@ -284,10 +312,10 @@ private Q_SLOTS:
QVERIFY(QFileInfo(path).isDir());
}
} else {
if (spy.isEmpty()) {
QVERIFY(spy.wait(1000));
if (fileCreatedSpy.isEmpty()) {
QVERIFY(fileCreatedSpy.wait(2000));
}
emittedUrl = spy.at(0).at(0).toUrl();
emittedUrl = fileCreatedSpy.at(0).at(0).toUrl();
QVERIFY(QFile::exists(path));
if (actionText != QLatin1String("Link to File")) {
QFile file(path);
@@ -306,14 +334,19 @@ private Q_SLOTS:
KNewFileMenu menu(this);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
const auto list = menu.menu()->actions();
auto it = std::find_if(list.cbegin(), list.cend(), [](QAction *act) {
const auto list1 = menu.menu()->actions();
auto it = std::find_if(list1.cbegin(), list1.cend(), [](QAction *act) {
return act->text() == QStringLiteral("Custom...");
});
QVERIFY(it != list.cend());
QVERIFY(it != list1.cend());
// There is a separator between system-wide templates and the ones
// from the user's home
QVERIFY((*--it)->isSeparator());
const auto list2 = menu.menu()->actions();
it = std::find_if(list2.cbegin(), list2.cend(), [](QAction *act) {
return act->text() == QStringLiteral("Doesn't exist");
});
QVERIFY(it == list2.cend());
}
void testParsingSimpleTemplates()
@@ -333,6 +366,238 @@ private Q_SLOTS:
QVERIFY(itScript != list.cend());
}
void testForbidTildeUsername_data()
{
QString tildeUsername = QLatin1Char('~') + QDir::home().dirName();
QTest::addColumn<QString>("actionText"); // the action we're clicking on
QTest::addColumn<QString>("typedFilename"); // what the user is typing
QTest::addColumn<bool>("filenameAllowed"); // is the filename allowed, compared to OK button status
QTest::newRow("text file is ~username.txt") << "Text File" << tildeUsername + ".txt" << true;
QTest::newRow("text file is ~username") << "Text File" << tildeUsername << false;
QTest::newRow("html file is ~username.html") << "HTML File" << tildeUsername + ".html" << true;
QTest::newRow("html file is ~username") << "HTML File" << tildeUsername << false;
QTest::newRow("folder starts with ~") << "Folder..."
<< "~folder1" << true;
QTest::newRow("folder name is ~username") << "Folder..." << tildeUsername << false;
}
void testForbidTildeUsername()
{
QFETCH(QString, actionText);
QFETCH(QString, typedFilename);
QFETCH(bool, filenameAllowed);
QWidget parentWidget;
KNewFileMenu menu(this);
menu.setModal(false);
menu.setParentWidget(&parentWidget);
menu.setSelectDirWhenAlreadyExist(false);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
openActionText(&menu, actionText);
QDialog *dialog;
// QTRY_ because a NameFinderJob could be running and the dialog will be shown when
// it finishes.
QTRY_VERIFY(dialog = parentWidget.findChild<QDialog *>());
QLineEdit *lineEdit = dialog->findChild<QLineEdit *>();
KMessageWidget *msgWidget = dialog->findChild<KMessageWidget *>();
QSignalSpy msgSpy(msgWidget, &KMessageWidget::showAnimationFinished);
QVERIFY(lineEdit);
lineEdit->setText(typedFilename);
QVERIFY(msgSpy.wait(1000));
QDialogButtonBox *buttonsList = dialog->findChild<QDialogButtonBox *>();
QVERIFY(buttonsList);
auto okButton = buttonsList->button(QDialogButtonBox::Ok);
QVERIFY(okButton);
auto cancelButton = buttonsList->button(QDialogButtonBox::Cancel);
QVERIFY(cancelButton);
// No need to create new file, we just want to see if the OK button is enabled or not
QCOMPARE(okButton->isEnabled(), filenameAllowed);
cancelButton->click();
}
void testNoCustomIconOnFile()
{
QWidget parentWidget;
KNewFileMenu menu(this);
menu.setModal(false);
menu.setParentWidget(&parentWidget);
menu.setSelectDirWhenAlreadyExist(false);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
openActionText(&menu, QStringLiteral("Text File"));
QDialog *dialog;
// QTRY_ because a NameFinderJob could be running and the dialog will be shown when
// it finishes.
QTRY_VERIFY(dialog = parentWidget.findChild<QDialog *>());
KCollapsibleGroupBox *chooseIconBox = dialog->findChild<KCollapsibleGroupBox *>();
QVERIFY(chooseIconBox);
QVERIFY(!chooseIconBox->isVisibleTo(dialog));
QVERIFY(!chooseIconBox->isExpanded());
}
void testFolderIconCollection_data()
{
QTest::addColumn<int>("buttonIndex");
QTest::addColumn<QString>("iconName");
QTest::addColumn<bool>("expectsIcon");
QTest::newRow("default") << 0 << QStringLiteral("inode-directory") << false;
QTest::newRow("red") << 1 << QStringLiteral("folder-red") << true;
QTest::newRow("yellow") << 2 << QStringLiteral("folder-yellow") << true;
QTest::newRow("orange") << 3 << QStringLiteral("folder-orange") << true;
QTest::newRow("green") << 4 << QStringLiteral("folder-green") << true;
QTest::newRow("cyan") << 5 << QStringLiteral("folder-cyan") << true;
QTest::newRow("blue") << 6 << QStringLiteral("folder-blue") << true;
QTest::newRow("violet") << 7 << QStringLiteral("folder-violet") << true;
QTest::newRow("brown") << 8 << QStringLiteral("folder-brown") << true;
QTest::newRow("grey") << 9 << QStringLiteral("folder-grey") << true;
QTest::newRow("bookmark") << 10 << QStringLiteral("folder-bookmark") << true;
QTest::newRow("cloud") << 11 << QStringLiteral("folder-cloud") << true;
QTest::newRow("development") << 12 << QStringLiteral("folder-development") << true;
QTest::newRow("games") << 13 << QStringLiteral("folder-games") << true;
QTest::newRow("mail") << 14 << QStringLiteral("folder-mail") << true;
QTest::newRow("music") << 15 << QStringLiteral("folder-music") << true;
QTest::newRow("print") << 16 << QStringLiteral("folder-print") << true;
QTest::newRow("tar") << 17 << QStringLiteral("folder-tar") << true;
QTest::newRow("temp") << 18 << QStringLiteral("folder-temp") << true;
QTest::newRow("important") << 19 << QStringLiteral("folder-important") << true;
}
void testFolderIconCollection()
{
QFETCH(int, buttonIndex);
QFETCH(QString, iconName);
QFETCH(bool, expectsIcon);
// Reset persisted icon-picker state before the first row so re-runs are deterministic.
if (buttonIndex == 0) {
KSharedConfig::openStateConfig(QStringLiteral("kiostaterc"))
->group(QStringLiteral("New File Menu"))
.deleteEntry(QStringLiteral("ShowFolderIconPicker"));
}
QWidget parentWidget;
KNewFileMenu menu(this);
menu.setModal(false);
menu.setParentWidget(&parentWidget);
menu.setSelectDirWhenAlreadyExist(false);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
openActionText(&menu, QStringLiteral("Folder..."));
QDialog *dialog;
// QTRY_ because a NameFinderJob could be running and the dialog will be shown when
// it finishes.
QTRY_VERIFY(dialog = parentWidget.findChild<QDialog *>());
KCollapsibleGroupBox *chooseIconBox = dialog->findChild<KCollapsibleGroupBox *>();
QVERIFY(chooseIconBox);
QVERIFY(chooseIconBox->isVisibleTo(dialog));
// It should remember that it was expanded (state is reset above for the first row).
if (buttonIndex == 0) {
QVERIFY(!chooseIconBox->isExpanded());
} else {
QVERIFY(chooseIconBox->isExpanded());
}
chooseIconBox->setExpanded(true);
QGridLayout *folderIconGrid = chooseIconBox->findChild<QGridLayout *>();
QVERIFY(folderIconGrid);
QCOMPARE(folderIconGrid->count(), 20);
QLabel *iconLabel = dialog->findChild<QLabel *>("iconLabel");
QVERIFY(iconLabel);
const QString defaultFolderIconName = QStringLiteral("inode-directory");
QCOMPARE(iconLabel->property("iconName").toString(), defaultFolderIconName);
QToolButton *firstIconButton = qobject_cast<QToolButton *>(folderIconGrid->itemAt(0)->widget());
QVERIFY(firstIconButton);
QCOMPARE(firstIconButton->icon().name(), defaultFolderIconName);
QVERIFY(firstIconButton->isChecked());
QToolButton *button = qobject_cast<QToolButton *>(folderIconGrid->itemAt(buttonIndex)->widget());
QVERIFY(button);
QCOMPARE(button->icon().name(), iconName);
button->click();
QVERIFY(button->isChecked());
QCOMPARE(iconLabel->property("iconName").toString(), iconName);
QDialogButtonBox *buttonsList = dialog->findChild<QDialogButtonBox *>();
QVERIFY(buttonsList);
auto okButton = buttonsList->button(QDialogButtonBox::Ok);
QVERIFY(okButton);
QSignalSpy folderSpy(&menu, &KNewFileMenu::directoryCreated);
QSignalSpy dirCreationRejectedSpy(&menu, &KNewFileMenu::directoryCreationRejected);
QSignalSpy fileCreationRejectedSpy(&menu, &KNewFileMenu::fileCreationRejected);
okButton->click();
QVERIFY(folderSpy.wait(1000));
QUrl emittedUrl = folderSpy.at(0).at(0).toUrl();
const QString desktopPath = emittedUrl.toLocalFile() + QLatin1String("/.directory");
QCOMPARE(QFileInfo::exists(desktopPath), expectsIcon);
if (expectsIcon) {
KDesktopFile desktopFile{desktopPath};
QCOMPARE(desktopFile.readIcon(), iconName);
}
QCOMPARE(dirCreationRejectedSpy.count(), 0);
QCOMPARE(fileCreationRejectedSpy.count(), 0);
}
void testDirectoryCreationRejected()
{
QWidget parentWidget;
KNewFileMenu menu(this);
menu.setModal(false);
menu.setParentWidget(&parentWidget);
menu.setSelectDirWhenAlreadyExist(false);
menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
menu.checkUpToDate();
openActionText(&menu, QStringLiteral("Folder..."));
QDialog *dialog;
// QTRY_ because a NameFinderJob could be running and the dialog will be shown when
// it finishes.
QTRY_VERIFY(dialog = parentWidget.findChild<QDialog *>());
QDialogButtonBox *buttonsList = dialog->findChild<QDialogButtonBox *>();
QVERIFY(buttonsList);
auto cancelButton = buttonsList->button(QDialogButtonBox::Cancel);
QVERIFY(cancelButton);
QSignalSpy folderSpy(&menu, &KNewFileMenu::directoryCreated);
QSignalSpy dirCreationRejectedSpy(&menu, &KNewFileMenu::directoryCreationRejected);
QSignalSpy fileCreationRejectedSpy(&menu, &KNewFileMenu::fileCreationRejected);
cancelButton->click();
QCOMPARE(folderSpy.count(), 0);
QCOMPARE(dirCreationRejectedSpy.count(), 1);
QCOMPARE(fileCreationRejectedSpy.count(), 0);
}
// TODO test custom folder icon and that it remembers it.
// But that needs access to KNewFileMenuPrivate and therefore a bit of shuffling of files and classes.
private:
QTemporaryDir m_tmpDir;
QString m_xdgConfigDir;
@@ -130,25 +130,28 @@ void KRecentDocumentTest::testXbelBookmarkMaxEntries()
config.writeEntry(QStringLiteral("UseRecent"), true);
config.writeEntry(QStringLiteral("MaxEntries"), 3);
auto tempFiles = QList<QFile *>();
auto currentPath = QDir::currentPath();
QStringList tempFiles;
for (int i = 0; i < 15; ++i) {
QFile *tempFile = new QFile(QDir::currentPath() + "/temp File" + QString::number(i));
QVERIFY(tempFile->open(QIODevice::WriteOnly));
tempFile->close();
tempFiles << tempFile;
QString fileName(QStringLiteral("%1/temp File %2").arg(currentPath, QString::number(i)));
QFile tempFile(fileName);
QVERIFY(tempFile.open(QIODevice::WriteOnly));
tempFile.close();
KRecentDocument::add(QUrl::fromLocalFile(tempFile->fileName()), QStringLiteral("my-application"));
KRecentDocument::add(QUrl::fromLocalFile(fileName), QStringLiteral("my-application"));
tempFiles.push_back(fileName);
}
const auto recentUrls = KRecentDocument::recentUrls();
QCOMPARE(recentUrls.length(), 3);
for (int i = 0; i < 3; ++i) {
QCOMPARE(recentUrls.at(i).fileName(), "temp File" + QString::number(i + 12));
QCOMPARE(recentUrls.at(i).fileName(), QStringLiteral("temp File %1").arg(QString::number(i + 12)));
}
for (auto &f : tempFiles) {
f->remove();
for (const auto &fileName : tempFiles) {
QFile::remove(fileName);
}
}
@@ -9,6 +9,7 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include <KUriFilter>
#include <KUser>
#include <QLoggingCategory>
#include <KPluginMetaData>
@@ -238,7 +239,13 @@ void KUriFilterTest::localFiles_data()
addRow("~/.bashrc", QDir::homePath() + QStringLiteral("/.bashrc"), KUriFilterData::LocalFile, QStringList(QStringLiteral("kshorturifilter")));
}
addRow("~", QDir::homePath(), KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")), QStringLiteral("/tmp"));
addRow("~bin", nullptr, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")));
// this test case relies on the 'bin' user being there
KUser user("bin");
if (user.isValid()) {
addRow("~bin", nullptr, KUriFilterData::LocalDir, QStringList(QStringLiteral("kshorturifilter")));
}
addRow("~does_not_exist", nullptr, KUriFilterData::Error, QStringList(QStringLiteral("kshorturifilter")));
addRow("~/does_not_exist", QDir::homePath() + "/does_not_exist", KUriFilterData::LocalFile, QStringList(QStringLiteral("kshorturifilter")));
@@ -156,7 +156,7 @@ void KUrlNavigatorTest::testHistoryInsert()
QCOMPARE(m_navigator->historySize(), 4);
}
/**
/*!
* When the current URL is inside an archive and the user goes "up", it is expected
* that the new URL is that of the folder containing the archive (unless the URL was
* in a subfolder inside the archive). Furthermore, the protocol should be "file".
@@ -202,6 +202,7 @@ void KUrlNavigatorTest::testUrlParsing_data()
QTest::newRow("homeDir") << QStringLiteral("~") << QUrl::fromLocalFile(QDir::homePath());
KUser user(KUser::UseRealUserID);
QTest::newRow("userHomeDir") << (QStringLiteral("~") + user.loginName()) << QUrl::fromLocalFile(user.homeDir());
QTest::newRow("enviromentVariables") << QStringLiteral("$HOME") << QUrl::fromLocalFile(user.homeDir());
}
void KUrlNavigatorTest::testUrlParsing()
@@ -219,12 +220,17 @@ void KUrlNavigatorTest::testUrlParsing()
void KUrlNavigatorTest::testRelativePaths()
{
#ifdef Q_OS_WIN
QSKIP("Skipping test using a symlink on Windows");
#endif
QTemporaryDir tempDir;
const QString tempDirPath = tempDir.path();
const QString dirA = tempDirPath + QLatin1String("/a");
const QString dirB = tempDirPath + QLatin1String("/a/b");
const QString dirC = tempDirPath + QLatin1String("/.c");
const QString link = tempDirPath + QLatin1String("/l");
const QString trash = QLatin1String("trash:/");
createTestDirectory(dirA);
createTestDirectory(dirB);
createTestDirectory(dirC);
@@ -238,6 +244,7 @@ void KUrlNavigatorTest::testRelativePaths()
const QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath);
const QUrl dirAUrl = QUrl::fromLocalFile(dirA);
const QUrl linkUrl = QUrl::fromLocalFile(link);
const QUrl trashUrl = QUrl(trash);
// Change to tempDir
m_navigator->setLocationUrl(tempDirUrl);
@@ -263,7 +270,7 @@ void KUrlNavigatorTest::testRelativePaths()
// Test "..", which should go up in the dir hierarchy
m_navigator->editor()->setCurrentText(QStringLiteral(".."));
QTest::keyClick(m_navigator->editor(), Qt::Key_Enter);
QTRY_COMPARE(m_navigator->locationUrl(), tempDirUrl);
QTRY_COMPARE(m_navigator->locationUrl().adjusted(QUrl::StripTrailingSlash), tempDirUrl);
// Replace all the text with ".c"
m_navigator->editor()->setCurrentText(QStringLiteral(".c"));
@@ -298,6 +305,14 @@ void KUrlNavigatorTest::testRelativePaths()
m_navigator->editor()->setCurrentText(QStringLiteral("../../l"));
QTest::keyClick(m_navigator->editor(), Qt::Key_Enter);
QTRY_COMPARE(m_navigator->locationUrl(), linkUrl);
// Go to trash:
m_navigator->setLocationUrl(trashUrl);
m_navigator->setUrlEditable(true);
// Replace all the text with "a"
m_navigator->editor()->setCurrentText(QStringLiteral("/a"));
QTest::keyClick(m_navigator->editor(), Qt::Key_Enter);
QTRY_COMPARE(m_navigator->locationUrl(), QUrl::fromLocalFile("/a"));
}
void KUrlNavigatorTest::testFixUrlPath_data()
@@ -45,6 +45,57 @@ void ListDirTest::numFilesTestCase()
QCOMPARE(m_receivedEntryCount, numOfFiles);
}
void ListDirTest::detailsTestCase_data()
{
QTest::addColumn<KIO::StatDetails>("details");
QTest::addColumn<QList<KIO::UDSEntry::StandardFieldTypes>>("expectedFields");
QTest::newRow("StatBasic") << KIO::StatDetails(KIO::StatDetail::StatBasic)
<< QList<KIO::UDSEntry::StandardFieldTypes>{
KIO::UDSEntry::UDS_NAME,
KIO::UDSEntry::UDS_FILE_TYPE,
};
QTest::newRow("StatBasicWithInode") << (KIO::StatDetail::StatBasic | KIO::StatDetail::StatInode)
<< QList<KIO::UDSEntry::StandardFieldTypes>{
KIO::UDSEntry::UDS_INODE,
KIO::UDSEntry::UDS_NAME,
KIO::UDSEntry::UDS_FILE_TYPE,
KIO::UDSEntry::UDS_SIZE,
KIO::UDSEntry::UDS_ACCESS,
KIO::UDSEntry::UDS_DEVICE_ID,
};
}
void ListDirTest::detailsTestCase()
{
#ifdef Q_OS_WIN
QSKIP("Setting details is not supported on Windows");
#endif
QFETCH(KIO::StatDetails, details);
QFETCH(QList<KIO::UDSEntry::StandardFieldTypes>, expectedFields);
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());
const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path());
createEmptyTestFiles(10, tempDir.path());
auto job = std::unique_ptr<KIO::ListJob>(KIO::listDir(dirUrl, KIO::HideProgressInfo, KIO::ListJob::ListFlag::IncludeHidden));
job->setDetails(details);
job->setUiDelegate(nullptr);
QSignalSpy spy(job.get(), &KIO::ListJob::entries);
QVERIFY(spy.wait(1000));
const KIO::UDSEntryList entries = spy.takeFirst().at(1).value<KIO::UDSEntryList>();
for (const KIO::UDSEntry &entry : entries) {
QCOMPARE(entry.count(), expectedFields.count());
for (KIO::UDSEntry::StandardFieldTypes expectedField : expectedFields) {
QVERIFY(entry.contains(expectedField));
}
}
}
void ListDirTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries)
{
m_receivedEntryCount += entries.count();
@@ -22,6 +22,9 @@ private Q_SLOTS:
void numFilesTestCase_data();
void numFilesTestCase();
void detailsTestCase_data();
void detailsTestCase();
void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
private:
@@ -0,0 +1,136 @@
/*
SPDX-FileCopyrightText: 2025 Friedrich W. H. Kossebau <kossebau@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "metadatatest.h"
#include <KIO/MetaData>
#include <QTest>
using namespace Qt::StringLiterals;
// typedef needed for passing _data() method
using TestDataMap = QMap<QString, QString>;
void MetaDataTest::testToVariant()
{
const TestDataMap data = {
{u"keyA"_s, u"valueOne"_s},
{u"keyB"_s, u"valueTwo"_s},
};
// create
KIO::MetaData metaData;
for (const auto &[key, value] : data.asKeyValueRange()) {
metaData.insert(key, value);
}
QCOMPARE(metaData.size(), data.size());
// call to be tested method
const QVariant variant = metaData.toVariant();
// inspect properties
QCOMPARE(variant.typeId(), QMetaType::QVariantMap);
const QVariantMap variantMap = variant.toMap();
QCOMPARE(variantMap.size(), data.size());
auto it = variantMap.begin();
for (const auto &[expectedKey, expectedValue] : data.asKeyValueRange()) {
const QString &mapKey = it.key();
QCOMPARE(mapKey, expectedKey);
const QVariant &mapValue = it.value();
QCOMPARE(mapValue.typeId(), QMetaType::QString);
QCOMPARE(mapValue.toString(), expectedValue);
++it;
}
}
// typedef needed for passing _data() method
// type used as arg type for MetaData::operator+=()
using VariantMap = QMap<QString, QVariant>;
void MetaDataTest::testOperatorPlusEqualVariantMap_data()
{
QTest::addColumn<TestDataMap>("originalData");
QTest::addColumn<VariantMap>("operandData");
QTest::addColumn<TestDataMap>("expectedData");
const QString keyA = u"keyA"_s;
const QString keyB = u"keyB"_s;
const QString valueOne = u"valueOne"_s;
const QString valueTwo = u"valueTwo"_s;
const QString digit3 = u"3"_s;
/* clang-format off */
QTest::newRow("empty+empty")
<< TestDataMap()
<< VariantMap()
<< TestDataMap();
QTest::newRow("A+B")
<< TestDataMap{{keyA, valueOne}}
<< VariantMap{{keyB, QVariant(valueTwo)}}
<< TestDataMap{{keyA, valueOne}, {keyB, valueTwo}};
QTest::newRow("A+3")
<< TestDataMap{{keyA, valueOne}}
<< VariantMap{{keyB, QVariant(3)}}
<< TestDataMap{{keyA, valueOne}, {keyB, digit3}};
QTest::newRow("A+A")
<< TestDataMap{{keyA, valueOne}}
<< VariantMap{{keyA, QVariant(valueOne)}}
<< TestDataMap{{keyA, valueOne}};
QTest::newRow("A+otherA")
<< TestDataMap{{keyA, valueOne}}
<< VariantMap{{keyA, QVariant(valueTwo)}}
<< TestDataMap{{keyA, valueTwo}};
QTest::newRow("A+otherTypeA")
<< TestDataMap{{keyA, valueOne}}
<< VariantMap{{keyA, QVariant(3)}}
<< TestDataMap{{keyA, digit3}};
/* clang-format on */
}
void MetaDataTest::testOperatorPlusEqualVariantMap()
{
QFETCH(const TestDataMap, originalData);
QFETCH(const VariantMap, operandData);
QFETCH(const TestDataMap, expectedData);
// create
KIO::MetaData metaData;
for (const auto &[key, value] : originalData.asKeyValueRange()) {
metaData.insert(key, value);
}
QCOMPARE(metaData.size(), originalData.size());
// call to be tested method
metaData += operandData;
// check result
QCOMPARE(metaData.size(), expectedData.size());
auto it = metaData.begin();
for (const auto &[expectedKey, expectedValue] : expectedData.asKeyValueRange()) {
const QString &metaDataKey = it.key();
QCOMPARE(metaDataKey, expectedKey);
const QString &metaDataValue = it.value();
QCOMPARE(metaDataValue, expectedValue);
++it;
}
}
QTEST_GUILESS_MAIN(MetaDataTest)
#include "moc_metadatatest.cpp"
@@ -0,0 +1,23 @@
/*
SPDX-FileCopyrightText: 2025 Friedrich W. H. Kossebau <kossebau@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KIO_METADATATEST_H
#define KIO_METADATATEST_H
#include <QObject>
class MetaDataTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testToVariant();
void testOperatorPlusEqualVariantMap_data();
void testOperatorPlusEqualVariantMap();
};
#endif
@@ -76,6 +76,8 @@ void MimeTypeFinderJobTest::determineMimeType()
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->mimeType(), mimeType);
// Check that the result is the same when accessing the source, skip on Windows
#ifndef Q_OS_WIN
// Check that the result is the same when accessing the source
// file through a symbolic link (bug #436708)
const QString srcLink = srcDir + QLatin1String("/link_") + fileName;
@@ -85,6 +87,7 @@ void MimeTypeFinderJobTest::determineMimeType()
job = new KIO::MimeTypeFinderJob(linkUrl, this);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->mimeType(), mimeType);
#endif
}
void MimeTypeFinderJobTest::invalidUrl()
@@ -101,7 +101,10 @@ static void createSrcFile(const QString &path)
static QString readFile(const QString &path)
{
QFile file(path);
file.open(QIODevice::ReadOnly);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open file in readonly" << path;
exit(1);
}
return QString::fromLocal8Bit(file.readAll()).trimmed();
}
@@ -168,7 +171,9 @@ void OpenUrlJobTest::startProcess()
// We can find out that it was executed because it writes to "dest".
const QString dest = m_tempDir.path() + "/dest";
QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest));
QCOMPARE(readFile(dest), srcFile);
QTRY_COMPARE(readFile(dest), srcFile);
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
void OpenUrlJobTest::noServiceNoHandler()
@@ -187,7 +192,7 @@ void OpenUrlJobTest::noServiceNoHandler()
void OpenUrlJobTest::invalidUrl()
{
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl(":/"), QStringLiteral("text/plain"), this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl(":/"), QStringLiteral("text/plain"), this));
QVERIFY(!job->exec());
QCOMPARE(job->error(), KIO::ERR_MALFORMED_URL);
QCOMPARE(job->errorString(), QStringLiteral("Malformed URL\nRelative URL's path component contains ':' before any '/'; source was \":/\"; path = \":/\""));
@@ -218,7 +223,7 @@ void OpenUrlJobTest::refuseRunningLocalBinaries()
QFETCH(QString, mimeType);
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(QCoreApplication::applicationFilePath()), mimeType, this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl::fromLocalFile(QCoreApplication::applicationFilePath()), mimeType, this));
QVERIFY(!job->exec());
QCOMPARE(job->error(), KJob::UserDefinedError);
QVERIFY2(job->errorString().contains("For security reasons, launching executables is not allowed in this context."), qPrintable(job->errorString()));
@@ -237,7 +242,7 @@ void OpenUrlJobTest::refuseRunningRemoteNativeExecutables()
QFETCH(QString, mimeType);
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("protocol://host/path/exe"), mimeType, this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl("protocol://host/path/exe"), mimeType, this));
job->setRunExecutables(true); // even with this enabled, an error will occur
QVERIFY(!job->exec());
QCOMPARE(job->error(), KJob::UserDefinedError);
@@ -254,7 +259,7 @@ void OpenUrlJobTest::notAuthorized()
cg.sync();
loadUrlActionRestrictions(cg);
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl("file:///"), QStringLiteral("text/plain"), this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl("file:///"), QStringLiteral("text/plain"), this));
QVERIFY(!job->exec());
QCOMPARE(job->error(), KIO::ERR_ACCESS_DENIED);
QCOMPARE(job->errorString(), QStringLiteral("Access denied to file:///."));
@@ -338,7 +343,7 @@ void OpenUrlJobTest::runNativeExecutable()
// Note that it's missing executable permissions
// When using OpenUrlJob to run the executable
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(scriptFile), mimeType, this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl::fromLocalFile(scriptFile), mimeType, this));
job->setRunExecutables(true); // startProcess tests the case where this isn't set
job->setUiDelegate(new KJobUiDelegate);
@@ -517,7 +522,7 @@ void OpenUrlJobTest::launchExternalBrowser()
dest = m_tempDir.path() + QLatin1String("/dest"); // see the .desktop file in writeApplicationDesktopFile
}
QTRY_VERIFY(QFileInfo::exists(dest)); // TRY because CommandLineLauncherJob finishes immediately
QCOMPARE(readFile(dest), remoteImage.toString());
QTRY_COMPARE(readFile(dest), remoteImage.toString());
// Restore settings
KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")).deleteEntry("BrowserApplication");
@@ -526,7 +531,7 @@ void OpenUrlJobTest::launchExternalBrowser()
void OpenUrlJobTest::nonExistingFile()
{
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(QStringLiteral("/does/not/exist")), this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl::fromLocalFile(QStringLiteral("/does/not/exist")), this));
QVERIFY(!job->exec());
QCOMPARE(job->error(), KIO::ERR_DOES_NOT_EXIST);
QCOMPARE(job->errorString(), "The file or folder /does/not/exist does not exist.");
@@ -556,7 +561,7 @@ void OpenUrlJobTest::ftpUrlWithKIO()
{
// This is just to test the statFile() code at least a bit
const QUrl url(QStringLiteral("ftp://localhost:2")); // unlikely that anything is running on that port
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(url, this));
QVERIFY(!job->exec());
QVERIFY(job->errorString() == QLatin1String("Could not connect to host localhost: Connection refused.")
|| job->errorString() == QLatin1String("Could not connect to host localhost: Network unreachable."));
@@ -570,9 +575,9 @@ void OpenUrlJobTest::takeOverAfterMimeTypeFound()
const QString srcFile = srcDir + QLatin1String("/image.jpg");
createSrcFile(srcFile);
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(srcFile), this);
std::unique_ptr<KIO::OpenUrlJob> job(new KIO::OpenUrlJob(QUrl::fromLocalFile(srcFile), this));
QString foundMime = QStringLiteral("NONE");
connect(job, &KIO::OpenUrlJob::mimeTypeFound, this, [&](const QString &mimeType) {
connect(job.get(), &KIO::OpenUrlJob::mimeTypeFound, this, [&](const QString &mimeType) {
foundMime = mimeType;
job->kill();
});
@@ -195,7 +195,6 @@ void KIOPasteTest::testPasteJob()
QVERIFY(spy.isValid());
QVERIFY(spyCopyJobStarted.isValid());
job->setUiDelegate(nullptr);
const bool expectedSuccess = !expectedFileName.isEmpty();
int copying = 0, moving = 0;
connect(job, &KIO::PasteJob::copyJobStarted, this, [this, &copying, &moving](KIO::CopyJob *copyJob) {
@@ -207,8 +206,8 @@ void KIOPasteTest::testPasteJob()
});
});
QCOMPARE(job->exec(), expectedSuccess);
if (expectedSuccess) {
QCOMPARE(job->exec(), true);
if (!expectedFileName.isEmpty()) {
const QString destFile = destDir + '/' + expectedFileName;
QVERIFY2(QFile::exists(destFile), qPrintable(expectedFileName));
if (isDir) {
@@ -1,120 +0,0 @@
/*
SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "privilegejobtest.h"
#include <QTest>
#include <KIO/ChmodJob>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/MkpathJob>
#include <KIO/SimpleJob>
#include <KIO/TransferJob>
#include "kiotesthelper.h"
QTEST_MAIN(PrivilegeJobTest)
void PrivilegeJobTest::initTestCase()
{
cleanupTestCase();
homeTmpDir();
m_testFilePath = homeTmpDir() + "testfile";
createTestFile(m_testFilePath);
QVERIFY(QFile::exists(m_testFilePath));
QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::ExeOwner));
}
void PrivilegeJobTest::cleanupTestCase()
{
QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
QDir(homeTmpDir()).removeRecursively();
}
void PrivilegeJobTest::privilegeChmod()
{
KFileItem item(QUrl::fromLocalFile(m_testFilePath));
const mode_t origPerm = item.permissions();
mode_t newPerm = origPerm ^ S_IWGRP;
QVERIFY(newPerm != origPerm);
// Remove search permission
QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner));
KFileItemList items;
items << item;
KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP, QString(), QString(), false, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QLatin1String("PrivilegeOperationAllowed"));
// Bring it back
QVERIFY(QFile::setPermissions(homeTmpDir(), QFileDevice::ReadOwner | QFileDevice::ExeOwner));
}
void PrivilegeJobTest::privilegeCopy()
{
const QUrl src = QUrl::fromLocalFile(m_testFilePath);
const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "newtestfile");
KIO::CopyJob *job = KIO::copy(src, dest, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
void PrivilegeJobTest::privilegeDelete()
{
const QUrl url = QUrl::fromLocalFile(m_testFilePath);
KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
void PrivilegeJobTest::privilegeMkpath()
{
const QUrl dirUrl = QUrl::fromLocalFile(homeTmpDir() + "testdir");
KIO::MkpathJob *job = KIO::mkpath(dirUrl, QUrl(), KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
void PrivilegeJobTest::privilegePut()
{
const QUrl url = QUrl::fromLocalFile(homeTmpDir() + "putfile");
KIO::TransferJob *job = KIO::put(url, -1, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
void PrivilegeJobTest::privilegeRename()
{
const QUrl src = QUrl::fromLocalFile(homeTmpDir() + "testfile");
const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "newtestfile");
KIO::SimpleJob *job = KIO::rename(src, dest, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
void PrivilegeJobTest::privileSymlink()
{
const QString target = homeTmpDir() + "testfile";
const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + "symlink");
KIO::SimpleJob *job = KIO::symlink(target, dest, KIO::HideProgressInfo);
job->addMetaData("UnitTesting", "true");
job->setUiDelegate(nullptr);
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->queryMetaData("TestData"), QStringLiteral("PrivilegeOperationAllowed"));
}
#include "moc_privilegejobtest.cpp"
@@ -1,31 +0,0 @@
/*
SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef PRIVILEGEJOBTEST_H
#define PRIVILEGEJOBTEST_H
#include <QObject>
class PrivilegeJobTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void privilegeChmod();
void privilegeCopy();
void privilegeDelete();
void privilegeMkpath();
void privilegePut();
void privilegeRename();
void privileSymlink();
private:
QString m_testFilePath;
};
#endif
@@ -7,85 +7,222 @@
#include <QTest>
#include <QThreadPool>
#include <QtConcurrentRun>
#include <QPointer>
#include <QScopeGuard>
#include <QThread>
#include <QTimer>
#include "kio/filecopyjob.h"
#include "kio/transferjob.h"
#include "kiotesthelper.h" // homeTmpDir, createTestFile etc.
#include "workerthread_p.h" // KIO::WorkerThread test hooks
class KIOThreadTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void concurrentCopying();
void asyncConcurrentCopying();
void copyJobFromThread();
void cancelJobWhileWorkerIsBlocked();
void cancelDoesNotFlushUnrelatedDeferredDeletes();
void cleanupTestCase();
private:
struct FileData;
bool copyLocalFile(FileData *fileData);
static bool copyLocalFile(const QString &src, const QString &dest);
};
void KIOThreadTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
// Start with a clean base dir
cleanupTestCase();
homeTmpDir(); // create it
QCOMPARE(sizeof(int), sizeof(QAtomicInt));
Q_UNUSED(createTestDirectory);
homeTmpDir();
}
void KIOThreadTest::cleanupTestCase()
{
QDir(homeTmpDir()).removeRecursively();
// A worker deref'd from inside an event loop (a job completing or being cancelled
// mid-dispatch) deletes its WorkerThread asynchronously through QThread::finished ->
// deleteLater(), rather than joining the thread under a running loop (that join is the
// bug 468673 deadlock). In a running application the next event-loop iteration reaps
// those threads; a test process exits right after its last loop, so on some Qt versions
// the queued DeferredDelete events are never delivered and LeakSanitizer reports the
// worker threads and their QPluginLoaders as leaked. Force-deliver them here.
// This drain used to live in ~Worker(), but flushing the global queue from a destructor
// re-entrantly freed unrelated objects mid-destruction; doing it once from the test
// teardown is the correct scope.
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
struct KIOThreadTest::FileData {
QString src;
QString dest;
};
bool KIOThreadTest::copyLocalFile(FileData *fileData)
bool KIOThreadTest::copyLocalFile(const QString &src, const QString &dest)
{
// to verify the test harness: return QFile::copy(fileData->src, fileData->dest);
const QUrl u = QUrl::fromLocalFile(fileData->src);
const QUrl d = QUrl::fromLocalFile(fileData->dest);
// copy the file with file_copy
KIO::Job *job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo);
// qDebug() << job << u << d;
const QUrl u = QUrl::fromLocalFile(src);
const QUrl d = QUrl::fromLocalFile(dest);
std::unique_ptr<KIO::Job> job(KIO::file_copy(u, d, -1, KIO::HideProgressInfo));
job->setUiDelegate(nullptr);
bool ret = job->exec();
// qDebug() << job << "done";
return ret;
return job->exec();
}
void KIOThreadTest::concurrentCopying()
void KIOThreadTest::asyncConcurrentCopying()
{
const int numThreads = 20;
QList<FileData> data(numThreads);
for (int i = 0; i < numThreads; ++i) {
data[i].src = homeTmpDir() + "file" + QString::number(i);
data[i].dest = homeTmpDir() + "file" + QString::number(i) + "_copied";
createTestFile(data[i].src);
const int numFiles = 10;
QList<QString> srcs;
QList<QString> dests;
srcs.reserve(numFiles);
dests.reserve(numFiles);
for (int i = 0; i < numFiles; ++i) {
srcs << homeTmpDir() + QLatin1String("file") + QString::number(i);
dests << homeTmpDir() + QLatin1String("file") + QString::number(i) + QLatin1String("_copy");
createTestFile(srcs.last());
}
QThreadPool tp;
tp.setMaxThreadCount(numThreads);
QList<QFuture<bool>> futures(numThreads);
for (int i = 0; i < numThreads; ++i) {
futures[i] = QtConcurrent::run(&tp, &KIOThreadTest::copyLocalFile, this, &data[i]);
}
QVERIFY(tp.waitForDone(60000));
for (int i = 0; i < numThreads; ++i) {
QVERIFY(QFile::exists(data[i].dest));
QVERIFY(futures.at(i).result());
// All jobs are queued before the event loop runs so the scheduler
// dispatches them concurrently from a single SchedulerPrivate instance.
int completedJobs = 0;
QEventLoop loop;
for (int i = 0; i < numFiles; ++i) {
auto *job = KIO::file_copy(QUrl::fromLocalFile(srcs.at(i)), QUrl::fromLocalFile(dests.at(i)), -1, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
connect(job, &KJob::result, this, [&completedJobs, numFiles, &loop](KJob *j) {
QVERIFY(!j->error());
if (++completedJobs == numFiles) {
loop.quit();
}
});
}
QTimer::singleShot(std::chrono::seconds(60), &loop, &QEventLoop::quit);
loop.exec();
QCOMPARE(completedJobs, numFiles);
for (const QString &dest : std::as_const(dests)) {
QVERIFY(QFile::exists(dest));
}
}
void KIOThreadTest::copyJobFromThread()
{
const QString src = homeTmpDir() + QLatin1String("src_thread");
const QString dest = homeTmpDir() + QLatin1String("dst_thread");
createTestFile(src);
// One thread: Q_PLUGIN_INSTANCE is not thread-safe for concurrent first access.
bool jobSucceeded = false;
auto *thread = QThread::create([&src, &dest, &jobSucceeded]() {
jobSucceeded = copyLocalFile(src, dest);
});
thread->start();
QVERIFY(thread->wait(30000));
delete thread;
QVERIFY(jobSucceeded);
QVERIFY(QFile::exists(dest));
}
void KIOThreadTest::cancelJobWhileWorkerIsBlocked()
{
// Regression test for the Worker::deref() deadlock (bug 468673). Cancelling a running job
// derefs its in-process worker, and a worker thread that is mid-transfer can only finish
// once the main thread keeps running its event loop (a real worker blocks in
// waitForBytesWritten() until the main thread drains its socket). If deref() joins that
// thread synchronously while we are inside the event loop dispatching the worker's data,
// the main thread blocks on the join instead of running its event loop, and both threads
// wedge. This used to surface only downstream (Dolphin's concurrent directory-size
// listings); reproduce it here. WorkerThread's BUILD_TESTING exit gate makes the worker's
// "needs the main thread to make progress" dependency deterministic: without the fix this
// test deadlocks and is killed by the harness timeout; with it the loop drains cleanly.
KIO::WorkerThread::setTestExitGateEnabled(true);
auto gateGuard = qScopeGuard([] {
// Make sure no worker (including idle ones torn down at exit) can stay gated.
KIO::WorkerThread::setTestExitGateEnabled(false);
KIO::WorkerThread::releaseTestExitGate();
});
const QString path = homeTmpDir() + QLatin1String("bigfile");
const QByteArray bigData(16 * 1024 * 1024, 'a'); // big enough that data() fires mid-transfer
QFile f(path);
QVERIFY(f.open(QIODevice::WriteOnly));
QCOMPARE(f.write(bigData), qint64(bigData.size()));
f.close();
auto *job = KIO::get(QUrl::fromLocalFile(path), KIO::NoReload, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
QEventLoop loop;
bool cancelled = false;
connect(job, &KIO::TransferJob::data, this, [&](KIO::Job *, const QByteArray &) {
if (cancelled) {
return;
}
cancelled = true;
// Cancel from inside the data dispatch (loopLevel > 0): this derefs the gated worker.
// A synchronous join here would block the main thread before it can release the gate.
job->kill();
// Release the gate and finish - but only from the event loop. With the deadlock bug,
// control never returns here, so the worker stays gated and the test hangs.
QTimer::singleShot(0, this, [] {
KIO::WorkerThread::releaseTestExitGate();
});
QTimer::singleShot(100, &loop, &QEventLoop::quit);
});
// Absolute safety net; with the fix the 100ms timer above quits the loop far sooner.
QTimer::singleShot(std::chrono::seconds(30), &loop, &QEventLoop::quit);
loop.exec();
QVERIFY(cancelled);
}
void KIOThreadTest::cancelDoesNotFlushUnrelatedDeferredDeletes()
{
// Regression test: Worker::~Worker() must not flush the global deferred-delete queue
// (QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete)). ~Worker() runs
// synchronously when a running job is cancelled, and that often happens deep inside an
// unrelated object's destruction. Flushing every pending deleteLater() from there
// re-entrantly deletes objects that are merely scheduled for deletion - or, worse, already
// being destroyed higher up the stack - which crashed on shutdown (a
// new-delete-type-mismatch / use-after-free). Assert the observable behaviour: tearing a
// worker down must not process an unrelated, still-pending deleteLater().
const QString path = homeTmpDir() + QLatin1String("bigfile_flush");
const QByteArray bigData(16 * 1024 * 1024, 'a'); // big enough that data() fires mid-transfer
QFile f(path);
QVERIFY(f.open(QIODevice::WriteOnly));
QCOMPARE(f.write(bigData), qint64(bigData.size()));
f.close();
auto *job = KIO::get(QUrl::fromLocalFile(path), KIO::NoReload, KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
QEventLoop loop;
bool checked = false;
QPointer<QObject> victim;
connect(job, &KIO::TransferJob::data, this, [&](KIO::Job *, const QByteArray &) {
if (checked) {
return;
}
checked = true;
// Schedule an unrelated object for deletion and immediately, in the same
// synchronous block (no event-loop iteration in between, so the deleteLater is
// still pending), cancel the running job. Cancelling derefs and destroys its
// worker synchronously (Worker::kill() -> deref() -> ~Worker()). Only a
// re-entrant flush inside ~Worker() could consume the deleteLater here.
victim = new QObject;
victim->deleteLater();
job->kill();
QVERIFY(victim); // ~Worker() must not have flushed the unrelated deleteLater()
loop.quit();
});
QTimer::singleShot(std::chrono::seconds(30), &loop, &QEventLoop::quit);
loop.exec();
QVERIFY(checked);
QVERIFY(victim); // still alive: the worker teardown did not flush it
// Sanity: a real deferred-delete flush still works and reclaims the victim.
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
QVERIFY(!victim);
}
QTEST_MAIN(KIOThreadTest)
@@ -42,18 +42,21 @@ private Q_SLOTS:
void testAnotherV2Fill();
void testTwoVectorsFill();
void testUDSEntryHSFill();
void testUnionFill();
void testAnotherCompare();
void testTwoVectorKindEntryCompare();
void testAnotherV2Compare();
void testTwoVectorsCompare();
void testUDSEntryHSCompare();
void testUnionCompare();
void testAnotherApp();
void testTwoVectorKindEntryApp();
void testAnotherV2App();
void testTwoVectorsApp();
void testUDSEntryHSApp();
void testUnionApp();
void testspaceUsed();
@@ -213,15 +216,15 @@ class AnotherUDSEntry
{
private:
struct Field {
inline Field()
inline Field() noexcept
{
}
inline Field(const uint index, const QString &value)
inline Field(const uint index, const QString &value) noexcept
: m_str(value)
, m_index(index)
{
}
inline Field(const uint index, long long value = 0)
inline Field(const uint index, long long value = 0) noexcept
: m_long(value)
, m_index(index)
{
@@ -330,12 +333,12 @@ private:
inline Field()
{
}
inline Field(const uint index, const QString &value)
inline Field(const uint index, const QString &value) noexcept
: m_str(value)
, m_index(index)
{
}
inline Field(const uint index, long long value = 0)
inline Field(const uint index, long long value = 0) noexcept
: m_long(value)
, m_index(index)
{
@@ -435,13 +438,13 @@ private:
inline StringField()
{
}
inline StringField(const uint index, const QString &value)
inline StringField(const uint index, const QString &value) noexcept
: m_index(index)
, m_str(value)
{
}
// This operator helps to gain 1ms just comparing the key
inline bool operator==(const StringField &other) const
inline bool operator==(const StringField &other) const noexcept
{
return m_index == other.m_index;
}
@@ -450,16 +453,16 @@ private:
QString m_str;
};
struct NumberField {
inline NumberField()
inline NumberField() noexcept
{
}
inline NumberField(const uint index, long long value = 0)
inline NumberField(const uint index, long long value = 0) noexcept
: m_index(index)
, m_long(value)
{
}
// This operator helps to gain 1ms just comparing the key
inline bool operator==(const NumberField &other) const
inline bool operator==(const NumberField &other) const noexcept
{
return m_index == other.m_index;
}
@@ -553,6 +556,160 @@ public:
};
Q_DECLARE_TYPEINFO(TwoVectorKindEntry, Q_MOVABLE_TYPE);
// Single vector like AnotherUDSEntry, but the value is a union discriminated by the UDS_STRING bit
// already present in the index. This halves the per-field footprint (no unused QString next to every
// number, no unused long long next to every string) and skips constructing a QString for number
// fields entirely.
class UnionUDSEntry
{
private:
struct Field {
uint m_index = 0;
union {
long long m_long;
QString m_str;
};
inline bool isString() const noexcept
{
return m_index & KIO::UDSEntry::UDS_STRING;
}
inline Field() noexcept
: m_long(0)
{
}
inline Field(const uint index, const QString &value)
: m_index(index)
{
new (&m_str) QString(value);
}
inline Field(const uint index, long long value) noexcept
: m_index(index)
, m_long(value)
{
}
inline Field(const Field &other)
: m_index(other.m_index)
{
if (other.isString()) {
new (&m_str) QString(other.m_str);
} else {
m_long = other.m_long;
}
}
inline Field(Field &&other) noexcept
: m_index(other.m_index)
{
if (other.isString()) {
new (&m_str) QString(std::move(other.m_str));
} else {
m_long = other.m_long;
}
}
inline Field &operator=(const Field &other)
{
if (this != &other) {
if (isString()) {
m_str.~QString();
}
m_index = other.m_index;
if (other.isString()) {
new (&m_str) QString(other.m_str);
} else {
m_long = other.m_long;
}
}
return *this;
}
inline Field &operator=(Field &&other) noexcept
{
if (this != &other) {
if (isString()) {
m_str.~QString();
}
m_index = other.m_index;
if (other.isString()) {
new (&m_str) QString(std::move(other.m_str));
} else {
m_long = other.m_long;
}
}
return *this;
}
inline ~Field()
{
if (isString()) {
m_str.~QString();
}
}
};
std::vector<Field> storage;
public:
void reserve(int size)
{
storage.reserve(size);
}
void insert(uint udsField, const QString &value)
{
storage.emplace_back(udsField, value);
}
void replaceOrInsert(uint udsField, const QString &value)
{
for (Field &f : storage) {
if (f.m_index == udsField) {
f.m_str = value;
return;
}
}
storage.emplace_back(udsField, value);
}
void insert(uint udsField, long long value)
{
storage.emplace_back(udsField, value);
}
void replaceOrInsert(uint udsField, long long value)
{
for (Field &f : storage) {
if (f.m_index == udsField) {
f.m_long = value;
return;
}
}
storage.emplace_back(udsField, value);
}
int count() const
{
return storage.size();
}
QString stringValue(uint udsField) const
{
for (const Field &f : storage) {
if (f.m_index == udsField) {
return f.m_str;
}
}
return QString();
}
long long numberValue(uint udsField, long long defaultValue = -1) const
{
for (const Field &f : storage) {
if (f.m_index == udsField) {
return f.m_long;
}
}
return defaultValue;
}
QString spaceUsed()
{
return QStringLiteral("size:%1 space used:%2")
.arg(storage.size() * sizeof(Field) + sizeof(std::vector<Field>))
.arg(storage.capacity() * sizeof(Field) + sizeof(std::vector<Field>));
}
};
Q_DECLARE_TYPEINFO(UnionUDSEntry, Q_RELOCATABLE_TYPE);
template<class T>
static void fillUDSEntries(T &entry, time_t now_time_t, const QString &nameStr, const QString &groupStr)
{
@@ -698,6 +855,19 @@ void UdsEntryBenchmark::testUDSEntryHSApp()
testApp<UDSEntryHS>(this);
}
void UdsEntryBenchmark::testUnionFill()
{
testFill<UnionUDSEntry>(this);
}
void UdsEntryBenchmark::testUnionCompare()
{
testCompare<UnionUDSEntry>(this);
}
void UdsEntryBenchmark::testUnionApp()
{
testApp<UnionUDSEntry>(this);
}
template<class T>
void printSpaceUsed(UdsEntryBenchmark *bench)
{
@@ -713,6 +883,7 @@ void UdsEntryBenchmark::testspaceUsed()
printSpaceUsed<AnotherV2UDSEntry>(this);
printSpaceUsed<TwoVectorKindEntry>(this);
printSpaceUsed<UDSEntryHS>(this);
printSpaceUsed<UnionUDSEntry>(this);
}
QTEST_MAIN(UdsEntryBenchmark)
@@ -8,7 +8,7 @@
#include <QTest>
/**
/*!
* This benchmarks tests four typical uses of UDSEntry:
*
* (a) Store data in UDSEntries using
@@ -42,7 +42,7 @@ struct UDSTestField {
long long m_long;
};
/**
/*!
* Test that storing UDSEntries to a stream and then re-loading them works.
*/
void UDSEntryTest::testSaveLoad()
@@ -192,7 +192,7 @@ void UDSEntryTest::testSaveLoad()
}
}
/**
/*!
* Test to verify that move semantics work. This is only useful when ran through callgrind.
*/
void UDSEntryTest::testMove()
@@ -239,7 +239,7 @@ void UDSEntryTest::testMove()
}
}
/**
/*!
* Test to verify that equal semantics work.
*/
void UDSEntryTest::testEquality()
@@ -11,11 +11,11 @@
#include <kio/storedtransferjob.h>
#include <QBuffer>
#include <QObject>
#include <QProcess>
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTest>
#include <qobject.h>
class WebDAVTest : public QObject
{
@@ -0,0 +1,121 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2026 Méven Car <meven@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QEventLoop>
#include <QStandardPaths>
#include <QTest>
#include <QThread>
#include <atomic>
#include "kio/listjob.h"
#include "worker_p.h" // KIO::Worker::setTestWorkerFactory
#include "workerbase.h"
#include "workerfactory.h"
namespace
{
// A mock worker whose listDir() polls wasKilled() the way a real readdir loop must, recording
// whether the cancellation was delivered into the running call.
struct ListWorkerControl {
std::atomic<bool> startedListing{false};
std::atomic<bool> sawKilled{false};
std::atomic<bool> completedNormally{false};
void reset()
{
startedListing = false;
sawKilled = false;
completedNormally = false;
}
};
ListWorkerControl g_listWorker;
// A delivered cancellation reaches the worker within milliseconds, so this only bounds how long the
// test waits before treating the run as a failure.
constexpr int s_killTimeoutMs = 1000;
}
class WorkerCancelTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
}
void listDirObservesKill()
{
// A worker's long listDir() loop must be able to observe cancellation and bail (kio_file's
// readdir loop and kio_smb's listing both need this). The cancel signal already reaches the
// worker as wasKilled(). Verify it is delivered into a running listDir() so a polling loop
// stops instead of running to completion. A worker that does poll then stops promptly.
g_listWorker.reset();
class Factory : public KIO::WorkerFactory
{
public:
using KIO::WorkerFactory::WorkerFactory;
std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override
{
class ListWorker : public KIO::WorkerBase
{
public:
ListWorker(const QByteArray &pool, const QByteArray &app)
: WorkerBase(QByteArrayLiteral("kio-test"), pool, app)
{
}
KIO::WorkerResult listDir(const QUrl &) override
{
g_listWorker.startedListing.store(true);
// Stand in for a long readdir loop that polls wasKilled() every iteration.
QElapsedTimer elapsed;
elapsed.start();
while (!wasKilled() && elapsed.elapsed() < s_killTimeoutMs) {
QThread::msleep(1);
}
if (wasKilled()) {
g_listWorker.sawKilled.store(true);
} else {
g_listWorker.completedNormally.store(true);
}
return KIO::WorkerResult::pass();
}
};
return std::unique_ptr<KIO::WorkerBase>(new ListWorker(pool, app));
}
};
auto factory = std::make_shared<Factory>();
KIO::Worker::setTestWorkerFactory(factory);
auto *job = KIO::listDir(QUrl(QStringLiteral("kio-test://foo")), KIO::HideProgressInfo);
job->setUiDelegate(nullptr);
// Wait until the worker is inside listDir().
QElapsedTimer waited;
waited.start();
while (!g_listWorker.startedListing.load() && waited.elapsed() < s_killTimeoutMs) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
}
QVERIFY2(g_listWorker.startedListing.load(),
"timed out waiting for the worker to enter listDir() - the kio-test job was never dispatched to the worker");
// kill() aborts the worker (sets wasKilled) then joins it. The worker's listDir() must see
// the cancellation and return.
job->kill(KJob::EmitResult);
QVERIFY2(g_listWorker.sawKilled.load(),
"the worker ran to its timeout without observing wasKilled() - the kill was not delivered into the running listDir()");
QVERIFY(!g_listWorker.completedNormally.load());
}
};
QTEST_MAIN(WorkerCancelTest)
#include "workercanceltest.moc"
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<suppressions>
<suppress>
<!-- d_func() in hierachies -->
<!-- d_func() in hierarchies -->
<id>duplInheritedMember</id>
<fileName>src/*</fileName>
<symbolName>d_func</symbolName>
@@ -1,31 +0,0 @@
### KApiDox Project-specific Overrides File
# workers are plugins and not part of the API
EXCLUDE_PATTERNS += \
*/kioworkers/*
# define so that deprecated API is not skipped
PREDEFINED += \
"KIOCORE_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"KIOCORE_BUILD_DEPRECATED_SINCE(x, y)=1" \
"KIOCORE_DEPRECATED_VERSION(x, y, t)=" \
"KIOCORE_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOCORE_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \
"KIOCORE_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOGUI_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"KIOGUI_BUILD_DEPRECATED_SINCE(x, y)=1" \
"KIOGUI_DEPRECATED_VERSION(x, y, t)=" \
"KIOGUI_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOGUI_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \
"KIOGUI_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOWIDGETS_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"KIOWIDGETS_BUILD_DEPRECATED_SINCE(x, y)=1" \
"KIOWIDGETS_DEPRECATED_VERSION(x, y, t)=" \
"KIOWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOWIDGETS_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \
"KIOWIDGETS_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOFILEWIDGETS_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(x, y)=1" \
"KIOFILEWIDGETS_DEPRECATED_VERSION(x, y, t)=" \
"KIOFILEWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"KIOFILEWIDGETS_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \
"KIOFILEWIDGETS_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)="
@@ -38,12 +38,7 @@ configured to allow users to upload files.</para>
files at &FTP; archive sites throughout the world.
</para>
<para>
Source: Paraphrased from <ulink
url="http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm">
http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm</ulink>
</para>
<para> See the RFC: <ulink url="https://datatracker.ietf.org/doc/html/rfc959">https://datatracker.ietf.org/doc/html/rfc959</ulink>.</para>
<para> See the manual: <ulink url="man:/ftp">ftp</ulink>.</para>
@@ -5,21 +5,6 @@ Applications can provide "metadata" to the workers. Metadata can influence
the behavior of a worker and is usually protocol dependent. MetaData consists
of two strings: a "key" and a "value".
Any meta data whose "key" starts with the keywords {internal~currenthost} and
"{internal~allhosts}" will be treated as internal metadata and will not be made
available to client applications. Instead all such meta-data will be stored and
sent back to the appropriate KIO workers along with the other regular metadata values.
Use "{internal~currenthost}" to make the internal metadata available to all
KIO workers of the same protocol and host as the workers that generated it. If
you do not want to restrict the availability of the internal metadata to only
the current host, then use {internal~allhosts}. In either case the internal
metadata follows the rules of the regular metadata and therefore cannot be sent
from one protocol such as "http" to a completely different one like "ftp".
Please note that when internal meta-data values are sent back to KIO workers, the
keyword used to mark them internal will be stripped from the key name.
The following keys are currently in use:
Key Value(s) Description
@@ -15,9 +15,9 @@ framework-dependencies:
# kio calls org.kde.kded5 via dbus
- KDED
libraries:
- cmake: "KF6::KIOCore"
- cmake: "KF6::KIOFileWidgets"
- cmake: "KF6::KIOWidgets"
- cmake: "KF6::KIOCore"
- cmake: "KF6::KIOFileWidgets"
- cmake: "KF6::KIOWidgets"
cmakename: KF6KIO
public_lib: true
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -31,11 +31,10 @@
<para
>L'&FTP; és habitualment emprat per a recuperar informació i obtenir programari emmagatzemat en fitxers als llocs d'arxiu &FTP; per tot el món. </para>
<para
>Font: parafrasejada de <ulink url="http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm"
>http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm</ulink
> </para>
>Vegeu el RFC: <ulink url="https://datatracker.ietf.org/doc/html/rfc959"
>https://datatracker.ietf.org/doc/html/rfc959</ulink
>.</para>
<para
>Mireu el manual: <ulink url="man:/ftp"
File diff suppressed because it is too large Load Diff
@@ -47,26 +47,18 @@
<para
>L'últim exemple oferix un document de text. Per a documents &HTML; s'haurà d'especificar el tipus &MIME; <literal
>text/html</literal
>: <quote
><userinput
>: «<userinput
>data:text/html,&lt;title&gt;Testcase&lt;/title&gt;&lt;p&gt;Este és un cas de prova&lt;/p&gt;</userinput
></quote
>. Presentarà exactament la mateixa eixida que si el contingut fora carregat des d'un document separat. </para>
>». Presentarà exactament la mateixa eixida que si el contingut fora carregat des d'un document separat. </para>
<para
>També és possible especificar un joc de caràcters diferent. Cal tindre en compte que els caràcters de 8 bits s'han de representar per un signe de percentatge i dos dígits en codi hexadecimal: <quote
><userinput
>També és possible especificar un joc de caràcters diferent. Cal tindre en compte que els caràcters de 8 bits s'han de representar per un signe de percentatge i dos dígits en codi hexadecimal: «<userinput
>data:;charset=iso-8859-1,Ens indica qu%E8 %E9s el que ha de menester un ping%FC%ED</userinput
></quote
>, el qual donarà com a resultat <quote
><literal
>», el qual donarà com a resultat «<literal
>Ens indica qu&egrave; &eacute;s el que ha de menester un ping&uuml;&iacute;</literal
></quote
>, mentre que ometre l'atribut del joc de caràcters ens durà a alguna cosa com <quote
><literal
>», mentre que ometre l'atribut del joc de caràcters ens durà a alguna cosa com «<literal
>Ens indica qu? ?s el que ha de menester un ping??</literal
></quote
>. </para>
>». </para>
<para
>L'<ulink url="https://www.ietf.org/rfc/rfc2397.txt"
@@ -18,7 +18,7 @@
</articleinfo>
<para
>&FTP; és el servei d'Internet emprat per a transferir un fitxer de dades des del disc d'un ordinador cap al disc d'un altre, independentment del tipus de sistema operatiu. </para>
>&FTP; és el servei d'Internet emprat per a transferir un fitxer de dades des del disc d'un ordinador al disc des d'un altre, independentment del tipus de sistema operatiu. </para>
<para
>De mode pareixent a altres aplicacions d'Internet, &FTP; utilitza l'aproximació client-servidor &mdash; un usuari invoca un programa &FTP; en l'ordinador, li indica que contacte amb una màquina remota i després sol·licita la transferència d'un o més fitxers. El programa local d'&FTP; es convertix en un client que utilitza <acronym
@@ -26,23 +26,18 @@
> per a contactar amb un programa servidor d'&FTP; en l'ordinador remot. Cada vegada que un usuari sol·licita una transferència de fitxers, els programes client i servidor cooperen per a enviar una còpia de dades a través de la Internet.</para>
<para
>Els servidors &FTP; que accepten <quote
>&FTP; anònim</quote
> permeten que qualsevol usuari, no només usuaris amb comptes en l'amfitrió, naveguen pels arxius <quote
>ftp</quote
> i els descarreguen. Alguns servidors &FTP; també estan configurats per a permetre que els usuaris hi pugen fitxers.</para>
>Els servidors &FTP; que accepten «&FTP; anònim» permeten que qualsevol usuari, no només usuaris amb comptes en la màquina, naveguen pels arxius «ftp» i els descarreguen. Alguns servidors &FTP; també estan configurats per a permetre que els usuaris hi pugen fitxers.</para>
<para
>&FTP; és habitualment emprat per a recuperar informació i obtindre programari emmagatzemat en fitxers als llocs d'arxiu &FTP; per tot el món. </para>
>&FTP; és habitualment emprat per a recuperar informació i obtindre programari emmagatzemat en fitxers en els llocs d'arxiu &FTP; per tot el món. </para>
<para
>Font: parafrasejada de <ulink url="http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm"
>http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm</ulink
> </para>
>Vegeu el RFC: <ulink url="https://datatracker.ietf.org/doc/html/rfc959"
>https://datatracker.ietf.org/doc/html/rfc959</ulink
>.</para>
<para
>Vegeu el manual: <ulink url="man:/ftp"
>Mireu el manual: <ulink url="man:/ftp"
>ftp</ulink
>.</para>
@@ -39,7 +39,7 @@
<tip
><para
>Per favor, no envieu un correu electrònic a l'autor d'esta pàgina per a trobar el document que falta. Només vos dirà que seguiu les instruccions en esta pàgina. </para
>Per favor, no envieu un correu electrònic a l'autor d'esta pàgina per a trobar el document que falta. Només vos dirà que seguiu les instruccions d'esta pàgina. </para
></tip>
<simplesect>
@@ -52,17 +52,17 @@
>. Si la trobeu en este lloc, la vostra distribució la pot estar enviant en un paquet a part (&pex;, anomenat plasma-doc per a la documentació relacionada amb &plasma;). Per favor, utilitzeu el gestor de paquets de la vostra distribució per a trobar i instal·lar la documentació que falta.</para>
<para
>Si utilitzeu una distribució basada en codi font, com la Gentoo, assegureu-vos que no hi ha cap opció de configuració (indicadors «USE» en la Gentoo) que podrien haver desactivat la instal·lació de la documentació. </para>
>Si utilitzeu una distribució basada en codi font, com Gentoo, assegureu-vos que no hi ha cap opció de configuració (indicadors «USE» en Gentoo) que podrien haver desactivat la instal·lació de la documentació. </para>
<para
>Si ja ho heu fet, però encara obteniu esta pàgina en comptes del manual de l'aplicació, probablement heu trobat un error en el sistema d'ajuda. En este cas, informeu-ne en el <ulink url="https://bugs.kde.org/"
>Seguidor d'errors de &kde;</ulink
> (escriviu-hi en anglés) davall el producte KIO. </para>
> (escriviu-hi en anglés). </para>
<para
>Si no trobeu cap documentació en el <ulink url="https://docs.kde.org/"
>lloc &kde; Documentation</ulink
>, l'aplicació podria no disposar de documentació fora de línia. Informeu-ne en el <ulink url="https://bugs.kde.org/"
>lloc de la documentació de &kde;</ulink
>, l'aplicació podria no disposar de documentació fora de línia. Informeu-ne al <ulink url="https://bugs.kde.org/"
>Seguidor d'errors de &kde;</ulink
> (escriviu-hi en anglés) davall el producte per a l'aplicació. </para>
@@ -74,7 +74,7 @@
>. </para>
<para
>Per a aplicacions no &kde;, Poseu-vos en contacte amb l'autor de l'aplicació per a determinar si hi ha disponible documentació fora de línia.</para>
>Per a aplicacions no &kde;, poseu-vos en contacte amb l'autor de l'aplicació per a determinar si hi ha disponible documentació fora de línia.</para>
</simplesect>
</article>
@@ -20,7 +20,7 @@
<para
>El sistema d'ajuda de &kde; </para>
<para
>Vegeu <ulink url="help:/khelpcenter/index.html"
>Mireu <ulink url="help:/khelpcenter/index.html"
>&khelpcenter;</ulink
>. </para>
@@ -29,17 +29,17 @@
>rotocol.</para>
<para
>El procés de treball «http» és utilitzat per totes les aplicacions &kde; per a gestionar les connexions amb servidors &HTTP;, és a dir, amb servidors web. L'ús més comú és visualitzar pàgines web en el navegador web &konqueror;.</para>
>El procés de treball KIO http és utilitzat per totes les aplicacions &kde; per a gestionar les connexions a servidors &HTTP;, és a dir, a servidors web. L'ús més comú és visualitzar pàgines web en el navegador web &konqueror;.</para>
<para
>Podeu utilitzar el procés de treball «https» en &konqueror; introduint un &URL;. <userinput
>Podeu utilitzar el procés de treball KIO https en &konqueror; introduint un &URL;. <userinput
>https://<replaceable
>www.kde.org</replaceable
></userinput
>.</para>
<para
>«https» és «http» encapsulat en un flux SSL/TLS.</para>
>https és http encapsulat en un flux SSL/TLS.</para>
<para
>SSL és el protocol «Secure Sockets Layer» (Capa de Connexió Segura), un protocol de seguretat que proporciona privadesa de comunicacions a Internet. El protocol permet que les aplicacions client/servidor es comuniquen d'una forma que està dissenyada per a previndre l'escolta, l'alteració i la falsificació de missatges. </para>
<para
@@ -28,7 +28,7 @@
>A</emphasis
>uthoring i <emphasis
>V</emphasis
>ersioning) per a la World Wide Web. Us permetrà la gestió senzilla de documents i scripts en un servidor &HTTP;, i disposa de característiques addicionals dissenyades per a simplificar la gestió de versions entre múltiples autors.</para>
>ersioning) per a la World Wide Web. Us permetrà la gestió senzilla de documents i scripts en un servidor &HTTP;, i disposa de característiques addicionals dissenyades per a simplificar la gestió de versions entre diversos autors.</para>
<para
>L'ús d'este protocol és simple. Introduïu la ubicació que voleu veure, pareixent a un &URL; &HTTP; excepte pel prefix del nom del protocol webdav://. Per exemple, <userinput
@@ -55,7 +55,7 @@
<para
><acronym
>WebDAV</acronym
> permet l'accés a l'script que invoca per a presentar la pàgina específica, de manera que es poden fer canvis en el mateix script.</para>
> us permet l'accés a l'script que invoca per a presentar la pàgina específica, de manera que es poden fer canvis en el mateix script.</para>
</listitem>
</varlistentry>
<varlistentry>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -46,28 +46,23 @@
</articleinfo>
<para
>&FTP; es el servicio de Internet utilizado para transferir un archivo de datos desde el disco de un equipo al disco de otro independientemente del tipo de sistema operativo. </para>
>&FTP; es el servicio de Internet que se usa para transferir un archivo de datos desde el disco de un equipo al disco de otro independientemente del tipo de sistema operativo. </para>
<para
>Al igual que con otras aplicaciones de Internet, &FTP; utiliza la aproximación cliente-servidor &mdash; un usuario invoca un programa &FTP; en el equipo, le indica que contacte con un equipo remoto y después solicita la transferencia de uno o más archivos. El programa local de &FTP; se convierte en un cliente que utiliza <acronym
>Al igual que con otras aplicaciones de Internet, &FTP; usa la aproximación cliente-servidor: un usuario invoca un programa &FTP; en el equipo, le indica que contacte con un equipo remoto y después solicita la transferencia de uno o más archivos. El programa local de &FTP; se convierte en un cliente que usa <acronym
>TCP</acronym
> para contactar con un servidor &FTP; en el equipo remoto. Cada vez que un usuario solicita una transferencia de archivos, los programas cliente y servidor cooperan para enviar una copia de datos a través de Internet.</para>
> para contactar con un servidor &FTP; en el equipo remoto. Cada vez que un usuario solicita una transferencia de archivos, los programas cliente y servidor cooperan para enviar una copia de los datos a través de Internet.</para>
<para
>Los servidores &FTP; que permiten <quote
>&FTP; anónimo</quote
> permiten que cualquier usuario, no solo usuarios con cuentas en la máquina, naveguen por los archivos comprimidos <quote
>ftp</quote
> y los descarguen. Algunos servidores &FTP; están configurados para permitir que los usuarios carguen archivos en ellos.</para>
>Los servidores &FTP; que usan «&FTP; anónimo» permiten que cualquier usuario, no solo los que tienen cuentas en la máquina, naveguen por los archivos comprimidos «ftp» y los descarguen. Algunos servidores &FTP; están configurados para permitir que los usuarios carguen archivos en ellos.</para>
<para
>El &FTP; se utiliza habitualmente para recuperar información y obtener software almacenado en sitios &FTP; de archivos comprimidos por todo el mundo. </para>
>El &FTP; se usa habitualmente para recuperar información y obtener software almacenado en sitios &FTP; de archivos comprimidos de todo el mundo. </para>
<para
>Fuente: Parafraseado de <ulink url="http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm"
> http://tlc.nlm.nih.gov/resources/tutorials/internetdistlrn/ftpdef.htm</ulink
> </para>
>Consulte la RFC: <ulink url="https://datatracker.ietf.org/doc/html/rfc959"
>https://datatracker.ietf.org/doc/html/rfc959</ulink
>.</para>
<para
>Consulte el manual: <ulink url="man:/ftp"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -58,7 +58,7 @@
><userinput
>data:;charset=iso-8859-1,Gr%FC%DFe aus Schl%E4gl</userinput
></quote
> produit la chaîne <quote
> produit la chaine <quote
><literal
>Gr&uuml;&szlig;e aus Schl&auml;gl</literal
></quote

Some files were not shown because too many files have changed in this diff Show More