init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
set(KCOREADDONS_INTERNAL_SKIP_PLUGIN_INSTALLATION ON)
include(ECMAddTests)
include(ConfigureChecks.cmake) #configure checks for QFileSystemWatcher
include(../KF5CoreAddonsMacros.cmake)
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
message(STATUS "QtTest not found, autotests will not be built.")
return()
endif()
if(NOT CMAKE_BUILD_TYPE MATCHES "[Dd]ebug$")
set(ENABLE_BENCHMARKS 1)
endif()
add_library(autotests_static STATIC)
# Needed to link this static lib to shared libs
set_property(TARGET autotests_static PROPERTY POSITION_INDEPENDENT_CODE ON)
ecm_qt_declare_logging_category(autotests_static
HEADER kcoreaddons_debug.h
IDENTIFIER KCOREADDONS_DEBUG
CATEGORY_NAME kf.coreaddons
)
target_link_libraries(autotests_static Qt${QT_MAJOR_VERSION}::Core)
configure_file(config-tests.h.in config-tests.h)
macro(build_plugin pname)
add_library(${pname} MODULE ${ARGN})
ecm_mark_as_test(${pname})
target_link_libraries(${pname} KF5::CoreAddons autotests_static)
endmacro()
# Build some sample plugins
build_plugin(jsonplugin jsonplugin.cpp)
build_plugin(jsonplugin2 jsonplugin2.cpp)
build_plugin(versionedplugin versionedplugin.cpp)
build_plugin(unversionedplugin unversionedplugin.cpp)
build_plugin(multiplugin multiplugin.cpp)
build_plugin(alwaysunloadplugin alwaysunloadplugin.cpp)
build_plugin(qtplugin qtplugin.cpp)
kcoreaddons_add_plugin(jsonplugin_cmake_macro SOURCES kpluginclass.cpp INSTALL_NAMESPACE "namespace")
ecm_mark_as_test(jsonplugin_cmake_macro)
target_link_libraries(jsonplugin_cmake_macro KF5::CoreAddons autotests_static)
kcoreaddons_add_plugin(pluginwithoutmetadata SOURCES pluginwithoutmetadata.cpp INSTALL_NAMESPACE "namespace")
ecm_mark_as_test(pluginwithoutmetadata)
target_link_libraries(pluginwithoutmetadata KF5::CoreAddons autotests_static)
add_library(org.kde.test MODULE jsonplugin.cpp)
target_link_libraries(org.kde.test KF5::CoreAddons)
add_definitions( -DKDELIBS4CONFIGMIGRATOR_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
if (WIN32)
set(autotests_OPTIONAL_SRCS
${autotests_OPTIONAL_SRCS}
klistopenfilesjobtest_win.cpp
)
endif ()
if (UNIX)
set(autotests_OPTIONAL_SRCS
${autotests_OPTIONAL_SRCS}
klistopenfilesjobtest_unix.cpp
)
endif ()
if (NOT ${QT_MAJOR_VERSION} STREQUAL "6")
set(autotests_OPTIONAL_SRCS
${autotests_OPTIONAL_SRCS}
kdelibs4migrationtest.cpp
kdelibs4configmigratortest.cpp
)
endif ()
ecm_add_tests(
kaboutdatatest.cpp
kaboutdataapplicationdatatest.cpp
kautosavefiletest.cpp
kcompositejobtest.cpp
kformattest.cpp
kjobtest.cpp
kosreleasetest.cpp
kpluginfactorytest.cpp
kpluginloadertest.cpp
kpluginmetadatatest.cpp
kprocesstest.cpp
krandomtest.cpp
kshareddatacachetest.cpp
kshelltest.cpp
kurlmimedatatest.cpp
kstringhandlertest.cpp
kmacroexpandertest.cpp
kusertest.cpp
kprocesslisttest.cpp
kfileutilstest.cpp
kfuzzymatchertest.cpp
knetworkmountstestcanonical.cpp
knetworkmountstestnoconfig.cpp
knetworkmountstestpaths.cpp
knetworkmountsteststatic.cpp
klibexectest.cpp
kmemoryinfotest.cpp
kruntimeplatformtest.cpp
${autotests_OPTIONAL_SRCS}
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test KF5::CoreAddons autotests_static
)
if(TARGET klistopenfilesjobtest_unix AND CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
find_package(Qt${QT_MAJOR_VERSION}Network ${REQUIRED_QT_VERSION} CONFIG QUIET)
target_link_libraries(klistopenfilesjobtest_unix Qt${QT_MAJOR_VERSION}::Network)
endif()
kcoreaddons_add_plugin(static_jsonplugin_cmake_macro SOURCES statickpluginclass.cpp INSTALL_NAMESPACE "staticnamespace" STATIC)
target_link_libraries(static_jsonplugin_cmake_macro KF5::CoreAddons autotests_static)
kcoreaddons_add_plugin(static_jsonplugin_cmake_macro_2 SOURCES statickpluginclass_2.cpp INSTALL_NAMESPACE "staticnamespace2" STATIC)
target_link_libraries(static_jsonplugin_cmake_macro_2 KF5::CoreAddons autotests_static)
kcoreaddons_add_plugin(static_plugin_without_metadata SOURCES staticpluginwithoutmetadata.cpp INSTALL_NAMESPACE "staticnamespace3" STATIC)
target_link_libraries(static_plugin_without_metadata KF5::CoreAddons autotests_static)
kcoreaddons_target_static_plugins(kpluginfactorytest "staticnamespace")
kcoreaddons_target_static_plugins(kpluginfactorytest "staticnamespace2")
kcoreaddons_target_static_plugins(kpluginmetadatatest "staticnamespace")
kcoreaddons_target_static_plugins(kpluginmetadatatest "staticnamespace2")
kcoreaddons_target_static_plugins(kpluginmetadatatest "staticnamespace3")
if(NOT CMAKE_CROSSCOMPILING)
ecm_add_tests(desktoptojsontest.cpp LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test KF5::CoreAddons autotests_static)
target_compile_definitions(desktoptojsontest PRIVATE
DESKTOP_TO_JSON_EXE="$<TARGET_FILE:desktoptojson>"
)
endif()
add_library(ktexttohtmlteststatic STATIC ${CMAKE_SOURCE_DIR}/src/lib/text/ktexttohtml.cpp)
# include the binary dir in order to get kcoreaddons_export.h
target_include_directories(ktexttohtmlteststatic PUBLIC ${KCoreAddons_BINARY_DIR}/src/lib)
# fake static linking to prevent the export macros on Windows from kicking in
target_compile_definitions(ktexttohtmlteststatic PUBLIC -DKCOREADDONS_STATIC_DEFINE=1)
target_link_libraries(ktexttohtmlteststatic PUBLIC Qt${QT_MAJOR_VERSION}::Test autotests_static)
ecm_add_test(ktexttohtmltest.cpp
TEST_NAME ktexttohtmltest
LINK_LIBRARIES ktexttohtmlteststatic
)
add_executable(ktexttohtmlbenchmarktest ktexttohtmlbenchmarktest.cpp ${CMAKE_SOURCE_DIR}/src/lib/text/ktexttohtml.cpp)
target_link_libraries(ktexttohtmlbenchmarktest PUBLIC ktexttohtmlteststatic)
add_executable(kprocesstest_helper kprocesstest_helper.cpp)
target_link_libraries(kprocesstest_helper KF5::CoreAddons)
target_compile_definitions(kpluginloadertest PRIVATE
JSONPLUGIN_FILE="$<TARGET_FILE:jsonplugin>"
VERSIONEDPLUGIN_FILE="$<TARGET_FILE:versionedplugin>"
UNVERSIONEDPLUGIN_FILE="$<TARGET_FILE:unversionedplugin>"
MULTIPLUGIN_FILE="$<TARGET_FILE:multiplugin>"
ALWAYSUNLOADPLUGIN_FILE="$<TARGET_FILE:alwaysunloadplugin>"
)
set(KDIRWATCH_BACKENDS_TO_TEST Stat)#Stat is always compiled
if (HAVE_SYS_INOTIFY_H)
list(APPEND KDIRWATCH_BACKENDS_TO_TEST INotify)
endif()
if (HAVE_FAM)
list(APPEND KDIRWATCH_BACKENDS_TO_TEST Fam)
endif()
if (HAVE_QFILESYSTEMWATCHER)
list(APPEND KDIRWATCH_BACKENDS_TO_TEST QFSWatch)
endif()
foreach(_backendName ${KDIRWATCH_BACKENDS_TO_TEST})
string(TOLOWER ${_backendName} _lowercaseBackendName)
set(BACKEND_TEST_TARGET kdirwatch_${_lowercaseBackendName}_unittest)
set(BACKEND_BENCHMARK_TARGET kdirwatch_${_lowercaseBackendName}_benchmarktest)
add_executable(${BACKEND_TEST_TARGET} kdirwatch_unittest.cpp)
target_link_libraries(${BACKEND_TEST_TARGET} Qt${QT_MAJOR_VERSION}::Test KF5::CoreAddons autotests_static)
if(FAM_FOUND)
target_include_directories(${BACKEND_TEST_TARGET} PRIVATE ${FAM_INCLUDE_DIR})
target_link_libraries(${BACKEND_TEST_TARGET} ${FAM_LIBRARIES})
endif()
if(NOT WIN32)
target_link_libraries(${BACKEND_TEST_TARGET} Threads::Threads)
endif()
ecm_mark_as_test(${BACKEND_TEST_TARGET})
add_test(NAME ${BACKEND_TEST_TARGET} COMMAND ${BACKEND_TEST_TARGET})
target_compile_definitions(${BACKEND_TEST_TARGET} PUBLIC -DKDIRWATCH_TEST_METHOD=\"${_backendName}\")
add_executable(${BACKEND_BENCHMARK_TARGET} kdirwatch_benchmarktest.cpp)
target_compile_definitions(${BACKEND_BENCHMARK_TARGET} PUBLIC -DKDIRWATCH_TEST_METHOD=\"${_backendName}\")
target_link_libraries(${BACKEND_BENCHMARK_TARGET} Qt${QT_MAJOR_VERSION}::Test KF5::CoreAddons autotests_static)
if(NOT WIN32)
target_link_libraries(${BACKEND_BENCHMARK_TARGET} Threads::Threads)
endif()
endforeach()

View File

@@ -0,0 +1,9 @@
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_MAJOR_VERSION}::Core)
check_cxx_source_compiles(
"#include <QtCore/QFileSystemWatcher>
int main()
{
QFileSystemWatcher *watcher = new QFileSystemWatcher();
delete watcher;
return 0;
}" HAVE_QFILESYSTEMWATCHER)

View File

@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "alwaysunloadplugin.h"
#include "kcoreaddons_debug.h"
#include <QDebug>
#include <kexportplugin.h>
#include <kpluginfactory.h>
AlwaysUnloadPlugin::AlwaysUnloadPlugin(QObject *parent, const QVariantList &args)
: QObject(parent)
{
qCDebug(KCOREADDONS_DEBUG) << "Created AlwaysUnloadPlugin with args" << args;
}
K_PLUGIN_FACTORY(AlwaysUnloadPluginFactory, registerPlugin<AlwaysUnloadPlugin>();)
#include "alwaysunloadplugin.moc"
#include "moc_alwaysunloadplugin.cpp"

View File

@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef ALWAYSUNLOADPLUGIN_H
#define ALWAYSUNLOADPLUGIN_H
#include <QObject>
class AlwaysUnloadPlugin : public QObject
{
Q_OBJECT
public:
AlwaysUnloadPlugin(QObject *parent, const QVariantList &args);
};
#endif // ALWAYSUNLOADPLUGIN_H

View File

@@ -0,0 +1 @@
#cmakedefine01 ENABLE_BENCHMARKS

View File

View File

@@ -0,0 +1,90 @@
[Desktop Entry]
Name=NSA Plugin
Name[ast]=Complementu NSA
Name[bs]=NSA dodatak
Name[ca]=Connector de la NSA
Name[ca@valencia]=Connector de la NSA
Name[cs]=Modul NSA
Name[da]=NSA-plugin
Name[de]=NSA-Modul
Name[el]=NSA Plugin
Name[en_GB]=NSA Plugin
Name[es]=Complemento NSA
Name[fi]=NSA-liitännäinen
Name[gd]=Plugan NSA
Name[gl]=Complemento de NSA
Name[he]=תוסף NSA
Name[hu]=NSA bővítmény
Name[it]=Estensione NSA
Name[ko]=NSA 플러그인
Name[nb]=NSA programtillegg
Name[nl]=NSA-plug-in
Name[nn]=NSA-tillegg
Name[pl]=Wtyczka NSA
Name[pt]='Plugin' da NSA
Name[pt_BR]=Plugin NSA
Name[ru]=Модуль ФСБ
Name[sk]=NSA plugin
Name[sl]=Vstavek NSA
Name[sr]=НСА‑ов прикључак
Name[sr@ijekavian]=НСА‑ов прикључак
Name[sr@ijekavianlatin]=NSAov priključak
Name[sr@latin]=NSAov priključak
Name[sv]=NSA-insticksprogram
Name[tr]=NSA Eklentisi
Name[uk]=Додаток NSA
Name[x-test]=xxNSA Pluginxx
Name[zh_CN]=NSA 插件
Name[zh_TW]=NSA 外掛程式
Comment=Test Plugin Spy
Comment[ast]=Complementu de prueba qu'escluca
Comment[bs]=Špijun provjere dodataka
Comment[ca]=Connector de proves espia
Comment[ca@valencia]=Connector de proves espia
Comment[cs]=Testovací modul Spy
Comment[da]=Test-plugin spion
Comment[de]=Spionage-Testmodul
Comment[el]=Test Plugin Spy
Comment[en_GB]=Test Plugin Spy
Comment[es]=Probar espía de complementos
Comment[fi]=Testivakoiluliitännäinen
Comment[gd]=Plugan deuchainneach brathadair
Comment[gl]=Complemento espía de proba
Comment[he]=בדיקת תוסף ריגול
Comment[hu]=Kémbővítmény tesztelése
Comment[it]=Estensione di prova Spy
Comment[ko]=테스트 플러그인 첩자
Comment[nb]=Test tilleggsspion
Comment[nl]=Plug-in Spy testen
Comment[nn]=Spion for test-tillegg
Comment[pl]=Wypróbuj szpiega wtyczki
Comment[pt]=Espião dos 'Plugins' de Testes
Comment[pt_BR]=Plugin de teste de espionagem
Comment[ru]=Тестовый прослушивающий модуль
Comment[sk]=Testovací plugin špión
Comment[sl]=Preizkusni vohunski vstavek
Comment[sr]=Пробни прикључак шпијун
Comment[sr@ijekavian]=Пробни прикључак шпијун
Comment[sr@ijekavianlatin]=Probni priključak špijun
Comment[sr@latin]=Probni priključak špijun
Comment[sv]=Testa insticksprogramspion
Comment[tr]=Test Eklenti Ajanı
Comment[uk]=Тестовий додаток
Comment[x-test]=xxTest Plugin Spyxx
Comment[zh_CN]=Test Plugin Spy
Comment[zh_TW]=測試外掛程式
Type=Service
Icon=preferences-system-time
MimeType=image/png;application/pdf;
X-KDE-ServiceTypes=KService/NSA
X-KDE-Library=fakeplugin
X-KDE-FormFactors=mediacenter,desktop
X-KDE-PluginInfo-Author=Sebastian Kügler
X-KDE-PluginInfo-Email=sebas@kde.org
X-KDE-PluginInfo-Name=fakeplugin
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=https://kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-License=LGPL
X-KDE-PluginInfo-EnabledByDefault=true

View File

View File

View File

@@ -0,0 +1,90 @@
[Desktop Entry]
Name=NSA Plugin
Name[ast]=Complementu NSA
Name[bs]=NSA dodatak
Name[ca]=Connector de la NSA
Name[ca@valencia]=Connector de la NSA
Name[cs]=Modul NSA
Name[da]=NSA-plugin
Name[de]=NSA-Modul
Name[el]=NSA Plugin
Name[en_GB]=NSA Plugin
Name[es]=Complemento NSA
Name[fi]=NSA-liitännäinen
Name[gd]=Plugan NSA
Name[gl]=Complemento de NSA
Name[he]=תוסף NSA
Name[hu]=NSA bővítmény
Name[it]=Estensione NSA
Name[ko]=NSA 플러그인
Name[nb]=NSA programtillegg
Name[nl]=NSA-plug-in
Name[nn]=NSA-tillegg
Name[pl]=Wtyczka NSA
Name[pt]='Plugin' da NSA
Name[pt_BR]=Plugin NSA
Name[ru]=Модуль ФСБ
Name[sk]=NSA plugin
Name[sl]=Vstavek NSA
Name[sr]=НСА‑ов прикључак
Name[sr@ijekavian]=НСА‑ов прикључак
Name[sr@ijekavianlatin]=NSAov priključak
Name[sr@latin]=NSAov priključak
Name[sv]=NSA-insticksprogram
Name[tr]=NSA Eklentisi
Name[uk]=Додаток NSA
Name[x-test]=xxNSA Pluginxx
Name[zh_CN]=NSA 插件
Name[zh_TW]=NSA 外掛程式
Comment=Test Plugin Spy
Comment[ast]=Complementu de prueba qu'escluca
Comment[bs]=Špijun provjere dodataka
Comment[ca]=Connector de proves espia
Comment[ca@valencia]=Connector de proves espia
Comment[cs]=Testovací modul Spy
Comment[da]=Test-plugin spion
Comment[de]=Spionage-Testmodul
Comment[el]=Test Plugin Spy
Comment[en_GB]=Test Plugin Spy
Comment[es]=Probar espía de complementos
Comment[fi]=Testivakoiluliitännäinen
Comment[gd]=Plugan deuchainneach brathadair
Comment[gl]=Complemento espía de proba
Comment[he]=בדיקת תוסף ריגול
Comment[hu]=Kémbővítmény tesztelése
Comment[it]=Estensione di prova Spy
Comment[ko]=테스트 플러그인 첩자
Comment[nb]=Test tilleggsspion
Comment[nl]=Plug-in Spy testen
Comment[nn]=Spion for test-tillegg
Comment[pl]=Wypróbuj szpiega wtyczki
Comment[pt]=Espião dos 'Plugins' de Testes
Comment[pt_BR]=Plugin de teste de espionagem
Comment[ru]=Тестовый прослушивающий модуль
Comment[sk]=Testovací plugin špión
Comment[sl]=Preizkusni vohunski vstavek
Comment[sr]=Пробни прикључак шпијун
Comment[sr@ijekavian]=Пробни прикључак шпијун
Comment[sr@ijekavianlatin]=Probni priključak špijun
Comment[sr@latin]=Probni priključak špijun
Comment[sv]=Testa insticksprogramspion
Comment[tr]=Test Eklenti Ajanı
Comment[uk]=Тестовий додаток
Comment[x-test]=xxTest Plugin Spyxx
Comment[zh_CN]=Test Plugin Spy
Comment[zh_TW]=測試外掛程式
Type=Service
Icon=preferences-system-time
Hidden=true
X-KDE-ServiceTypes=KService/NSA
X-KDE-Library=fakeplugin
X-KDE-PluginInfo-Author=Sebastian Kügler
X-KDE-PluginInfo-Email=sebas@kde.org
X-KDE-PluginInfo-Name=fakeplugin
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=https://kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-License=LGPL
X-KDE-PluginInfo-EnabledByDefault=true

View File

@@ -0,0 +1,22 @@
NAME="Name"
VERSION="100.5"
ID=theid
ID_LIKE="otherid otherotherid"
VERSION_CODENAME=versioncodename
VERSION_ID="500.1"
PRETTY_NAME="Pretty Name #1"
ANSI_COLOR="1;34"
CPE_NAME="cpe:/o:foo:bar:100"
HOME_URL="https://url.home"
DOCUMENTATION_URL="https://url.docs"
SUPPORT_URL="https://url.support"
BUG_REPORT_URL="https://url.bugs"
PRIVACY_POLICY_URL="https://url.privacy"
BUILD_ID="105.5"
# comment
VARIANT="Test = Edition"
BROKENLINE_SHOULD_BE_IGNORED
VARIANT_ID=test
# indented comment
LOGO=start-here-test
DEBIAN_BTS="debbugs://bugs.debian.org/"

View File

@@ -0,0 +1,46 @@
[Desktop Entry]
Name=Bad Groups
Name[ca]=Grups dolents
Name[ca@valencia]=Grups dolents
Name[da]=Dårlige grupper
Name[de]=Schlechte Gruppen
Name[el]=Κακές ομάδες
Name[en_GB]=Bad Groups
Name[es]=Grupos incorrectos
Name[fi]=Huonot ryhmät
Name[gl]=Grupos malos
Name[it]=Gruppi errati
Name[ko]=불량 그룹
Name[nl]=Foute groepen
Name[pl]=Złe grupy
Name[pt]=Grupos Inválidos
Name[pt_BR]=Grupos inválidos
Name[sk]=Zlé skupiny
Name[sl]=Slabe skupine
Name[sr]=Лоше групе
Name[sr@ijekavian]=Лоше групе
Name[sr@ijekavianlatin]=Loše grupe
Name[sr@latin]=Loše grupe
Name[sv]=Felaktiga grupper
Name[uk]=Погані групи
Name[x-test]=xxBad Groupsxx
Name[zh_CN]=坏分组
Type=Service
# one value for every property definition in bad-groups-servicetype.desktop
ThisIsOkay=10
#empty
=11
#missing terminator
MissingTerminator=12
# empty and missing terminator
=13
# completely empty
=14
SomeOtherProperty=15
# extra spaces in group name (should be okay)
TrailingSpacesAreOkay=16
#missing type
MissingType=17
InvalidType=18
# ok again after invalid ones
ThisIsOkayAgain=19

View File

@@ -0,0 +1,34 @@
[Desktop Entry]
Type=ServiceType
[PropertyDef::ThisIsOkay]
Type=int
# missing name
[PropertyDef::]
Type=int
# missing terminator
[PropertyDef::MissingTerminator
Type=int
# empty and missing terminator
[PropertyDef::
Type=int
# completely empty group
[
Type=int
# completely empty group
[DoesNotStartWithPropertyDef::SomeOtherProperty]
Type=int
# extra spaces
[PropertyDef::TrailingSpacesAreOkay ]
Type=int
# missing Type=key
[PropertyDef::MissingType]
NoType=int
# invalid Type=key
[PropertyDef::InvalidType]
Type=integer
[PropertyDef::ThisIsOkayAgain]
Type=int

View File

@@ -0,0 +1,6 @@
[Desktop Entry]
Type=ServiceType
[PropertyDef::X-Test-Bool]
Type=bool

View File

@@ -0,0 +1,39 @@
[Desktop Entry]
Name=Example
Name[ca]=Exemple
Name[ca@valencia]=Exemple
Name[da]=Eksempel
Name[de]=Beispiel
Name[el]=Παράδειγμα
Name[en_GB]=Example
Name[es]=Ejemplo
Name[fi]=Esimerkki
Name[gl]=Exemplo
Name[it]=Esempio
Name[ko]=예제
Name[nb]=Eksempel
Name[nl]=Voorbeeld
Name[pl]=Przykład
Name[pt]=Exemplo
Name[pt_BR]=Exemplo
Name[sk]=Príklad
Name[sl]=Primer
Name[sr]=Пример
Name[sr@ijekavian]=Пример
Name[sr@ijekavianlatin]=Primer
Name[sr@latin]=Primer
Name[sv]=Exempel
Name[uk]=Приклад
Name[x-test]=xxExamplexx
Name[zh_CN]=例子
Type=Service
X-KDE-ServiceTypes=example/servicetype,bar/foo
X-Test-Integer=42
X-Test-Double=42.42
X-Test-List=a,b,c,def
X-Test-String=foobar
X-Test-Bool=true
# not defined -> string
X-Test-Unknown=true
# QSize not supported -> string
X-Test-Size=10,20

View File

@@ -0,0 +1,18 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=example/servicetype
[PropertyDef::X-Test-Integer]
Type=int
[PropertyDef::X-Test-Double]
Type=double
[PropertyDef::X-Test-Bool]
Type=bool
[PropertyDef::X-Test-List]
Type=QStringList
[PropertyDef::X-Test-String]
Type=QString
# this is not supported -> should not convert
# was used by KDE4 plasma-applet.desktop but that is no longer the case
[PropertyDef::X-Test-Size]
Type=QSize

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=KDEDModule
[PropertyDef::X-KDE-FactoryName]
Type=QString
[PropertyDef::X-KDE-Kded-autoload]
Type=bool
[PropertyDef::X-KDE-Kded-load-on-demand]
Type=bool

View File

@@ -0,0 +1,70 @@
# this is a copy of kdevelopplugin.desktop as an example of a real service type definition
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=KDevelop/NonExistentPlugin
X-KDE-Derived=KPluginInfo
#Name=KDevelop Plugin
# mandatory, versioning - prevent DLL hell
[PropertyDef::X-KDevelop-Version]
Type=int
# optional, determines whether a plugin is loaded only after
# a project is opened, or is a global plugin.
# If it is not set, the plugin can only be loaded by the
# user or via requesting one of its dependencies
# allowed values: Global, Project
[PropertyDef::X-KDevelop-Category]
Type=QString
# mandatory, GUI-Operation Mode, determines whether a plugin
# can work without having a mainwindow/partcontroller
# running
# allowed values: GUI, NoGUI
[PropertyDef::X-KDevelop-Mode]
Type=QString
# optional, Arguments to pass to the plugin
[PropertyDef::X-KDevelop-Args]
Type=QString
# optional, Interfaces that a plugin implements
# usually values start with org.kdevelop
[PropertyDef::X-KDevelop-Interfaces]
Type=QStringList
# optional, interfaces that this plugin depends
# on
[PropertyDef::X-KDevelop-IRequired]
Type=QStringList
# optional, interfaces that this plugin can use,
# but the plugin still works if the interfaces are
# not available.
[PropertyDef::X-KDevelop-IOptional]
Type=QStringList
# optional, mimetypes supported by a language plugin
[PropertyDef::X-KDevelop-SupportedMimeTypes]
Type=QStringList
# optional, language supported by a language plugin
[PropertyDef::X-KDevelop-Language]
Type=QString
# optional, defines whether the plugin can be disabled
# by the user. Possible values are "AlwaysOn" and "UserSelectable".
# If the property is missing then UserSelectable is assumed
[PropertyDef::X-KDevelop-LoadMode]
Type=QString
# optional, list of filters for "projectfiles" for the project plugin
# For example: Makefile,Makefile.* for Makefile's
[PropertyDef::X-KDevelop-ProjectFilesFilter]
Type=QStringList
# optional, description for the projectfiles filter
[PropertyDef::X-KDevelop-ProjectFilesFilterDescription]
Type=QString

View File

@@ -0,0 +1,7 @@
[Desktop Entry]
# Type must be ServiceType otherwise this file is invalid
Type=Service
# as this file is invalid check that this property is not converted
[PropertyDef::ShouldNotBeConvertedToInt]
Type=int

View File

@@ -0,0 +1,21 @@
{
"KPlugin": {
"Authors": [
{
"Name": "Aleix Pol"
}
],
"Description": "Test stuff.",
"Icon": "kdevelop",
"License": "GPL",
"Name": "Test"
},
"X-Plasma-MainScript": "ui/main.qml",
"X-Purpose-PluginTypes": [ "Export" ],
"SomeInt" : 42,
"SomeIntAsString" : "42",
"SomeStringNotAInt" : "not-a-string",
"SomeBool" : true,
"SomeBoolAsString" : "true",
"SomeBoolThatIsFalse": false
}

View File

@@ -0,0 +1,19 @@
[Desktop Entry]
Name=Parse Test
Comment=Two Steps Parsing Test
Type=Service
Icon=preferences-system-time
MimeType=image/png;application/pdf;
X-Test-List=first,second
X-KDE-ServiceTypes=example/servicetype
X-KDE-Library=fakeplugin
X-KDE-FormFactors=mediacenter,desktop
X-KDE-PluginInfo-Author=Sebastian Kügler
X-KDE-PluginInfo-Email=sebas@kde.org
X-KDE-PluginInfo-Name=fakeplugin
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=https://kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-License=LGPL
X-KDE-PluginInfo-EnabledByDefault=true

View File

@@ -0,0 +1,357 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcoreaddons_debug.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QProcess>
#include <QTemporaryFile>
#include <QTest>
#include <kcoreaddons_export.h>
namespace QTest
{
template<>
inline char *toString(const QJsonValue &val)
{
// simply reuse the QDebug representation
QString result;
QDebug(&result) << val;
return QTest::toString(result);
}
}
class DesktopToJsonTest : public QObject
{
Q_OBJECT
private:
void compareJson(const QJsonObject &actual, const QJsonObject &expected)
{
for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) {
if (expected.constFind(it.key()) == expected.constEnd()) {
qCritical() << "Result has key" << it.key() << "which is not expected!";
QFAIL("Invalid output");
}
if (it.value().isObject() && expected.value(it.key()).isObject()) {
compareJson(it.value().toObject(), expected.value(it.key()).toObject());
} else {
QCOMPARE(it.value(), expected.value(it.key()));
}
}
for (auto it = expected.constBegin(); it != expected.constEnd(); ++it) {
if (actual.constFind(it.key()) == actual.constEnd()) {
qCritical() << "Result is missing key" << it.key();
QFAIL("Invalid output");
}
if (it.value().isObject() && actual.value(it.key()).isObject()) {
compareJson(it.value().toObject(), actual.value(it.key()).toObject());
} else {
QCOMPARE(it.value(), actual.value(it.key()));
}
}
}
private Q_SLOTS:
void testDesktopToJson_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QJsonObject>("expectedResult");
QTest::addColumn<bool>("compatibilityMode");
QTest::addColumn<QStringList>("serviceTypes");
QJsonObject expectedResult;
QJsonObject kpluginObj;
QByteArray input =
// include an insignificant group
"[Some Group]\n"
"Foo=Bar\n"
"\n"
"[Desktop Entry]\n"
// only data inside [Desktop Entry] should be included
"Name=Example\n"
// empty lines
"\n"
" \n"
// make sure translations are included:
"Name[de_DE]=Beispiel\n"
// ignore comments:
"#Comment=Comment\n"
" #Comment=Comment\n"
"Categories=foo;bar;a\\;b\n"
// As the case is significant, the keys Name and NAME are not equivalent:
"CaseSensitive=ABC\n"
"CASESENSITIVE=abc\n"
// Space before and after the equals sign should be ignored:
"SpacesBeforeEq =foo\n"
"SpacesAfterEq= foo\n"
// Space before and after the equals sign should be ignored; the = sign is the actual delimiter.
// TODO: error in spec (spaces before and after the key??)
" SpacesBeforeKey=foo\n"
"SpacesAfterKey =foo\n"
// ignore trailing spaces
"TrailingSpaces=foo \n"
// However spaces in the value are significant:
"SpacesInValue=Hello, World!\n"
// The escape sequences \s, \n, \t, \r, and \\ are supported for values of
// type string and localestring, meaning ASCII space, newline, tab,
// carriage return, and backslash, respectively:
"EscapeSequences=So\\sme esc\\nap\\te se\\\\qu\\re\\\\nces\n" // make sure that the last n is a literal n not a newline!
// the standard keys that are used by plugins, make sure correct types are used:
"X-KDE-PluginInfo-Category=Examples\n" // string key
"X-KDE-PluginInfo-Version=1.0\n"
// The multiple values should be separated by a semicolon and the value of the key
// may be optionally terminated by a semicolon. Trailing empty strings must always
// be terminated with a semicolon. Semicolons in these values need to be escaped using \;.
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
"X-KDE-PluginInfo-Depends=foo,bar,esc\\,aped\n" // string list key
#endif
"X-KDE-ServiceTypes=\n" // empty string list
"X-KDE-PluginInfo-EnabledByDefault=true\n" // bool key
// now start a new group
"[New Group]\n"
"InWrongGroup=true\n";
expectedResult[QStringLiteral("Categories")] = QStringLiteral("foo;bar;a\\;b");
expectedResult[QStringLiteral("CaseSensitive")] = QStringLiteral("ABC");
expectedResult[QStringLiteral("CASESENSITIVE")] = QStringLiteral("abc");
expectedResult[QStringLiteral("SpacesBeforeEq")] = QStringLiteral("foo");
expectedResult[QStringLiteral("SpacesAfterEq")] = QStringLiteral("foo");
expectedResult[QStringLiteral("SpacesBeforeKey")] = QStringLiteral("foo");
expectedResult[QStringLiteral("SpacesAfterKey")] = QStringLiteral("foo");
expectedResult[QStringLiteral("TrailingSpaces")] = QStringLiteral("foo");
expectedResult[QStringLiteral("SpacesInValue")] = QStringLiteral("Hello, World!");
expectedResult[QStringLiteral("EscapeSequences")] = QStringLiteral("So me esc\nap\te se\\qu\re\\nces");
kpluginObj[QStringLiteral("Name")] = QStringLiteral("Example");
kpluginObj[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel");
kpluginObj[QStringLiteral("Category")] = QStringLiteral("Examples");
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
kpluginObj[QStringLiteral("Dependencies")] =
QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped"));
#endif
kpluginObj[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(QStringList());
kpluginObj[QStringLiteral("EnabledByDefault")] = true;
kpluginObj[QStringLiteral("Version")] = QStringLiteral("1.0");
QJsonObject compatResult = expectedResult;
compatResult[QStringLiteral("Name")] = QStringLiteral("Example");
compatResult[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel");
compatResult[QStringLiteral("X-KDE-PluginInfo-Category")] = QStringLiteral("Examples");
compatResult[QStringLiteral("X-KDE-PluginInfo-Version")] = QStringLiteral("1.0");
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
compatResult[QStringLiteral("X-KDE-PluginInfo-Depends")] =
QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped"));
#endif
compatResult[QStringLiteral("X-KDE-ServiceTypes")] = QJsonArray::fromStringList(QStringList());
compatResult[QStringLiteral("X-KDE-PluginInfo-EnabledByDefault")] = true;
expectedResult[QStringLiteral("KPlugin")] = kpluginObj;
QTest::newRow("newFormat") << input << expectedResult << false << QStringList();
QTest::newRow("compatFormat") << input << compatResult << true << QStringList();
// test conversion of a currently existing .desktop file (excluding most of the translations):
QByteArray kdevInput =
"[Desktop Entry]\n"
"Type = Service\n"
"Icon=text-x-c++src\n"
"Exec=blubb\n"
"Comment=C/C++ Language Support\n"
"Comment[fr]=Prise en charge du langage C/C++\n"
"Comment[it]=Supporto al linguaggio C/C++\n"
"Name=C++ Support\n"
"Name[fi]=C++-tuki\n"
"Name[fr]=Prise en charge du C++\n"
"GenericName=Language Support\n"
"GenericName[sl]=Podpora jeziku\n"
"ServiceTypes=KDevelop/NonExistentPlugin\n"
"X-KDE-Library=kdevcpplanguagesupport\n"
"X-KDE-PluginInfo-Name=kdevcppsupport\n"
"X-KDE-PluginInfo-Category=Language Support\n"
"X-KDevelop-Version=1\n"
"X-KDevelop-Language=C++\n"
"X-KDevelop-Args=CPP\n"
"X-KDevelop-Interfaces=ILanguageSupport\n"
"X-KDevelop-SupportedMimeTypes=text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\n"
"X-KDevelop-Mode=NoGUI\n"
"X-KDevelop-LoadMode=AlwaysOn";
QJsonParseError e;
QJsonObject kdevExpected = QJsonDocument::fromJson(
"{\n"
" \"GenericName\": \"Language Support\",\n"
" \"GenericName[sl]\": \"Podpora jeziku\",\n"
" \"KPlugin\": {\n"
" \"Category\": \"Language Support\",\n"
" \"Description\": \"C/C++ Language Support\",\n"
" \"Description[fr]\": \"Prise en charge du langage C/C++\",\n"
" \"Description[it]\": \"Supporto al linguaggio C/C++\",\n"
" \"Icon\": \"text-x-c++src\",\n"
" \"Id\": \"kdevcppsupport\",\n"
" \"Name\": \"C++ Support\",\n"
" \"Name[fi]\": \"C++-tuki\",\n"
" \"Name[fr]\": \"Prise en charge du C++\",\n"
" \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n"
" },\n"
" \"X-KDevelop-Args\": \"CPP\",\n"
" \"X-KDevelop-Interfaces\": \"ILanguageSupport\",\n"
" \"X-KDevelop-Language\": \"C++\",\n"
" \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n"
" \"X-KDevelop-Mode\": \"NoGUI\",\n"
" \"X-KDevelop-SupportedMimeTypes\": \"text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\",\n"
" \"X-KDevelop-Version\": \"1\"\n"
"}\n",
&e)
.object();
QCOMPARE(e.error, QJsonParseError::NoError);
QTest::newRow("kdevcpplanguagesupport no servicetype") << kdevInput << kdevExpected << false << QStringList();
QJsonObject kdevExpectedWithServiceType =
QJsonDocument::fromJson(
"{\n"
" \"GenericName\": \"Language Support\",\n"
" \"GenericName[sl]\": \"Podpora jeziku\",\n"
" \"KPlugin\": {\n"
" \"Category\": \"Language Support\",\n"
" \"Description\": \"C/C++ Language Support\",\n"
" \"Description[fr]\": \"Prise en charge du langage C/C++\",\n"
" \"Description[it]\": \"Supporto al linguaggio C/C++\",\n"
" \"Icon\": \"text-x-c++src\",\n"
" \"Id\": \"kdevcppsupport\",\n"
" \"Name\": \"C++ Support\",\n"
" \"Name[fi]\": \"C++-tuki\",\n"
" \"Name[fr]\": \"Prise en charge du C++\",\n"
" \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n"
" },\n"
" \"X-KDevelop-Args\": \"CPP\",\n"
" \"X-KDevelop-Interfaces\": [\"ILanguageSupport\"],\n"
" \"X-KDevelop-Language\": \"C++\",\n"
" \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n"
" \"X-KDevelop-Mode\": \"NoGUI\",\n"
" \"X-KDevelop-SupportedMimeTypes\": [\"text/x-chdr\", \"text/x-c++hdr\", \"text/x-csrc\", \"text/x-c++src\"],\n"
" \"X-KDevelop-Version\": 1\n"
"}\n",
&e)
.object();
QCOMPARE(e.error, QJsonParseError::NoError);
const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop");
QVERIFY(!kdevServiceTypePath.isEmpty());
QTest::newRow("kdevcpplanguagesupport with servicetype") << kdevInput << kdevExpectedWithServiceType << false << QStringList(kdevServiceTypePath);
// test conversion of the X-KDE-PluginInfo-Author + X-KDE-PluginInfo-Email key:
QByteArray authorInput =
"[Desktop Entry]\n"
"Type=Service\n"
"X-KDE-PluginInfo-Author=Foo Bar\n"
"X-KDE-PluginInfo-Email=foo.bar@baz.com\n";
QJsonObject authorsExpected = QJsonDocument::fromJson(
"{\n"
" \"KPlugin\": {\n"
" \"Authors\": [ { \"Name\": \"Foo Bar\", \"Email\": \"foo.bar@baz.com\" } ]\n"
" }\n }\n",
&e)
.object();
QCOMPARE(e.error, QJsonParseError::NoError);
QTest::newRow("authors") << authorInput << authorsExpected << false << QStringList();
// test case-insensitive conversion of boolean keys
const QString boolServiceType = QFINDTESTDATA("data/servicetypes/bool-servicetype.desktop");
QVERIFY(!boolServiceType.isEmpty());
QByteArray boolInput1 = "[Desktop Entry]\nType=Service\nX-Test-Bool=true\n";
QByteArray boolInput2 = "[Desktop Entry]\nType=Service\nX-Test-Bool=TRue\n";
QByteArray boolInput3 = "[Desktop Entry]\nType=Service\nX-Test-Bool=false\n";
QByteArray boolInput4 = "[Desktop Entry]\nType=Service\nX-Test-Bool=FALse\n";
auto boolResultTrue = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": true}", &e).object();
QCOMPARE(e.error, QJsonParseError::NoError);
auto boolResultFalse = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": false}", &e).object();
QCOMPARE(e.error, QJsonParseError::NoError);
QTest::newRow("bool true") << boolInput1 << boolResultTrue << false << QStringList(boolServiceType);
QTest::newRow("bool TRue") << boolInput2 << boolResultTrue << false << QStringList(boolServiceType);
QTest::newRow("bool false") << boolInput3 << boolResultFalse << false << QStringList(boolServiceType);
QTest::newRow("bool FALse") << boolInput4 << boolResultFalse << false << QStringList(boolServiceType);
// test conversion of kcookiejar.desktop (for some reason the wrong boolean values were committed)
QByteArray kcookiejarInput =
"[Desktop Entry]\n"
"Type= Service\n"
"Name=Cookie Jar\n"
"Comment=Stores network cookies\n"
"X-KDE-ServiceTypes=KDEDModule\n"
"X-KDE-Library=kf5/kded/kcookiejar\n"
"X-KDE-Kded-autoload=false\n"
"X-KDE-Kded-load-on-demand=true\n";
auto kcookiejarResult = QJsonDocument::fromJson(
"{\n"
" \"KPlugin\": {\n"
" \"Description\": \"Stores network cookies\",\n"
" \"Name\": \"Cookie Jar\",\n"
" \"ServiceTypes\": [\n"
" \"KDEDModule\"\n"
" ]\n"
" },\n"
"\"X-KDE-Kded-autoload\": false,\n"
"\"X-KDE-Kded-load-on-demand\": true\n"
"}\n",
&e)
.object();
const QString kdedmoduleServiceType = QFINDTESTDATA("data/servicetypes/fake-kdedmodule.desktop");
QVERIFY(!kdedmoduleServiceType.isEmpty());
QTest::newRow("kcookiejar") << kcookiejarInput << kcookiejarResult << false << QStringList(kdedmoduleServiceType);
}
void testDesktopToJson()
{
QTemporaryFile output;
QTemporaryFile inputFile;
QVERIFY(inputFile.open());
QVERIFY(output.open()); // create the file
QFETCH(QByteArray, input);
QFETCH(QJsonObject, expectedResult);
QFETCH(bool, compatibilityMode);
QFETCH(QStringList, serviceTypes);
output.close();
inputFile.write(input);
inputFile.flush();
inputFile.close();
QProcess proc;
proc.setProgram(QStringLiteral(DESKTOP_TO_JSON_EXE));
QStringList arguments = QStringList() << QStringLiteral("-i") << inputFile.fileName() << QStringLiteral("-o") << output.fileName();
if (compatibilityMode) {
arguments << QStringLiteral("-c");
}
for (const QString &s : std::as_const(serviceTypes)) {
arguments << QStringLiteral("-s") << s;
}
proc.setArguments(arguments);
proc.start();
QVERIFY(proc.waitForFinished(10000));
QByteArray errorOut = proc.readAllStandardError();
if (!errorOut.isEmpty()) {
qCWarning(KCOREADDONS_DEBUG).nospace() << "desktoptojson STDERR:\n\n" << errorOut.constData() << "\n";
}
QCOMPARE(proc.exitCode(), 0);
QVERIFY(output.open());
QByteArray jsonString = output.readAll();
QJsonParseError e;
QJsonDocument doc = QJsonDocument::fromJson(jsonString, &e);
QCOMPARE(e.error, QJsonParseError::NoError);
QJsonObject result = doc.object();
compareJson(result, expectedResult);
QVERIFY(!QTest::currentTestFailed());
}
};
QTEST_MAIN(DesktopToJsonTest)
#include "desktoptojsontest.moc"

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "jsonplugin.h"
#include <kpluginfactory.h>
JsonPlugin::JsonPlugin(QObject *parent, const QVariantList &args)
: QObject(parent)
{
Q_UNUSED(args)
}
K_PLUGIN_FACTORY_WITH_JSON(jsonpluginfa, "jsonplugin.json", registerPlugin<JsonPlugin>();)
#include "jsonplugin.moc"
#include "moc_jsonplugin.cpp"

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef JSONPLUGIN_H
#define JSONPLUGIN_H
#include <QObject>
class JsonPlugin : public QObject
{
Q_OBJECT
public:
explicit JsonPlugin(QObject *parent, const QVariantList &args);
};
#endif // JSONPLUGIN_H

View File

@@ -0,0 +1,12 @@
{
"KPlugin": {
"Description": "This is a plugin",
"Description[nl]": "Dit is een plug-in",
"Description[pt]": "Isto é um 'plugin'",
"Description[pt_BR]": "Isto é um plugin",
"Description[sv]": "Det här är ett insticksprogram",
"Description[uk]": "Це додаток",
"Description[x-test]": "xxThis is a pluginxx",
"MimeTypes": [ "text/plain", "image/png" ]
}
}

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "jsonplugin2.h"
#include <kpluginfactory.h>
JsonPlugin2::JsonPlugin2(QObject *parent, const QVariantList &args)
: QObject(parent)
{
Q_UNUSED(args)
}
K_PLUGIN_FACTORY_WITH_JSON(jsonplugin2, "jsonplugin2.json", registerPlugin<JsonPlugin2>();)
#include "jsonplugin2.moc"
#include "moc_jsonplugin2.cpp"

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef JSONPLUGIN2_H
#define JSONPLUGIN2_H
#include <QObject>
class JsonPlugin2 : public QObject
{
Q_OBJECT
public:
explicit JsonPlugin2(QObject *parent, const QVariantList &args);
};
#endif // JSONPLUGIN_H

View File

@@ -0,0 +1,13 @@
{
"KPlugin": {
"Description": "This is another plugin",
"Description[nl]": "Dit is een andere plug-in",
"Description[pt]": "Este é outro 'plugin'",
"Description[pt_BR]": "Isto é outro plugin",
"Description[sv]": "Det här är ännu ett insticksprogram",
"Description[uk]": "Це інший додаток",
"Description[x-test]": "xxThis is another pluginxx",
"Id": "foobar",
"MimeTypes": [ "text/html" ]
}
}

View File

@@ -0,0 +1,106 @@
/*
SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau <kossebau@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
// test object
#include <kaboutdata.h>
// Qt
#include <QObject>
#include <QTest>
// Separate test for reading & setting applicationData
// to ensure a separate process where no other test case has
// directly or indirectly called KAboutData::setApplicationData before
// and thus created the global KAboutData object
class KAboutDataApplicationDataTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testInteractionWithQApplicationData();
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76)
void testRegisterPluginData();
#endif
};
static const char AppName[] = "app";
static const char ProgramName[] = "ProgramName";
static const char Version[] = "Version";
static const char OrganizationDomain[] = "no.where";
static const char DesktopFileName[] = "org.kde.someapp";
static const char AppName2[] = "otherapp";
static const char ProgramName2[] = "OtherProgramName";
static const char Version2[] = "OtherVersion";
static const char OrganizationDomain2[] = "other.no.where";
static const char DesktopFileName2[] = "org.kde.otherapp";
void KAboutDataApplicationDataTest::testInteractionWithQApplicationData()
{
// init the app metadata the Qt way
QCoreApplication *app = QCoreApplication::instance();
app->setApplicationName(QLatin1String(AppName));
app->setProperty("applicationDisplayName", QLatin1String(ProgramName));
app->setApplicationVersion(QLatin1String(Version));
app->setOrganizationDomain(QLatin1String(OrganizationDomain));
app->setProperty("desktopFileName", QLatin1String(DesktopFileName));
// without setting before, get KAboutData::applicationData
const KAboutData applicationAboutData = KAboutData::applicationData();
// should be initialized with Q*Application metadata
QCOMPARE(applicationAboutData.componentName(), QLatin1String(AppName));
QCOMPARE(applicationAboutData.displayName(), QLatin1String(ProgramName));
QCOMPARE(applicationAboutData.organizationDomain(), QLatin1String(OrganizationDomain));
QCOMPARE(applicationAboutData.version(), QLatin1String(Version));
QCOMPARE(applicationAboutData.desktopFileName(), QLatin1String(DesktopFileName));
// now set some new KAboutData, with different values
KAboutData aboutData2(QString::fromLatin1(AppName2), QString::fromLatin1(ProgramName2), QString::fromLatin1(Version2));
aboutData2.setOrganizationDomain(OrganizationDomain2);
aboutData2.setDesktopFileName(QLatin1String(DesktopFileName2));
KAboutData::setApplicationData(aboutData2);
// check that Q*Application metadata has been updated, as expected per API definition
QCOMPARE(app->applicationName(), QLatin1String(AppName2));
QCOMPARE(app->property("applicationDisplayName").toString(), QLatin1String(ProgramName2));
QCOMPARE(app->organizationDomain(), QLatin1String(OrganizationDomain2));
QCOMPARE(app->applicationVersion(), QLatin1String(Version2));
QCOMPARE(app->property("desktopFileName").toString(), QLatin1String(DesktopFileName2));
// and check as well KAboutData::applicationData itself
const KAboutData applicationAboutData2 = KAboutData::applicationData();
QCOMPARE(applicationAboutData2.componentName(), QLatin1String(AppName2));
QCOMPARE(applicationAboutData2.displayName(), QLatin1String(ProgramName2));
QCOMPARE(applicationAboutData2.organizationDomain(), QLatin1String(OrganizationDomain2));
QCOMPARE(applicationAboutData2.version(), QLatin1String(Version2));
QCOMPARE(applicationAboutData2.desktopFileName(), QLatin1String(DesktopFileName2));
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 76)
void KAboutDataApplicationDataTest::testRegisterPluginData()
{
for (const auto &name : {QStringLiteral("foo"), QStringLiteral("bar")}) {
QVERIFY(!KAboutData::pluginData(name));
KAboutData::registerPluginData(KAboutData(name));
auto v1 = KAboutData::pluginData(name);
QVERIFY(v1);
QCOMPARE(v1->componentName(), name);
// re-registering will overwrite and not trigger memory leaks (check LSAN)
KAboutData::registerPluginData(KAboutData(name));
// the pointer staid the same, as QHash is node based
QCOMPARE(KAboutData::pluginData(name), v1);
}
}
#endif
QTEST_MAIN(KAboutDataApplicationDataTest)
#include "kaboutdataapplicationdatatest.moc"

View File

@@ -0,0 +1,445 @@
/*
SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau <kossebau@kde.org>
SPDX-FileCopyrightText: 2017 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
// test object
#include <kaboutdata.h>
// Qt
#include <QFile>
#include <QLatin1String>
#include <QObject>
#include <QTest>
#include <QTextStream>
#ifndef Q_OS_WIN
void initLocale()
{
qputenv("LC_ALL", "en_US.utf-8");
}
Q_CONSTRUCTOR_FUNCTION(initLocale)
#endif
class KAboutDataTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testLongFormConstructorWithDefaults();
void testLongFormConstructor();
void testShortFormConstructor();
void testSetAddLicense();
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
void testSetProgramIconName();
#endif
void testSetDesktopFileName();
void testCopying();
void testKAboutDataOrganizationDomain();
void testLicenseSPDXID();
void testLicenseOrLater();
void testProductName();
};
static const char AppName[] = "app";
static const char ProgramName[] = "ProgramName";
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
static const char ProgramIconName[] = "program-icon";
#endif
static const char Version[] = "Version";
static const char ShortDescription[] = "ShortDescription";
static const char CopyrightStatement[] = "CopyrightStatement";
static const char Text[] = "Text";
static const char HomePageAddress[] = "http://test.no.where/";
static const char HomePageSecure[] = "https://test.no.where/";
static const char OrganizationDomain[] = "no.where";
static const char BugsEmailAddress[] = "bugs@no.else";
static const char LicenseText[] = "free to write, reading forbidden";
static const char LicenseFileName[] = "testlicensefile";
static const char LicenseFileText[] = "free to write, reading forbidden, in the file";
void KAboutDataTest::testLongFormConstructorWithDefaults()
{
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown);
QCOMPARE(aboutData.componentName(), QString::fromLatin1(AppName));
QCOMPARE(aboutData.productName(), QString::fromLatin1(AppName));
QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName));
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
QCOMPARE(aboutData.programIconName(), QString::fromLatin1(AppName));
#endif
QCOMPARE(aboutData.programLogo(), QVariant());
QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1("kde.org"));
QCOMPARE(aboutData.version(), QString::fromLatin1(Version));
QCOMPARE(aboutData.homepage(), QString());
QCOMPARE(aboutData.bugAddress(), QString::fromLatin1("submit@bugs.kde.org"));
QVERIFY(aboutData.authors().isEmpty());
QVERIFY(aboutData.credits().isEmpty());
QVERIFY(aboutData.translators().isEmpty());
QCOMPARE(aboutData.otherText(), QString());
QCOMPARE(aboutData.licenses().count(), 1);
// We don't know the default text, do we?
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
QCOMPARE(aboutData.copyrightStatement(), QString());
QCOMPARE(aboutData.shortDescription(), (QLatin1String(ShortDescription)));
QCOMPARE(aboutData.customAuthorPlainText(), QString());
QCOMPARE(aboutData.customAuthorRichText(), QString());
QVERIFY(!aboutData.customAuthorTextEnabled());
QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app"));
QCOMPARE(aboutData.internalVersion(), Version);
QCOMPARE(aboutData.internalProgramName(), ProgramName);
QCOMPARE(aboutData.internalBugAddress(), "submit@bugs.kde.org");
QCOMPARE(aboutData.internalProductName(), nullptr);
}
void KAboutDataTest::testLongFormConstructor()
{
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown,
QLatin1String(CopyrightStatement),
QLatin1String(Text),
QString::fromLatin1(HomePageAddress),
QString::fromLatin1(BugsEmailAddress));
QCOMPARE(aboutData.componentName(), QLatin1String(AppName));
QCOMPARE(aboutData.productName(), QLatin1String(AppName));
QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName));
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
QCOMPARE(aboutData.programIconName(), QLatin1String(AppName));
#endif
QCOMPARE(aboutData.programLogo(), QVariant());
QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1(OrganizationDomain));
QCOMPARE(aboutData.version(), QString::fromLatin1(Version));
QCOMPARE(aboutData.homepage(), QString::fromLatin1(HomePageAddress));
QCOMPARE(aboutData.bugAddress(), QString::fromLatin1(BugsEmailAddress));
QVERIFY(aboutData.authors().isEmpty());
QVERIFY(aboutData.credits().isEmpty());
QVERIFY(aboutData.translators().isEmpty());
QCOMPARE(aboutData.otherText(), QLatin1String(Text));
QCOMPARE(aboutData.licenses().count(), 1);
// We don't know the default text, do we?
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
QCOMPARE(aboutData.copyrightStatement(), QLatin1String(CopyrightStatement));
QCOMPARE(aboutData.shortDescription(), QLatin1String(ShortDescription));
QCOMPARE(aboutData.customAuthorPlainText(), QString());
QCOMPARE(aboutData.customAuthorRichText(), QString());
QVERIFY(!aboutData.customAuthorTextEnabled());
QCOMPARE(aboutData.desktopFileName(), QStringLiteral("where.no.app"));
QCOMPARE(aboutData.internalVersion(), Version);
QCOMPARE(aboutData.internalProgramName(), ProgramName);
QCOMPARE(aboutData.internalBugAddress(), BugsEmailAddress);
QCOMPARE(aboutData.internalProductName(), nullptr);
// We support http and https protocols on the homepage address, ensure they
// give the same org. domain and desktop file name.
KAboutData aboutDataSecure(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown,
QLatin1String(CopyrightStatement),
QLatin1String(Text),
QString::fromLatin1(HomePageSecure),
QString::fromLatin1(BugsEmailAddress));
QCOMPARE(aboutDataSecure.componentName(), QLatin1String(AppName));
QCOMPARE(aboutDataSecure.productName(), QLatin1String(AppName));
QCOMPARE(aboutDataSecure.organizationDomain(), QString::fromLatin1(OrganizationDomain));
QCOMPARE(aboutDataSecure.desktopFileName(), QStringLiteral("where.no.app"));
}
void KAboutDataTest::testShortFormConstructor()
{
KAboutData aboutData(QString::fromLatin1(AppName), QLatin1String(ProgramName), QString::fromLatin1(Version));
QCOMPARE(aboutData.componentName(), QString::fromLatin1(AppName));
QCOMPARE(aboutData.productName(), QString::fromLatin1(AppName));
QCOMPARE(aboutData.displayName(), QLatin1String(ProgramName));
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
QCOMPARE(aboutData.programIconName(), QString::fromLatin1(AppName));
#endif
QCOMPARE(aboutData.programLogo(), QVariant());
QCOMPARE(aboutData.organizationDomain(), QString::fromLatin1("kde.org"));
QCOMPARE(aboutData.version(), QString::fromLatin1(Version));
QCOMPARE(aboutData.homepage(), QString());
QCOMPARE(aboutData.bugAddress(), QString::fromLatin1("submit@bugs.kde.org"));
QVERIFY(aboutData.authors().isEmpty());
QVERIFY(aboutData.credits().isEmpty());
QVERIFY(aboutData.translators().isEmpty());
QCOMPARE(aboutData.otherText(), QString());
QCOMPARE(aboutData.licenses().count(), 1);
// We don't know the default text, do we?
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
QCOMPARE(aboutData.copyrightStatement(), QString());
QCOMPARE(aboutData.shortDescription(), QString());
QCOMPARE(aboutData.customAuthorPlainText(), QString());
QCOMPARE(aboutData.customAuthorRichText(), QString());
QVERIFY(!aboutData.customAuthorTextEnabled());
QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app"));
QCOMPARE(aboutData.internalVersion(), Version);
QCOMPARE(aboutData.internalProgramName(), ProgramName);
QCOMPARE(aboutData.internalBugAddress(), "submit@bugs.kde.org");
QCOMPARE(aboutData.internalProductName(), nullptr);
}
void KAboutDataTest::testKAboutDataOrganizationDomain()
{
KAboutData data(QString::fromLatin1("app"),
QLatin1String("program"),
QString::fromLatin1("version"),
QLatin1String("description"),
KAboutLicense::LGPL,
QLatin1String("copyright"),
QLatin1String("hello world"),
QStringLiteral("http://www.koffice.org"));
QCOMPARE(data.organizationDomain(), QString::fromLatin1("koffice.org"));
QCOMPARE(data.desktopFileName(), QStringLiteral("org.koffice.app"));
KAboutData data2(QString::fromLatin1("app"),
QLatin1String("program"),
QString::fromLatin1("version"),
QLatin1String("description"),
KAboutLicense::LGPL,
QString::fromLatin1("copyright"),
QLatin1String("hello world"),
QStringLiteral("app"));
QCOMPARE(data2.organizationDomain(), QString::fromLatin1("kde.org"));
QCOMPARE(data2.desktopFileName(), QStringLiteral("org.kde.app"));
}
void KAboutDataTest::testSetAddLicense()
{
// prepare a file with a license text
QFile licenseFile(QString::fromLatin1(LicenseFileName));
licenseFile.open(QIODevice::WriteOnly);
QTextStream licenseFileStream(&licenseFile);
licenseFileStream << LicenseFileText;
licenseFile.close();
const QString copyrightStatement = QLatin1String(CopyrightStatement);
const QString lineFeed = QString::fromLatin1("\n\n");
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown,
QLatin1String(CopyrightStatement),
QLatin1String(Text),
QString::fromLatin1(HomePageAddress),
QString::fromLatin1(BugsEmailAddress));
// set to GPL2
aboutData.setLicense(KAboutLicense::GPL_V2);
QCOMPARE(aboutData.licenses().count(), 1);
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v2"));
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 2"));
// QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL2Text) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
// set to Unknown again
aboutData.setLicense(KAboutLicense::Unknown);
QCOMPARE(aboutData.licenses().count(), 1);
// We don't know the default text, do we?
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::ShortName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).name(KAboutLicense::FullName), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).name(KAboutLicense::FullName).isEmpty());
// QCOMPARE( aboutData.licenses().at(0).text(), QString(WarningText) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
// add GPL3
aboutData.addLicense(KAboutLicense::GPL_V3);
QCOMPARE(aboutData.licenses().count(), 1);
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v3"));
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 3"));
// QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL3Text) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
// add GPL2, Custom and File
aboutData.addLicense(KAboutLicense::GPL_V2);
aboutData.addLicenseText(QLatin1String(LicenseText));
aboutData.addLicenseTextFile(QLatin1String(LicenseFileName));
QCOMPARE(aboutData.licenses().count(), 4);
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v3"));
QCOMPARE(aboutData.licenses().at(0).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 3"));
// QCOMPARE( aboutData.licenses().at(0).text(), QString(GPL3Text) );
QVERIFY(!aboutData.licenses().at(0).text().isEmpty());
QCOMPARE(aboutData.licenses().at(1).name(KAboutLicense::ShortName), QString::fromLatin1("GPL v2"));
QCOMPARE(aboutData.licenses().at(1).name(KAboutLicense::FullName), QString::fromLatin1("GNU General Public License Version 2"));
// QCOMPARE( aboutData.licenses().at(1).text(), QString(GPL2Text) );
QVERIFY(!aboutData.licenses().at(1).text().isEmpty());
QCOMPARE(aboutData.licenses().at(2).name(KAboutLicense::ShortName), QString::fromLatin1("Custom"));
QCOMPARE(aboutData.licenses().at(2).name(KAboutLicense::FullName), QString::fromLatin1("Custom"));
QCOMPARE(aboutData.licenses().at(2).text(), QLatin1String(LicenseText));
QCOMPARE(aboutData.licenses().at(3).name(KAboutLicense::ShortName), QString::fromLatin1("Custom"));
QCOMPARE(aboutData.licenses().at(3).name(KAboutLicense::FullName), QString::fromLatin1("Custom"));
QCOMPARE(aboutData.licenses().at(3).text(), QString(copyrightStatement + lineFeed + QLatin1String(LicenseFileText)));
}
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 2)
void KAboutDataTest::testSetProgramIconName()
{
const QString programIconName(QString::fromLatin1(ProgramIconName));
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown,
QLatin1String(CopyrightStatement),
QLatin1String(Text),
QString::fromLatin1(HomePageAddress),
QString::fromLatin1(BugsEmailAddress));
// Deprecated, still want to test this though. Silence GCC warnings.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// set different iconname
aboutData.setProgramIconName(programIconName);
#pragma GCC diagnostic pop
QCOMPARE(aboutData.programIconName(), programIconName);
}
#endif
void KAboutDataTest::testCopying()
{
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::GPL_V2);
{
KAboutData aboutData2(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::GPL_V3);
aboutData2.addLicense(KAboutLicense::GPL_V2, KAboutLicense::OrLaterVersions);
aboutData = aboutData2;
}
QList<KAboutLicense> licenses = aboutData.licenses();
QCOMPARE(licenses.count(), 2);
QCOMPARE(licenses.at(0).key(), KAboutLicense::GPL_V3);
QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-3.0"));
// check it doesn't crash
QVERIFY(!licenses.at(0).text().isEmpty());
QCOMPARE(licenses.at(1).key(), KAboutLicense::GPL_V2);
QCOMPARE(aboutData.licenses().at(1).spdx(), QStringLiteral("GPL-2.0+"));
// check it doesn't crash
QVERIFY(!licenses.at(1).text().isEmpty());
}
void KAboutDataTest::testSetDesktopFileName()
{
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::Unknown);
QCOMPARE(aboutData.desktopFileName(), QStringLiteral("org.kde.app"));
// set different desktopFileName
aboutData.setDesktopFileName(QStringLiteral("foo.bar.application"));
QCOMPARE(aboutData.desktopFileName(), QStringLiteral("foo.bar.application"));
}
void KAboutDataTest::testLicenseSPDXID()
{
// Input with + should output with +.
auto license = KAboutLicense::byKeyword(QStringLiteral("GPLv2+"));
QCOMPARE(license.spdx(), QStringLiteral("GPL-2.0+"));
// Input without should output without.
license = KAboutLicense::byKeyword(QStringLiteral("GPLv2"));
QCOMPARE(license.spdx(), QStringLiteral("GPL-2.0"));
// Input of spdx with or-later should also work
license = KAboutLicense::byKeyword(QStringLiteral("GPL-2.0-or-later"));
QCOMPARE(license.spdx(), QStringLiteral("GPL-2.0+")); // TODO: should not return the deprecatd version
// we should be able to match by spdx too
// create a KAboutLicense from enum, then make sure going to spdx and back gives the same enum
for (int i = 1; i <= KAboutLicense::LGPL_V2_1; ++i) { /*current highest enum value*/
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::GPL_V2);
aboutData.setLicense(KAboutLicense::LicenseKey(i));
QVERIFY(aboutData.licenses().count() == 1);
const auto license = aboutData.licenses().constFirst();
auto licenseFromKeyword = KAboutLicense::byKeyword(license.spdx());
QCOMPARE(license.key(), licenseFromKeyword.key());
}
}
void KAboutDataTest::testLicenseOrLater()
{
// For kaboutdata we can replace the license with an orLater version. Or add a second one.
KAboutData aboutData(QString::fromLatin1(AppName),
QLatin1String(ProgramName),
QString::fromLatin1(Version),
QLatin1String(ShortDescription),
KAboutLicense::GPL_V2);
QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-2.0"));
aboutData.setLicense(KAboutLicense::GPL_V2, KAboutLicense::OrLaterVersions);
QCOMPARE(aboutData.licenses().at(0).spdx(), QStringLiteral("GPL-2.0+"));
aboutData.addLicense(KAboutLicense::LGPL_V3, KAboutLicense::OrLaterVersions);
bool foundLGPL = false;
const QList<KAboutLicense> licenses = aboutData.licenses();
for (const auto &license : licenses) {
if (license.key() == KAboutLicense::LGPL_V3) {
QCOMPARE(license.spdx(), QStringLiteral("LGPL-3.0+"));
foundLGPL = true;
break;
}
}
QCOMPARE(foundLGPL, true);
}
void KAboutDataTest::testProductName()
{
KAboutData aboutData(QString::fromLatin1(AppName), QString::fromLatin1(ProgramName));
QCOMPARE(aboutData.productName(), QString::fromLatin1(AppName));
QCOMPARE(aboutData.internalProductName(), nullptr);
aboutData.setProductName("frameworks-kcoreaddons/aboutdata");
QCOMPARE(aboutData.productName(), QString::fromLatin1("frameworks-kcoreaddons/aboutdata"));
QCOMPARE(aboutData.internalProductName(), "frameworks-kcoreaddons/aboutdata");
}
QTEST_MAIN(KAboutDataTest)
#include "kaboutdatatest.moc"

View File

@@ -0,0 +1,158 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kautosavefiletest.h"
#include <QFile>
#include <QTextStream>
#include <QtAlgorithms>
#include <QTemporaryFile>
#include <kautosavefile.h>
#include <QTest>
QTEST_MAIN(KAutoSaveFileTest)
void KAutoSaveFileTest::initTestCase()
{
QCoreApplication::instance()->setApplicationName(QLatin1String("qttest")); // TODO do this in qtestlib itself
}
void KAutoSaveFileTest::cleanupTestCase()
{
for (const QString &fileToRemove : std::as_const(filesToRemove)) {
QFile::remove(fileToRemove);
}
}
void KAutoSaveFileTest::test_readWrite()
{
QTemporaryFile file;
QVERIFY(file.open());
QUrl normalFile = QUrl::fromLocalFile(QFileInfo(file).absoluteFilePath());
// Test basic functionality
KAutoSaveFile saveFile(normalFile);
QVERIFY(!QFile::exists(saveFile.fileName()));
QVERIFY(saveFile.open(QIODevice::ReadWrite));
QString inText = QString::fromLatin1("This is test data one.\n");
{
QTextStream ts(&saveFile);
ts << inText;
ts.flush();
}
saveFile.close();
{
QFile testReader(saveFile.fileName());
testReader.open(QIODevice::ReadWrite);
QTextStream ts(&testReader);
QString outText = ts.readAll();
QCOMPARE(outText, inText);
}
filesToRemove << file.fileName();
}
void KAutoSaveFileTest::test_fileNameMaxLength()
{
// In KAutoSaveFilePrivate::tempFile() the name of the kautosavefile that's going to be created
// is concatanated in the form:
// fileName + junk.truncated + protocol + _ + path.truncated + junk
// see tempFile() for details.
//
// Make sure that the generated filename (e.g. as you would get from QUrl::fileName()) doesn't
// exceed NAME_MAX (the maximum length allowed for filenames, see e.g. /usr/include/linux/limits.h)
// otherwise the file can't be opened.
//
// see https://phabricator.kde.org/D24489
QString s;
s.fill(QLatin1Char('b'), 80);
// create a long path that:
// - exceeds NAME_MAX (255)
// - is less than the maximum allowed path length, PATH_MAX (4096)
// see e.g. /usr/include/linux/limits.h
const QString path = QDir::tempPath() + QLatin1Char('/') + s + QLatin1Char('/') + s + QLatin1Char('/') + s + QLatin1Char('/') + s;
QFile file(path + QLatin1Char('/') + QLatin1String("testFile.txt"));
QUrl normalFile = QUrl::fromLocalFile(file.fileName());
KAutoSaveFile saveFile(normalFile);
QVERIFY(!QFile::exists(saveFile.fileName()));
QVERIFY(saveFile.open(QIODevice::ReadWrite));
filesToRemove << file.fileName();
}
void KAutoSaveFileTest::test_fileStaleFiles()
{
QUrl normalFile = QUrl::fromLocalFile(QDir::temp().absoluteFilePath(QStringLiteral("test directory/tîst me.txt")));
KAutoSaveFile saveFile(normalFile);
QVERIFY(saveFile.open(QIODevice::ReadWrite));
saveFile.write("testdata");
// Make sure the stale file is found
const auto listOfStaleFiles = saveFile.staleFiles(normalFile, QStringLiteral("qttest"));
QVERIFY(listOfStaleFiles.count() == 1);
saveFile.releaseLock();
qDeleteAll(listOfStaleFiles);
// Make sure the stale file is deleted
QVERIFY(saveFile.staleFiles(normalFile, QStringLiteral("qttest")).isEmpty());
}
void KAutoSaveFileTest::test_applicationStaleFiles()
{
// TODO
}
void KAutoSaveFileTest::test_locking()
{
QUrl normalFile(QString::fromLatin1("fish://user@example.com/home/remote/test.txt"));
KAutoSaveFile saveFile(normalFile);
QVERIFY(!QFile::exists(saveFile.fileName()));
QVERIFY(saveFile.open(QIODevice::ReadWrite));
const QList<KAutoSaveFile *> staleFiles(KAutoSaveFile::staleFiles(normalFile));
QVERIFY(!staleFiles.isEmpty());
KAutoSaveFile *saveFile2 = staleFiles.at(0);
const QString fn = saveFile2->fileName();
// It looks like $XDG_DATA_HOME/stalefiles/qttest/test.txtXXXfish_%2Fhome%2FremoteXXXXXXX
QVERIFY2(fn.contains(QLatin1String("stalefiles/qttest/test.txt")), qPrintable(fn));
QVERIFY2(fn.contains(QLatin1String("fish_%2Fhome%2Fremote")), qPrintable(fn));
QVERIFY(QFile::exists(saveFile2->fileName()));
QVERIFY(!saveFile2->open(QIODevice::ReadWrite));
saveFile.releaseLock();
QVERIFY(saveFile2->open(QIODevice::ReadWrite));
qDeleteAll(staleFiles);
}
#include "moc_kautosavefiletest.cpp"

View File

@@ -0,0 +1,31 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Jacob R Rideout <kde@jacobrideout.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kautosavefiletest_h
#define kautosavefiletest_h
#include <QObject>
#include <QStringList>
class KAutoSaveFileTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void test_readWrite();
void test_fileNameMaxLength();
void test_fileStaleFiles();
void test_applicationStaleFiles();
void test_locking();
void cleanupTestCase();
private:
QStringList filesToRemove;
};
#endif

View File

@@ -0,0 +1,98 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Kevin Funk <kevin@kfunk.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kcompositejobtest.h"
#include <QSignalSpy>
#include <QTest>
#include <QTimer>
TestJob::TestJob(QObject *parent)
: KJob(parent)
{
}
void TestJob::start()
{
QTimer::singleShot(1000, this, &TestJob::doEmit);
}
void TestJob::doEmit()
{
emitResult();
}
void CompositeJob::start()
{
if (hasSubjobs()) {
subjobs().first()->start();
} else {
emitResult();
}
}
bool CompositeJob::addSubjob(KJob *job)
{
return KCompositeJob::addSubjob(job);
}
void CompositeJob::slotResult(KJob *job)
{
KCompositeJob::slotResult(job);
if (!error() && hasSubjobs()) {
// start next
subjobs().first()->start();
} else {
setError(job->error());
setErrorText(job->errorText());
emitResult();
}
}
KCompositeJobTest::KCompositeJobTest()
: loop(this)
{
}
/**
* In case a composite job is deleted during execution
* we still want to assure that we don't crash
*
* see bug: https://bugs.kde.org/show_bug.cgi?id=230692
*/
void KCompositeJobTest::testDeletionDuringExecution()
{
QObject *someParent = new QObject;
KJob *job = new TestJob(someParent);
CompositeJob *compositeJob = new CompositeJob;
compositeJob->setAutoDelete(false);
QVERIFY(compositeJob->addSubjob(job));
QCOMPARE(job->parent(), compositeJob);
QSignalSpy destroyed_spy(job, &QObject::destroyed);
// check if job got reparented properly
delete someParent;
someParent = nullptr;
// the job should still exist, because it is a child of KCompositeJob now
QCOMPARE(destroyed_spy.size(), 0);
// start async, the subjob takes 1 second to finish
compositeJob->start();
// delete the job during the execution
delete compositeJob;
compositeJob = nullptr;
// at this point, the subjob should be deleted, too
QCOMPARE(destroyed_spy.size(), 1);
}
QTEST_GUILESS_MAIN(KCompositeJobTest)
#include "moc_kcompositejobtest.cpp"

View File

@@ -0,0 +1,61 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Kevin Funk <kevin@kfunk.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCOMPOSITEJOBTEST_H
#define KCOMPOSITEJOBTEST_H
#include <QEventLoop>
#include <QObject>
#include "kcompositejob.h"
class TestJob : public KJob
{
Q_OBJECT
public:
explicit TestJob(QObject *parent = nullptr);
/// Takes 1 second to finish
void start() override;
private Q_SLOTS:
void doEmit();
};
class CompositeJob : public KCompositeJob
{
Q_OBJECT
public:
explicit CompositeJob(QObject *parent = nullptr)
: KCompositeJob(parent)
{
}
void start() override;
bool addSubjob(KJob *job) override;
protected Q_SLOTS:
void slotResult(KJob *job) override;
};
class KCompositeJobTest : public QObject
{
Q_OBJECT
public:
KCompositeJobTest();
private Q_SLOTS:
void testDeletionDuringExecution();
private:
QEventLoop loop;
};
#endif // KCOMPOSITEJOBTEST_H

View File

@@ -0,0 +1,127 @@
/*
SPDX-FileCopyrightText: 2014 Montel Laurent <montel@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
// test object
#include "kdelibs4configmigrator.h"
// Qt
#include <QFile>
#include <QObject>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTest>
class Kdelibs4ConfigMigratorTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void shouldNotMigrateIfKde4HomeDirDoesntExist();
void shouldMigrateIfKde4HomeDirExist();
void shouldMigrateConfigFiles();
void shouldMigrateUiFiles();
};
void Kdelibs4ConfigMigratorTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
const QString configHome = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QDir(configHome).removeRecursively();
const QString dataHome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QDir(dataHome).removeRecursively();
}
void Kdelibs4ConfigMigratorTest::shouldNotMigrateIfKde4HomeDirDoesntExist()
{
qputenv("KDEHOME", "");
Kdelibs4ConfigMigrator migration(QLatin1String("foo"));
QCOMPARE(migration.migrate(), false);
}
void Kdelibs4ConfigMigratorTest::shouldMigrateIfKde4HomeDirExist()
{
QTemporaryDir kdehomeDir;
QVERIFY(kdehomeDir.isValid());
const QString kdehome = kdehomeDir.path();
qputenv("KDEHOME", QFile::encodeName(kdehome));
Kdelibs4ConfigMigrator migration(QLatin1String("foo"));
QCOMPARE(migration.migrate(), true);
}
void Kdelibs4ConfigMigratorTest::shouldMigrateConfigFiles()
{
QTemporaryDir kdehomeDir;
const QString kdehome = kdehomeDir.path();
qputenv("KDEHOME", QFile::encodeName(kdehome));
// Generate kde4 config dir
const QString configPath = kdehome + QLatin1Char('/') + QLatin1String("share/config/");
QDir().mkpath(configPath);
QVERIFY(QDir(configPath).exists());
const QStringList listConfig = {QLatin1String("foorc"), QLatin1String("foo1rc")};
for (const QString &config : listConfig) {
QFile fooConfigFile(QLatin1String(KDELIBS4CONFIGMIGRATOR_DATA_DIR) + QLatin1Char('/') + config);
QVERIFY(fooConfigFile.exists());
const QString storedConfigFilePath = configPath + QLatin1Char('/') + config;
QVERIFY(QFile::copy(fooConfigFile.fileName(), storedConfigFilePath));
QCOMPARE(QStandardPaths::locate(QStandardPaths::GenericConfigLocation, config), QString());
}
Kdelibs4ConfigMigrator migration(QLatin1String("foo"));
migration.setConfigFiles(listConfig);
QVERIFY(migration.migrate());
for (const QString &config : listConfig) {
const QString migratedConfigFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, config);
QVERIFY(!migratedConfigFile.isEmpty());
QVERIFY(QFile(migratedConfigFile).exists());
QFile::remove(migratedConfigFile);
}
}
void Kdelibs4ConfigMigratorTest::shouldMigrateUiFiles()
{
QTemporaryDir kdehomeDir;
const QString kdehome = kdehomeDir.path();
qputenv("KDEHOME", QFile::encodeName(kdehome));
const QString appName = QLatin1String("foo");
// Generate kde4 data dir
const QString dataPath = kdehome + QLatin1Char('/') + QLatin1String("share/apps/");
QDir().mkpath(dataPath);
QVERIFY(QDir(dataPath).exists());
const QString xdgDatahome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QStringList listUi;
listUi << QLatin1String("appuirc") << QLatin1String("appui1rc");
for (const QString &uifile : std::as_const(listUi)) {
QFile fooConfigFile(QLatin1String(KDELIBS4CONFIGMIGRATOR_DATA_DIR) + QLatin1Char('/') + uifile);
QVERIFY(fooConfigFile.exists());
QDir().mkpath(dataPath + QLatin1Char('/') + appName);
const QString storedConfigFilePath = dataPath + QLatin1Char('/') + appName + QLatin1Char('/') + uifile;
QVERIFY(QFile::copy(fooConfigFile.fileName(), storedConfigFilePath));
const QString xdgUiFile = xdgDatahome + QLatin1String("/kxmlgui5/") + appName + QLatin1Char('/') + uifile;
QVERIFY(!QFile::exists(xdgUiFile));
}
Kdelibs4ConfigMigrator migration(appName);
migration.setUiFiles(QStringList() << listUi);
QVERIFY(migration.migrate());
for (const QString &uifile : std::as_const(listUi)) {
const QString xdgUiFile = xdgDatahome + QLatin1String("/kxmlgui5/") + appName + QLatin1Char('/') + uifile;
QVERIFY(QFile(xdgUiFile).exists());
QFile::remove(xdgUiFile);
}
}
QTEST_MAIN(Kdelibs4ConfigMigratorTest)
#include "kdelibs4configmigratortest.moc"

View File

@@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
// test object
#include <kdelibs4migration.h>
// Qt
#include <QFile>
#include <QObject>
#include <QTemporaryDir>
#include <QTest>
class MigrationTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testPaths();
};
void MigrationTest::testPaths()
{
// Setup
QTemporaryDir kdehomeDir;
QVERIFY(kdehomeDir.isValid());
QString kdehome = kdehomeDir.path();
qputenv("KDEHOME", QFile::encodeName(kdehome));
QString oldConfigDir = kdehome + QStringLiteral("/share/config/");
QVERIFY(QDir().mkpath(oldConfigDir));
QString oldAppsDir = kdehome + QStringLiteral("/share/apps/");
QVERIFY(QDir().mkpath(oldAppsDir));
// Test
Kdelibs4Migration migration;
QVERIFY(migration.kdeHomeFound());
QCOMPARE(migration.saveLocation("config"), oldConfigDir);
QCOMPARE(migration.saveLocation("data"), oldAppsDir);
}
QTEST_MAIN(MigrationTest)
#include "kdelibs4migrationtest.moc"

View File

@@ -0,0 +1,119 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <kdirwatch.h>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTest>
#include <QThread>
#include <sys/stat.h>
#ifdef Q_OS_UNIX
#include <unistd.h> // ::link()
#endif
#include "config-tests.h"
#include "kcoreaddons_debug.h"
#include "kdirwatch_test_utils.h"
using namespace KDirWatchTestUtils;
class KDirWatch_UnitTest : public QObject
{
Q_OBJECT
public:
KDirWatch_UnitTest()
{
// Speed up the test by making the kdirwatch timer (to compress changes) faster
qputenv("KDIRWATCH_POLLINTERVAL", "50");
qputenv("KDIRWATCH_METHOD", KDIRWATCH_TEST_METHOD);
s_staticObjectUsingSelf();
m_path = m_tempDir.path() + QLatin1Char('/');
KDirWatch *dirW = &s_staticObject()->m_dirWatch;
qCDebug(KCOREADDONS_DEBUG) << "Using method" << methodToString(dirW->internalMethod());
}
private Q_SLOTS: // test methods
void initTestCase()
{
QFileInfo pathInfo(m_path);
QVERIFY(pathInfo.isDir() && pathInfo.isWritable());
// By creating the files upfront, we save waiting a full second for an mtime change
createFile(m_path + QLatin1String("ExistingFile"));
createFile(m_path + QLatin1String("TestFile"));
createFile(m_path + QLatin1String("nested_0"));
createFile(m_path + QLatin1String("nested_1"));
s_staticObject()->m_dirWatch.addFile(m_path + QLatin1String("ExistingFile"));
}
void benchCreateTree();
void benchCreateWatcher();
void benchNotifyWatcher();
private:
QTemporaryDir m_tempDir;
QString m_path;
};
QTEST_MAIN(KDirWatch_UnitTest)
void KDirWatch_UnitTest::benchCreateTree()
{
#if !ENABLE_BENCHMARKS
QSKIP("Benchmarks are disabled in debug mode");
#endif
QTemporaryDir dir;
QBENCHMARK {
createDirectoryTree(dir.path());
}
}
void KDirWatch_UnitTest::benchCreateWatcher()
{
#if !ENABLE_BENCHMARKS
QSKIP("Benchmarks are disabled in debug mode");
#endif
QTemporaryDir dir;
createDirectoryTree(dir.path());
QBENCHMARK {
KDirWatch watch;
watch.addDir(dir.path(), KDirWatch::WatchSubDirs | KDirWatch::WatchFiles);
}
}
void KDirWatch_UnitTest::benchNotifyWatcher()
{
#if !ENABLE_BENCHMARKS
QSKIP("Benchmarks are disabled in debug mode");
#endif
QTemporaryDir dir;
// create the dir once upfront
auto numFiles = createDirectoryTree(dir.path());
waitUntilMTimeChange(dir.path());
KDirWatch watch;
watch.addDir(dir.path(), KDirWatch::WatchSubDirs | KDirWatch::WatchFiles);
// now touch all the files repeatedly and wait for the dirty updates to come in
QSignalSpy spy(&watch, &KDirWatch::dirty);
QBENCHMARK {
createDirectoryTree(dir.path());
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), numFiles, 30000);
spy.clear();
}
}
#include "kdirwatch_benchmarktest.moc"

View File

@@ -0,0 +1,142 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <kdirwatch.h>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTest>
#include <QThread>
#include "kcoreaddons_debug.h"
class StaticObject
{
public:
KDirWatch m_dirWatch;
};
Q_GLOBAL_STATIC(StaticObject, s_staticObject)
class StaticObjectUsingSelf // like KSambaShare does, bug 353080
{
public:
StaticObjectUsingSelf()
{
KDirWatch::self();
}
~StaticObjectUsingSelf()
{
if (KDirWatch::exists() && KDirWatch::self()->contains(QDir::homePath())) {
KDirWatch::self()->removeDir(QDir::homePath());
}
}
};
Q_GLOBAL_STATIC(StaticObjectUsingSelf, s_staticObjectUsingSelf)
namespace KDirWatchTestUtils
{
// Just to make the inotify packets bigger
static const char s_filePrefix[] = "This_is_a_test_file_";
static const char *methodToString(KDirWatch::Method method)
{
switch (method) {
case KDirWatch::FAM:
return "Fam";
case KDirWatch::INotify:
return "INotify";
case KDirWatch::Stat:
return "Stat";
case KDirWatch::QFSWatch:
return "QFSWatch";
}
return "ERROR!";
}
// helper method: create a file
inline void createFile(const QString &path, bool slow = false)
{
QFile file(path);
QVERIFY(file.open(QIODevice::WriteOnly));
#ifdef Q_OS_FREEBSD
// FreeBSD has inotify implemented as user-space library over native kevent API.
// When using it, one has to open() a file to start watching it, so workaround
// test breakage by giving inotify time to react to file creation.
// Full context: https://github.com/libinotify-kqueue/libinotify-kqueue/issues/10
if (!slow) {
QThread::msleep(1);
}
#else
Q_UNUSED(slow)
#endif
file.write(QByteArray("foo"));
file.close();
}
inline int createDirectoryTree(const QString &basePath, int depth = 4)
{
int filesCreated = 0;
const int numFiles = 10;
for (int i = 0; i < numFiles; ++i) {
createFile(basePath + QLatin1Char('/') + QLatin1String(s_filePrefix) + QString::number(i));
++filesCreated;
}
if (depth <= 0) {
return filesCreated;
}
const int numFolders = 5;
for (int i = 0; i < numFolders; ++i) {
const QString childPath = basePath + QLatin1String("/subdir") + QString::number(i);
QDir().mkdir(childPath);
filesCreated += createDirectoryTree(childPath, depth - 1);
}
return filesCreated;
}
inline void waitUntilAfter(const QDateTime &ctime)
{
int totalWait = 0;
QDateTime now;
Q_FOREVER {
now = QDateTime::currentDateTime();
if (now.toMSecsSinceEpoch() / 1000 == ctime.toMSecsSinceEpoch() / 1000) // truncate milliseconds
{
totalWait += 50;
QTest::qWait(50);
} else {
QVERIFY(now > ctime); // can't go back in time ;)
QTest::qWait(50); // be safe
break;
}
}
// if (totalWait > 0)
qCDebug(KCOREADDONS_DEBUG) << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate);
}
inline void waitUntilMTimeChange(const QString &path)
{
// Wait until the current second is more than the file's mtime
// otherwise this change will go unnoticed
QFileInfo fi(path);
QVERIFY(fi.exists());
const QDateTime ctime = fi.lastModified();
waitUntilAfter(ctime);
}
inline void waitUntilNewSecond()
{
QDateTime now = QDateTime::currentDateTime();
waitUntilAfter(now);
}
}

View File

@@ -0,0 +1,767 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <kdirwatch.h>
#include <kdirwatch_p.h>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTest>
#include <QThread>
#include <sys/stat.h>
#ifdef Q_OS_UNIX
#include <unistd.h> // ::link()
#endif
#include "kcoreaddons_debug.h"
#include "kdirwatch_test_utils.h"
using namespace KDirWatchTestUtils;
// Debugging notes: to see which inotify signals are emitted, either set s_verboseDebug=true
// at the top of kdirwatch.cpp, or use the command-line tool "inotifywait -m /path"
class KDirWatch_UnitTest : public QObject
{
Q_OBJECT
public:
KDirWatch_UnitTest()
{
// Speed up the test by making the kdirwatch timer (to compress changes) faster
qputenv("KDIRWATCH_POLLINTERVAL", "50");
qputenv("KDIRWATCH_METHOD", KDIRWATCH_TEST_METHOD);
s_staticObjectUsingSelf();
m_path = m_tempDir.path() + QLatin1Char('/');
KDirWatch *dirW = &s_staticObject()->m_dirWatch;
m_stat = dirW->internalMethod() == KDirWatch::Stat;
m_slow = (dirW->internalMethod() == KDirWatch::FAM || m_stat);
qCDebug(KCOREADDONS_DEBUG) << "Using method" << methodToString(dirW->internalMethod());
}
private Q_SLOTS: // test methods
void initTestCase()
{
QFileInfo pathInfo(m_path);
QVERIFY(pathInfo.isDir() && pathInfo.isWritable());
// By creating the files upfront, we save waiting a full second for an mtime change
createFile(m_path + QLatin1String("ExistingFile"));
createFile(m_path + QLatin1String("TestFile"));
createFile(m_path + QLatin1String("nested_0"));
createFile(m_path + QLatin1String("nested_1"));
s_staticObject()->m_dirWatch.addFile(m_path + QLatin1String("ExistingFile"));
}
void touchOneFile();
void touch1000Files();
void watchAndModifyOneFile();
void removeAndReAdd();
void watchNonExistent();
void watchNonExistentWithSingleton();
void testDelete();
void testDeleteAndRecreateFile();
void testDeleteAndRecreateDir();
void testMoveTo();
void nestedEventLoop();
void testHardlinkChange();
void stopAndRestart();
void testRefcounting();
void testRelativeRefcounting();
void testMoveToThread();
protected Q_SLOTS: // internal slots
void nestedEventLoopSlot();
private:
QList<QVariantList> waitForDirtySignal(KDirWatch &watch, int expected);
QList<QVariantList> waitForDeletedSignal(KDirWatch &watch, int expected);
bool waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path);
bool waitForRecreationSignal(KDirWatch &watch, const QString &path);
bool verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath);
QString createFile(int num);
void createFile(const QString &file)
{
KDirWatchTestUtils::createFile(file, m_slow);
}
void removeFile(int num);
void appendToFile(const QString &path);
void appendToFile(int num);
QTemporaryDir m_tempDir;
QString m_path;
bool m_slow;
bool m_stat;
};
QTEST_MAIN(KDirWatch_UnitTest)
static const int s_maxTries = 50;
// helper method: create a file (identified by number)
QString KDirWatch_UnitTest::createFile(int num)
{
const QString fileName = QLatin1String(s_filePrefix) + QString::number(num);
KDirWatchTestUtils::createFile(m_path + fileName, m_slow);
return m_path + fileName;
}
// helper method: delete a file (identified by number)
void KDirWatch_UnitTest::removeFile(int num)
{
const QString fileName = QLatin1String(s_filePrefix) + QString::number(num);
QFile::remove(m_path + fileName);
}
// helper method: modifies a file
void KDirWatch_UnitTest::appendToFile(const QString &path)
{
QVERIFY(QFile::exists(path));
waitUntilMTimeChange(path);
QFile file(path);
QVERIFY(file.open(QIODevice::Append | QIODevice::WriteOnly));
file.write(QByteArray("foobar"));
file.close();
}
// helper method: modifies a file (identified by number)
void KDirWatch_UnitTest::appendToFile(int num)
{
const QString fileName = QLatin1String(s_filePrefix) + QString::number(num);
appendToFile(m_path + fileName);
}
static QString removeTrailingSlash(const QString &path)
{
if (path.endsWith(QLatin1Char('/'))) {
return path.left(path.length() - 1);
} else {
return path;
}
}
// helper method
QList<QVariantList> KDirWatch_UnitTest::waitForDirtySignal(KDirWatch &watch, int expected)
{
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
int numTries = 0;
// Give time for KDirWatch to notify us
while (spyDirty.count() < expected) {
if (++numTries > s_maxTries) {
qWarning() << "Timeout waiting for KDirWatch. Got" << spyDirty.count() << "dirty() signals, expected" << expected;
return std::move(spyDirty);
}
spyDirty.wait(50);
}
return std::move(spyDirty);
}
bool KDirWatch_UnitTest::waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path)
{
const QString expectedPath = removeTrailingSlash(path);
while (true) {
QSignalSpy spyDirty(&watch, sig);
int numTries = 0;
// Give time for KDirWatch to notify us
while (spyDirty.isEmpty()) {
if (++numTries > s_maxTries) {
qWarning() << "Timeout waiting for KDirWatch signal" << QByteArray(sig).mid(1) << "(" << path << ")";
return false;
}
spyDirty.wait(50);
}
return verifySignalPath(spyDirty, sig, expectedPath);
}
}
bool KDirWatch_UnitTest::verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath)
{
for (int i = 0; i < spy.count(); ++i) {
const QString got = spy[i][0].toString();
if (got == expectedPath) {
return true;
}
if (got.startsWith(expectedPath + QLatin1Char('/'))) {
qCDebug(KCOREADDONS_DEBUG) << "Ignoring (inotify) notification of" << (sig + 1) << '(' << got << ')';
continue;
}
qWarning() << "Expected" << sig << '(' << expectedPath << ')' << "but got" << sig << '(' << got << ')';
return false;
}
return false;
}
bool KDirWatch_UnitTest::waitForRecreationSignal(KDirWatch &watch, const QString &path)
{
// When watching for a deleted + created signal pair, the two might come so close that
// using waitForOneSignal will miss the created signal. This function monitors both all
// the time to ensure both are received.
//
// In addition, it allows dirty() to be emitted (for that same path) as an alternative
const QString expectedPath = removeTrailingSlash(path);
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
QSignalSpy spyDeleted(&watch, &KDirWatch::deleted);
QSignalSpy spyCreated(&watch, &KDirWatch::created);
int numTries = 0;
while (spyDeleted.isEmpty() && spyDirty.isEmpty()) {
if (++numTries > s_maxTries) {
return false;
}
spyDeleted.wait(50);
while (!spyDirty.isEmpty()) {
if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated
spyDirty.removeFirst();
}
}
}
if (!spyDirty.isEmpty()) {
return true;
}
// Don't bother waiting for the created signal if the signal spy already received a signal.
if (spyCreated.isEmpty() && !spyCreated.wait(50 * s_maxTries)) {
qWarning() << "Timeout waiting for KDirWatch signal created(QString) (" << path << ")";
return false;
}
return verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath);
}
QList<QVariantList> KDirWatch_UnitTest::waitForDeletedSignal(KDirWatch &watch, int expected)
{
QSignalSpy spyDeleted(&watch, &KDirWatch::created);
int numTries = 0;
// Give time for KDirWatch to notify us
while (spyDeleted.count() < expected) {
if (++numTries > s_maxTries) {
qWarning() << "Timeout waiting for KDirWatch. Got" << spyDeleted.count() << "deleted() signals, expected" << expected;
return std::move(spyDeleted);
}
spyDeleted.wait(50);
}
return std::move(spyDeleted);
}
void KDirWatch_UnitTest::touchOneFile() // watch a dir, create a file in it
{
KDirWatch watch;
watch.addDir(m_path);
watch.startScan();
waitUntilMTimeChange(m_path);
// dirty(the directory) should be emitted.
QSignalSpy spyCreated(&watch, &KDirWatch::created);
createFile(0);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
QCOMPARE(spyCreated.count(), 0); // "This is not emitted when creating a file is created in a watched directory."
removeFile(0);
}
void KDirWatch_UnitTest::touch1000Files()
{
KDirWatch watch;
watch.addDir(m_path);
watch.startScan();
waitUntilMTimeChange(m_path);
const int fileCount = 100;
for (int i = 0; i < fileCount; ++i) {
createFile(i);
}
QList<QVariantList> spy = waitForDirtySignal(watch, fileCount);
if (watch.internalMethod() == KDirWatch::INotify) {
QVERIFY(spy.count() >= fileCount);
} else {
// More stupid backends just see one mtime change on the directory
QVERIFY(spy.count() >= 1);
}
for (int i = 0; i < fileCount; ++i) {
removeFile(i);
}
}
void KDirWatch_UnitTest::watchAndModifyOneFile() // watch a specific file, and modify it
{
KDirWatch watch;
const QString existingFile = m_path + QLatin1String("ExistingFile");
watch.addFile(existingFile);
watch.startScan();
if (m_slow) {
waitUntilNewSecond();
}
appendToFile(existingFile);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile));
}
void KDirWatch_UnitTest::removeAndReAdd()
{
KDirWatch watch;
watch.addDir(m_path);
// This triggers bug #374075.
watch.addDir(QStringLiteral(":/kio5/newfile-templates"));
watch.startScan();
if (watch.internalMethod() != KDirWatch::INotify) {
waitUntilNewSecond(); // necessary for mtime checks in scanEntry
}
createFile(0);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
// Just like KDirLister does: remove the watch, then re-add it.
watch.removeDir(m_path);
watch.addDir(m_path);
if (watch.internalMethod() != KDirWatch::INotify) {
waitUntilMTimeChange(m_path); // necessary for FAM and QFSWatcher
}
createFile(1);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
}
void KDirWatch_UnitTest::watchNonExistent()
{
KDirWatch watch;
// Watch "subdir", that doesn't exist yet
const QString subdir = m_path + QLatin1String("subdir");
QVERIFY(!QFile::exists(subdir));
watch.addDir(subdir);
watch.startScan();
if (m_slow) {
waitUntilNewSecond();
}
// Now create it, KDirWatch should emit created()
qCDebug(KCOREADDONS_DEBUG) << "Creating" << subdir;
QDir().mkdir(subdir);
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), subdir));
KDirWatch::statistics();
// Play with addDir/removeDir, just for fun
watch.addDir(subdir);
watch.removeDir(subdir);
watch.addDir(subdir);
// Now watch files that don't exist yet
const QString file = subdir + QLatin1String("/0");
watch.addFile(file); // doesn't exist yet
const QString file1 = subdir + QLatin1String("/1");
watch.addFile(file1); // doesn't exist yet
watch.removeFile(file1); // forget it again
KDirWatch::statistics();
QVERIFY(!QFile::exists(file));
// Now create it, KDirWatch should emit created
qCDebug(KCOREADDONS_DEBUG) << "Creating" << file;
createFile(file);
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file));
appendToFile(file);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file));
// Create the file after all; we're not watching for it, but the dir will emit dirty
createFile(file1);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), subdir));
}
void KDirWatch_UnitTest::watchNonExistentWithSingleton()
{
const QString file = QLatin1String("/root/.ssh/authorized_keys");
KDirWatch::self()->addFile(file);
// When running this test in KDIRWATCH_METHOD=QFSWatch, or when FAM is not available
// and we fallback to qfswatch when inotify fails above, we end up creating the fsWatch
// in the kdirwatch singleton. Bug 261541 discovered that Qt hanged when deleting fsWatch
// once QCoreApp was gone, this is what this test is about.
}
void KDirWatch_UnitTest::testDelete()
{
const QString file1 = m_path + QLatin1String("del");
if (!QFile::exists(file1)) {
createFile(file1);
}
waitUntilMTimeChange(file1);
// Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed)
KDirWatch watch;
watch.addFile(file1);
KDirWatch::statistics();
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
QFile::remove(file1);
QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1));
QTest::qWait(40); // just in case delayed processing would emit it
QCOMPARE(spyDirty.count(), 0);
}
void KDirWatch_UnitTest::testDeleteAndRecreateFile() // Useful for /etc/localtime for instance
{
const QString subdir = m_path + QLatin1String("subdir");
QDir().mkdir(subdir);
const QString file1 = subdir + QLatin1String("/1");
if (!QFile::exists(file1)) {
createFile(file1);
}
waitUntilMTimeChange(file1);
// Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed)
KDirWatch watch;
watch.addFile(file1);
// Make sure this even works multiple times, as needed for ksycoca
for (int i = 0; i < 5; ++i) {
if (m_slow || watch.internalMethod() == KDirWatch::QFSWatch) {
waitUntilNewSecond();
}
qCDebug(KCOREADDONS_DEBUG) << "Attempt #" << (i + 1) << "removing+recreating" << file1;
// When watching for a deleted + created signal pair, the two might come so close that
// using waitForOneSignal will miss the created signal. This function monitors both all
// the time to ensure both are received.
//
// In addition, allow dirty() to be emitted (for that same path) as an alternative
const QString expectedPath = file1;
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
QSignalSpy spyDeleted(&watch, &KDirWatch::deleted);
QSignalSpy spyCreated(&watch, &KDirWatch::created);
// WHEN
QFile::remove(file1);
// And recreate immediately, to try and fool KDirWatch with unchanged ctime/mtime ;)
// (This emulates the /etc/localtime case)
createFile(file1);
// THEN
int numTries = 0;
while (spyDeleted.isEmpty() && spyDirty.isEmpty()) {
if (++numTries > s_maxTries) {
QFAIL("Failed to detect file deletion and recreation through either a deleted/created signal pair or through a dirty signal!");
return;
}
spyDeleted.wait(50);
while (!spyDirty.isEmpty()) {
if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated
spyDirty.removeFirst();
} else {
break;
}
}
}
if (!spyDirty.isEmpty()) {
continue; // all ok
}
// Don't bother waiting for the created signal if the signal spy already received a signal.
if (spyCreated.isEmpty() && !spyCreated.wait(50 * s_maxTries)) {
qWarning() << "Timeout waiting for KDirWatch signal created(QString) (" << expectedPath << ")";
QFAIL("Timeout waiting for KDirWatch signal created, after deleted was emitted");
return;
}
QVERIFY(verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath));
}
waitUntilMTimeChange(file1);
appendToFile(file1);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
}
void KDirWatch_UnitTest::testDeleteAndRecreateDir()
{
// Like KDirModelTest::testOverwriteFileWithDir does at the end.
// The linux-2.6.31 bug made kdirwatch emit deletion signals about the -new- dir!
QTemporaryDir *tempDir1 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("olddir-"));
KDirWatch watch;
const QString path1 = tempDir1->path() + QLatin1Char('/');
watch.addDir(path1);
delete tempDir1;
QTemporaryDir *tempDir2 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("newdir-"));
const QString path2 = tempDir2->path() + QLatin1Char('/');
watch.addDir(path2);
QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), path1));
delete tempDir2;
}
void KDirWatch_UnitTest::testMoveTo()
{
// This reproduces the famous digikam crash, #222974
// A watched file was being rewritten (overwritten by ksavefile),
// which gives inotify notifications "moved_to" followed by "delete_self"
//
// What happened then was that the delayed slotRescan
// would adjust things, making it status==Normal but the entry was
// listed as a "non-existent sub-entry" for the parent directory.
// That's inconsistent, and after removeFile() a dangling sub-entry would be left.
// Initial data: creating file subdir/1
const QString file1 = m_path + QLatin1String("moveTo");
createFile(file1);
KDirWatch watch;
watch.addDir(m_path);
watch.addFile(file1);
watch.startScan();
if (watch.internalMethod() != KDirWatch::INotify) {
waitUntilMTimeChange(m_path);
}
// Atomic rename of "temp" to "file1", much like KAutoSave would do when saving file1 again
// ### TODO: this isn't an atomic rename anymore. We need ::rename for that, or API from Qt.
const QString filetemp = m_path + QLatin1String("temp");
createFile(filetemp);
QFile::remove(file1);
QVERIFY(QFile::rename(filetemp, file1)); // overwrite file1 with the tempfile
qCDebug(KCOREADDONS_DEBUG) << "Overwrite file1 with tempfile";
QSignalSpy spyCreated(&watch, &KDirWatch::created);
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
// Getting created() on an unwatched file is an inotify bonus, it's not part of the requirements.
if (watch.internalMethod() == KDirWatch::INotify) {
QCOMPARE(spyCreated.count(), 1);
QCOMPARE(spyCreated[0][0].toString(), file1);
QCOMPARE(spyDirty.size(), 2);
QCOMPARE(spyDirty[1][0].toString(), filetemp);
}
// make sure we're still watching it
appendToFile(file1);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
watch.removeFile(file1); // now we remove it
// Just touch another file to trigger a findSubEntry - this where the crash happened
waitUntilMTimeChange(m_path);
createFile(filetemp);
#ifdef Q_OS_WIN
if (watch.internalMethod() == KDirWatch::QFSWatch) {
QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue);
}
#endif
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
}
void KDirWatch_UnitTest::nestedEventLoop() // #220153: watch two files, and modify 2nd while in slot for 1st
{
KDirWatch watch;
const QString file0 = m_path + QLatin1String("nested_0");
watch.addFile(file0);
const QString file1 = m_path + QLatin1String("nested_1");
watch.addFile(file1);
watch.startScan();
if (m_slow) {
waitUntilNewSecond();
}
appendToFile(file0);
// use own spy, to connect it before nestedEventLoopSlot, otherwise it reverses order
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
connect(&watch, &KDirWatch::dirty, this, &KDirWatch_UnitTest::nestedEventLoopSlot);
waitForDirtySignal(watch, 1);
QVERIFY(spyDirty.count() >= 2);
QCOMPARE(spyDirty[0][0].toString(), file0);
QCOMPARE(spyDirty[spyDirty.count() - 1][0].toString(), file1);
}
void KDirWatch_UnitTest::nestedEventLoopSlot()
{
const KDirWatch *const_watch = qobject_cast<const KDirWatch *>(sender());
KDirWatch *watch = const_cast<KDirWatch *>(const_watch);
// let's not come in this slot again
disconnect(watch, &KDirWatch::dirty, this, &KDirWatch_UnitTest::nestedEventLoopSlot);
const QString file1 = m_path + QLatin1String("nested_1");
appendToFile(file1);
// The nested event processing here was from a messagebox in #220153
QList<QVariantList> spy = waitForDirtySignal(*watch, 1);
QVERIFY(spy.count() >= 1);
QCOMPARE(spy[spy.count() - 1][0].toString(), file1);
// Now the user pressed reload...
const QString file0 = m_path + QLatin1String("nested_0");
watch->removeFile(file0);
watch->addFile(file0);
}
void KDirWatch_UnitTest::testHardlinkChange()
{
#ifdef Q_OS_UNIX
// The unittest for the "detecting hardlink change to /etc/localtime" problem
// described on kde-core-devel (2009-07-03).
// It shows that watching a specific file doesn't inform us that the file is
// being recreated. Better watch the directory, for that.
// Well, it works with inotify (and fam - which uses inotify I guess?)
const QString existingFile = m_path + QLatin1String("ExistingFile");
KDirWatch watch;
watch.addFile(existingFile);
watch.startScan();
QFile::remove(existingFile);
const QString testFile = m_path + QLatin1String("TestFile");
QVERIFY(::link(QFile::encodeName(testFile).constData(), QFile::encodeName(existingFile).constData()) == 0); // make ExistingFile "point" to TestFile
QVERIFY(QFile::exists(existingFile));
QVERIFY(waitForRecreationSignal(watch, existingFile));
// The mtime of the existing file is the one of "TestFile", so it's old.
// We won't detect the change then, if we use that as baseline for waiting.
// We really need msec granularity, but that requires using statx which isn't available everywhere...
waitUntilNewSecond();
appendToFile(existingFile);
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile));
#else
QSKIP("Unix-specific");
#endif
}
void KDirWatch_UnitTest::stopAndRestart()
{
KDirWatch watch;
watch.addDir(m_path);
watch.startScan();
waitUntilMTimeChange(m_path);
watch.stopDirScan(m_path);
qCDebug(KCOREADDONS_DEBUG) << "create file 2 at" << QDateTime::currentDateTime().toMSecsSinceEpoch();
const QString file2 = createFile(2);
QSignalSpy spyDirty(&watch, &KDirWatch::dirty);
QTest::qWait(200);
QCOMPARE(spyDirty.count(), 0); // suspended -> no signal
watch.restartDirScan(m_path);
QTest::qWait(200);
#ifndef Q_OS_WIN
QCOMPARE(spyDirty.count(), 0); // as documented by restartDirScan: no signal
// On Windows, however, signals will get emitted, due to the ifdef Q_OS_WIN in the timestamp
// comparison ("trust QFSW since the mtime of dirs isn't modified")
#endif
KDirWatch::statistics();
waitUntilMTimeChange(m_path); // necessary for the mtime comparison in scanEntry
qCDebug(KCOREADDONS_DEBUG) << "create file 3 at" << QDateTime::currentDateTime().toMSecsSinceEpoch();
const QString file3 = createFile(3);
#ifdef Q_OS_WIN
if (watch.internalMethod() == KDirWatch::QFSWatch) {
QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue);
}
#endif
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
QFile::remove(file2);
QFile::remove(file3);
}
void KDirWatch_UnitTest::testRefcounting()
{
#if QT_CONFIG(cxx11_future)
bool initialExists = false;
bool secondExists = true; // the expectation is it will be set false
auto thread = QThread::create([&] {
QTemporaryDir dir;
{
KDirWatch watch;
watch.addFile(dir.path());
initialExists = KDirWatch::exists();
} // out of scope, the internal private should have been unset
secondExists = KDirWatch::exists();
});
thread->start();
thread->wait();
delete thread;
QVERIFY(initialExists);
QVERIFY(!secondExists);
#endif
}
void KDirWatch_UnitTest::testRelativeRefcounting()
{
// Relative files aren't supported but should still result in correct ref
// handling. Specifically when watch1 gets destroyed it should take all
// its entries with it regardless of whether the entry was well-formed
KDirWatch watch0;
if (watch0.internalMethod() != KDirWatch::INotify) {
// Only test on inotify. Otherwise Entry count expectations may diverge.
return;
}
const auto initialSize = watch0.d->m_mapEntries.size();
{
KDirWatch watch1;
watch1.addFile(QStringLiteral("AVeryRelativePath.txt"));
// NOTE: addFile actually adds two entires: one for '.' to watch for the appearance of the file and one for the file.
QCOMPARE(watch0.d->m_mapEntries.size(), initialSize + 2);
}
// NOTE: we leak the directory entry from above but it has no clients so it's mostly harmless
QCOMPARE(watch0.d->m_mapEntries.size(), initialSize + 1);
}
void KDirWatch_UnitTest::testMoveToThread()
{
QTemporaryDir dir;
{
const QRegularExpression expression(QStringLiteral("KDirwatch is moving its thread. This is not supported at this time;.+"));
QTest::ignoreMessage(QtCriticalMsg, expression);
auto watch = new KDirWatch;
watch->addDir(dir.path());
auto thread = new QThread;
watch->moveToThread(thread);
thread->start();
waitUntilMTimeChange(dir.path());
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, watch, &QObject::deleteLater);
thread->quit();
thread->wait();
}
// trigger an event on the now deleted watch. This should not crash!
const QString file = dir.path() + QLatin1String("/bar");
createFile(file);
waitUntilMTimeChange(file);
}
#include "kdirwatch_unittest.moc"

View File

@@ -0,0 +1,93 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfileutilstest.h"
#include <KFileUtils>
#include <QStandardPaths>
#include <QTest>
QTEST_MAIN(KFileUtilsTest)
void KFileUtilsTest::testSuggestName_data()
{
QTest::addColumn<QString>("oldName");
QTest::addColumn<QStringList>("existingFiles");
QTest::addColumn<QString>("expectedOutput");
QTest::newRow("non-existing") << "foobar" << QStringList() << "foobar (1)";
QTest::newRow("existing") << "foobar" << QStringList(QStringLiteral("foobar")) << "foobar (1)";
QTest::newRow("existing_1") << "foobar" << (QStringList() << QStringLiteral("foobar") << QStringLiteral("foobar (1)")) << "foobar (2)";
QTest::newRow("extension") << "foobar.txt" << QStringList() << "foobar (1).txt";
QTest::newRow("extension_exists") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt")) << "foobar (1).txt";
QTest::newRow("extension_exists_1") << "foobar.txt" << (QStringList() << QStringLiteral("foobar.txt") << QStringLiteral("foobar (1).txt"))
<< "foobar (2).txt";
QTest::newRow("two_extensions") << "foobar.tar.gz" << QStringList() << "foobar (1).tar.gz";
QTest::newRow("two_extensions_exists") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz")) << "foobar (1).tar.gz";
QTest::newRow("two_extensions_exists_1") << "foobar.tar.gz" << (QStringList() << QStringLiteral("foobar.tar.gz") << QStringLiteral("foobar (1).tar.gz"))
<< "foobar (2).tar.gz";
QTest::newRow("with_space") << "foo bar" << QStringList(QStringLiteral("foo bar")) << "foo bar (1)";
QTest::newRow("dot_at_beginning") << ".aFile.tar.gz" << QStringList() << ".aFile (1).tar.gz";
QTest::newRow("dots_at_beginning") << "..aFile.tar.gz" << QStringList() << "..aFile (1).tar.gz";
QTest::newRow("empty_basename") << ".txt" << QStringList() << ". (1).txt";
QTest::newRow("empty_basename_2dots") << "..txt" << QStringList() << ". (1).txt";
QTest::newRow("basename_with_dots") << "filename.5.3.2.tar.gz" << QStringList() << "filename.5.3.2 (1).tar.gz";
QTest::newRow("unknown_extension_trashinfo") << "fileFromHome.trashinfo" << QStringList() << "fileFromHome (1).trashinfo";
}
void KFileUtilsTest::testSuggestName()
{
QFETCH(QString, oldName);
QFETCH(QStringList, existingFiles);
QFETCH(QString, expectedOutput);
QTemporaryDir dir;
const QUrl baseUrl = QUrl::fromLocalFile(dir.path());
for (const QString &localFile : std::as_const(existingFiles)) {
QFile file(dir.path() + QLatin1Char('/') + localFile);
QVERIFY(file.open(QIODevice::WriteOnly));
}
QCOMPARE(KFileUtils::suggestName(baseUrl, oldName), expectedOutput);
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID)
#define XDG_PLATFORM
#endif
void KFileUtilsTest::testfindAllUniqueFiles()
{
#ifndef XDG_PLATFORM
QSKIP("This test requires XDG_DATA_DIRS; no way to configure QStandardPaths on Windows");
#endif
const QString testBaseDirPath = QDir::currentPath() + QLatin1String("/kfileutilstestdata/");
QDir testDataBaseDir(testBaseDirPath);
testDataBaseDir.mkpath(QStringLiteral("."));
testDataBaseDir.mkpath(QStringLiteral("testdir1/testDirName"));
testDataBaseDir.mkpath(QStringLiteral("testdir2/testDirName"));
testDataBaseDir.mkpath(QStringLiteral("testdir3/testDirName"));
QFile file1(testBaseDirPath + QLatin1String("/testdir1/testDirName/testfile.test"));
file1.open(QFile::WriteOnly);
QFile file2(testBaseDirPath + QLatin1String("/testdir2/testDirName/testfile.test"));
file2.open(QFile::WriteOnly);
QFile file3(testBaseDirPath + QLatin1String("/testdir3/testDirName/differentfile.test"));
file3.open(QFile::WriteOnly);
QFile file4(testBaseDirPath + QLatin1String("/testdir3/testDirName/nomatch.txt"));
file4.open(QFile::WriteOnly);
qputenv("XDG_DATA_DIRS", qPrintable(QStringLiteral("%1testdir1:%1testdir2:%1testdir3").arg(testBaseDirPath)));
const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("testDirName"), QStandardPaths::LocateDirectory);
const QStringList expected = {testDataBaseDir.filePath(QStringLiteral("testdir1/testDirName/testfile.test")),
testDataBaseDir.filePath(QStringLiteral("testdir3/testDirName/differentfile.test"))};
const QStringList actual = KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.test")});
QCOMPARE(actual, expected);
}
#include "moc_kfileutilstest.cpp"

View File

@@ -0,0 +1,22 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILEUTILSTEST_H
#define KFILEUTILSTEST_H
#include <QObject>
class KFileUtilsTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testSuggestName_data();
void testSuggestName();
void testfindAllUniqueFiles();
};
#endif

View File

@@ -0,0 +1,402 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kformattest.h"
#include <QTest>
#include "kformat.h"
void setupEnvironment()
{
#ifndef Q_OS_WIN
// ignore translations
qputenv("XDG_DATA_DIRS", "does-not-exist");
#endif
}
Q_CONSTRUCTOR_FUNCTION(setupEnvironment)
void KFormatTest::formatByteSize()
{
QLocale locale(QLocale::c());
locale.setNumberOptions(QLocale::DefaultNumberOptions); // Qt >= 5.6 sets QLocale::OmitGroupSeparator for the C locale
KFormat format(locale);
QCOMPARE(format.formatByteSize(0), QStringLiteral("0 B"));
QCOMPARE(format.formatByteSize(50), QStringLiteral("50 B"));
QCOMPARE(format.formatByteSize(500), QStringLiteral("500 B"));
QCOMPARE(format.formatByteSize(5000), QStringLiteral("4.9 KiB"));
QCOMPARE(format.formatByteSize(50000), QStringLiteral("48.8 KiB"));
QCOMPARE(format.formatByteSize(500000), QStringLiteral("488.3 KiB"));
QCOMPARE(format.formatByteSize(5000000), QStringLiteral("4.8 MiB"));
QCOMPARE(format.formatByteSize(50000000), QStringLiteral("47.7 MiB"));
QCOMPARE(format.formatByteSize(500000000), QStringLiteral("476.8 MiB"));
#if (defined(__WORDSIZE) && (__WORDSIZE == 64)) || defined(_LP64) || defined(__LP64__) || defined(__ILP64__)
QCOMPARE(format.formatByteSize(5000000000), QStringLiteral("4.7 GiB"));
QCOMPARE(format.formatByteSize(50000000000), QStringLiteral("46.6 GiB"));
QCOMPARE(format.formatByteSize(500000000000), QStringLiteral("465.7 GiB"));
QCOMPARE(format.formatByteSize(5000000000000), QStringLiteral("4.5 TiB"));
QCOMPARE(format.formatByteSize(50000000000000), QStringLiteral("45.5 TiB"));
QCOMPARE(format.formatByteSize(500000000000000), QStringLiteral("454.7 TiB"));
#endif
QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.0 KiB"));
QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1,023 B"));
QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.1 MiB")); // 1.2 metric
QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.0 KiB"));
QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1,023 B"));
QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.1 MiB")); // 1.2 metric
QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.0 KB"));
QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1,023 B"));
QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.1 MB"));
QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.0 KB"));
QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1,023 B"));
QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.1 MB"));
QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB"));
QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB"));
QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.2 MB"));
QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB"));
QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB"));
QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.2 MB"));
// Ensure all units are represented
QCOMPARE(format.formatByteSize(2.0e9, 1, KFormat::MetricBinaryDialect), QStringLiteral("2.0 GB"));
QCOMPARE(format.formatByteSize(3.2e12, 1, KFormat::MetricBinaryDialect), QStringLiteral("3.2 TB"));
QCOMPARE(format.formatByteSize(4.1e15, 1, KFormat::MetricBinaryDialect), QStringLiteral("4.1 PB"));
QCOMPARE(format.formatByteSize(6.7e18, 2, KFormat::MetricBinaryDialect), QStringLiteral("6.70 EB"));
QCOMPARE(format.formatByteSize(5.6e20, 2, KFormat::MetricBinaryDialect), QStringLiteral("560.00 EB"));
QCOMPARE(format.formatByteSize(2.3e22, 2, KFormat::MetricBinaryDialect), QStringLiteral("23.00 ZB"));
QCOMPARE(format.formatByteSize(1.0e27, 1, KFormat::MetricBinaryDialect), QStringLiteral("1,000.0 YB"));
// Spattering of specific units
QCOMPARE(format.formatByteSize(823000, 3, KFormat::IECBinaryDialect, KFormat::UnitMegaByte), QStringLiteral("0.785 MiB"));
QCOMPARE(format.formatByteSize(1234034.0, 4, KFormat::JEDECBinaryDialect, KFormat::UnitByte), QStringLiteral("1,234,034 B"));
// Check examples from the documentation
QCOMPARE(format.formatByteSize(1000, 1, KFormat::MetricBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 kB"));
QCOMPARE(format.formatByteSize(1000, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 KiB"));
QCOMPARE(format.formatByteSize(1000, 1, KFormat::JEDECBinaryDialect, KFormat::UnitKiloByte), QStringLiteral("1.0 KB"));
}
void KFormatTest::formatValue()
{
QLocale locale(QLocale::c());
locale.setNumberOptions(QLocale::DefaultNumberOptions); // Qt >= 5.6 sets QLocale::OmitGroupSeparator for the C locale
KFormat format(locale);
// Check examples from the documentation
QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB"));
QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::IECBinaryDialect), QStringLiteral("1.0 KiB"));
QCOMPARE(format.formatValue(1000, KFormat::Unit::Byte, 1, KFormat::UnitPrefix::Kilo, KFormat::JEDECBinaryDialect), QStringLiteral("1.0 KB"));
// Check examples from the documentation
QCOMPARE(format.formatValue(1000, KFormat::Unit::Bit, 1, KFormat::UnitPrefix::Kilo, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kbit"));
QCOMPARE(format.formatValue(1000, QStringLiteral("bit"), 1, KFormat::UnitPrefix::Kilo), QStringLiteral("1.0 kbit"));
QCOMPARE(format.formatValue(1000, QStringLiteral("bit/s"), 1, KFormat::UnitPrefix::Kilo), QStringLiteral("1.0 kbit/s"));
QCOMPARE(format.formatValue(100, QStringLiteral("bit/s")), QStringLiteral("100.0 bit/s"));
QCOMPARE(format.formatValue(1000, QStringLiteral("bit/s")), QStringLiteral("1.0 kbit/s"));
QCOMPARE(format.formatValue(10e3, QStringLiteral("bit/s")), QStringLiteral("10.0 kbit/s"));
QCOMPARE(format.formatValue(10e6, QStringLiteral("bit/s")), QStringLiteral("10.0 Mbit/s"));
QCOMPARE(format.formatValue(0.010, KFormat::Unit::Meter, 1, KFormat::UnitPrefix::Milli, KFormat::MetricBinaryDialect), QStringLiteral("10.0 mm"));
QCOMPARE(format.formatValue(10.12e-6, KFormat::Unit::Meter, 2, KFormat::UnitPrefix::Micro, KFormat::MetricBinaryDialect), QString::fromUtf8("10.12 µm"));
QCOMPARE(format.formatValue(10.55e-6, KFormat::Unit::Meter, 1, KFormat::UnitPrefix::AutoAdjust, KFormat::MetricBinaryDialect),
QString::fromUtf8("10.6 µm"));
}
enum TimeConstants {
MSecsInDay = 86400000,
MSecsInHour = 3600000,
MSecsInMinute = 60000,
MSecsInSecond = 1000,
};
void KFormatTest::formatDuration()
{
KFormat format(QLocale::c());
quint64 singleSecond = 3 * MSecsInSecond + 700;
quint64 doubleSecond = 33 * MSecsInSecond + 700;
quint64 singleMinute = 8 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 doubleMinute = 38 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 singleHour = 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 doubleHour = 15 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 singleDay = 1 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 doubleDay = 10 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700;
quint64 roundingIssues = 2 * MSecsInHour + 59 * MSecsInMinute + 59 * MSecsInSecond + 900;
quint64 largeValue = 9999999999;
// Default format
QCOMPARE(format.formatDuration(singleSecond), QStringLiteral("0:00:04"));
QCOMPARE(format.formatDuration(doubleSecond), QStringLiteral("0:00:34"));
QCOMPARE(format.formatDuration(singleMinute), QStringLiteral("0:08:04"));
QCOMPARE(format.formatDuration(doubleMinute), QStringLiteral("0:38:04"));
QCOMPARE(format.formatDuration(singleHour), QStringLiteral("5:08:04"));
QCOMPARE(format.formatDuration(doubleHour), QStringLiteral("15:08:04"));
QCOMPARE(format.formatDuration(singleDay), QStringLiteral("29:08:04"));
QCOMPARE(format.formatDuration(doubleDay), QStringLiteral("245:08:04"));
QCOMPARE(format.formatDuration(roundingIssues), QStringLiteral("3:00:00"));
QCOMPARE(format.formatDuration(largeValue), QStringLiteral("2777:46:40"));
// ShowMilliseconds format
KFormat::DurationFormatOptions options = KFormat::ShowMilliseconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00:03.700"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:00:33.700"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08:03.700"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38:03.700"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08:03.700"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08:03.700"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08:03.700"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08:03.700"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2:59:59.900"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("2777:46:39.999"));
// HideSeconds format
options = KFormat::HideSeconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:01"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3:00"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("2777:47"));
// FoldHours format
options = KFormat::FoldHours;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:04"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:34"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:04"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:04"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:04"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:04"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:04"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:04"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180:00"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("166666:40"));
// FoldHours ShowMilliseconds format
options = KFormat::FoldHours;
options = options | KFormat::ShowMilliseconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:03.700"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:33.700"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:03.700"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:03.700"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:03.700"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:03.700"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:03.700"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:03.700"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179:59.900"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("166666:39.999"));
// InitialDuration format
options = KFormat::InitialDuration;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m04s"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m34s"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m04s"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m04s"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m04s"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m04s"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m04s"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m04s"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m00s"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("2777h46m40s"));
// InitialDuration and ShowMilliseconds format
options = KFormat::InitialDuration;
options = options | KFormat::ShowMilliseconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m03.700s"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m33.700s"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m03.700s"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m03.700s"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m03.700s"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m03.700s"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m03.700s"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m03.700s"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2h59m59.900s"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("2777h46m39.999s"));
// InitialDuration and HideSeconds format
options = KFormat::InitialDuration;
options = options | KFormat::HideSeconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h01m"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("2777h47m"));
// InitialDuration and FoldHours format
options = KFormat::InitialDuration;
options = options | KFormat::FoldHours;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m04s"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m34s"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m04s"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m04s"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m04s"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m04s"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m04s"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m04s"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180m00s"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("166666m40s"));
// InitialDuration and FoldHours and ShowMilliseconds format
options = KFormat::InitialDuration;
options = options | KFormat::FoldHours | KFormat::ShowMilliseconds;
QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m03.700s"));
QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m33.700s"));
QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m03.700s"));
QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m03.700s"));
QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m03.700s"));
QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m03.700s"));
QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m03.700s"));
QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m03.700s"));
QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179m59.900s"));
QCOMPARE(format.formatDuration(largeValue, options), QStringLiteral("166666m39.999s"));
}
void KFormatTest::formatDecimalDuration()
{
KFormat format(QLocale::c());
QCOMPARE(format.formatDecimalDuration(10), QStringLiteral("10 millisecond(s)"));
QCOMPARE(format.formatDecimalDuration(10, 3), QStringLiteral("10 millisecond(s)"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 10), QStringLiteral("1.01 seconds"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 1, 3), QStringLiteral("1.001 seconds"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond), QStringLiteral("1.17 minutes"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond, 3), QStringLiteral("1.167 minutes"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute), QStringLiteral("1.17 hours"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute, 3), QStringLiteral("1.167 hours"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour), QStringLiteral("1.42 days"));
QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour, 3), QStringLiteral("1.417 days"));
}
void KFormatTest::formatSpelloutDuration()
{
KFormat format(QLocale::c());
QCOMPARE(format.formatSpelloutDuration(1000), QStringLiteral("1 second(s)"));
QCOMPARE(format.formatSpelloutDuration(5000), QStringLiteral("5 second(s)"));
QCOMPARE(format.formatSpelloutDuration(60000), QStringLiteral("1 minute(s)"));
QCOMPARE(format.formatSpelloutDuration(300000), QStringLiteral("5 minute(s)"));
QCOMPARE(format.formatSpelloutDuration(3600000), QStringLiteral("1 hour(s)"));
QCOMPARE(format.formatSpelloutDuration(18000000), QStringLiteral("5 hour(s)"));
QCOMPARE(format.formatSpelloutDuration(75000), QStringLiteral("1 minute(s) and 15 second(s)"));
// Problematic case #1 (there is a reference to this case on kformat.cpp)
QCOMPARE(format.formatSpelloutDuration(119999), QStringLiteral("2 minute(s)"));
// This case is strictly 2 hours, 15 minutes and 59 seconds. However, since the range is
// pretty high between hours and seconds, formatSpelloutDuration always omits seconds when there
// are hours in scene.
QCOMPARE(format.formatSpelloutDuration(8159000), QStringLiteral("2 hour(s) and 15 minute(s)"));
// This case is strictly 1 hour and 10 seconds. For the same reason, formatSpelloutDuration
// detects that 10 seconds is just garbage compared to 1 hour, and omits it on the result.
QCOMPARE(format.formatSpelloutDuration(3610000), QStringLiteral("1 hour(s)"));
}
void KFormatTest::formatRelativeDate()
{
KFormat format(QLocale::c());
QDate testDate = QDate::currentDate();
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Today"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Today"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Today"));
testDate = QDate::currentDate().addDays(1);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Tomorrow"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Tomorrow"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Tomorrow"));
testDate = QDate::currentDate().addDays(-1);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Yesterday"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Yesterday"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Yesterday"));
testDate = QDate::currentDate().addDays(2);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("In two days"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("In two days"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("In two days"));
testDate = QDate::currentDate().addDays(-2);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Two days ago"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Two days ago"));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Two days ago"));
testDate = QDate::currentDate().addDays(-3);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat));
testDate = QDate::currentDate().addDays(3);
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat));
QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat));
testDate = QDate(); // invalid date
QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Invalid date"));
QDateTime now = QDateTime::currentDateTime();
// An hour ago is **usually** today, except after midnight; just bump
// to after 2am to make the "today" test work.
if (now.time().hour() == 0) {
now = now.addSecs(7201);
}
QDateTime testDateTime = now.addSecs(-3600);
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat),
QStringLiteral("Today at %1").arg(testDateTime.toString(QStringLiteral("hh:mm:ss"))));
// 1 second ago
now = QDateTime::currentDateTime();
testDateTime = now.addSecs(-1);
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("Just now"));
// 5 minutes ago
testDateTime = now.addSecs(-300);
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("5 minute(s) ago"));
testDateTime = QDateTime(QDate::currentDate().addDays(8), QTime(3, 0, 0));
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::LongFormat),
QStringLiteral("%1 at %2")
.arg(QLocale::c().toString(testDateTime.date(), QLocale::LongFormat), QLocale::c().toString(testDateTime.time(), QLocale::ShortFormat)));
// 2021-10-03 07:33:57.000
testDateTime = QDateTime::fromMSecsSinceEpoch(1633239237000, Qt::UTC);
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::LongFormat),
QStringLiteral("%1 at %2")
.arg(QLocale::c().toString(testDateTime.date(), QLocale::LongFormat), QLocale::c().toString(testDateTime.time(), QLocale::ShortFormat)));
QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat),
QStringLiteral("%1 at %2")
.arg(QLocale::c().toString(testDateTime.date(), QLocale::ShortFormat), QLocale::c().toString(testDateTime.time(), QLocale::ShortFormat)));
// With a different local for double check
QLocale englishLocal = QLocale::English;
KFormat formatEnglish(englishLocal);
QCOMPARE(formatEnglish.formatRelativeDateTime(testDateTime, QLocale::LongFormat), QStringLiteral("Sunday, October 3, 2021 at 5:33 AM"));
}
QTEST_MAIN(KFormatTest)
#include "moc_kformattest.cpp"

View File

@@ -0,0 +1,31 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFORMATTEST_H
#define KFORMATTEST_H
#include <QObject>
class KFormatTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void formatByteSize();
void formatDuration();
void formatDecimalDuration();
void formatSpelloutDuration();
void formatRelativeDate();
void formatValue();
};
#endif // KFORMATTEST_H

View File

@@ -0,0 +1,237 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfuzzymatchertest.h"
#include <QString>
#include <QStringList>
#include <QTest>
#include <algorithm>
#include "kfuzzymatcher.h"
QTEST_MAIN(KFuzzyMatcherTest)
void KFuzzyMatcherTest::testMatchSimple_data()
{
QTest::addColumn<QString>("pattern");
QTest::addColumn<QString>("inputstr");
QTest::addColumn<bool>("expected");
QTest::newRow("AbcD") << QStringLiteral("AbcD") << QStringLiteral("AbCdefg") << true;
QTest::newRow("WithSpace") << QStringLiteral("Wa qa") << QStringLiteral("Wa qar") << true;
QTest::newRow("RTL") << QStringLiteral("ارو") << QStringLiteral("اردو") << true;
QTest::newRow("WithSep") << QStringLiteral("tf") << QStringLiteral("the_file") << true;
QTest::newRow("Umlaut") << QStringLiteral("Häu") << QStringLiteral("Häuser") << true;
QTest::newRow("Unmatched") << QStringLiteral("Name") << QStringLiteral("Nam") << false;
QTest::newRow("Empty Pattern") << QString("") << QStringLiteral("Nam") << true;
}
void KFuzzyMatcherTest::testMatchSimple()
{
QFETCH(QString, pattern);
QFETCH(QString, inputstr);
QFETCH(bool, expected);
QVERIFY(KFuzzyMatcher::matchSimple(pattern, inputstr) == expected);
}
void KFuzzyMatcherTest::testMatch_data()
{
QTest::addColumn<QString>("pattern");
QTest::addColumn<QStringList>("input");
QTest::addColumn<QStringList>("expected");
QTest::addColumn<int>("size");
// clang-format off
QTest::newRow("pattern=sort") << QStringLiteral("sort")
<< QStringList{
QStringLiteral("Sort"),
QStringLiteral("Some other right test"),
QStringLiteral("Soup rate"),
QStringLiteral("Someother"),
QStringLiteral("irrelevant"),
}
<< QStringList{
QStringLiteral("Sort"),
QStringLiteral("Some other right test"),
QStringLiteral("Soup rate"),
}
<< 3;
QTest::newRow("pattern=kateapp") << QStringLiteral("kaapp")
<< QStringList{
QStringLiteral("kateapp.cpp"),
QStringLiteral("kate_application"),
QStringLiteral("kateapp.h"),
QStringLiteral("katepap.c")
}
<< QStringList{
QStringLiteral("kate_application"),
QStringLiteral("kateapp.h"),
QStringLiteral("kateapp.cpp")
}
<< 3;
QTest::newRow("pattern=this") << QStringLiteral("this")
<< QStringList{
QStringLiteral("th"),
QStringLiteral("ths"),
QStringLiteral("thsi")
}
<< QStringList{
}
<< 0;
QTest::newRow("pattern=marath") << QStringLiteral("marath")
<< QStringList{
QStringLiteral("Maralen of the Mornsong"),
QStringLiteral("Silumgar, the Drifting Death"),
QStringLiteral("Maralen of the Mornsong Avatar"),
QStringLiteral("Marshaling the Troops"),
QStringLiteral("Homeward Path"),
QStringLiteral("Marath, Will of the Wild"),
QStringLiteral("Marshal's Anthem"),
QStringLiteral("Marchesa, the Black Rose"),
QStringLiteral("Mark for Death"),
QStringLiteral("Master Apothecary"),
QStringLiteral("Mazirek, Kraul Death Priest"),
QStringLiteral("Akroma, Angel of Wrath"),
QStringLiteral("Akroma, Angel of Wrath Avatar"),
QStringLiteral("Commander's Authority"),
QStringLiteral("Shaman of the Great Hunt"),
QStringLiteral("Halimar Wavewatch"),
QStringLiteral("Pyromancer's Swath")
}
<< QStringList{
QStringLiteral("Marath, Will of the Wild"),
QStringLiteral("Maralen of the Mornsong"),
QStringLiteral("Maralen of the Mornsong Avatar"),
QStringLiteral("Marshal's Anthem"),
QStringLiteral("Marshaling the Troops"),
QStringLiteral("Marchesa, the Black Rose"),
QStringLiteral("Mark for Death"),
QStringLiteral("Master Apothecary"),
QStringLiteral("Mazirek, Kraul Death Priest"),
QStringLiteral("Akroma, Angel of Wrath"),
QStringLiteral("Akroma, Angel of Wrath Avatar"),
QStringLiteral("Commander's Authority"),
QStringLiteral("Homeward Path"),
QStringLiteral("Shaman of the Great Hunt"),
QStringLiteral("Halimar Wavewatch"),
QStringLiteral("Pyromancer's Swath"),
QStringLiteral("Silumgar, the Drifting Death")
}
<< 17;
// This tests our recursive best match
QTest::newRow("pattern=lll") << QStringLiteral("lll")
<< QStringList{
QStringLiteral("SVisualLoggerLogsList.h"),
QStringLiteral("SimpleFileLogger.cpp"),
QStringLiteral("StringHandlerLogList.txt"),
QStringLiteral("LeapFromLostAllan"),
QStringLiteral("BumpLLL"),
}
<< QStringList{
QStringLiteral("SVisualLoggerLogsList.h"),
QStringLiteral("LeapFromLostAllan"),
QStringLiteral("BumpLLL"),
QStringLiteral("StringHandlerLogList.txt"),
QStringLiteral("SimpleFileLogger.cpp"),
}
<< 5;
QTest::newRow("pattern=") << QString("")
<< QStringList{
QStringLiteral("th"),
QStringLiteral("ths"),
QStringLiteral("thsi")
}
<< QStringList{
QStringLiteral("th"),
QStringLiteral("ths"),
QStringLiteral("thsi")
}
<< 3;
// clang-format on
}
static QStringList matchHelper(const QString &pattern, const QStringList &input)
{
QVector<QPair<QString, int>> actual;
for (int i = 0; i < input.size(); ++i) {
KFuzzyMatcher::Result res = KFuzzyMatcher::match(pattern, input.at(i));
if (res.matched) {
actual.push_back({input.at(i), res.score});
}
}
// sort descending based on score
std::sort(actual.begin(), actual.end(), [](const QPair<QString, int> &l, const QPair<QString, int> &r) {
return l.second > r.second;
});
QStringList actualOut;
for (const auto &s : actual) {
actualOut << s.first;
}
return actualOut;
}
void KFuzzyMatcherTest::testMatch()
{
QFETCH(QString, pattern);
QFETCH(QStringList, input);
QFETCH(QStringList, expected);
QFETCH(int, size);
const QStringList actual = matchHelper(pattern, input);
QCOMPARE(actual.size(), size);
QCOMPARE(actual, expected);
}
void KFuzzyMatcherTest::testMatchedRanges_data()
{
QTest::addColumn<QString>("pattern");
QTest::addColumn<QString>("string");
using Range = QPair<int, int>;
QTest::addColumn<QVector<Range>>("expectedRanges");
QTest::addColumn<bool>("matchingOnly");
QTest::newRow("Emtpy") << QString("") << QString("") << QVector<Range>{} << true;
QTest::newRow("Hello") << QStringLiteral("Hlo") << QStringLiteral("Hello") << QVector<Range>{{0, 1}, {3, 2}} << true;
QTest::newRow("lll") << QStringLiteral("lll") << QStringLiteral("SVisualLoggerLogsList") << QVector<Range>{{7, 1}, {13, 1}, {17, 1}} << true;
QTest::newRow("Sort") << QStringLiteral("sort") << QStringLiteral("SorT") << QVector<Range>{{0, 4}} << true;
QTest::newRow("Unmatching") << QStringLiteral("git") << QStringLiteral("gti") << QVector<Range>{} << true;
QTest::newRow("UnmatchingWithAllMatches") << QStringLiteral("git") << QStringLiteral("gti") << QVector<Range>{{0, 1}, {2, 1}} << false;
}
void KFuzzyMatcherTest::testMatchedRanges()
{
QFETCH(QString, pattern);
QFETCH(QString, string);
QFETCH(bool, matchingOnly);
using Range = QPair<int, int>;
QFETCH(QVector<Range>, expectedRanges);
const auto matchMode = matchingOnly ? KFuzzyMatcher::RangeType::FullyMatched : KFuzzyMatcher::RangeType::All;
auto resultRanges = KFuzzyMatcher::matchedRanges(pattern, string, matchMode);
QCOMPARE(resultRanges.size(), expectedRanges.size());
bool res = std::equal(expectedRanges.begin(), expectedRanges.end(), resultRanges.begin(), [](const Range &l, const KFuzzyMatcher::Range &r) {
return l.first == r.start && l.second == r.length;
});
QVERIFY(res);
}
#include "moc_kfuzzymatchertest.cpp"

View File

@@ -0,0 +1,26 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFUZZYMATCHERTEST_H
#define KFUZZYMATCHERTEST_H
#include <QObject>
class KFuzzyMatcherTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testMatchSimple_data();
void testMatchSimple();
void testMatch_data();
void testMatch();
void testMatchedRanges_data();
void testMatchedRanges();
};
#endif // KFUZZYMATCHERTEST_H

View File

@@ -0,0 +1,613 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kjobtest.h"
#include <QMetaEnum>
#include <QSignalSpy>
#include <QTest>
#include <QTimer>
#include <QVector>
#include <string>
QTEST_MAIN(KJobTest)
KJobTest::KJobTest()
: loop(this)
{
}
void KJobTest::testEmitResult_data()
{
QTest::addColumn<int>("errorCode");
QTest::addColumn<QString>("errorText");
QTest::newRow("no error") << int(KJob::NoError) << QString();
QTest::newRow("error no text") << 2 << QString();
QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?";
}
void KJobTest::testEmitResult()
{
TestJob *job = new TestJob;
connect(job, &KJob::result, this, [this](KJob *job) {
slotResult(job);
loop.quit();
});
QFETCH(int, errorCode);
QFETCH(QString, errorText);
job->setError(errorCode);
job->setErrorText(errorText);
QSignalSpy destroyed_spy(job, &QObject::destroyed);
job->start();
QVERIFY(!job->isFinished());
loop.exec();
QVERIFY(job->isFinished());
QCOMPARE(m_lastError, errorCode);
QCOMPARE(m_lastErrorText, errorText);
// Verify that the job is not deleted immediately...
QCOMPARE(destroyed_spy.size(), 0);
QTimer::singleShot(0, &loop, &QEventLoop::quit);
// ... but when we enter the event loop again.
loop.exec();
QCOMPARE(destroyed_spy.size(), 1);
}
void KJobTest::testProgressTracking()
{
TestJob *testJob = new TestJob;
KJob *job = testJob;
qRegisterMetaType<KJob *>("KJob*");
qRegisterMetaType<qulonglong>("qulonglong");
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QSignalSpy processed_spy(job, qOverload<KJob *, KJob::Unit, qulonglong>(&KJob::processedAmount));
QSignalSpy total_spy(job, qOverload<KJob *, KJob::Unit, qulonglong>(&KJob::totalAmount));
QSignalSpy percent_spy(job, qOverload<KJob *, ulong>(&KJob::percent));
#endif
QSignalSpy processedChanged_spy(job, &KJob::processedAmountChanged);
QSignalSpy totalChanged_spy(job, &KJob::totalAmountChanged);
QSignalSpy percentChanged_spy(job, &KJob::percentChanged);
/* Process a first item. Corresponding signal should be emitted.
* Total size didn't change.
* Since the total size is unknown, no percent signal is emitted.
*/
testJob->setProcessedSize(1);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 1);
QCOMPARE(processed_spy.at(0).at(0).value<KJob *>(), static_cast<KJob *>(job));
QCOMPARE(processed_spy.at(0).at(2).value<qulonglong>(), qulonglong(1));
QCOMPARE(total_spy.size(), 0);
QCOMPARE(percent_spy.size(), 0);
#endif
QCOMPARE(processedChanged_spy.size(), 1);
QCOMPARE(processedChanged_spy.at(0).at(0).value<KJob *>(), static_cast<KJob *>(job));
QCOMPARE(processedChanged_spy.at(0).at(2).value<qulonglong>(), qulonglong(1));
QCOMPARE(totalChanged_spy.size(), 0);
QCOMPARE(percentChanged_spy.size(), 0);
/* Now, we know the total size. It's signaled.
* The new percentage is signaled too.
*/
testJob->setTotalSize(10);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 1);
QCOMPARE(total_spy.size(), 1);
QCOMPARE(total_spy.at(0).at(0).value<KJob *>(), job);
QCOMPARE(total_spy.at(0).at(2).value<qulonglong>(), qulonglong(10));
QCOMPARE(percent_spy.size(), 1);
QCOMPARE(percent_spy.at(0).at(0).value<KJob *>(), job);
QCOMPARE(percent_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(10));
#endif
QCOMPARE(processedChanged_spy.size(), 1);
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(totalChanged_spy.at(0).at(0).value<KJob *>(), job);
QCOMPARE(totalChanged_spy.at(0).at(2).value<qulonglong>(), qulonglong(10));
QCOMPARE(percentChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.at(0).at(0).value<KJob *>(), job);
QCOMPARE(percentChanged_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(10));
/* We announce a new percentage by hand.
* Total, and processed didn't change, so no signal is emitted for them.
*/
testJob->setPercent(15);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 1);
QCOMPARE(total_spy.size(), 1);
QCOMPARE(percent_spy.size(), 2);
QCOMPARE(percent_spy.at(1).at(0).value<KJob *>(), job);
QCOMPARE(percent_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(15));
#endif
QCOMPARE(processedChanged_spy.size(), 1);
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.size(), 2);
QCOMPARE(percentChanged_spy.at(1).at(0).value<KJob *>(), job);
QCOMPARE(percentChanged_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(15));
/* We make some progress.
* Processed size and percent are signaled.
*/
testJob->setProcessedSize(3);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 2);
QCOMPARE(processed_spy.at(1).at(0).value<KJob *>(), job);
QCOMPARE(processed_spy.at(1).at(2).value<qulonglong>(), qulonglong(3));
QCOMPARE(total_spy.size(), 1);
QCOMPARE(percent_spy.size(), 3);
QCOMPARE(percent_spy.at(2).at(0).value<KJob *>(), job);
QCOMPARE(percent_spy.at(2).at(1).value<unsigned long>(), static_cast<unsigned long>(30));
#endif
QCOMPARE(processedChanged_spy.size(), 2);
QCOMPARE(processedChanged_spy.at(1).at(0).value<KJob *>(), job);
QCOMPARE(processedChanged_spy.at(1).at(2).value<qulonglong>(), qulonglong(3));
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.size(), 3);
QCOMPARE(percentChanged_spy.at(2).at(0).value<KJob *>(), job);
QCOMPARE(percentChanged_spy.at(2).at(1).value<unsigned long>(), static_cast<unsigned long>(30));
/* We set a new total size, but equals to the previous one.
* No signal is emitted.
*/
testJob->setTotalSize(10);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 2);
QCOMPARE(total_spy.size(), 1);
QCOMPARE(percent_spy.size(), 3);
#endif
QCOMPARE(processedChanged_spy.size(), 2);
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.size(), 3);
/* We 'lost' the previous work done.
* Signals both percentage and new processed size.
*/
testJob->setProcessedSize(0);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 3);
QCOMPARE(processed_spy.at(2).at(0).value<KJob *>(), job);
QCOMPARE(processed_spy.at(2).at(2).value<qulonglong>(), qulonglong(0));
QCOMPARE(total_spy.size(), 1);
QCOMPARE(percent_spy.size(), 4);
QCOMPARE(percent_spy.at(3).at(0).value<KJob *>(), job);
QCOMPARE(percent_spy.at(3).at(1).value<unsigned long>(), static_cast<unsigned long>(0));
#endif
QCOMPARE(processedChanged_spy.size(), 3);
QCOMPARE(processedChanged_spy.at(2).at(0).value<KJob *>(), job);
QCOMPARE(processedChanged_spy.at(2).at(2).value<qulonglong>(), qulonglong(0));
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.size(), 4);
QCOMPARE(percentChanged_spy.at(3).at(0).value<KJob *>(), job);
QCOMPARE(percentChanged_spy.at(3).at(1).value<unsigned long>(), static_cast<unsigned long>(0));
/* We process more than the total size!?
* Signals both percentage and new processed size.
* Percentage is 150%
*
* Might sounds weird, but verify that this case is handled gracefully.
*/
testJob->setProcessedSize(15);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(processed_spy.size(), 4);
QCOMPARE(processed_spy.at(3).at(0).value<KJob *>(), job);
QCOMPARE(processed_spy.at(3).at(2).value<qulonglong>(), qulonglong(15));
QCOMPARE(total_spy.size(), 1);
QCOMPARE(percent_spy.size(), 5);
QCOMPARE(percent_spy.at(4).at(0).value<KJob *>(), job);
QCOMPARE(percent_spy.at(4).at(1).value<unsigned long>(), static_cast<unsigned long>(150));
#endif
QCOMPARE(processedChanged_spy.size(), 4);
QCOMPARE(processedChanged_spy.at(3).at(0).value<KJob *>(), job);
QCOMPARE(processedChanged_spy.at(3).at(2).value<qulonglong>(), qulonglong(15));
QCOMPARE(totalChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.size(), 5);
QCOMPARE(percentChanged_spy.at(4).at(0).value<KJob *>(), job);
QCOMPARE(percentChanged_spy.at(4).at(1).value<unsigned long>(), static_cast<unsigned long>(150));
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
processed_spy.clear();
total_spy.clear();
percent_spy.clear();
#endif
processedChanged_spy.clear();
totalChanged_spy.clear();
percentChanged_spy.clear();
/**
* Try again with Files as the progress unit
*/
testJob->setProgressUnit(KJob::Files);
testJob->setProcessedSize(16);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(percent_spy.size(), 0); // no impact on percent
#endif
QCOMPARE(percentChanged_spy.size(), 0);
testJob->setTotalFiles(5);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(percent_spy.size(), 1);
QCOMPARE(percent_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(0));
#endif
QCOMPARE(percentChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(0));
testJob->setProcessedFiles(2);
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 80)
QCOMPARE(percent_spy.size(), 2);
QCOMPARE(percent_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(40));
#endif
QCOMPARE(percentChanged_spy.size(), 2);
QCOMPARE(percentChanged_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(40));
delete job;
}
void KJobTest::testExec_data()
{
QTest::addColumn<int>("errorCode");
QTest::addColumn<QString>("errorText");
QTest::newRow("no error") << int(KJob::NoError) << QString();
QTest::newRow("error no text") << 2 << QString();
QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?";
}
void KJobTest::testExec()
{
TestJob *job = new TestJob;
QFETCH(int, errorCode);
QFETCH(QString, errorText);
job->setError(errorCode);
job->setErrorText(errorText);
int resultEmitted = 0;
// Prove to Kai Uwe that one can connect a job to a lambdas, despite the "private" signal
connect(job, &KJob::result, this, [&resultEmitted](KJob *) {
++resultEmitted;
});
QSignalSpy destroyed_spy(job, &QObject::destroyed);
QVERIFY(!job->isFinished());
bool status = job->exec();
QVERIFY(job->isFinished());
QCOMPARE(resultEmitted, 1);
QCOMPARE(status, (errorCode == KJob::NoError));
QCOMPARE(job->error(), errorCode);
QCOMPARE(job->errorText(), errorText);
// Verify that the job is not deleted immediately...
QCOMPARE(destroyed_spy.size(), 0);
QTimer::singleShot(0, &loop, &QEventLoop::quit);
// ... but when we enter the event loop again.
loop.exec();
QCOMPARE(destroyed_spy.size(), 1);
}
void KJobTest::testKill_data()
{
QTest::addColumn<int>("killVerbosity");
QTest::addColumn<int>("errorCode");
QTest::addColumn<QString>("errorText");
QTest::addColumn<int>("resultEmitCount");
QTest::addColumn<int>("finishedEmitCount");
QTest::newRow("killed with result") << int(KJob::EmitResult) << int(KJob::KilledJobError) << QString() << 1 << 1;
QTest::newRow("killed quietly") << int(KJob::Quietly) << int(KJob::KilledJobError) << QString() << 0 << 1;
}
void KJobTest::testKill()
{
auto *const job = setupErrorResultFinished();
QSignalSpy destroyed_spy(job, &QObject::destroyed);
QFETCH(int, killVerbosity);
QFETCH(int, errorCode);
QFETCH(QString, errorText);
QFETCH(int, resultEmitCount);
QFETCH(int, finishedEmitCount);
QVERIFY(!job->isFinished());
job->kill(static_cast<KJob::KillVerbosity>(killVerbosity));
QVERIFY(job->isFinished());
loop.processEvents(QEventLoop::AllEvents, 2000);
QCOMPARE(m_lastError, errorCode);
QCOMPARE(m_lastErrorText, errorText);
QCOMPARE(job->error(), errorCode);
QCOMPARE(job->errorText(), errorText);
QCOMPARE(m_resultCount, resultEmitCount);
QCOMPARE(m_finishedCount, finishedEmitCount);
// Verify that the job is not deleted immediately...
QCOMPARE(destroyed_spy.size(), 0);
QTimer::singleShot(0, &loop, &QEventLoop::quit);
// ... but when we enter the event loop again.
loop.exec();
QCOMPARE(destroyed_spy.size(), 1);
QVERIFY(m_jobFinishCount.size() == (finishedEmitCount - resultEmitCount));
m_jobFinishCount.clear();
}
void KJobTest::testDestroy()
{
auto *const job = setupErrorResultFinished();
QVERIFY(!job->isFinished());
delete job;
QCOMPARE(m_lastError, static_cast<int>(KJob::NoError));
QCOMPARE(m_lastErrorText, QString{});
QCOMPARE(m_resultCount, 0);
QCOMPARE(m_finishedCount, 1);
QVERIFY(m_jobFinishCount.size() == 1);
m_jobFinishCount.clear();
}
void KJobTest::testEmitAtMostOnce_data()
{
QTest::addColumn<bool>("autoDelete");
QTest::addColumn<QVector<Action>>("actions");
const auto actionName = [](Action action) {
return QMetaEnum::fromType<Action>().valueToKey(static_cast<int>(action));
};
for (bool autoDelete : {true, false}) {
for (Action a : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) {
for (Action b : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) {
const auto dataTag = std::string{actionName(a)} + '-' + actionName(b) + (autoDelete ? "-autoDelete" : "");
QTest::newRow(dataTag.c_str()) << autoDelete << QVector<Action>{a, b};
}
}
}
}
void KJobTest::testEmitAtMostOnce()
{
auto *const job = setupErrorResultFinished();
QSignalSpy destroyed_spy(job, &QObject::destroyed);
QFETCH(bool, autoDelete);
job->setAutoDelete(autoDelete);
QFETCH(QVector<Action>, actions);
for (auto action : actions) {
switch (action) {
case Action::Start:
job->start(); // in effect calls QTimer::singleShot(0, ... emitResult)
break;
case Action::KillQuietly:
QTimer::singleShot(0, job, [=] {
job->kill(KJob::Quietly);
});
break;
case Action::KillVerbosely:
QTimer::singleShot(0, job, [=] {
job->kill(KJob::EmitResult);
});
break;
}
}
QVERIFY(!job->isFinished());
loop.processEvents(QEventLoop::AllEvents, 2000);
QCOMPARE(destroyed_spy.size(), autoDelete);
if (!autoDelete) {
QVERIFY(job->isFinished());
}
QVERIFY(!actions.empty());
// The first action alone should determine the job's error and result.
const auto firstAction = actions.front();
const int errorCode = firstAction == Action::Start ? KJob::NoError : KJob::KilledJobError;
QCOMPARE(m_lastError, errorCode);
QCOMPARE(m_lastErrorText, QString{});
if (!autoDelete) {
QCOMPARE(job->error(), m_lastError);
QCOMPARE(job->errorText(), m_lastErrorText);
}
QCOMPARE(m_resultCount, firstAction == Action::KillQuietly ? 0 : 1);
QCOMPARE(m_finishedCount, 1);
if (!autoDelete) {
delete job;
}
}
void KJobTest::testDelegateUsage()
{
TestJob *job1 = new TestJob;
TestJob *job2 = new TestJob;
TestJobUiDelegate *delegate = new TestJobUiDelegate;
QPointer<TestJobUiDelegate> guard(delegate);
QVERIFY(job1->uiDelegate() == nullptr);
job1->setUiDelegate(delegate);
QVERIFY(job1->uiDelegate() == delegate);
QVERIFY(job2->uiDelegate() == nullptr);
job2->setUiDelegate(delegate);
QVERIFY(job2->uiDelegate() == nullptr);
delete job1;
delete job2;
QVERIFY(guard.isNull()); // deleted by job1
}
void KJobTest::testNestedExec()
{
m_innerJob = nullptr;
QTimer::singleShot(100, this, &KJobTest::slotStartInnerJob);
m_outerJob = new WaitJob();
m_outerJob->exec();
}
void KJobTest::slotStartInnerJob()
{
QTimer::singleShot(100, this, &KJobTest::slotFinishOuterJob);
m_innerJob = new WaitJob();
m_innerJob->exec();
}
void KJobTest::slotFinishOuterJob()
{
QTimer::singleShot(100, this, &KJobTest::slotFinishInnerJob);
m_outerJob->makeItFinish();
}
void KJobTest::slotFinishInnerJob()
{
m_innerJob->makeItFinish();
}
void KJobTest::slotResult(KJob *job)
{
const auto testJob = qobject_cast<const TestJob *>(job);
QVERIFY(testJob);
QVERIFY(testJob->isFinished());
// Ensure the job has already emitted finished() if we are tracking from
// setupErrorResultFinished
if (m_jobFinishCount.contains(job)) {
QVERIFY(m_jobFinishCount.value(job) == 1);
QVERIFY(m_jobFinishCount.remove(job) == 1 /* num items removed */);
}
if (job->error()) {
m_lastError = job->error();
m_lastErrorText = job->errorText();
} else {
m_lastError = KJob::NoError;
m_lastErrorText.clear();
}
m_resultCount++;
}
void KJobTest::slotFinished(KJob *job)
{
QVERIFY2(m_jobFinishCount.value(job) == 0, "Ensure we have not double-emitted KJob::finished()");
m_jobFinishCount[job]++;
if (job->error()) {
m_lastError = job->error();
m_lastErrorText = job->errorText();
} else {
m_lastError = KJob::NoError;
m_lastErrorText.clear();
}
m_finishedCount++;
}
TestJob *KJobTest::setupErrorResultFinished()
{
m_lastError = KJob::UserDefinedError;
m_lastErrorText.clear();
m_resultCount = 0;
m_finishedCount = 0;
auto *job = new TestJob;
m_jobFinishCount[job] = 0;
connect(job, &KJob::result, this, &KJobTest::slotResult);
connect(job, &KJob::finished, this, &KJobTest::slotFinished);
return job;
}
TestJob::TestJob()
: KJob()
{
}
TestJob::~TestJob()
{
}
void TestJob::start()
{
QTimer::singleShot(0, this, [this] {
emitResult();
});
}
bool TestJob::doKill()
{
return true;
}
void TestJob::setError(int errorCode)
{
KJob::setError(errorCode);
}
void TestJob::setErrorText(const QString &errorText)
{
KJob::setErrorText(errorText);
}
void TestJob::setProcessedSize(qulonglong size)
{
KJob::setProcessedAmount(KJob::Bytes, size);
}
void TestJob::setTotalSize(qulonglong size)
{
KJob::setTotalAmount(KJob::Bytes, size);
}
void TestJob::setProcessedFiles(qulonglong files)
{
KJob::setProcessedAmount(KJob::Files, files);
}
void TestJob::setTotalFiles(qulonglong files)
{
KJob::setTotalAmount(KJob::Files, files);
}
void TestJob::setPercent(unsigned long percentage)
{
KJob::setPercent(percentage);
}
void WaitJob::start()
{
}
void WaitJob::makeItFinish()
{
emitResult();
}
void TestJobUiDelegate::connectJob(KJob *job)
{
QVERIFY(job->uiDelegate() != nullptr);
}
#include "moc_kjobtest.cpp"

View File

@@ -0,0 +1,109 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KJOBTEST_H
#define KJOBTEST_H
#include "kjob.h"
#include "kjobuidelegate.h"
#include <QEventLoop>
#include <QMap>
#include <QObject>
class TestJob : public KJob
{
Q_OBJECT
public:
TestJob();
~TestJob() override;
void start() override;
using KJob::isFinished;
using KJob::setProgressUnit;
protected:
bool doKill() override;
public:
void setError(int errorCode);
void setErrorText(const QString &errorText);
void setProcessedSize(qulonglong size);
void setTotalSize(qulonglong size);
void setProcessedFiles(qulonglong files);
void setTotalFiles(qulonglong files);
void setPercent(unsigned long percentage);
};
class TestJobUiDelegate : public KJobUiDelegate
{
Q_OBJECT
protected:
virtual void connectJob(KJob *job);
};
class WaitJob;
class KJobTest : public QObject
{
Q_OBJECT
public:
enum class Action {
Start,
KillQuietly,
KillVerbosely,
};
Q_ENUM(Action)
KJobTest();
public Q_SLOTS:
// These slots need to be public, otherwise qtestlib calls them as part of the test
void slotStartInnerJob();
void slotFinishOuterJob();
void slotFinishInnerJob();
private Q_SLOTS:
void testEmitResult_data();
void testEmitResult();
void testProgressTracking();
void testExec_data();
void testExec();
void testKill_data();
void testKill();
void testDestroy();
void testEmitAtMostOnce_data();
void testEmitAtMostOnce();
void testDelegateUsage();
void testNestedExec();
void slotResult(KJob *job);
void slotFinished(KJob *job);
private:
TestJob *setupErrorResultFinished();
QEventLoop loop;
int m_lastError;
QString m_lastErrorText;
int m_resultCount;
int m_finishedCount;
WaitJob *m_outerJob;
WaitJob *m_innerJob;
QMap<KJob *, int> m_jobFinishCount;
};
class WaitJob : public KJob
{
Q_OBJECT
public:
void start() override;
void makeItFinish();
};
#endif

View File

@@ -0,0 +1,56 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
*/
#include <QObject>
#include <QTest>
#include <QFileInfo>
#include <KLibexec>
class KLibexecTest : public QObject
{
Q_OBJECT
const QString m_relative = QStringLiteral("fakeexec/kf" QT_STRINGIFY(QT_VERSION_MAJOR));
const QString m_fixtureName =
#ifdef Q_OS_WIN
QStringLiteral("klibexectest-fixture-binary.exe");
#else
QStringLiteral("klibexectest-fixture-binary");
#endif
QString m_fixtureDir;
QString m_fixturePath;
private Q_SLOTS:
void initTestCase()
{
m_fixtureDir = QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + m_relative);
m_fixturePath = QDir::cleanPath(m_fixtureDir + QDir::separator() + m_fixtureName);
QVERIFY(QDir().mkpath(m_fixtureDir));
QFile fixture(m_fixturePath);
QVERIFY(fixture.open(QFile::ReadWrite));
fixture.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
m_fixtureDir = QFileInfo(m_fixtureDir).canonicalFilePath();
m_fixturePath = QFileInfo(m_fixtureDir).canonicalFilePath();
}
void testPath()
{
QCOMPARE(KLibexec::path(m_relative), m_fixtureDir);
}
void testKDEFrameworksPaths()
{
auto paths = KLibexec::kdeFrameworksPaths(m_relative);
QVERIFY(paths.contains(QCoreApplication::applicationDirPath()));
QVERIFY(paths.contains(m_fixtureDir));
// not exhaustive verification
}
};
QTEST_MAIN(KLibexecTest)
#include "klibexectest.moc"

View File

@@ -0,0 +1,149 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "klistopenfilesjobtest_unix.h"
#include "klistopenfilesjob.h"
#include <QCoreApplication>
#include <QStandardPaths>
#include <QStringLiteral>
#include <QTemporaryDir>
#include <QTest>
#include <algorithm>
#ifdef Q_OS_FREEBSD
// See implementation note in testOpenFiles()
#include <QProcess>
#endif
QTEST_MAIN(KListOpenFilesJobTest)
void initLocale()
{
qputenv("LC_ALL", "en_US.utf-8");
}
Q_CONSTRUCTOR_FUNCTION(initLocale)
namespace
{
bool hasLsofInstalled()
{
return !QStandardPaths::findExecutable(QStringLiteral("lsof")).isEmpty();
}
}
void KListOpenFilesJobTest::testOpenFiles()
{
if (!hasLsofInstalled()) {
QSKIP("lsof is not installed - skipping test");
}
// Create a file and hold it open, so that lsof must report us
QTemporaryDir tempDir;
QFile tempFile(tempDir.path() + QStringLiteral("/file"));
QVERIFY(tempFile.open(QIODevice::WriteOnly));
bool xfail_zfs = false; // Expected failure because of ZFS
#ifdef Q_OS_FREEBSD
// FIXME: On FreeBSD, lsof does not support zfs (as of 2022), see
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=253553
//
// This affects regular files only. So for FreeBSD, check if
// the tempDir seems to be on a ZFS filesystem, e.g.
//
// ```
// [adridg@beastie .../invent/kcoreaddons]$ lsof +d /tmp > /dev/null
// lsof: WARNING: no ZFS support has been defined.
// See 00FAQ for more information.
// ```
{
QProcess lsof;
lsof.start(QStringLiteral("lsof"), {QStringLiteral("+d"), tempDir.path()});
lsof.waitForFinished();
auto stderr = lsof.readAllStandardError();
xfail_zfs = (lsof.exitCode() != 0) && stderr.contains("ZFS");
}
#endif
auto job = new KListOpenFilesJob(tempDir.path());
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->error(), KJob::NoError);
auto processInfoList = job->processInfoList();
if (xfail_zfs) {
// Ths list is empty, so the subsequent find and validity-checks
// don't make sense.
QEXPECT_FAIL("", "lsof(8) does not support regular files on ZFS", Abort);
}
QVERIFY(!processInfoList.empty());
auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [](const KProcessList::KProcessInfo &info) {
return info.pid() == QCoreApplication::applicationPid();
});
QVERIFY(testProcessIterator != processInfoList.end());
const auto &processInfo = *testProcessIterator;
QVERIFY(processInfo.isValid());
QCOMPARE(processInfo.pid(), QCoreApplication::applicationPid());
}
void KListOpenFilesJobTest::testNoOpenFiles()
{
if (!hasLsofInstalled()) {
QSKIP("lsof is not installed - skipping test");
}
QTemporaryDir tempDir;
auto job = new KListOpenFilesJob(tempDir.path());
QVERIFY2(job->exec(), qPrintable(job->errorString()));
QCOMPARE(job->error(), KJob::NoError);
QVERIFY(job->processInfoList().empty());
}
void KListOpenFilesJobTest::testNonExistingDir()
{
if (!hasLsofInstalled()) {
QSKIP("lsof is not installed - skipping test");
}
QString nonExistingDir(QStringLiteral("/does/not/exist"));
auto job = new KListOpenFilesJob(nonExistingDir);
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KListOpenFilesJob::Error::DoesNotExist));
QCOMPARE(job->errorText(), QStringLiteral("Path %1 doesn't exist").arg(nonExistingDir));
QVERIFY(job->processInfoList().empty());
}
/**
* @brief Helper class to temporarily set an environment variable and reset it on destruction
*/
class ScopedEnvVariable
{
public:
ScopedEnvVariable(const QLatin1String &Name, const QByteArray &NewValue)
: name(Name)
, originalValue(qgetenv(name.latin1()))
{
qputenv(name.latin1(), NewValue);
}
~ScopedEnvVariable()
{
qputenv(name.latin1(), originalValue);
}
private:
const QLatin1String name;
const QByteArray originalValue;
};
void KListOpenFilesJobTest::testLsofNotFound()
{
// This test relies on clearing the PATH variable so that lsof is not found
ScopedEnvVariable emptyPathEnvironment(QLatin1String("PATH"), QByteArray());
QDir path(QCoreApplication::applicationDirPath());
auto job = new KListOpenFilesJob(path.path());
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KListOpenFilesJob::Error::InternalError));
QVERIFY(job->processInfoList().empty());
}
#include "moc_klistopenfilesjobtest_unix.cpp"

View File

@@ -0,0 +1,24 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KLISTOPENFILESJOBTEST_UNIX_H
#define KLISTOPENFILESJOBTEST_UNIX_H
#include <QObject>
class KListOpenFilesJobTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testOpenFiles();
void testNoOpenFiles();
void testNonExistingDir();
void testLsofNotFound();
};
#endif

View File

@@ -0,0 +1,26 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "klistopenfilesjobtest_win.h"
#include "klistopenfilesjob.h"
#include <QCoreApplication>
#include <QStringLiteral>
#include <QTest>
QTEST_MAIN(KListOpenFilesJobTest)
void KListOpenFilesJobTest::testNotSupported()
{
QDir path(QCoreApplication::applicationDirPath());
auto job = new KListOpenFilesJob(path.path());
job->exec();
QCOMPARE(job->error(), static_cast<int>(KListOpenFilesJob::Error::NotSupported));
QCOMPARE(job->errorText(), QStringLiteral("KListOpenFilesJob is not supported on Windows"));
QVERIFY(job->processInfoList().empty());
}
#include "moc_klistopenfilesjobtest_win.cpp"

View File

@@ -0,0 +1,21 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KLISTOPENFILESJOBTEST_WIN_H
#define KLISTOPENFILESJOBTEST_WIN_H
#include <QObject>
class KListOpenFilesJobTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testNotSupported();
};
#endif

View File

@@ -0,0 +1,261 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003, 2008 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2005 Thomas Braxton <brax108@cox.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <QTest>
#include <kmacroexpander.h>
#include <QHash>
#include <QObject>
class KMacroExpanderTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void expandMacros();
void expandMacrosShellQuote();
void expandMacrosShellQuoteParens();
void expandMacrosSubClass();
};
class MyCExpander : public KCharMacroExpander
{
QString exp;
public:
MyCExpander()
: KCharMacroExpander()
, exp("expanded")
{
}
protected:
bool expandMacro(QChar ch, QStringList &ret) override
{
if (ch == 'm') {
ret = QStringList(exp);
return true;
}
return false;
}
};
class MyWExpander : public KWordMacroExpander
{
QString exp;
public:
MyWExpander()
: KWordMacroExpander()
, exp("expanded")
{
}
protected:
bool expandMacro(const QString &str, QStringList &ret) override
{
if (str == QLatin1String("macro")) {
ret = QStringList(exp);
return true;
}
return false;
}
};
void KMacroExpanderTest::expandMacros()
{
QHash<QChar, QStringList> map;
QStringList list;
QString s;
list << QString("Restaurant \"Chew It\"");
map.insert('n', list);
list.clear();
list << QString("element1") << QString("'element2'");
map.insert('l', list);
s = "%% text %l text %n";
QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("% text element1 'element2' text Restaurant \"Chew It\""));
s = "text \"%l %n\" text";
QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("text \"element1 'element2' Restaurant \"Chew It\"\" text"));
QHash<QChar, QString> map2;
map2.insert('a', "%n");
map2.insert('f', "filename.txt");
map2.insert('u', "https://www.kde.org/index.html");
map2.insert('n', "Restaurant \"Chew It\"");
s = "Title: %a - %f - %u - %n - %%";
QCOMPARE(KMacroExpander::expandMacros(s, map2), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %"));
QHash<QString, QString> smap;
smap.insert("foo", "%n");
smap.insert("file", "filename.txt");
smap.insert("url", "https://www.kde.org/index.html");
smap.insert("name", "Restaurant \"Chew It\"");
s = "Title: %foo - %file - %url - %name - %";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %"));
s = "%foo - %file - %url - %name";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\""));
s = "Title: %{foo} - %{file} - %{url} - %{name} - %";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %"));
s = "%{foo} - %{file} - %{url} - %{name}";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\""));
s = "Title: %foo-%file-%url-%name-%";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n-filename.txt-https://www.kde.org/index.html-Restaurant \"Chew It\"-%"));
s = "Title: %{file} %{url";
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: filename.txt %{url"));
s = " * Copyright (C) 2008 %{AUTHOR}";
smap.clear();
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String(" * Copyright (C) 2008 %{AUTHOR}"));
}
void KMacroExpanderTest::expandMacrosShellQuote()
{
QHash<QChar, QStringList> map;
QStringList list;
QString s;
list << QString("Restaurant \"Chew It\"");
map.insert('n', list);
list.clear();
list << QString("element1") << QString("'element2'") << QString("\"element3\"");
map.insert('l', list);
#ifdef Q_OS_WIN
s = "text %l %n text";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map),
QLatin1String("text element1 'element2' \\^\"element3\\^\" \"Restaurant \"\\^\"\"Chew It\"\\^\" text"));
s = "text \"%l %n\" text";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map),
QLatin1String("text \"element1 'element2' \"\\^\"\"element3\"\\^\"\" Restaurant \"\\^\"\"Chew It\"\\^\"\"\" text"));
#else
s = "text %l %n text";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 ''\\''element2'\\''' '\"element3\"' 'Restaurant \"Chew It\"' text"));
s = "text \"%l %n\" text";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text \"element1 'element2' \\\"element3\\\" Restaurant \\\"Chew It\\\"\" text"));
#endif
QHash<QChar, QString> map2;
map2.insert('a', "%n");
map2.insert('f', "filename.txt");
map2.insert('u', "https://www.kde.org/index.html");
map2.insert('n', "Restaurant \"Chew It\"");
#ifdef Q_OS_WIN
s = "Title: %a - %f - %u - %n - %% - %VARIABLE% foo";
QCOMPARE(
KMacroExpander::expandMacrosShellQuote(s, map2),
QLatin1String(
"Title: %PERCENT_SIGN%n - filename.txt - https://www.kde.org/index.html - \"Restaurant \"\\^\"\"Chew It\"\\^\" - %PERCENT_SIGN% - %VARIABLE% foo"));
s = "kedit --caption %n %f";
map2.insert('n', "Restaurant 'Chew It'");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
s = "kedit --caption \"%n\" %f";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
map2.insert('n', "Restaurant \"Chew It\"");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \"\\^\"\"Chew It\"\\^\"\"\" filename.txt"));
map2.insert('n', "Restaurant %HOME%");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant %PERCENT_SIGN%HOME%PERCENT_SIGN%\" filename.txt"));
s = "kedit c:\\%f";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit c:\\filename.txt"));
s = "kedit \"c:\\%f\"";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\filename.txt\""));
map2.insert('f', "\"filename.txt\"");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\\\\"\\^\"\"filename.txt\"\\^\"\"\""));
map2.insert('f', "path\\");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\path\\\\\"\"\""));
#else
s = "Title: %a - %f - %u - %n - %%";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2),
QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - 'Restaurant \"Chew It\"' - %"));
s = "kedit --caption %n %f";
map2.insert('n', "Restaurant 'Chew It'");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption 'Restaurant '\\''Chew It'\\''' filename.txt"));
s = "kedit --caption \"%n\" %f";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
map2.insert('n', "Restaurant \"Chew It\"");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\\"Chew It\\\"\" filename.txt"));
map2.insert('n', "Restaurant $HOME");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\$HOME\" filename.txt"));
map2.insert('n', "Restaurant `echo hello`");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\`echo hello\\`\" filename.txt"));
s = "kedit --caption \"`echo %n`\" %f";
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"$( echo 'Restaurant `echo hello`')\" filename.txt"));
#endif
}
class DummyMacroExpander : public KMacroExpanderBase
{
public:
DummyMacroExpander()
: KMacroExpanderBase(QChar(0x4567))
{
}
protected:
int expandPlainMacro(const QString &, int, QStringList &) override
{
return 0;
}
int expandEscapedMacro(const QString &, int, QStringList &) override
{
return 0;
}
};
void KMacroExpanderTest::expandMacrosShellQuoteParens()
{
QString s;
s = "( echo \"just testing (parens)\" ) ) after";
int pos = 0;
DummyMacroExpander kmx;
QVERIFY(kmx.expandMacrosShellQuote(s, pos));
QCOMPARE(s.mid(pos), QLatin1String(") after"));
QVERIFY(!kmx.expandMacrosShellQuote(s));
}
void KMacroExpanderTest::expandMacrosSubClass()
{
QString s;
MyCExpander mx1;
s = "subst %m but not %n equ %%";
mx1.expandMacros(s);
QCOMPARE(s, QLatin1String("subst expanded but not %n equ %"));
MyWExpander mx2;
s = "subst %macro but not %not equ %%";
mx2.expandMacros(s);
QCOMPARE(s, QLatin1String("subst expanded but not %not equ %"));
}
QTEST_MAIN(KMacroExpanderTest)
#include "kmacroexpandertest.moc"

View File

@@ -0,0 +1,39 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2022 Mirco Miranda
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmemoryinfotest.h"
#include <QTest>
#include "kmemoryinfo.h"
QTEST_GUILESS_MAIN(KMemoryInfoTest)
KMemoryInfoTest::KMemoryInfoTest(QObject *parent)
: QObject(parent)
{
}
void KMemoryInfoTest::isNull()
{
KMemoryInfo m;
QVERIFY(!m.isNull());
}
void KMemoryInfoTest::operators()
{
KMemoryInfo m;
auto m1 = m;
QVERIFY(m == m1);
// paranoia check
QVERIFY(m.totalPhysical() != 0);
QCOMPARE(m.totalPhysical(), m1.totalPhysical());
}
#include "moc_kmemoryinfotest.cpp"

View File

@@ -0,0 +1,28 @@
/*
This file is part of the KDE Frameworks
SPDX-FileCopyrightText: 2022 Mirco Miranda
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMEMORYINFOTEST_H
#define KMEMORYINFOTEST_H
#include <QObject>
/**
* @brief The KMemoryInfoTest class
*/
class KMemoryInfoTest : public QObject
{
Q_OBJECT
public:
KMemoryInfoTest(QObject *parent = nullptr);
private Q_SLOTS:
void isNull();
void operators();
};
#endif // KMEMORYINFOTEST_H

View File

@@ -0,0 +1,235 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knetworkmountstestcanonical.h"
#include <KNetworkMounts>
#include <QFile>
#include <QProcess>
#include <QStandardPaths>
#include <QTest>
QTEST_MAIN(KNetworkMountsTestCanonical)
void KNetworkMountsTestCanonical::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
m_configFileName = QStringLiteral("%1/network_mounts").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
QFile::remove(m_configFileName);
QVERIFY(!QFile::exists(m_configFileName));
// create directory structure
QVERIFY(m_tmpDir.isValid());
const QString relLinkToPath = QStringLiteral("dir");
const QString relSymlinkDirectory = QStringLiteral("symlinkDirectory");
const QStringList relPaths = {relLinkToPath,
QStringLiteral("dir/subdir1"),
QStringLiteral("dir/subdir1/subdir1"),
QStringLiteral("dir/subdir1/subdir2"),
QStringLiteral("dir/subdir1/subdir3"),
QStringLiteral("dir/subdir2"),
QStringLiteral("dir/subdir2/subdir1"),
QStringLiteral("dir/subdir2/subdir2"),
QStringLiteral("dir/subdir2/subdir3"),
relSymlinkDirectory};
const QString relSymlinkToSmbPath = QStringLiteral("symlinkToSmbPath");
QDir dir(m_tmpDir.path());
for (const QString &relPath : relPaths) {
QVERIFY(dir.mkpath(relPath));
QVERIFY(QFile::exists(m_tmpDir.path() + QLatin1Char('/') + relPath));
const QString fileName = m_tmpDir.path() + QLatin1Char('/') + relPath + QLatin1String("/file.txt");
QFile file(fileName);
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
file.close();
QVERIFY(QFile::exists(fileName));
}
const QString linkToPath = m_tmpDir.path() + QLatin1Char('/') + relLinkToPath;
// SymlinkToNetworkMount
const QString symlinkToSmbPath = dir.path() + QLatin1Char('/') + relSymlinkToSmbPath;
QVERIFY(QFile::link(linkToPath, symlinkToSmbPath));
qDebug() << "linkToPath=" << linkToPath << ", symlinkToSmbPath=" << symlinkToSmbPath;
// SymlinkDirectory
QVERIFY(dir.cd(relSymlinkDirectory));
const QString symlinkDirectory = dir.path();
const QString linkStr = symlinkDirectory + QLatin1Char('/') + relLinkToPath;
QVERIFY(QFile::link(linkToPath, linkStr));
qDebug() << "linkToPath=" << linkToPath << ", symlinkDirectory=" << symlinkDirectory << ", linkStr=" << linkStr;
// setup config
KNetworkMounts::self()->setEnabled(true);
const QStringList paths = {linkToPath};
KNetworkMounts::self()->setPaths(paths, KNetworkMounts::SmbPaths);
const QStringList savedPaths = {linkToPath + QLatin1Char('/')};
QCOMPARE(KNetworkMounts::self()->paths(), savedPaths);
// SymlinkDirectory
const QStringList symlinkDirectories = {symlinkDirectory};
KNetworkMounts::self()->setPaths(symlinkDirectories, KNetworkMounts::SymlinkDirectory);
const QStringList savedSymlinkDirectories = {symlinkDirectory + QLatin1Char('/')};
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), savedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), savedSymlinkDirectories);
// SymlinkToNetworkMount
// addPath
KNetworkMounts::self()->addPath(symlinkToSmbPath, KNetworkMounts::SymlinkToNetworkMount);
const QString savedSymlinkToSmbPath = symlinkToSmbPath + QLatin1Char('/');
const QStringList savedSymlinkToSmbPaths = {savedSymlinkToSmbPath};
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), savedSymlinkToSmbPaths);
// setPaths
const QStringList symlinkToSmbPaths = {symlinkToSmbPath};
KNetworkMounts::self()->setPaths(symlinkToSmbPaths, KNetworkMounts::SymlinkToNetworkMount);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), savedSymlinkToSmbPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), savedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), savedSymlinkDirectories);
}
void KNetworkMountsTestCanonical::cleanupTestCase()
{
KNetworkMounts::self()->sync();
QFile::remove(m_configFileName);
}
void KNetworkMountsTestCanonical::testCanonicalSymlinkPath_data()
{
QTest::addColumn<QString>("relPath");
QTest::addColumn<QString>("symlinkedRelPath");
// SymlinkDirectory
QTest::newRow("symlinkDirectory/dir") << "dir"
<< "symlinkDirectory/dir";
QTest::newRow("symlinkDirectory/dir/file.txt") << "dir/file.txt"
<< "symlinkDirectory/dir/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir1") << "dir/subdir1"
<< "symlinkDirectory/dir/subdir1";
QTest::newRow("symlinkDirectory/dir/subdir1/file.txt") << "dir/subdir1/file.txt"
<< "symlinkDirectory/dir/subdir1/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir1") << "dir/subdir1/subdir1"
<< "symlinkDirectory/dir/subdir1/subdir1";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir1/file.txt") << "dir/subdir1/subdir1/file.txt"
<< "symlinkDirectory/dir/subdir1/subdir1/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir2") << "dir/subdir1/subdir2"
<< "symlinkDirectory/dir/subdir1/subdir2";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir2/file.txt") << "dir/subdir1/subdir2/file.txt"
<< "symlinkDirectory/dir/subdir1/subdir2/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir3") << "dir/subdir1/subdir3"
<< "symlinkDirectory/dir/subdir1/subdir3";
QTest::newRow("symlinkDirectory/dir/subdir1/subdir3/file.txt") << "dir/subdir1/subdir3/file.txt"
<< "symlinkDirectory/dir/subdir1/subdir3/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir2") << "dir/subdir2"
<< "symlinkDirectory/dir/subdir2";
QTest::newRow("symlinkDirectory/dir/subdir2/file.txt") << "dir/subdir2/file.txt"
<< "symlinkDirectory/dir/subdir2/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir1") << "dir/subdir2/subdir1"
<< "symlinkDirectory/dir/subdir2/subdir1";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir1/file.txt") << "dir/subdir2/subdir1/file.txt"
<< "symlinkDirectory/dir/subdir2/subdir1/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir2") << "dir/subdir2/subdir2"
<< "symlinkDirectory/dir/subdir2/subdir2";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir2/file.txt") << "dir/subdir2/subdir2/file.txt"
<< "symlinkDirectory/dir/subdir2/subdir2/file.txt";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir3") << "dir/subdir2/subdir3"
<< "symlinkDirectory/dir/subdir2/subdir3";
QTest::newRow("symlinkDirectory/dir/subdir2/subdir3/file.txt") << "dir/subdir2/subdir3/file.txt"
<< "symlinkDirectory/dir/subdir2/subdir3/file.txt";
QTest::newRow("symlinkDirectory") << "symlinkDirectory"
<< "symlinkDirectory";
QTest::newRow("symlinkDirectory/file.txt") << "symlinkDirectory/file.txt"
<< "symlinkDirectory/file.txt";
// SymlinkToNetworkMount
QTest::newRow("symlinkToSmbPath") << "dir"
<< "symlinkToSmbPath";
QTest::newRow("symlinkToSmbPath/file.txt") << "dir/file.txt"
<< "symlinkToSmbPath/file.txt";
QTest::newRow("symlinkToSmbPath/subdir1") << "dir/subdir1"
<< "symlinkToSmbPath/subdir1";
QTest::newRow("symlinkToSmbPath/subdir1/file.txt") << "dir/subdir1/file.txt"
<< "symlinkToSmbPath/subdir1/file.txt";
QTest::newRow("symlinkToSmbPath/subdir1/subdir1") << "dir/subdir1/subdir1"
<< "symlinkToSmbPath/subdir1/subdir1";
QTest::newRow("symlinkToSmbPath/subdir1/subdir1/file.txt") << "dir/subdir1/subdir1/file.txt"
<< "symlinkToSmbPath/subdir1/subdir1/file.txt";
QTest::newRow("symlinkToSmbPath/subdir1/subdir2") << "dir/subdir1/subdir2"
<< "symlinkToSmbPath/subdir1/subdir2";
QTest::newRow("symlinkToSmbPath/subdir1/subdir2/file.txt") << "dir/subdir1/subdir2/file.txt"
<< "symlinkToSmbPath/subdir1/subdir2/file.txt";
QTest::newRow("symlinkToSmbPath/subdir1/subdir3") << "dir/subdir1/subdir3"
<< "symlinkToSmbPath/subdir1/subdir3";
QTest::newRow("symlinkToSmbPath/subdir1/subdir3/file.txt") << "dir/subdir1/subdir3/file.txt"
<< "symlinkToSmbPath/subdir1/subdir3/file.txt";
QTest::newRow("symlinkToSmbPath/subdir2") << "dir/subdir2"
<< "symlinkToSmbPath/subdir2";
QTest::newRow("symlinkToSmbPath/subdir2/file.txt") << "dir/subdir2/file.txt"
<< "symlinkToSmbPath/subdir2/file.txt";
QTest::newRow("symlinkToSmbPath/subdir2/subdir1") << "dir/subdir2/subdir1"
<< "symlinkToSmbPath/subdir2/subdir1";
QTest::newRow("symlinkToSmbPath/subdir2/subdir1/file.txt") << "dir/subdir2/subdir1/file.txt"
<< "symlinkToSmbPath/subdir2/subdir1/file.txt";
QTest::newRow("symlinkToSmbPath/subdir2/subdir2") << "dir/subdir2/subdir2"
<< "symlinkToSmbPath/subdir2/subdir2";
QTest::newRow("symlinkToSmbPath/subdir2/subdir2/file.txt") << "dir/subdir2/subdir2/file.txt"
<< "symlinkToSmbPath/subdir2/subdir2/file.txt";
QTest::newRow("symlinkToSmbPath/subdir2/subdir3") << "dir/subdir2/subdir3"
<< "symlinkToSmbPath/subdir2/subdir3";
QTest::newRow("symlinkToSmbPath/subdir2/subdir3/file.txt") << "dir/subdir2/subdir3/file.txt"
<< "symlinkToSmbPath/subdir2/subdir3/file.txt";
}
void KNetworkMountsTestCanonical::testCanonicalSymlinkPath()
{
QFETCH(QString, relPath);
QFETCH(QString, symlinkedRelPath);
#ifdef Q_OS_WIN
QSKIP("QFile::link creates a shortcut on Windows, not a symlink, so no effect on canonical paths, skipped");
#endif
const QString path = m_tmpDir.path() + QLatin1Char('/') + relPath;
const QString symlinkedPath = m_tmpDir.path() + QLatin1Char('/') + symlinkedRelPath;
const QString canonicalPath = QFileInfo(symlinkedPath).canonicalFilePath();
// default with cache
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
QCOMPARE(path, canonicalPath);
qDebug() << "path=" << path << ", canonicalPath=" << canonicalPath << ", symlinkedPath=" << symlinkedPath;
// from cache
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
// no cache
KNetworkMounts::self()->clearCache();
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
KNetworkMounts::self()->clearCache();
KNetworkMounts::self()->setOption(KNetworkMounts::SymlinkPathsUseCache, false);
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
// with cache
KNetworkMounts::self()->setOption(KNetworkMounts::SymlinkPathsUseCache, true);
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(symlinkedPath), canonicalPath);
}
#include "moc_knetworkmountstestcanonical.cpp"

View File

@@ -0,0 +1,29 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTSTESTCANONICAL_H
#define KNETWORKMOUNTSTESTCANONICAL_H
#include <QObject>
#include <QTemporaryDir>
class KNetworkMountsTestCanonical : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testCanonicalSymlinkPath_data();
void testCanonicalSymlinkPath();
private:
QString m_configFileName;
QTemporaryDir m_tmpDir;
};
#endif

View File

@@ -0,0 +1,144 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knetworkmountstestnoconfig.h"
#include <KNetworkMounts>
#include <QFile>
#include <QStandardPaths>
#include <QTest>
QTEST_MAIN(KNetworkMountsTestNoConfig)
void KNetworkMountsTestNoConfig::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
m_configFileName = QStringLiteral("%1/network_mounts").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
QFile::remove(m_configFileName);
QVERIFY(!QFile::exists(m_configFileName));
}
void KNetworkMountsTestNoConfig::cleanupTestCase()
{
QVERIFY(!QFile::exists(m_configFileName));
QVERIFY(!KNetworkMounts::self()->isEnabled());
KNetworkMounts::self()->sync();
QFile::remove(m_configFileName);
}
void KNetworkMountsTestNoConfig::testNoConfigPathTypes_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<KNetworkMounts::KNetworkMountsType>("type");
QTest::newRow("NfsPaths/") << "/" << KNetworkMounts::NfsPaths;
QTest::newRow("SmbPaths/") << "/" << KNetworkMounts::SmbPaths;
QTest::newRow("SymlinkDirectory/") << "/" << KNetworkMounts::SymlinkDirectory;
QTest::newRow("SymlinkToNetworkMount/") << "/" << KNetworkMounts::SymlinkToNetworkMount;
QTest::newRow("Any/") << "/" << KNetworkMounts::Any;
QTest::newRow("NfsPaths/mnt") << "/mnt" << KNetworkMounts::NfsPaths;
QTest::newRow("SmbPaths/mnt") << "/mnt" << KNetworkMounts::SmbPaths;
QTest::newRow("SymlinkDirectory/mnt") << "/mnt" << KNetworkMounts::SymlinkDirectory;
QTest::newRow("SymlinkToNetworkMount/mnt") << "/mnt" << KNetworkMounts::SymlinkToNetworkMount;
QTest::newRow("Any/mnt") << "/mnt" << KNetworkMounts::Any;
QTest::newRow("NfsPaths/mnt/") << "/mnt/" << KNetworkMounts::NfsPaths;
QTest::newRow("SmbPaths/mnt/") << "/mnt/" << KNetworkMounts::SmbPaths;
QTest::newRow("SymlinkDirectory/mnt/") << "/mnt/" << KNetworkMounts::SymlinkDirectory;
QTest::newRow("SymlinkToNetworkMount/mnt/") << "/mnt/" << KNetworkMounts::SymlinkToNetworkMount;
QTest::newRow("Any/mnt/") << "/mnt/" << KNetworkMounts::Any;
}
void KNetworkMountsTestNoConfig::testNoConfigPathTypes()
{
QFETCH(QString, path);
QFETCH(KNetworkMounts::KNetworkMountsType, type);
QVERIFY(!QFile::exists(m_configFileName));
QVERIFY(!KNetworkMounts::self()->isEnabled());
QCOMPARE(KNetworkMounts::self()->paths(type), QStringList());
QCOMPARE(KNetworkMounts::self()->paths(), QStringList());
QCOMPARE(KNetworkMounts::self()->canonicalSymlinkPath(path), path);
QVERIFY(!KNetworkMounts::self()->isSlowPath(path, type));
QVERIFY(!KNetworkMounts::self()->isSlowPath(path));
}
void KNetworkMountsTestNoConfig::testNoConfigPathOptions_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<KNetworkMounts::KNetworkMountOption>("option");
QTest::newRow("LowSideEffectsOptimizations/") << "/" << KNetworkMounts::LowSideEffectsOptimizations;
QTest::newRow("MediumSideEffectsOptimizations/") << "/" << KNetworkMounts::MediumSideEffectsOptimizations;
QTest::newRow("StrongSideEffectsOptimizations/") << "/" << KNetworkMounts::StrongSideEffectsOptimizations;
QTest::newRow("KDirWatchUseINotify/") << "/" << KNetworkMounts::KDirWatchUseINotify;
QTest::newRow("KDirWatchDontAddWatches/") << "/" << KNetworkMounts::KDirWatchDontAddWatches;
QTest::newRow("SymlinkPathsUseCache/") << "/" << KNetworkMounts::SymlinkPathsUseCache;
QTest::newRow("LowSideEffectsOptimizations/mnt") << "/mnt" << KNetworkMounts::LowSideEffectsOptimizations;
QTest::newRow("MediumSideEffectsOptimizations/mnt") << "/mnt" << KNetworkMounts::MediumSideEffectsOptimizations;
QTest::newRow("StrongSideEffectsOptimizations/mnt") << "/mnt" << KNetworkMounts::StrongSideEffectsOptimizations;
QTest::newRow("KDirWatchUseINotify/mnt") << "/mnt" << KNetworkMounts::KDirWatchUseINotify;
QTest::newRow("KDirWatchDontAddWatches/mnt") << "/mnt" << KNetworkMounts::KDirWatchDontAddWatches;
QTest::newRow("SymlinkPathsUseCache/mnt") << "/mnt" << KNetworkMounts::SymlinkPathsUseCache;
QTest::newRow("LowSideEffectsOptimizations/mnt/") << "/mnt/" << KNetworkMounts::LowSideEffectsOptimizations;
QTest::newRow("MediumSideEffectsOptimizations/mnt/") << "/mnt/" << KNetworkMounts::MediumSideEffectsOptimizations;
QTest::newRow("StrongSideEffectsOptimizations/mnt/") << "/mnt/" << KNetworkMounts::StrongSideEffectsOptimizations;
QTest::newRow("KDirWatchUseINotify/mnt/") << "/mnt/" << KNetworkMounts::KDirWatchUseINotify;
QTest::newRow("KDirWatchDontAddWatches/mnt/") << "/mnt/" << KNetworkMounts::KDirWatchDontAddWatches;
QTest::newRow("SymlinkPathsUseCache/mnt/") << "/mnt/" << KNetworkMounts::SymlinkPathsUseCache;
}
void KNetworkMountsTestNoConfig::testNoConfigPathOptions()
{
QFETCH(QString, path);
QFETCH(KNetworkMounts::KNetworkMountOption, option);
QVERIFY(!KNetworkMounts::self()->isOptionEnabledForPath(path, option));
}
void KNetworkMountsTestNoConfig::testNoConfigOptions_data()
{
QTest::addColumn<KNetworkMounts::KNetworkMountOption>("option");
QTest::addColumn<bool>("default_value");
QTest::addColumn<bool>("expected_value");
QTest::newRow("LowSideEffectsOptimizations_false") << KNetworkMounts::LowSideEffectsOptimizations << false << false;
QTest::newRow("LowSideEffectsOptimizations_true") << KNetworkMounts::LowSideEffectsOptimizations << true << true;
QTest::newRow("MediumSideEffectsOptimizationss_false") << KNetworkMounts::MediumSideEffectsOptimizations << false << false;
QTest::newRow("MediumSideEffectsOptimizations_true") << KNetworkMounts::MediumSideEffectsOptimizations << true << true;
QTest::newRow("StrongSideEffectsOptimizations_false") << KNetworkMounts::StrongSideEffectsOptimizations << false << false;
QTest::newRow("StrongSideEffectsOptimizationss_true") << KNetworkMounts::StrongSideEffectsOptimizations << true << true;
QTest::newRow("KDirWatchUseINotify_false") << KNetworkMounts::KDirWatchUseINotify << false << false;
QTest::newRow("KDirWatchUseINotifys_true") << KNetworkMounts::KDirWatchUseINotify << true << true;
QTest::newRow("KDirWatchDontAddWatches_false") << KNetworkMounts::KDirWatchDontAddWatches << false << false;
QTest::newRow("KDirWatchDontAddWatches_true") << KNetworkMounts::KDirWatchDontAddWatches << true << true;
QTest::newRow("SymlinkPathsUseCache_false") << KNetworkMounts::SymlinkPathsUseCache << false << false;
QTest::newRow("SymlinkPathsUseCache_true") << KNetworkMounts::SymlinkPathsUseCache << true << true;
}
void KNetworkMountsTestNoConfig::testNoConfigOptions()
{
QFETCH(KNetworkMounts::KNetworkMountOption, option);
QFETCH(bool, default_value);
QFETCH(bool, expected_value);
QCOMPARE(KNetworkMounts::self()->isOptionEnabled(option, default_value), expected_value);
}
#include "moc_knetworkmountstestnoconfig.cpp"

View File

@@ -0,0 +1,33 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTSTESTNOCONFIG_H
#define KNETWORKMOUNTSTESTNOCONFIG_H
#include <QObject>
class KNetworkMountsTestNoConfig : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testNoConfigPathTypes_data();
void testNoConfigPathTypes();
void testNoConfigPathOptions_data();
void testNoConfigPathOptions();
void testNoConfigOptions_data();
void testNoConfigOptions();
private:
QString m_configFileName;
};
#endif

View File

@@ -0,0 +1,157 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knetworkmountstestpaths.h"
#include <KNetworkMounts>
#include <QFile>
#include <QStandardPaths>
#include <QTest>
QTEST_MAIN(KNetworkMountsTestPaths)
void KNetworkMountsTestPaths::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
m_configFileName = QStringLiteral("%1/network_mounts").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
QFile::remove(m_configFileName);
QVERIFY(!QFile::exists(m_configFileName));
KNetworkMounts::self()->setEnabled(true);
QVERIFY(KNetworkMounts::self()->isEnabled());
KNetworkMounts::self()->sync();
QVERIFY(QFile::exists(m_configFileName));
QVERIFY(KNetworkMounts::self()->isEnabled());
// nfs path
const QString nfsPath = QStringLiteral("/mnt/nfs");
const QString savedNfsPath = QStringLiteral("/mnt/nfs/");
const QStringList savedNfsPaths = {savedNfsPath};
KNetworkMounts::self()->addPath(nfsPath, KNetworkMounts::NfsPaths);
QStringList allSavedPaths = savedNfsPaths;
QCOMPARE(KNetworkMounts::self()->paths(), allSavedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), QStringList());
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::NfsPaths), savedNfsPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), QStringList());
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), QStringList());
// smb shares
const QStringList paths = {QStringLiteral("/mnt/server1"), QStringLiteral("/mnt/server2")};
const QStringList savedSmbPaths = {QStringLiteral("/mnt/server1/"), QStringLiteral("/mnt/server2/")};
KNetworkMounts::self()->setPaths(paths, KNetworkMounts::SmbPaths);
allSavedPaths << savedSmbPaths;
QCOMPARE(KNetworkMounts::self()->paths(), allSavedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), savedSmbPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::NfsPaths), savedNfsPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), QStringList());
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), QStringList());
// symlink dir
const QStringList symlinkDirs = {QStringLiteral("/home/user/netshares")};
const QStringList savedSymlinkDirs = {QStringLiteral("/home/user/netshares/")};
KNetworkMounts::self()->setPaths(symlinkDirs, KNetworkMounts::SymlinkDirectory);
allSavedPaths << savedSymlinkDirs;
QCOMPARE(KNetworkMounts::self()->paths(), allSavedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), savedSmbPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), savedSymlinkDirs);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::NfsPaths), savedNfsPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), QStringList());
// symlink to nfs or smb
const QString symlinkToNfs = QStringLiteral("/somedir/symlinkToNfs");
const QString savedSymlinkToNfs = QStringLiteral("/somedir/symlinkToNfs/");
const QStringList savedSymlinkToNfsPaths = {savedSymlinkToNfs};
KNetworkMounts::self()->addPath(symlinkToNfs, KNetworkMounts::SymlinkToNetworkMount);
allSavedPaths << savedSymlinkToNfsPaths;
QCOMPARE(KNetworkMounts::self()->paths(), allSavedPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SmbPaths), savedSmbPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkDirectory), savedSymlinkDirs);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::NfsPaths), savedNfsPaths);
QCOMPARE(KNetworkMounts::self()->paths(KNetworkMounts::SymlinkToNetworkMount), savedSymlinkToNfsPaths);
}
void KNetworkMountsTestPaths::cleanupTestCase()
{
KNetworkMounts::self()->sync();
QFile::remove(m_configFileName);
}
void KNetworkMountsTestPaths::testPaths_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected_path_option");
QTest::addColumn<bool>("expected_path");
QTest::addColumn<bool>("expected_symlink_dir");
QTest::addColumn<bool>("expected_symlink_to_nfs_or_smb");
QTest::addColumn<bool>("expected_nfs");
QTest::addColumn<bool>("expected_smb");
QTest::newRow("fast_path") << "/mnt" << false << false << false << false << false << false;
QTest::newRow("fast_path_slash_end") << "/mnt/" << false << false << false << false << false << false;
QTest::newRow("slow_path1") << "/mnt/server1" << true << true << false << false << false << true;
QTest::newRow("slow_path2") << "/mnt/server2" << true << true << false << false << false << true;
QTest::newRow("slow_path2_dir") << "/mnt/server2/dir" << true << true << false << false << false << true;
QTest::newRow("slow_path2_dir_subdir") << "/mnt/server2/dir/subdir" << true << true << false << false << false << true;
QTest::newRow("slow_path2_dir_subdir_slash_end") << "/mnt/server2/dir/subdir/" << true << true << false << false << false << true;
QTest::newRow("slow_symlink_path") << "/home/user/netshares" << true << true << true << false << false << false;
QTest::newRow("fast_path_root") << "/" << false << false << false << false << false << false;
QTest::newRow("fast_path_home") << "/home" << false << false << false << false << false << false;
QTest::newRow("fast_path_home_user") << "/home/user" << false << false << false << false << false << false;
QTest::newRow("slow_symlink_path_subdir1") << "/home/user/netshares/subdir1" << true << true << true << false << false << false;
QTest::newRow("slow_symlink_path_subdir1_subdir2") << "/home/user/netshares/subdir1/subdir2" << true << true << true << false << false << false;
QTest::newRow("slow_symlink_path_subdir1_subdir2_slash_end") << "/home/user/netshares/subdir1/subdir2/" << true << true << true << false << false << false;
QTest::newRow("slow_path_nfs") << "/mnt/nfs" << true << true << false << false << true << false;
QTest::newRow("slow_path_nfs_dir") << "/mnt/nfs/dir" << true << true << false << false << true << false;
QTest::newRow("slow_path_nfs_dir_subdir") << "/mnt/nfs/dir/subdir" << true << true << false << false << true << false;
QTest::newRow("slow_path_nfs_dir_subdir_slash_end") << "/mnt/nfs/dir/subdir/" << true << true << false << false << true << false;
QTest::newRow("slow_path_symlink_to_nfs") << "/somedir/symlinkToNfs" << true << true << false << true << false << false;
QTest::newRow("slow_path_symlink_to_nfs_dir") << "/somedir/symlinkToNfs/dir" << true << true << false << true << false << false;
QTest::newRow("slow_path_symlink_to_nfs_dir_subdir") << "/somedir/symlinkToNfs/dir/subdir" << true << true << false << true << false << false;
QTest::newRow("slow_path_symlink_to_nfs_dir_subdir_slash_end") << "/somedir/symlinkToNfs/dir/subdir/" << true << true << false << true << false << false;
}
void KNetworkMountsTestPaths::testPaths()
{
QFETCH(QString, path);
QFETCH(bool, expected_path_option);
QFETCH(bool, expected_path);
QFETCH(bool, expected_symlink_dir);
QFETCH(bool, expected_symlink_to_nfs_or_smb);
QFETCH(bool, expected_nfs);
QFETCH(bool, expected_smb);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::SymlinkPathsUseCache), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::KDirWatchUseINotify), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::KDirWatchDontAddWatches), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::LowSideEffectsOptimizations), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::MediumSideEffectsOptimizations), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isOptionEnabledForPath(path, KNetworkMounts::StrongSideEffectsOptimizations), expected_path_option);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path), expected_path);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::KNetworkMountsType::Any), expected_path);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::SymlinkDirectory), expected_symlink_dir);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::SymlinkToNetworkMount), expected_symlink_to_nfs_or_smb);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::NfsPaths), expected_nfs);
QCOMPARE(KNetworkMounts::self()->isSlowPath(path, KNetworkMounts::SmbPaths), expected_smb);
}
#include "moc_knetworkmountstestpaths.cpp"

View File

@@ -0,0 +1,27 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTSTESTPATHS_H
#define KNETWORKMOUNTSTESTPATHS_H
#include <QObject>
class KNetworkMountsTestPaths : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testPaths_data();
void testPaths();
private:
QString m_configFileName;
};
#endif

View File

@@ -0,0 +1,112 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knetworkmountsteststatic.h"
// include static functions
#include "knetworkmounts_p.h"
#include <KNetworkMounts>
#include <QFile>
#include <QStandardPaths>
#include <QTest>
QTEST_MAIN(KNetworkMountsTestStatic)
void KNetworkMountsTestStatic::testStaticFunctions_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<QStringList>("paths");
QTest::addColumn<bool>("expected_is_slash_added_to_path");
QTest::addColumn<QString>("expected_path_str");
QTest::addColumn<bool>("expected_is_slash_added_to_paths");
QTest::addColumn<QStringList>("expected_paths_str");
QTest::addColumn<QString>("expected_matching");
QTest::newRow("empty1") << QString() << QStringList() << false << "" << false << QStringList() << QString();
QTest::newRow("empty2") << "" << (QStringList() << QString() << QString()) << false << "" << false << (QStringList() << QString() << QString())
<< QString();
QTest::newRow("/1") << "/" << QStringList() << false << "/" << false << QStringList() << QString();
QTest::newRow("/2") << "/" << (QStringList() << QString() << QString()) << false << "/" << false << (QStringList() << QString() << QString()) << QString();
QTest::newRow("/3") << "/" << (QStringList() << QStringLiteral("/")) << false << "/" << false << (QStringList() << QStringLiteral("/")) << "/";
QTest::newRow("/4") << "/" << (QStringList() << QStringLiteral("/") << QString()) << false << "/" << false
<< (QStringList() << QStringLiteral("/") << QString()) << "/";
QTest::newRow("/mnt1") << "/mnt" << QStringList() << true << "/mnt/" << false << QStringList() << QString();
QTest::newRow("/mnt2") << "/mnt" << (QStringList() << QStringLiteral("/mnt")) << true << "/mnt/" << true << (QStringList() << QStringLiteral("/mnt/"))
<< "/mnt";
QTest::newRow("/mnt3") << "/mnt" << (QStringList() << QStringLiteral("/mnt/")) << true << "/mnt/" << false << (QStringList() << QStringLiteral("/mnt/"))
<< "/mnt/";
QTest::newRow("/mnt/test1") << "/mnt" << (QStringList() << QStringLiteral("/mnt/test1") << QStringLiteral("/mnt/test2/")) << true << "/mnt/" << true
<< (QStringList() << QStringLiteral("/mnt/test1/") << QStringLiteral("/mnt/test2/")) << "";
QTest::newRow("/mnt/test2") << "/mnt/test2" << (QStringList() << QStringLiteral("/mnt/test1/") << QStringLiteral("/mnt/test2/")) << true << "/mnt/test2/"
<< false << (QStringList() << QStringLiteral("/mnt/test1/") << QStringLiteral("/mnt/test2/")) << "/mnt/test2/";
QTest::newRow("/mnt/test3") << "/mnt/test2/" << (QStringList() << QStringLiteral("/mnt/test1/") << QStringLiteral("/mnt/test2/")) << false << "/mnt/test2/"
<< false << (QStringList() << QStringLiteral("/mnt/test1/") << QStringLiteral("/mnt/test2/")) << "/mnt/test2/";
}
void KNetworkMountsTestStatic::testStaticFunctions()
{
QFETCH(QString, path);
QFETCH(QStringList, paths);
QFETCH(bool, expected_is_slash_added_to_path);
QFETCH(QString, expected_path_str);
QFETCH(bool, expected_is_slash_added_to_paths);
QFETCH(QStringList, expected_paths_str);
QFETCH(QString, expected_matching);
QCOMPARE(getMatchingPath(path, paths), expected_matching);
QCOMPARE(ensureTrailingSlash(&path), expected_is_slash_added_to_path);
QCOMPARE(path, expected_path_str);
QCOMPARE(ensureTrailingSlashes(&paths), expected_is_slash_added_to_paths);
QCOMPARE(paths, expected_paths_str);
}
void KNetworkMountsTestStatic::testStaticKNetworkMountOptionToString_data()
{
QTest::addColumn<KNetworkMounts::KNetworkMountOption>("option");
QTest::addColumn<QString>("string");
QTest::newRow("LowSideEffectsOptimizations") << KNetworkMounts::LowSideEffectsOptimizations << "LowSideEffectsOptimizations";
QTest::newRow("MediumSideEffectsOptimizations") << KNetworkMounts::MediumSideEffectsOptimizations << "MediumSideEffectsOptimizations";
QTest::newRow("StrongSideEffectsOptimizations") << KNetworkMounts::StrongSideEffectsOptimizations << "StrongSideEffectsOptimizations";
QTest::newRow("KDirWatchUseINotify") << KNetworkMounts::KDirWatchUseINotify << "KDirWatchUseINotify";
QTest::newRow("KDirWatchDontAddWatches") << KNetworkMounts::KDirWatchDontAddWatches << "KDirWatchDontAddWatches";
QTest::newRow("SymlinkPathsUseCache") << KNetworkMounts::SymlinkPathsUseCache << "SymlinkPathsUseCache";
}
void KNetworkMountsTestStatic::testStaticKNetworkMountOptionToString()
{
QFETCH(KNetworkMounts::KNetworkMountOption, option);
QFETCH(QString, string);
QCOMPARE(enumToString(option), string);
}
void KNetworkMountsTestStatic::testStaticKNetworkMountsTypeToString_data()
{
QTest::addColumn<KNetworkMounts::KNetworkMountsType>("type");
QTest::addColumn<QString>("string");
QTest::newRow("NfsPaths") << KNetworkMounts::NfsPaths << "NfsPaths";
QTest::newRow("SmbPaths") << KNetworkMounts::SmbPaths << "SmbPaths";
QTest::newRow("SymlinkDirectory") << KNetworkMounts::SymlinkDirectory << "SymlinkDirectory";
QTest::newRow("SymlinkToNetworkMount") << KNetworkMounts::SymlinkToNetworkMount << "SymlinkToNetworkMount";
QTest::newRow("Any") << KNetworkMounts::Any << "Any";
}
void KNetworkMountsTestStatic::testStaticKNetworkMountsTypeToString()
{
QFETCH(KNetworkMounts::KNetworkMountsType, type);
QFETCH(QString, string);
QCOMPARE(enumToString(type), string);
}
#include "moc_knetworkmountsteststatic.cpp"

View File

@@ -0,0 +1,28 @@
/*
This software is a contribution of the LiMux project of the city of Munich.
SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNETWORKMOUNTSTESTSTATIC_H
#define KNETWORKMOUNTSTESTSTATIC_H
#include <QObject>
class KNetworkMountsTestStatic : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testStaticFunctions_data();
void testStaticFunctions();
void testStaticKNetworkMountOptionToString_data();
void testStaticKNetworkMountOptionToString();
void testStaticKNetworkMountsTypeToString_data();
void testStaticKNetworkMountsTypeToString();
private:
QString m_configFileName;
};
#endif

View File

@@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2014-2019 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QTest>
#include "kosrelease.h"
class KOSReleaseTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testParse()
{
KOSRelease r(QFINDTESTDATA("data/os-release"));
QCOMPARE(r.name(), QStringLiteral("Name"));
QCOMPARE(r.version(), QStringLiteral("100.5"));
QCOMPARE(r.id(), QStringLiteral("theid"));
QCOMPARE(r.idLike(), QStringList({QStringLiteral("otherid"), QStringLiteral("otherotherid")}));
QCOMPARE(r.versionCodename(), QStringLiteral("versioncodename"));
QCOMPARE(r.versionId(), QStringLiteral("500.1"));
QCOMPARE(r.prettyName(), QStringLiteral("Pretty Name #1"));
QCOMPARE(r.ansiColor(), QStringLiteral("1;34"));
QCOMPARE(r.cpeName(), QStringLiteral("cpe:/o:foo:bar:100"));
QCOMPARE(r.homeUrl(), QStringLiteral("https://url.home"));
QCOMPARE(r.documentationUrl(), QStringLiteral("https://url.docs"));
QCOMPARE(r.supportUrl(), QStringLiteral("https://url.support"));
QCOMPARE(r.bugReportUrl(), QStringLiteral("https://url.bugs"));
QCOMPARE(r.privacyPolicyUrl(), QStringLiteral("https://url.privacy"));
QCOMPARE(r.buildId(), QStringLiteral("105.5"));
QCOMPARE(r.variant(), QStringLiteral("Test = Edition"));
QCOMPARE(r.variantId(), QStringLiteral("test"));
QCOMPARE(r.logo(), QStringLiteral("start-here-test"));
QCOMPARE(r.extraKeys(), QStringList({QStringLiteral("DEBIAN_BTS")}));
QCOMPARE(r.extraValue(QStringLiteral("DEBIAN_BTS")), QStringLiteral("debbugs://bugs.debian.org/"));
}
};
QTEST_MAIN(KOSReleaseTest)
#include "kosreleasetest.moc"

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "kpluginfactory.h"
class SimplePluginClass : public QObject
{
Q_OBJECT
public:
explicit SimplePluginClass(QObject * /*parent*/, const QVariantList & /*args*/)
{
}
};
K_PLUGIN_CLASS_WITH_JSON(SimplePluginClass, "jsonplugin.json")
#include "kpluginclass.moc"

View File

@@ -0,0 +1,139 @@
/*
SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QTest>
#include <QPluginLoader>
#include <kpluginfactory.h>
#include <kpluginloader.h>
// We do not have QWidgets as a dependency, this is a simple placeholder for the type to be fully qualified
class QWidget : public QObject
{
};
class KPluginFactoryTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testCreate()
{
KPluginFactory::Result<KPluginFactory> factoryResult = KPluginFactory::loadFactory(KPluginMetaData(QStringLiteral("multiplugin")));
auto factory = factoryResult.plugin;
QVERIFY(factory);
QVariantList args;
args << QStringLiteral("Some") << QStringLiteral("args") << 5;
QObject *obj = factory->create<QObject>(this, args);
QVERIFY(obj);
QCOMPARE(obj->objectName(), QString::fromLatin1("MultiPlugin1"));
QObject *obj2 = factory->create<QObject>(this, args);
QVERIFY(obj2);
QCOMPARE(obj2->objectName(), QString::fromLatin1("MultiPlugin1"));
QVERIFY(obj != obj2);
delete obj;
delete obj2;
// Try creating a part without keyword/args
QWidget parentWidget;
QObject *partTest = factory->create<QObject>(&parentWidget, this);
QVERIFY(partTest);
delete partTest;
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89)
obj = factory->create<QObject>(QStringLiteral("secondary"), this, args);
QVERIFY(obj);
QCOMPARE(obj->objectName(), QString::fromLatin1("MultiPlugin2"));
obj2 = factory->create<QObject>(QStringLiteral("secondary"), this, args);
QVERIFY(obj2);
QCOMPARE(obj2->objectName(), QString::fromLatin1("MultiPlugin2"));
QVERIFY(obj != obj2);
delete obj;
delete obj2;
#endif
}
void testPluginWithoutMetaData()
{
KPluginFactory::Result<KPluginFactory> factoryResult = KPluginFactory::loadFactory(KPluginMetaData(QStringLiteral("namespace/pluginwithoutmetadata")));
QVERIFY(factoryResult);
auto plugin = factoryResult.plugin->create<QObject>();
QVERIFY(plugin);
QCOMPARE(plugin->metaObject()->className(), "PluginWithoutMetaData");
delete plugin;
}
void testResultingCMakeMacroPlugin()
{
KPluginMetaData data(QStringLiteral("namespace/jsonplugin_cmake_macro"));
QVERIFY(data.isValid());
auto instance = QPluginLoader(data.fileName()).instance();
QVERIFY(instance);
QCOMPARE(instance->metaObject()->className(), "jsonplugin_cmake_macro_factory");
}
void testCreateUsingUtilityMethods()
{
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QStringLiteral("jsonplugin")), nullptr, QVariantList());
QVERIFY(result.plugin);
QCOMPARE(result.plugin->metaObject()->className(), "JsonPlugin");
QVERIFY(result.errorString.isEmpty());
QCOMPARE(result.errorReason, KPluginFactory::NO_PLUGIN_ERROR);
delete result.plugin;
}
void testCreateUsingUtilityMethodsErrorHandling()
{
{
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QFINDTESTDATA("jsonplugin.json")), nullptr, QVariantList());
QVERIFY(!result.plugin);
QCOMPARE(result.errorReason, KPluginFactory::INVALID_PLUGIN);
}
{
// it is a valid plugin, but does not contain a KPluginFactory
QVERIFY(QPluginLoader(QStringLiteral("qtplugin")).instance());
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QStringLiteral("qtplugin")), nullptr, QVariantList());
QVERIFY(!result.plugin);
// But does not contain a valid plugin factory
QCOMPARE(result.errorReason, KPluginFactory::INVALID_FACTORY);
}
{
// it is a QObject, but not a KPluginFactoryTest instance
auto result = KPluginFactory::instantiatePlugin<KPluginFactoryTest>(KPluginMetaData(QStringLiteral("jsonplugin")), nullptr, QVariantList());
QVERIFY(!result.plugin);
QCOMPARE(result.errorReason, KPluginFactory::INVALID_KPLUGINFACTORY_INSTANTIATION);
QVERIFY(result.errorText.contains("KPluginFactoryTest"));
}
}
void testStaticPlugins()
{
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace"));
QCOMPARE(plugins.count(), 1);
auto result = KPluginFactory::instantiatePlugin<QObject>(plugins.first());
QVERIFY(result);
delete result.plugin;
}
void testNonExistingPlugin()
{
KPluginMetaData data(QStringLiteral("does/not/exist"));
QVERIFY(!data.isValid());
const auto res = KPluginFactory::instantiatePlugin<QObject>(data);
QVERIFY(!res);
QCOMPARE(res.errorReason, KPluginFactory::INVALID_PLUGIN);
QCOMPARE(res.errorText, QStringLiteral("Could not find plugin does/not/exist"));
}
};
QTEST_MAIN(KPluginFactoryTest)
#include "kpluginfactorytest.moc"

View File

@@ -0,0 +1,389 @@
/*
SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QFileInfo>
#include <QTest>
#include "kcoreaddons_debug.h"
#include <kpluginloader.h>
#include <kpluginmetadata.h>
class LibraryPathRestorer
{
public:
explicit LibraryPathRestorer(const QStringList &paths)
: mPaths(paths)
{
}
~LibraryPathRestorer()
{
QCoreApplication::setLibraryPaths(mPaths);
}
private:
QStringList mPaths;
};
class KPluginLoaderTest : public QObject
{
Q_OBJECT
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86)
private Q_SLOTS:
void testFindPlugin_missing()
{
const QString location = KPluginLoader::findPlugin(QStringLiteral("idonotexist"));
QVERIFY2(location.isEmpty(), qPrintable(location));
}
void testFindPlugin()
{
const QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
QVERIFY2(!location.isEmpty(), qPrintable(location));
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 84)
void testPluginVersion()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QCOMPARE(vplugin.pluginVersion(), quint32(5));
KPluginLoader vplugin2(QStringLiteral("versionedplugin"));
QCOMPARE(vplugin2.pluginVersion(), quint32(5));
KPluginLoader uplugin(QStringLiteral("unversionedplugin"));
QCOMPARE(uplugin.pluginVersion(), quint32(-1));
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QCOMPARE(jplugin.pluginVersion(), quint32(-1));
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QCOMPARE(eplugin.pluginVersion(), quint32(-1));
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QCOMPARE(noplugin.pluginVersion(), quint32(-1));
}
#endif
void testPluginName()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QCOMPARE(vplugin.pluginName(), QString::fromLatin1("versionedplugin"));
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QCOMPARE(jplugin.pluginName(), QString::fromLatin1("jsonplugin"));
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY2(eplugin.pluginName().isEmpty(), qPrintable(eplugin.pluginName()));
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QCOMPARE(noplugin.pluginName(), QString::fromLatin1("idonotexist"));
}
void testFactory()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QVERIFY(vplugin.factory());
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QVERIFY(jplugin.factory());
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY(!eplugin.factory());
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY(!noplugin.factory());
}
void testErrorString()
{
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QCOMPARE(eplugin.errorString(), QString::fromLatin1("there was an error"));
}
void testFileName()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QCOMPARE(QFileInfo(vplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(VERSIONEDPLUGIN_FILE)).canonicalFilePath());
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QCOMPARE(QFileInfo(jplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(JSONPLUGIN_FILE)).canonicalFilePath());
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY2(eplugin.fileName().isEmpty(), qPrintable(eplugin.fileName()));
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY2(noplugin.fileName().isEmpty(), qPrintable(noplugin.fileName()));
}
void testInstance()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QVERIFY(vplugin.instance());
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QVERIFY(jplugin.instance());
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY(!eplugin.instance());
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY(!noplugin.instance());
}
void testIsLoaded()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QVERIFY(!vplugin.isLoaded());
QVERIFY(vplugin.load());
QVERIFY(vplugin.isLoaded());
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QVERIFY(!jplugin.isLoaded());
QVERIFY(jplugin.load());
QVERIFY(jplugin.isLoaded());
KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
QVERIFY(!aplugin.isLoaded());
QVERIFY(aplugin.load());
QVERIFY(aplugin.isLoaded());
if (aplugin.unload()) {
QVERIFY(!aplugin.isLoaded());
} else {
qCDebug(KCOREADDONS_DEBUG) << "Could not unload alwaysunloadplugin:" << aplugin.errorString();
}
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY(!eplugin.isLoaded());
QVERIFY(!eplugin.load());
QVERIFY(!eplugin.isLoaded());
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY(!noplugin.isLoaded());
QVERIFY(!noplugin.load());
QVERIFY(!noplugin.isLoaded());
}
void testLoad()
{
KPluginLoader vplugin(QStringLiteral("versionedplugin"));
QVERIFY(vplugin.load());
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QVERIFY(jplugin.load());
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY(!eplugin.load());
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY(!noplugin.load());
}
void testLoadHints()
{
KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
QCOMPARE(aplugin.loadHints(), QLibrary::PreventUnloadHint);
aplugin.setLoadHints(QLibrary::ResolveAllSymbolsHint);
// setLoadHints merges in this scenario in the patch collection [1] but not in raw Qt5
// [1] https://invent.kde.org/qt/qt/qtbase/-/merge_requests/285
QVERIFY(aplugin.loadHints() == (QLibrary::ResolveAllSymbolsHint | QLibrary::PreventUnloadHint)
|| aplugin.loadHints() == QLibrary::ResolveAllSymbolsHint);
}
void testMetaData()
{
KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
QJsonObject ametadata = aplugin.metaData();
QVERIFY(!ametadata.isEmpty());
QVERIFY(ametadata.keys().contains(QLatin1String("IID")));
QJsonValue ametadata_metadata = ametadata.value(QStringLiteral("MetaData"));
QVERIFY(ametadata_metadata.toObject().isEmpty());
QVERIFY(!aplugin.isLoaded()); // didn't load anything
KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
QJsonObject jmetadata = jplugin.metaData();
QVERIFY(!jmetadata.isEmpty());
QJsonValue jmetadata_metadata = jmetadata.value(QStringLiteral("MetaData"));
QVERIFY(jmetadata_metadata.isObject());
QJsonObject jmetadata_obj = jmetadata_metadata.toObject();
QVERIFY(!jmetadata_obj.isEmpty());
QJsonValue comment = jmetadata_obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Description"));
QVERIFY(comment.isString());
QCOMPARE(comment.toString(), QString::fromLatin1("This is a plugin"));
KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
QVERIFY(eplugin.metaData().isEmpty());
KPluginLoader noplugin(QStringLiteral("idonotexist"));
QVERIFY(noplugin.metaData().isEmpty());
}
void testUnload()
{
KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
QVERIFY(aplugin.load());
// may need QEXPECT_FAIL on some platforms...
QVERIFY(aplugin.unload());
}
void testInstantiatePlugins()
{
const QString plugin1Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
QVERIFY2(!plugin1Path.isEmpty(), qPrintable(plugin1Path));
const QString plugin2Path = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
QVERIFY2(!plugin2Path.isEmpty(), qPrintable(plugin2Path));
const QString plugin3Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
QVERIFY2(!plugin3Path.isEmpty(), qPrintable(plugin3Path));
QTemporaryDir temp;
QVERIFY(temp.isValid());
QDir dir(temp.path());
QVERIFY2(QFile::copy(plugin1Path, dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())),
qPrintable(dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())));
QVERIFY2(QFile::copy(plugin2Path, dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())),
qPrintable(dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())));
QVERIFY2(QFile::copy(plugin3Path, dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())),
qPrintable(dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())));
// only jsonplugin, since unversionedplugin has no json metadata
QList<QObject *> plugins = KPluginLoader::instantiatePlugins(temp.path());
QCOMPARE(plugins.size(), 2);
QStringList classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className())
<< QString::fromLatin1(plugins[1]->metaObject()->className());
classNames.sort();
QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
qDeleteAll(plugins);
// try filter
plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
return md.pluginId() == QLatin1String("jsonplugin");
});
QCOMPARE(plugins.size(), 1);
QCOMPARE(plugins[0]->metaObject()->className(), "jsonpluginfa");
qDeleteAll(plugins);
plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
return md.pluginId() == QLatin1String("unversionedplugin");
});
QCOMPARE(plugins.size(), 0);
plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
return md.pluginId() == QLatin1String("foobar"); // ID does not match file name, is set in JSON
});
QCOMPARE(plugins.size(), 1);
QCOMPARE(plugins[0]->metaObject()->className(), "jsonplugin2");
qDeleteAll(plugins);
// check that parent gets set
plugins = KPluginLoader::instantiatePlugins(
temp.path(),
[](const KPluginMetaData &) {
return true;
},
this);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0]->parent(), this);
QCOMPARE(plugins[1]->parent(), this);
qDeleteAll(plugins);
const QString subDirName = dir.dirName();
QVERIFY(dir.cdUp()); // should now point to /tmp on Linux
LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
// instantiate using relative path
// make sure library path is set up correctly
QCoreApplication::setLibraryPaths(QStringList() << dir.absolutePath());
QVERIFY(!QDir::isAbsolutePath(subDirName));
plugins = KPluginLoader::instantiatePlugins(subDirName);
QCOMPARE(plugins.size(), 2);
classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className()) << QString::fromLatin1(plugins[1]->metaObject()->className());
classNames.sort();
QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
qDeleteAll(plugins);
}
void testForEachPlugin()
{
const QString jsonPluginSrc = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
QVERIFY2(!jsonPluginSrc.isEmpty(), qPrintable(jsonPluginSrc));
const QString unversionedPluginSrc = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
QVERIFY2(!unversionedPluginSrc.isEmpty(), qPrintable(unversionedPluginSrc));
const QString jsonPlugin2Src = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
QVERIFY2(!jsonPlugin2Src.isEmpty(), qPrintable(jsonPlugin2Src));
QTemporaryDir temp;
QVERIFY(temp.isValid());
QDir dir(temp.path());
QVERIFY(dir.mkdir(QStringLiteral("for-each-plugin")));
QVERIFY(dir.cd(QStringLiteral("for-each-plugin")));
const QString jsonPluginDest = dir.absoluteFilePath(QFileInfo(jsonPluginSrc).fileName());
QVERIFY2(QFile::copy(jsonPluginSrc, jsonPluginDest), qPrintable(jsonPluginDest));
const QString unversionedPluginDest = dir.absoluteFilePath(QFileInfo(unversionedPluginSrc).fileName());
QVERIFY2(QFile::copy(unversionedPluginSrc, unversionedPluginDest), qPrintable(unversionedPluginDest));
// copy jsonplugin2 to a "for-each-plugin" subdirectory in a different directory
QTemporaryDir temp2;
QVERIFY(temp2.isValid());
QDir dir2(temp2.path());
QVERIFY(dir2.mkdir(QStringLiteral("for-each-plugin")));
QVERIFY(dir2.cd(QStringLiteral("for-each-plugin")));
const QString jsonPlugin2Dest = dir2.absoluteFilePath(QFileInfo(jsonPlugin2Src).fileName());
QVERIFY2(QFile::copy(jsonPlugin2Src, jsonPlugin2Dest), qPrintable(jsonPlugin2Dest));
QStringList foundPlugins;
QStringList expectedPlugins;
const auto addToFoundPlugins = [&](const QString &path) {
QVERIFY(!path.isEmpty());
foundPlugins.append(path);
};
// test finding with absolute path
expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
expectedPlugins.sort();
KPluginLoader::forEachPlugin(dir.path(), addToFoundPlugins);
foundPlugins.sort();
QCOMPARE(foundPlugins, expectedPlugins);
expectedPlugins = QStringList() << jsonPlugin2Dest;
expectedPlugins.sort();
foundPlugins.clear();
KPluginLoader::forEachPlugin(dir2.path(), addToFoundPlugins);
foundPlugins.sort();
QCOMPARE(foundPlugins, expectedPlugins);
// now test relative paths
LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
QCoreApplication::setLibraryPaths(QStringList() << temp.path());
expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
expectedPlugins.sort();
foundPlugins.clear();
KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
foundPlugins.sort();
QCOMPARE(foundPlugins, expectedPlugins);
QCoreApplication::setLibraryPaths(QStringList() << temp2.path());
expectedPlugins = QStringList() << jsonPlugin2Dest;
expectedPlugins.sort();
foundPlugins.clear();
KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
foundPlugins.sort();
QCOMPARE(foundPlugins, expectedPlugins);
QCoreApplication::setLibraryPaths(QStringList() << temp.path() << temp2.path());
expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest << jsonPlugin2Dest;
expectedPlugins.sort();
foundPlugins.clear();
KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
foundPlugins.sort();
QCOMPARE(foundPlugins, expectedPlugins);
}
#endif
};
QTEST_MAIN(KPluginLoaderTest)
#include "kpluginloadertest.moc"

View File

@@ -0,0 +1,716 @@
/*
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QPluginLoader>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTest>
#include "kcoreaddons_debug.h"
#include <kaboutdata.h>
#include <kpluginloader.h>
#include <kpluginmetadata.h>
#include <QLocale>
#include <QLoggingCategory>
namespace QTest
{
template<>
inline char *toString(const QJsonValue &val)
{
// simply reuse the QDebug representation
QString result;
QDebug(&result) << val;
return QTest::toString(result);
}
}
class LibraryPathRestorer
{
public:
explicit LibraryPathRestorer(const QStringList &paths)
: mPaths(paths)
{
}
~LibraryPathRestorer()
{
QCoreApplication::setLibraryPaths(mPaths);
}
private:
QStringList mPaths;
};
class KPluginMetaDataTest : public QObject
{
Q_OBJECT
bool m_canMessage = false;
void doMessagesWorkInternal()
{
}
Q_REQUIRED_RESULT bool doMessagesWork()
{
// Q_SKIP returns, but since this is called in multiple tests we want to return a bool so the caller can
// return easily.
auto internalCheck = [this] {
// Make sure output is well formed AND generated. To that end we cannot run this test when any of the
// overriding environment variables are set.
// https://bugs.kde.org/show_bug.cgi?id=387006
if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) {
QSKIP("QT_MESSAGE_PATTERN prevents warning expectations from matching");
}
if (qEnvironmentVariableIsSet("QT_LOGGING_RULES")) {
QSKIP("QT_LOGGING_RULES prevents warning expectations from matching");
}
if (qEnvironmentVariableIsSet("QT_LOGGING_CONF")) {
QSKIP("QT_LOGGING_CONF prevents warning expectations from matching");
}
m_canMessage = true;
// Ensure all frameworks output is enabled so the expectations can match.
// qtlogging.ini may have disabled it but we can fix that because setFilterRules overrides the ini files.
QLoggingCategory::setFilterRules(QStringLiteral("kf.*=true"));
};
internalCheck();
return m_canMessage;
}
private Q_SLOTS:
void testFromPluginLoader()
{
QString location;
location = QPluginLoader(QStringLiteral("jsonplugin")).fileName();
QVERIFY2(!location.isEmpty(), "Could not find jsonplugin");
// now that this file is translated we need to read it instead of hardcoding the contents here
QString jsonLocation = QFINDTESTDATA("jsonplugin.json");
QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json");
QFile jsonFile(jsonLocation);
QVERIFY(jsonFile.open(QFile::ReadOnly));
QJsonParseError e;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e);
QCOMPARE(e.error, QJsonParseError::NoError);
location = QFileInfo(location).absoluteFilePath();
KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("jsonplugin")));
KPluginMetaData fromFullPath(location);
KPluginMetaData fromRelativePath(QStringLiteral("jsonplugin"));
KPluginMetaData fromRawData(jsonDoc.object(), location);
auto description = QStringLiteral("This is a plugin");
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86)
KPluginMetaData fromKPluginLoader(KPluginLoader(QStringLiteral("jsonplugin")));
QVERIFY(fromKPluginLoader.isValid());
QCOMPARE(fromKPluginLoader.description(), description);
QCOMPARE(fromKPluginLoader, fromKPluginLoader);
QCOMPARE(fromQPluginLoader, fromKPluginLoader);
QCOMPARE(fromKPluginLoader, fromQPluginLoader);
QCOMPARE(fromKPluginLoader, fromFullPath);
QCOMPARE(fromKPluginLoader, fromRawData);
QCOMPARE(fromFullPath, fromKPluginLoader);
QCOMPARE(fromRawData, fromKPluginLoader);
QVERIFY(!KPluginMetaData(KPluginLoader(QStringLiteral("doesnotexist"))).isValid());
#endif
QVERIFY(fromQPluginLoader.isValid());
QCOMPARE(fromQPluginLoader.description(), description);
QVERIFY(fromFullPath.isValid());
QCOMPARE(fromFullPath.description(), description);
QVERIFY(fromRelativePath.isValid());
QCOMPARE(fromRelativePath.description(), description);
QVERIFY(fromRawData.isValid());
QCOMPARE(fromRawData.description(), description);
// check operator==
QCOMPARE(fromRawData, fromRawData);
QCOMPARE(fromQPluginLoader, fromQPluginLoader);
QCOMPARE(fromFullPath, fromFullPath);
QCOMPARE(fromQPluginLoader, fromFullPath);
QCOMPARE(fromQPluginLoader, fromRawData);
QCOMPARE(fromFullPath, fromQPluginLoader);
QCOMPARE(fromFullPath, fromRawData);
QCOMPARE(fromRawData, fromQPluginLoader);
QCOMPARE(fromRawData, fromFullPath);
QVERIFY(!KPluginMetaData(QPluginLoader(QStringLiteral("doesnotexist"))).isValid());
QVERIFY(!KPluginMetaData(QJsonObject(), QString()).isValid());
}
void testAllKeys()
{
QJsonParseError e;
QJsonObject jo = QJsonDocument::fromJson(
"{\n"
" \"KPlugin\": {\n"
" \"Name\": \"Date and Time\",\n"
" \"Description\": \"Date and time by timezone\",\n"
" \"Icon\": \"preferences-system-time\",\n"
" \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n"
" \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n"
" \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n"
" \"Category\": \"Date and Time\",\n"
" \"Dependencies\": [ \"foo\", \"bar\"],\n"
" \"EnabledByDefault\": \"true\",\n"
" \"ExtraInformation\": \"Something else\",\n"
" \"License\": \"LGPL\",\n"
" \"Copyright\": \"(c) Alex Richardson 2015\",\n"
" \"Id\": \"time\",\n"
" \"Version\": \"1.0\",\n"
" \"Website\": \"https://plasma.kde.org/\",\n"
" \"MimeTypes\": [ \"image/png\" ],\n"
" \"ServiceTypes\": [\"Plasma/DataEngine\"]\n"
" }\n}\n",
&e)
.object();
QCOMPARE(e.error, QJsonParseError::NoError);
KPluginMetaData m(jo, QString());
QVERIFY(m.isValid());
QCOMPARE(m.pluginId(), QStringLiteral("time"));
QCOMPARE(m.name(), QStringLiteral("Date and Time"));
QCOMPARE(m.description(), QStringLiteral("Date and time by timezone"));
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 87)
QCOMPARE(m.extraInformation(), QStringLiteral("Something else"));
#endif
QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time"));
QCOMPARE(m.category(), QStringLiteral("Date and Time"));
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
QCOMPARE(m.dependencies(), QStringList() << QStringLiteral("foo") << QStringLiteral("bar"));
#endif
QCOMPARE(m.authors().size(), 1);
QCOMPARE(m.authors().constFirst().name(), QStringLiteral("Aaron Seigo"));
QCOMPARE(m.authors().constFirst().emailAddress(), QStringLiteral("aseigo@kde.org"));
QCOMPARE(m.translators().size(), 1);
QCOMPARE(m.translators().constFirst().name(), QStringLiteral("No One"));
QCOMPARE(m.translators().constFirst().emailAddress(), QStringLiteral("no.one@kde.org"));
QCOMPARE(m.otherContributors().size(), 1);
QCOMPARE(m.otherContributors().constFirst().name(), QStringLiteral("No One"));
QCOMPARE(m.otherContributors().constFirst().emailAddress(), QStringLiteral("no.one@kde.org"));
QVERIFY(m.isEnabledByDefault());
QCOMPARE(m.license(), QStringLiteral("LGPL"));
QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015"));
QCOMPARE(m.version(), QStringLiteral("1.0"));
QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/"));
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89)
QCOMPARE(m.serviceTypes(), QStringList() << QStringLiteral("Plasma/DataEngine"));
#endif
QCOMPARE(m.mimeTypes(), QStringList() << QStringLiteral("image/png"));
}
void testTranslations()
{
QJsonParseError e;
QJsonObject jo = QJsonDocument::fromJson(
"{ \"KPlugin\": {\n"
"\"Name\": \"Name\",\n"
"\"Name[de]\": \"Name (de)\",\n"
"\"Name[de_DE]\": \"Name (de_DE)\",\n"
"\"Description\": \"Description\",\n"
"\"Description[de]\": \"Beschreibung (de)\",\n"
"\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n"
"}\n}",
&e)
.object();
KPluginMetaData m(jo, QString());
QLocale::setDefault(QLocale::c());
QCOMPARE(m.name(), QStringLiteral("Name"));
QCOMPARE(m.description(), QStringLiteral("Description"));
QLocale::setDefault(QLocale(QStringLiteral("de_DE")));
QCOMPARE(m.name(), QStringLiteral("Name (de_DE)"));
QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)"));
QLocale::setDefault(QLocale(QStringLiteral("de_CH")));
QCOMPARE(m.name(), QStringLiteral("Name (de)"));
QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)"));
QLocale::setDefault(QLocale(QStringLiteral("fr_FR")));
QCOMPARE(m.name(), QStringLiteral("Name"));
QCOMPARE(m.description(), QStringLiteral("Description"));
}
void testReadStringList()
{
if (!doMessagesWork()) {
return;
}
QJsonParseError e;
QJsonObject jo = QJsonDocument::fromJson(
"{\n"
"\"String\": \"foo\",\n"
"\"OneArrayEntry\": [ \"foo\" ],\n"
"\"Bool\": true,\n" // make sure booleans are accepted
"\"QuotedBool\": \"true\",\n" // make sure booleans are accepted
"\"Number\": 12345,\n" // number should also work
"\"QuotedNumber\": \"12345\",\n" // number should also work
"\"EmptyArray\": [],\n"
"\"NumberArray\": [1, 2, 3],\n"
"\"BoolArray\": [true, false, true],\n"
"\"StringArray\": [\"foo\", \"bar\"],\n"
"\"Null\": null,\n" // should return empty list
"\"QuotedNull\": \"null\",\n" // this is okay, it is a string
"\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay?
"\"Object\": { \"foo\": \"bar\" }\n" // should return empty list
"}",
&e)
.object();
QCOMPARE(e.error, QJsonParseError::NoError);
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Expected JSON property ")));
KPluginMetaData data(jo, QStringLiteral("test"));
QCOMPARE(data.value(QStringLiteral("String"), QStringList()), QStringList(QStringLiteral("foo")));
QCOMPARE(data.value(QStringLiteral("OneArrayEntry"), QStringList()), QStringList(QStringLiteral("foo")));
QCOMPARE(data.value(QStringLiteral("Bool"), QStringList()), QStringList(QStringLiteral("true")));
QCOMPARE(data.value(QStringLiteral("QuotedBool"), QStringList()), QStringList(QStringLiteral("true")));
QCOMPARE(data.value(QStringLiteral("Number"), QStringList()), QStringList(QStringLiteral("12345")));
QCOMPARE(data.value(QStringLiteral("QuotedNumber"), QStringList()), QStringList(QStringLiteral("12345")));
QCOMPARE(data.value(QStringLiteral("EmptyArray"), QStringList()), QStringList());
QCOMPARE(data.value(QStringLiteral("NumberArray"), QStringList()), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3"));
QCOMPARE(data.value(QStringLiteral("BoolArray"), QStringList()),
QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true"));
QCOMPARE(data.value(QStringLiteral("StringArray"), QStringList()), QStringList() << QStringLiteral("foo") << QStringLiteral("bar"));
QCOMPARE(data.value(QStringLiteral("Null"), QStringList()), QStringList());
QCOMPARE(data.value(QStringLiteral("QuotedNull"), QStringList()), QStringList(QStringLiteral("null")));
QCOMPARE(data.value(QStringLiteral("ArrayWithNull"), QStringList()), QStringList() << QStringLiteral("foo") << QString() << QStringLiteral("bar"));
QCOMPARE(data.value(QStringLiteral("Object"), QStringList()), QStringList());
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91)
void testFromDesktopFile()
{
const QString dfile = QFINDTESTDATA("data/fakeplugin.desktop");
KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile);
QVERIFY(md.isValid());
QCOMPARE(md.pluginId(), QStringLiteral("fakeplugin"));
QCOMPARE(md.fileName(), QStringLiteral("fakeplugin"));
QCOMPARE(md.metaDataFileName(), dfile);
QCOMPARE(md.iconName(), QStringLiteral("preferences-system-time"));
QCOMPARE(md.license(), QStringLiteral("LGPL"));
QCOMPARE(md.website(), QStringLiteral("https://kde.org/"));
QCOMPARE(md.category(), QStringLiteral("Examples"));
QCOMPARE(md.version(), QStringLiteral("1.0"));
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
QCOMPARE(md.dependencies(), QStringList());
#endif
QCOMPARE(md.isHidden(), false);
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89)
QCOMPARE(md.serviceTypes(), QStringList(QStringLiteral("KService/NSA")));
#endif
QCOMPARE(md.mimeTypes(), QStringList() << QStringLiteral("image/png") << QStringLiteral("application/pdf"));
auto kp = md.rawData()[QStringLiteral("KPlugin")].toObject();
QStringList formFactors = kp.value(QStringLiteral("FormFactors")).toVariant().toStringList();
QCOMPARE(formFactors, QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop"));
QCOMPARE(md.formFactors(), QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop"));
const QString dfilehidden = QFINDTESTDATA("data/hiddenplugin.desktop");
KPluginMetaData mdhidden = KPluginMetaData::fromDesktopFile(dfilehidden);
QVERIFY(mdhidden.isValid());
QCOMPARE(mdhidden.isHidden(), true);
}
void twoStepsParseTest()
{
QStandardPaths::setTestModeEnabled(true);
const QString dfile = QFINDTESTDATA("data/twostepsparsetest.desktop");
const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop");
KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile, QStringList() << typesPath);
QVERIFY(md.isValid());
QStringList list = md.value(QStringLiteral("X-Test-List"), QStringList());
QCOMPARE(list, QStringList({QStringLiteral("first"), QStringLiteral("second")}));
}
void testServiceTypes_data()
{
const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop");
const QString invalidServiceTypePath = QFINDTESTDATA("data/servicetypes/invalid-servicetype.desktop");
const QString exampleServiceTypePath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop");
QVERIFY(!kdevServiceTypePath.isEmpty());
QVERIFY(!invalidServiceTypePath.isEmpty());
QVERIFY(!exampleServiceTypePath.isEmpty());
}
void testServiceType()
{
if (!doMessagesWork()) {
return;
}
const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop");
QVERIFY(!typesPath.isEmpty());
const QString inputPath = QFINDTESTDATA("data/servicetypes/example-input.desktop");
QVERIFY(!inputPath.isEmpty());
QTest::ignoreMessage(
QtWarningMsg,
// We also print out a list of paths we searched in. With the ".+" we ensure that they are printed out,
// but don't make fragile assumptions on the exact message
QRegularExpression(QStringLiteral("Unable to find service type for service \"bar/foo\" listed in \"") + inputPath + QLatin1String("\" .+")));
KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath);
QVERIFY(md.isValid());
QCOMPARE(md.name(), QStringLiteral("Example"));
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89)
QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("example/servicetype") << QStringLiteral("bar/foo"));
#endif
QCOMPARE(md.rawData().size(), 8);
QVERIFY(md.rawData().value(QStringLiteral("KPlugin")).isObject());
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Integer")), QJsonValue(42));
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Bool")), QJsonValue(true));
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Double")), QJsonValue(42.42));
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-String")), QJsonValue(QStringLiteral("foobar")));
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-List")),
QJsonValue(
QJsonArray::fromStringList(QStringList() << QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << QStringLiteral("def"))));
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Size")), QJsonValue(QStringLiteral("10,20"))); // QSize no longer supported (and also no longer used)
QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Unknown")), QJsonValue(QStringLiteral("true"))); // unknown property -> string
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 88)
const QString charOverloadVlaue = md.value(QStringLiteral("X-Test-Unknown"), "true");
QCOMPARE(charOverloadVlaue, QStringLiteral("true"));
#endif
}
void testBadGroupsInServiceType()
{
if (!doMessagesWork()) {
return;
}
const QString typesPath = QFINDTESTDATA("data/servicetypes/bad-groups-servicetype.desktop");
QVERIFY(!typesPath.isEmpty());
const QString inputPath = QFINDTESTDATA("data/servicetypes/bad-groups-input.desktop");
QVERIFY(!inputPath.isEmpty());
QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::MissingTerminator\"");
QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::\"");
QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[\"");
QTest::ignoreMessage(QtWarningMsg, "Read empty .desktop file group name! Invalid file?");
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral("Skipping invalid group \"\" in service type \".*/bad-groups-servicetype.desktop\"")));
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral("Skipping invalid group \"DoesNotStartWithPropertyDef::SomeOtherProperty\" in service type "
"\".+/data/servicetypes/bad-groups-servicetype.desktop\"")));
QTest::ignoreMessage(QtWarningMsg, "Could not find Type= key in group \"PropertyDef::MissingType\"");
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral("Property type \"integer\" is not a known QVariant type. Found while parsing property "
"definition for \"InvalidType\" in \".+/data/servicetypes/bad-groups-servicetype.desktop\"")));
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=11\"")));
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=13\"")));
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=14\"")));
KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath);
QVERIFY(md.isValid());
QCOMPARE(md.name(), QStringLiteral("Bad Groups"));
QCOMPARE(md.rawData().size(), 8);
QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkay")), QJsonValue(10)); // integer
// 11 is empty group
QCOMPARE(md.rawData().value(QStringLiteral("MissingTerminator")), QJsonValue(12)); // accept missing group terminator (for now) -> integer
// 13 is empty group name
// 14 is empty group name
QCOMPARE(md.rawData().value(QStringLiteral("SomeOtherProperty")),
QJsonValue(QStringLiteral("15"))); // does not start with PropertyDef:: -> fall back to string
QCOMPARE(md.rawData().value(QStringLiteral("TrailingSpacesAreOkay")), QJsonValue(16)); // accept trailing spaces in group name -> integer
QCOMPARE(md.rawData().value(QStringLiteral("MissingType")), QJsonValue(QStringLiteral("17"))); // Type= missing -> fall back to string
QCOMPARE(md.rawData().value(QStringLiteral("InvalidType")), QJsonValue(QStringLiteral("18"))); // Type= is invalid -> fall back to string
QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid definition after invalid ones should still work -> integer
}
#endif
void testJSONMetadata()
{
const QString inputPath = QFINDTESTDATA("data/testmetadata.json");
KPluginMetaData md = KPluginMetaData::fromJsonFile(inputPath);
QVERIFY(md.isValid());
QCOMPARE(md.name(), QStringLiteral("Test"));
QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml"));
QJsonArray expected;
expected.append(QStringLiteral("Export"));
QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected);
QCOMPARE(md.value(QStringLiteral("SomeInt"), 24), 42);
QCOMPARE(md.value(QStringLiteral("SomeIntAsString"), 24), 42);
QCOMPARE(md.value(QStringLiteral("SomeStringNotAInt"), 24), 24);
QCOMPARE(md.value(QStringLiteral("DoesNotExist"), 24), 24);
QVERIFY(md.value(QStringLiteral("SomeBool"), false));
QVERIFY(!md.value(QStringLiteral("SomeBoolThatIsFalse"), true));
QVERIFY(md.value(QStringLiteral("SomeBoolAsString"), false));
QVERIFY(md.value(QStringLiteral("DoesNotExist"), true));
}
void testPathIsAbsolute_data()
{
QTest::addColumn<QString>("inputAbsolute");
QTest::addColumn<QString>("pluginPath");
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91)
// The .desktop file has X-KDE-Library, so .fileName() returns different file
QTest::newRow("desktop") << QFINDTESTDATA("data/fakeplugin.desktop") << QStringLiteral("fakeplugin");
#endif
// But for the .json based plugin both are the same.
QTest::newRow("json") << QFINDTESTDATA("data/testmetadata.json") << QFINDTESTDATA("data/testmetadata.json");
// And also for the library with embedded JSON metadata.
QPluginLoader shlibLoader(QCoreApplication::applicationDirPath() + QStringLiteral("/jsonplugin"));
QVERIFY2(!shlibLoader.fileName().isEmpty(), "Could not find jsonplugin");
QString shlibPath = QFileInfo(shlibLoader.fileName()).absoluteFilePath();
QTest::newRow("library") << shlibPath << shlibPath;
}
void testPathIsAbsolute()
{
// Test that the fileName() accessor always returns an absolute path if it was used.
QFETCH(QString, inputAbsolute);
QVERIFY2(QDir::isAbsolutePath(inputAbsolute), qPrintable(inputAbsolute));
QFETCH(QString, pluginPath);
const auto createMetaData = [](const QString &path) {
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91)
return KPluginMetaData(path);
#else
if (path.endsWith(QLatin1String(".json"))) {
return KPluginMetaData::fromJsonFile(path);
} else {
return KPluginMetaData(path);
}
#endif
};
KPluginMetaData mdAbsolute = createMetaData(inputAbsolute);
QVERIFY(mdAbsolute.isValid());
QCOMPARE(mdAbsolute.metaDataFileName(), inputAbsolute);
QCOMPARE(mdAbsolute.fileName(), pluginPath);
// All files that have been opened should be stored as absolute paths.
QString inputRelative;
if (QLibrary::isLibrary(inputAbsolute)) {
// We have a plugin without namespace, with the code path below we would end up with
// a path relative to the PWD, but we want to check a path relative to the plugin dir.
// Because of that we simply use the baseName of the file.
inputRelative = QFileInfo(inputAbsolute).baseName();
} else {
inputRelative = QDir::current().relativeFilePath(inputAbsolute);
}
QVERIFY2(QDir::isRelativePath(inputRelative), qPrintable(inputRelative));
KPluginMetaData mdRelative = createMetaData(inputRelative);
QVERIFY(mdRelative.isValid());
QCOMPARE(mdRelative.metaDataFileName(), inputAbsolute);
QCOMPARE(mdRelative.fileName(), pluginPath);
// Check that creating it with the parsed JSON object and a path keeps the path unchanged
const QJsonObject json = mdAbsolute.rawData();
QString pluginRelative = QDir::current().relativeFilePath(pluginPath);
QVERIFY2(QDir::isRelativePath(pluginRelative), qPrintable(pluginRelative));
// TODO: KF6: no need to test both constructors once they are merged into one overload.
KPluginMetaData mdFromJson1(json, pluginRelative, inputRelative);
QCOMPARE(mdFromJson1.metaDataFileName(), inputRelative);
// We should not be normalizing files that have not been openened, so both arguments should be unchanged.
QCOMPARE(mdFromJson1.fileName(), pluginRelative);
KPluginMetaData mdFromJson2(json, inputRelative);
QCOMPARE(mdFromJson2.metaDataFileName(), inputRelative);
QCOMPARE(mdFromJson2.fileName(), inputRelative);
}
void testFindPlugins()
{
QTemporaryDir temp;
QVERIFY(temp.isValid());
QDir dir(temp.path());
QVERIFY(dir.mkdir(QStringLiteral("kpluginmetadatatest")));
QVERIFY(dir.cd(QStringLiteral("kpluginmetadatatest")));
for (const QString &name : {QStringLiteral("jsonplugin"), QStringLiteral("unversionedplugin"), QStringLiteral("jsonplugin2")}) {
const QString pluginPath = QPluginLoader(name).fileName();
QVERIFY2(!pluginPath.isEmpty(), qPrintable(pluginPath));
QVERIFY2(QFile::copy(pluginPath, dir.absoluteFilePath(QFileInfo(pluginPath).fileName())),
qPrintable(dir.absoluteFilePath(QFileInfo(pluginPath).fileName())));
}
LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
// we only want plugins from our temporary dir
QCoreApplication::setLibraryPaths(QStringList() << temp.path());
auto sortPlugins = [](const KPluginMetaData &a, const KPluginMetaData &b) {
return a.pluginId() < b.pluginId();
};
// it should find jsonplugin and jsonplugin2 since unversionedplugin does not have any meta data
auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"));
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].pluginId(), QStringLiteral("foobar")); // ID is not the filename, it is set in the JSON metadata
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
QCOMPARE(plugins[1].pluginId(), QStringLiteral("jsonplugin"));
QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin"));
// filter accepts none
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) {
return false;
});
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 0);
// filter accepts all
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) {
return true;
});
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin"));
// mimetype filter. Only one match, jsonplugin2 is specific to text/html.
auto supportTextPlain = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QLatin1String("text/plain"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextPlain);
QCOMPARE(plugins.size(), 1);
QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin"));
// mimetype filter. Two matches, both support text/html, via inheritance.
auto supportTextHtml = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QLatin1String("text/html"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextHtml);
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin"));
// mimetype filter with invalid mimetype
auto supportDoesNotExist = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QLatin1String("does/not/exist"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportDoesNotExist);
QCOMPARE(plugins.size(), 0);
// invalid std::function as filter
plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"));
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin"));
// by plugin id
KPluginMetaData plugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("foobar"));
QVERIFY(plugin.isValid());
QCOMPARE(plugin.description(), QStringLiteral("This is another plugin"));
// by plugin invalid id
plugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("invalidid"));
QVERIFY(!plugin.isValid());
// absolute path, no filter
plugins = KPluginMetaData::findPlugins(dir.absolutePath());
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin"));
// This plugin has no explicit pluginId and will fall back to basename of file
const KPluginMetaData validPlugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("jsonplugin"));
QVERIFY(validPlugin.isValid());
QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin"));
// The basename matches, but the pluginId does not match
const KPluginMetaData nonMatchingPluginId = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("jsonplugin2"));
QVERIFY(!nonMatchingPluginId.isValid());
const KPluginMetaData nonExistingPlugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("invalidid"));
QVERIFY(!nonExistingPlugin.isValid());
}
void testStaticPlugins()
{
QCOMPARE(QPluginLoader::staticPlugins().count(), 0);
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace"));
QCOMPARE(plugins.count(), 1);
QCOMPARE(plugins.first().description(), QStringLiteral("This is a plugin"));
QCOMPARE(plugins.first().fileName(), QStringLiteral("staticnamespace/static_jsonplugin_cmake_macro"));
}
void testPluginsWithoutMetaData()
{
KPluginMetaData emptyMetaData(QStringLiteral("namespace/pluginwithoutmetadata"), KPluginMetaData::AllowEmptyMetaData);
QVERIFY(emptyMetaData.isValid());
QCOMPARE(emptyMetaData.pluginId(), QStringLiteral("pluginwithoutmetadata"));
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData);
QCOMPARE(plugins.count(), 2);
for (auto plugin : plugins) {
if (plugin.pluginId() == QLatin1String("pluginwithoutmetadata")) {
QVERIFY(plugin.isValid());
QVERIFY(plugin.rawData().isEmpty());
} else if (plugin.pluginId() == QLatin1String("jsonplugin_cmake_macro")) {
QVERIFY(plugin.isValid());
QVERIFY(!plugin.rawData().isEmpty());
} else {
Q_UNREACHABLE();
}
}
}
void testStaticPluginsWithoutMetadata()
{
QVERIFY(KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3")).isEmpty());
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3"), {}, KPluginMetaData::AllowEmptyMetaData);
QCOMPARE(plugins.count(), 1);
QVERIFY(plugins.first().isValid());
QCOMPARE(plugins.first().pluginId(), QStringLiteral("static_plugin_without_metadata"));
}
void testReverseDomainNotationPluginId()
{
KPluginMetaData data(QStringLiteral("org.kde.test"));
QVERIFY(data.isValid());
QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test"));
}
void testFindingPluginInAppDirFirst()
{
const QString originalPluginPath = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName();
const QString pluginFileName = QFileInfo(originalPluginPath).fileName();
const QString pluginNamespace = QStringLiteral("somepluginnamespace");
const QString pluginAppDir = QCoreApplication::applicationDirPath() + QLatin1Char('/') + pluginNamespace;
QDir(pluginAppDir).mkpath(QStringLiteral("."));
const QString pluginAppPath = pluginAppDir + QLatin1Char('/') + pluginFileName;
QFile::remove(pluginAppPath);
QVERIFY(QFile::copy(originalPluginPath, pluginAppPath));
QTemporaryDir temp;
QVERIFY(temp.isValid());
QDir dir(temp.path());
QVERIFY(dir.mkdir(pluginNamespace));
QVERIFY(dir.cd(pluginNamespace));
const QString pluginInNamespacePath = dir.absoluteFilePath(pluginFileName);
QVERIFY(QFile::copy(originalPluginPath, pluginInNamespacePath));
LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
QCoreApplication::setLibraryPaths(QStringList() << temp.path());
// Our plugin in the applicationDirPath should come first
const QString relativePathWithNamespace = QStringLiteral("somepluginnamespace/jsonplugin_cmake_macro");
KPluginMetaData data(relativePathWithNamespace);
QVERIFY(data.isValid());
QCOMPARE(data.fileName(), pluginAppPath);
// The other one must be valid
QVERIFY(KPluginMetaData(pluginInNamespacePath).isValid());
// And after removing the plugin in the applicationDirPath, it should be found
QVERIFY(QFile::remove(pluginAppPath));
QCOMPARE(KPluginMetaData(relativePathWithNamespace).fileName(), pluginInNamespacePath);
}
};
QTEST_MAIN(KPluginMetaDataTest)
#include "kpluginmetadatatest.moc"

View File

@@ -0,0 +1,84 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kprocesslisttest.h"
#include "kprocesslist.h"
#include "kuser.h"
#include <QCoreApplication>
#include <QTest>
#include <algorithm>
namespace
{
QString getTestExeName()
{
static QString testExeName = QCoreApplication::instance()->applicationFilePath().section(QLatin1Char('/'), -1);
return testExeName;
}
}
QTEST_MAIN(KProcessListTest)
void KProcessListTest::testKProcessInfoConstructionAssignment()
{
KProcessList::KProcessInfo processInfoDefaultConstructed;
QVERIFY(processInfoDefaultConstructed.isValid() == false);
const qint64 pid(42);
const QString name(QStringLiteral("/bin/some_exe"));
const QString user(QStringLiteral("some_user"));
KProcessList::KProcessInfo processInfo(pid, name, user);
QVERIFY(processInfo.isValid() == true);
QCOMPARE(processInfo.pid(), pid);
QCOMPARE(processInfo.name(), name);
QCOMPARE(processInfo.user(), user);
KProcessList::KProcessInfo processInfoCopy(processInfo);
QVERIFY(processInfoCopy.isValid() == true);
QCOMPARE(processInfoCopy.pid(), pid);
QCOMPARE(processInfoCopy.name(), name);
QCOMPARE(processInfoCopy.user(), user);
KProcessList::KProcessInfo processInfoAssignment;
processInfoAssignment = processInfo;
QVERIFY(processInfoAssignment.isValid() == true);
QCOMPARE(processInfoAssignment.pid(), pid);
QCOMPARE(processInfoAssignment.name(), name);
QCOMPARE(processInfoAssignment.user(), user);
}
void KProcessListTest::testProcessInfoList()
{
KProcessList::KProcessInfoList processInfoList = KProcessList::processInfoList();
QVERIFY(processInfoList.empty() == false);
auto testProcessIterator = std::find_if(processInfoList.begin(), processInfoList.end(), [](const KProcessList::KProcessInfo &info) {
return QDir::fromNativeSeparators(info.command()).endsWith(QLatin1String("/") + getTestExeName());
});
QVERIFY(testProcessIterator != processInfoList.end());
const auto &processInfo = *testProcessIterator;
QVERIFY(processInfo.isValid() == true);
QVERIFY(QDir::fromNativeSeparators(processInfo.command()).endsWith(QLatin1String("/") + getTestExeName()));
QCOMPARE(processInfo.name(), getTestExeName());
QCOMPARE(processInfo.pid(), QCoreApplication::applicationPid());
QCOMPARE(processInfo.user(), KUser().loginName());
}
void KProcessListTest::testProcessInfo()
{
const qint64 testExePid = QCoreApplication::applicationPid();
KProcessList::KProcessInfo processInfo = KProcessList::processInfo(testExePid);
QVERIFY(processInfo.isValid() == true);
QVERIFY(QDir::fromNativeSeparators(processInfo.command()).endsWith(QLatin1String("/") + getTestExeName()));
QCOMPARE(processInfo.pid(), testExePid);
QCOMPARE(processInfo.user(), KUser().loginName());
}
void KProcessListTest::testProcessInfoNotFound()
{
KProcessList::KProcessInfo processInfo = KProcessList::processInfo(-1);
QVERIFY(processInfo.isValid() == false);
}
#include "moc_kprocesslisttest.cpp"

View File

@@ -0,0 +1,24 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Hallas <david@davidhallas.dk>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPROCESSLISTTEST_H
#define KPROCESSLISTTEST_H
#include <QObject>
class KProcessListTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testKProcessInfoConstructionAssignment();
void testProcessInfoList();
void testProcessInfo();
void testProcessInfoNotFound();
};
#endif

View File

@@ -0,0 +1,122 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kprocesstest_helper.h"
#include <QFile>
#include <QObject>
#include <QStandardPaths>
#include <QTest>
#include <kprocess.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
class KProcessTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void test_channels();
void test_setShellCommand();
void test_inheritance();
};
// IOCCC nomination pending
static QString callHelper(KProcess::OutputChannelMode how)
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
QString helper = QCoreApplication::applicationDirPath() + QStringLiteral("/kprocesstest_helper");
#ifdef Q_OS_WIN
helper += QStringLiteral(".exe");
#endif
Q_ASSERT(QFile::exists(helper));
p.start(helper, QStringList() << QString::number(how) << QStringLiteral("--nocrashhandler"));
p.waitForFinished();
return QString::fromLatin1(p.readAllStandardOutput());
}
#define EO EOUT "\n"
#define EE EERR "\n"
#define TESTCHAN(me, ms, pout, rout, rerr) \
e = QStringLiteral("mode: " ms "\n" POUT pout ROUT rout RERR rerr); \
a = QStringLiteral("mode: " ms "\n") + callHelper(KProcess::me); \
QCOMPARE(a, e)
void KProcessTest::test_channels()
{
#ifdef Q_OS_UNIX
QString e;
QString a;
TESTCHAN(SeparateChannels, "separate", "", EO, EE);
TESTCHAN(ForwardedChannels, "forwarded", EO EE, "", "");
TESTCHAN(OnlyStderrChannel, "forwarded stdout", EO, "", EE);
TESTCHAN(OnlyStdoutChannel, "forwarded stderr", EE, EO, "");
TESTCHAN(MergedChannels, "merged", "", EO EE, "");
#else
Q_UNUSED(callHelper);
QSKIP("This test needs a UNIX system");
#endif
}
void KProcessTest::test_setShellCommand()
{
// Condition copied from kprocess.cpp
#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__)
QSKIP("This test needs a free UNIX system");
#else
KProcess p;
p.setShellCommand(QStringLiteral("cat"));
QCOMPARE(p.program().count(), 1);
QCOMPARE(p.program().at(0), QStandardPaths::findExecutable(QStringLiteral("cat")));
QVERIFY(p.program().at(0).endsWith(QLatin1String("/cat")));
p.setShellCommand(QStringLiteral("true || false"));
QCOMPARE(p.program(), QStringList() << QStringLiteral("/bin/sh") << QStringLiteral("-c") << QString::fromLatin1("true || false"));
#endif
}
void KProcessTest::test_inheritance()
{
KProcess kproc;
QProcess *qproc = &kproc;
const QString program = QStringLiteral("foobar");
const QStringList arguments{QStringLiteral("meow")};
kproc.setProgram(program, arguments);
QCOMPARE(qproc->program(), program);
QCOMPARE(qproc->arguments(), arguments);
kproc.clearProgram();
QCOMPARE(qproc->program(), QString());
QCOMPARE(qproc->arguments(), QStringList());
kproc << program << arguments;
QCOMPARE(qproc->program(), program);
QCOMPARE(qproc->arguments(), arguments);
kproc.clearProgram();
QCOMPARE(qproc->program(), QString());
QCOMPARE(qproc->arguments(), QStringList());
#ifdef Q_OS_UNIX
kproc.setShellCommand(QStringLiteral("/bin/true meow"));
QCOMPARE(qproc->program(), QStringLiteral("/bin/true"));
QCOMPARE(qproc->arguments(), arguments);
kproc.clearProgram();
QCOMPARE(qproc->program(), QString());
QCOMPARE(qproc->arguments(), QStringList());
#endif
}
QTEST_MAIN(KProcessTest)
#include "kprocesstest.moc"

View File

@@ -0,0 +1,34 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kprocesstest_helper.h"
#include <kprocess.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc < 2) {
printf("Missing parameter");
return -1;
}
KProcess p;
p.setShellCommand(QString::fromLatin1("echo " EOUT "; echo " EERR " >&2"));
p.setOutputChannelMode(static_cast<KProcess::OutputChannelMode>(atoi(argv[1])));
fputs(POUT, stdout);
fflush(stdout);
p.execute();
fputs(ROUT, stdout);
fputs(p.readAllStandardOutput().constData(), stdout);
fputs(RERR, stdout);
if (p.outputChannelMode() != KProcess::MergedChannels) {
fputs(p.readAllStandardError().constData(), stdout);
}
return 0;
}

View File

@@ -0,0 +1,13 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#define EOUT "foo - stdout"
#define EERR "bar - stderr"
#define POUT "program output:\n"
#define ROUT "received stdout:\n"
#define RERR "received stderr:\n"

View File

@@ -0,0 +1,282 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2016 Michael Pyne <mpyne@kde.org>
SPDX-FileCopyrightText: 2016 Arne Spiegelhauer <gm2.asp@gmail.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <krandom.h>
#include <krandomsequence.h>
#include <stdlib.h>
#include <QTest>
#include <QThread>
#include <QObject>
#include <QProcess>
#include <QRegularExpression>
#include <QString>
#include <QTextStream>
#include <QVarLengthArray>
#include <algorithm>
#include <iostream>
typedef QVarLengthArray<int> intSequenceType;
static const char *binpath;
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
static bool seqsAreEqual(const intSequenceType &l, const intSequenceType &r)
{
if (l.size() != r.size()) {
return false;
}
const intSequenceType::const_iterator last(l.end());
intSequenceType::const_iterator l_first(l.begin());
intSequenceType::const_iterator r_first(r.begin());
while (l_first != last && *l_first == *r_first) {
l_first++;
r_first++;
}
return l_first == last;
}
#endif
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
// Fills seq with random bytes produced by a new process. Seq should already
// be sized to the needed amount of random numbers.
static bool getChildRandSeq(intSequenceType &seq)
{
QProcess subtestProcess;
// Launch a separate process to generate random numbers to test first-time
// seeding.
subtestProcess.start(QLatin1String(binpath), QStringList() << QString::number(seq.count()));
subtestProcess.waitForFinished();
QTextStream childStream(subtestProcess.readAllStandardOutput());
std::generate(seq.begin(), seq.end(), [&]() {
int temp;
childStream >> temp;
return temp;
});
char c;
childStream >> c;
return c == '@' && childStream.status() == QTextStream::Ok;
}
#endif
class KRandomTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
void test_random();
#endif
void test_randomString();
void test_randomStringThreaded();
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
void test_KRS();
#endif
void test_shuffle();
};
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
void KRandomTest::test_random()
{
int testValue = KRandom::random();
QVERIFY(testValue >= 0);
QVERIFY(testValue < RAND_MAX);
// Verify seeding results in different numbers across different procs
// See bug 362161
intSequenceType out1(10);
intSequenceType out2(10);
QVERIFY(getChildRandSeq(out1));
QVERIFY(getChildRandSeq(out2));
QVERIFY(!seqsAreEqual(out1, out2));
}
#endif
void KRandomTest::test_randomString()
{
const int desiredLength = 12;
const QString testString = KRandom::randomString(desiredLength);
const QRegularExpression outputFormat(QRegularExpression::anchoredPattern(QStringLiteral("[A-Za-z0-9]+")));
const QRegularExpressionMatch match = outputFormat.match(testString);
QCOMPARE(testString.length(), desiredLength);
QVERIFY(match.hasMatch());
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 75)
void KRandomTest::test_KRS()
{
using std::all_of;
using std::generate;
const int maxInt = 50000;
KRandomSequence krs1;
KRandomSequence krs2;
intSequenceType out1(10);
intSequenceType out2(10);
generate(out1.begin(), out1.end(), [&]() {
return krs1.getInt(maxInt);
});
generate(out2.begin(), out2.end(), [&]() {
return krs2.getInt(maxInt);
});
QVERIFY(!seqsAreEqual(out1, out2));
QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
return x < maxInt;
}));
QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
return x < maxInt;
}));
// Compare same-seed
krs1.setSeed(5000);
krs2.setSeed(5000);
generate(out1.begin(), out1.end(), [&]() {
return krs1.getInt(maxInt);
});
generate(out2.begin(), out2.end(), [&]() {
return krs2.getInt(maxInt);
});
QVERIFY(seqsAreEqual(out1, out2));
QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
return x < maxInt;
}));
QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
return x < maxInt;
}));
// Compare same-seed and assignment ctor
krs1 = KRandomSequence(8000);
krs2 = KRandomSequence(8000);
generate(out1.begin(), out1.end(), [&]() {
return krs1.getInt(maxInt);
});
generate(out2.begin(), out2.end(), [&]() {
return krs2.getInt(maxInt);
});
QVERIFY(seqsAreEqual(out1, out2));
QVERIFY(all_of(out1.begin(), out1.end(), [&](int x) {
return x < maxInt;
}));
QVERIFY(all_of(out2.begin(), out2.end(), [&](int x) {
return x < maxInt;
}));
}
#endif
void KRandomTest::test_shuffle()
{
{
QRandomGenerator rg(1);
QList<int> list = {1, 2, 3, 4, 5};
const QList<int> shuffled = {5, 2, 4, 3, 1};
KRandom::shuffle(list, &rg);
QCOMPARE(list, shuffled);
}
{
QRandomGenerator rg(1);
QVector<int> vector = {1, 2, 3, 4, 5};
const QVector<int> shuffled = {5, 2, 4, 3, 1};
KRandom::shuffle(vector, &rg);
QCOMPARE(vector, shuffled);
}
{
QRandomGenerator rg(1);
std::vector<int> std_vector = {1, 2, 3, 4, 5};
const std::vector<int> shuffled = {5, 2, 4, 3, 1};
KRandom::shuffle(std_vector, &rg);
QCOMPARE(std_vector, shuffled);
}
}
class KRandomTestThread : public QThread
{
protected:
void run() override
{
result = KRandom::randomString(32);
};
public:
QString result;
};
void KRandomTest::test_randomStringThreaded()
{
static const int size = 5;
KRandomTestThread *threads[size];
for (int i = 0; i < size; ++i) {
threads[i] = new KRandomTestThread();
threads[i]->start();
}
QSet<QString> results;
for (int i = 0; i < size; ++i) {
threads[i]->wait(2000);
results.insert(threads[i]->result);
}
// each thread should have returned a unique result
QCOMPARE(results.size(), size);
for (int i = 0; i < size; ++i) {
delete threads[i];
}
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
// Used by getChildRandSeq... outputs random numbers to stdout and then
// exits the process.
static void childGenRandom(int count)
{
// No logic to 300, just wanted to avoid it accidentally being 2.4B...
if (count <= 0 || count > 300) {
exit(-1);
}
while (--count > 0) {
std::cout << KRandom::random() << ' ';
}
std::cout << KRandom::random() << '@';
exit(0);
}
#endif
// Manually implemented to dispatch to child process if needed to support
// subtests
int main([[maybe_unused]] int argc, char *argv[])
{
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 72)
if (argc > 1) {
childGenRandom(std::atoi(argv[1]));
Q_UNREACHABLE();
}
#endif
binpath = argv[0];
KRandomTest randomTest;
return QTest::qExec(&randomTest);
}
#include "krandomtest.moc"

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "kruntimeplatform.h"
#include <QObject>
#include <QTest>
class KRuntimePlatformTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRuntimePlatform()
{
qputenv("PLASMA_PLATFORM", "mobile:bigscreen");
QStringList expected{"mobile", "bigscreen"};
QCOMPARE(KRuntimePlatform::runtimePlatform(), expected);
}
};
QTEST_GUILESS_MAIN(KRuntimePlatformTest)
#include "kruntimeplatformtest.moc"

View File

@@ -0,0 +1,64 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <kshareddatacache.h>
#include <QStandardPaths>
#include <QTest>
#include <QObject>
#include <QStandardPaths>
#include <QString>
#include <string.h> // strcpy
class KSharedDataCacheTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void simpleInsert();
};
void KSharedDataCacheTest::initTestCase()
{
}
void KSharedDataCacheTest::simpleInsert()
{
const QLatin1String cacheName("myTestCache");
const QLatin1String key("mypic");
// clear the cache
QString cacheFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache");
QFile file(cacheFile);
if (file.exists()) {
QVERIFY(file.remove());
}
// insert something into it
KSharedDataCache cache(cacheName, 5 * 1024 * 1024);
#ifndef Q_OS_WIN // the windows implementation is currently only memory based and not really shared
QVERIFY(file.exists()); // make sure we got the cache filename right
#endif
QByteArray data;
data.resize(9228);
strcpy(data.data(), "Hello world");
QVERIFY(cache.insert(key, data));
// read it out again
QByteArray result;
QVERIFY(cache.find(key, &result));
QCOMPARE(result, data);
// another insert
strcpy(data.data(), "Hello KDE");
QVERIFY(cache.insert(key, data));
// and another read
QVERIFY(cache.find(key, &result));
QCOMPARE(result, data);
}
QTEST_MAIN(KSharedDataCacheTest)
#include "kshareddatacachetest.moc"

View File

@@ -0,0 +1,219 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003, 2007-2008 Oswald Buddenhagen <ossi@kde.org>
SPDX-FileCopyrightText: 2005 Thomas Braxton <brax108@cox.net>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <kshell.h>
#include <kuser.h>
#include <QTest>
#include <QDir>
#include <QObject>
#include <QString>
#include <QStringList>
class KShellTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void tildeExpand();
void tildeCollapse();
void quoteArg();
void joinArgs();
void splitJoin();
void quoteSplit();
void quoteSplit_data();
void abortOnMeta();
};
// The expansion of ~me isn't exactly QDir::homePath(), in case $HOME has a trailing slash, it's kept.
static QString myHomePath()
{
#ifdef Q_OS_WIN
return QDir::homePath();
#else
return QString::fromLocal8Bit(qgetenv("HOME"));
#endif
}
void KShellTest::tildeExpand()
{
QString me(KUser().loginName());
QCOMPARE(KShell::tildeExpand(QStringLiteral("~")), QDir::homePath());
QCOMPARE(KShell::tildeExpand(QStringLiteral("~/dir")), QString(QDir::homePath() + QStringLiteral("/dir")));
QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me), myHomePath());
QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me + QStringLiteral("/dir")), QString(myHomePath() + QStringLiteral("/dir")));
#ifdef Q_OS_WIN
QCOMPARE(KShell::tildeExpand(QStringLiteral("^~") + me), QString(QLatin1Char('~') + me));
#else
QCOMPARE(KShell::tildeExpand(QStringLiteral("\\~") + me), QString(QStringLiteral("~") + me));
#endif
}
void KShellTest::tildeCollapse()
{
QCOMPARE(KShell::tildeCollapse(QDir::homePath()), QStringLiteral("~"));
QCOMPARE(KShell::tildeCollapse(QDir::homePath() + QStringLiteral("/Documents")), QStringLiteral("~/Documents"));
QCOMPARE(KShell::tildeCollapse(QStringLiteral("/test/") + QDir::homePath()), QStringLiteral("/test/") + QDir::homePath());
}
void KShellTest::quoteArg()
{
#ifdef Q_OS_WIN
QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("\"a space\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("fds\\\"")), QStringLiteral("fds\\\\\\^\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\foo")), QStringLiteral("\\\\foo"));
QCOMPARE(KShell::quoteArg(QStringLiteral("\"asdf\"")), QStringLiteral("\\^\"asdf\\^\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("with\\")), QStringLiteral("\"with\\\\\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\")), QStringLiteral("\"\\\\\\\\\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("\"a space\\\"")), QStringLiteral("\\^\"\"a space\"\\\\\\^\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("as df\\")), QStringLiteral("\"as df\\\\\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("foo bar\"\\\"bla")), QStringLiteral("\"foo bar\"\\^\"\\\\\\^\"\"bla\""));
QCOMPARE(KShell::quoteArg(QStringLiteral("a % space")), QStringLiteral("\"a %PERCENT_SIGN% space\""));
#else
QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("'a space'"));
#endif
}
void KShellTest::joinArgs()
{
QStringList list;
list << QStringLiteral("this") << QStringLiteral("is") << QStringLiteral("a") << QStringLiteral("test");
QCOMPARE(KShell::joinArgs(list), QStringLiteral("this is a test"));
}
static QString sj(const QString &str, KShell::Options flags, KShell::Errors *ret)
{
return KShell::joinArgs(KShell::splitArgs(str, flags, ret));
}
void KShellTest::splitJoin()
{
KShell::Errors err = KShell::NoError;
#ifdef Q_OS_WIN
QCOMPARE(sj(QStringLiteral("\"(sulli)\" text"), KShell::NoOptions, &err), QStringLiteral("\"(sulli)\" text"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral(" ha\\ lo "), KShell::NoOptions, &err), QStringLiteral("\"ha\\\\\" lo"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err), QString());
QVERIFY(err == KShell::BadQuoting);
QCOMPARE(sj(QStringLiteral("no \" error\""), KShell::NoOptions, &err), QStringLiteral("no \" error\""));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::NoOptions, &err), QString());
QVERIFY(err == KShell::BadQuoting);
QCOMPARE(sj(QStringLiteral("BLA;asdf sdfess d"), KShell::NoOptions, &err), QStringLiteral("\"BLA;asdf\" sdfess d"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("B\"L\"A&sdf FOO|bar sdf wer "), KShell::NoOptions, &err), QStringLiteral("\"BLA&sdf\" \"FOO|bar\" sdf wer"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("\"\"\"just \"\" fine\"\"\""), KShell::NoOptions, &err), QStringLiteral("\\^\"\"just \"\\^\"\" fine\"\\^\""));
QVERIFY(err == KShell::NoError);
#else
QCOMPARE(sj(QString::fromUtf8("\"~qU4rK\" 'text' 'jo'\"jo\" $'crap' $'\\\\\\'\\e\\x21' ha\\ lo \\a"), KShell::NoOptions, &err),
QString::fromUtf8("'~qU4rK' text jojo crap '\\'\\''\x1b!' 'ha lo' a"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("\"~qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("~\"qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("~/\"dir\" 'text'"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral("/dir text")));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("~ 'text' ~"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral(" text ") + QDir::homePath()));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("\\~ blah"), KShell::TildeExpand, &err), QStringLiteral("'~' blah"));
QVERIFY(err == KShell::NoError);
QCOMPARE(sj(QStringLiteral("~qU4rK ~") + KUser().loginName(), KShell::TildeExpand, &err), QString(QStringLiteral("'~qU4rK' ") + myHomePath()));
QVERIFY(err == KShell::NoError);
const QString unicodeSpaceFileName = QStringLiteral("test テスト.txt"); // #345140
QCOMPARE(sj(unicodeSpaceFileName, KShell::AbortOnMeta | KShell::TildeExpand, &err), unicodeSpaceFileName);
QVERIFY(err == KShell::NoError);
#endif
}
void KShellTest::quoteSplit_data()
{
QTest::addColumn<QString>("string");
QTest::newRow("no space") << QStringLiteral("hiho");
QTest::newRow("regular space") << QStringLiteral("hi there");
QTest::newRow("special space") << QString::fromUtf8("如何定期清潔典型的電風扇 講義.pdf");
}
void KShellTest::quoteSplit()
{
QFETCH(QString, string);
// Splitting a quote arg should always just return one argument
const QStringList args = KShell::splitArgs(KShell::quoteArg(string));
QCOMPARE(args.count(), 1);
}
void KShellTest::abortOnMeta()
{
KShell::Errors err1 = KShell::NoError;
KShell::Errors err2 = KShell::NoError;
QCOMPARE(sj(QStringLiteral("text"), KShell::AbortOnMeta, &err1), QStringLiteral("text"));
QVERIFY(err1 == KShell::NoError);
#ifdef Q_OS_WIN
QVERIFY(KShell::splitArgs(QStringLiteral("BLA & asdf sdfess d"), KShell::AbortOnMeta, &err1).isEmpty());
QVERIFY(err1 == KShell::FoundMeta);
QVERIFY(KShell::splitArgs(QStringLiteral("foo %PATH% bar"), KShell::AbortOnMeta, &err1).isEmpty());
QVERIFY(err1 == KShell::FoundMeta);
QCOMPARE(sj(QStringLiteral("foo %PERCENT_SIGN% bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo %PERCENT_SIGN% bar"));
QVERIFY(err1 == KShell::NoError);
QCOMPARE(sj(QStringLiteral("@foo ^& bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo \"&\" bar"));
QVERIFY(err1 == KShell::NoError);
QCOMPARE(sj(QStringLiteral("\"BLA|asdf\" sdfess d"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|asdf\" sdfess d"));
QVERIFY(err1 == KShell::NoError);
QCOMPARE(sj(QStringLiteral("B\"L\"A\"|\"sdf \"FOO | bar\" sdf wer"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|sdf\" \"FOO | bar\" sdf wer"));
QVERIFY(err1 == KShell::NoError);
QCOMPARE(sj(QStringLiteral("b-q me \\\\^|\\\\\\^\""), KShell::AbortOnMeta, &err1), QStringLiteral("b-q me \"\\\\|\"\\\\\\^\""));
QVERIFY(err1 == KShell::NoError);
#else
QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err1), QString());
QVERIFY(err1 != KShell::NoError);
QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::AbortOnMeta, &err1), QString());
QVERIFY(err1 != KShell::NoError);
QVERIFY(sj(QStringLiteral("say `echo no error`"), KShell::NoOptions, &err1) != sj(QStringLiteral("say `echo no error`"), KShell::AbortOnMeta, &err2));
QVERIFY(err1 != err2);
QVERIFY(sj(QStringLiteral("BLA=say echo meta"), KShell::NoOptions, &err1) != sj(QStringLiteral("BLA=say echo meta"), KShell::AbortOnMeta, &err2));
QVERIFY(err1 != err2);
QVERIFY(sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::NoOptions, &err1)
== sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::AbortOnMeta, &err2));
#endif
}
QTEST_MAIN(KShellTest)
#include "kshelltest.moc"

View File

@@ -0,0 +1,258 @@
#include "kstringhandlertest.h"
#include <QRegularExpression>
#include <QTest>
QTEST_MAIN(KStringHandlerTest)
#include "kstringhandler.h"
QString KStringHandlerTest::test = QStringLiteral("The quick brown fox jumped over the lazy bridge. ");
void KStringHandlerTest::capwords()
{
QCOMPARE(KStringHandler::capwords(test), QStringLiteral("The Quick Brown Fox Jumped Over The Lazy Bridge. "));
}
void KStringHandlerTest::tagURLs()
{
QString test = QStringLiteral("Click on https://foo@bar:www.kde.org/yoyo/dyne.html#a1 for info.");
QCOMPARE(KStringHandler::tagUrls(test),
QStringLiteral("Click on <a href=\"https://foo@bar:www.kde.org/yoyo/dyne.html#a1\">https://foo@bar:www.kde.org/yoyo/dyne.html#a1</a> for info."));
test = QStringLiteral("http://www.foo.org/story$806");
QCOMPARE(KStringHandler::tagUrls(test), QStringLiteral("<a href=\"http://www.foo.org/story$806\">http://www.foo.org/story$806</a>"));
test = QStringLiteral("http://www.foo.org/bla-(bli)");
QCOMPARE(KStringHandler::tagUrls(test), QStringLiteral("<a href=\"http://www.foo.org/bla-(bli)\">http://www.foo.org/bla-(bli)</a>"));
test = QStringLiteral("http://www.foo.org/bla-bli");
QCOMPARE(KStringHandler::tagUrls(test), QStringLiteral("<a href=\"http://www.foo.org/bla-bli\">http://www.foo.org/bla-bli</a>"));
// Test with Unicode characters
test = QStringLiteral("Click on https://foo@bar:www.kde.org/ÿöyo/dyne.html#a1 for info.");
QCOMPARE(KStringHandler::tagUrls(test),
QStringLiteral("Click on <a href=\"https://foo@bar:www.kde.org/ÿöyo/dyne.html#a1\">https://foo@bar:www.kde.org/ÿöyo/dyne.html#a1</a> for info."));
}
void KStringHandlerTest::perlSplitTextSep()
{
QStringList expected;
expected << QStringLiteral("some") << QStringLiteral("string") << QStringLiteral("for") << QStringLiteral("you__here");
QCOMPARE(KStringHandler::perlSplit(QStringLiteral("__"), QStringLiteral("some__string__for__you__here"), 4), expected);
expected.clear();
expected << QStringLiteral("kparts") << QStringLiteral("reaches") << QStringLiteral("the parts other parts can't");
QCOMPARE(KStringHandler::perlSplit(QLatin1Char(' '), QStringLiteral("kparts reaches the parts other parts can't"), 3), expected);
}
void KStringHandlerTest::perlSplitRegexSep()
{
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 67)
QCOMPARE(KStringHandler::perlSplit(QRegExp(QStringLiteral("[! ]")), QStringLiteral("Split me up ! I'm bored ! OK ?"), 3),
(QStringList{QStringLiteral("Split"), QStringLiteral("me"), QStringLiteral("up ! I'm bored ! OK ?")}));
#endif
QCOMPARE(KStringHandler::perlSplit(QRegularExpression(QStringLiteral("[! ]")), QStringLiteral("Split me up ! I'm bored ! OK ?"), 3),
(QStringList{QStringLiteral("Split"), QStringLiteral("me"), QStringLiteral("up ! I'm bored ! OK ?")}));
QCOMPARE(KStringHandler::perlSplit(QRegularExpression(QStringLiteral("\\W")), QStringLiteral("aaa ggg cd ef"), 3),
(QStringList{QStringLiteral("aaa"), QStringLiteral("ggg"), QStringLiteral("cd ef")}));
// Test with Unicode characters
QCOMPARE(KStringHandler::perlSplit(QRegularExpression(QStringLiteral("\\W")), QStringLiteral("aaa gǵg cd ef"), 3),
(QStringList{QStringLiteral("aaa"), QStringLiteral("gǵg"), QStringLiteral("cd ef")}));
}
void KStringHandlerTest::obscure()
{
// See bug 167900, obscure() produced chars that could not properly be converted to and from
// UTF8. The result was that storing passwords with '!' in them did not work.
QString test = QStringLiteral("!TEST!");
QString obscured = KStringHandler::obscure(test);
QByteArray obscuredBytes = obscured.toUtf8();
QCOMPARE(KStringHandler::obscure(QString::fromUtf8(obscuredBytes.constData())), test);
}
// Zero-Width Space
static const QChar ZWSP(0x200b);
// Word Joiner
static const QChar WJ(0x2060);
void KStringHandlerTest::preProcessWrap_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<QString>("expected");
// Should result in no additional breaks
QTest::newRow("spaces") << "foo bar baz"
<< "foo bar baz";
// Should insert a ZWSP after each '_'
QTest::newRow("underscores") << "foo_bar_baz" << QString(QStringLiteral("foo_") + ZWSP + QStringLiteral("bar_") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP after each '-'
QTest::newRow("hyphens") << "foo-bar-baz" << QString(QStringLiteral("foo-") + ZWSP + QStringLiteral("bar-") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP after each '.'
QTest::newRow("periods") << "foo.bar.baz" << QString(QStringLiteral("foo.") + ZWSP + QStringLiteral("bar.") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP after each ','
QTest::newRow("commas") << "foo,bar,baz" << QString(QStringLiteral("foo,") + ZWSP + QStringLiteral("bar,") + ZWSP + QStringLiteral("baz"));
// Should result in no additional breaks since the '_'s are followed by spaces
QTest::newRow("mixed underscores and spaces") << "foo_ bar_ baz"
<< "foo_ bar_ baz";
// Should result in no additional breaks since the '_' is the last char
QTest::newRow("ends with underscore") << "foo_"
<< "foo_";
// Should insert a ZWSP before '(' and after ')'
QTest::newRow("parens") << "foo(bar)baz" << QString(QStringLiteral("foo") + ZWSP + QStringLiteral("(bar)") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP before '[' and after ']'
QTest::newRow("brackets") << "foo[bar]baz" << QString(QStringLiteral("foo") + ZWSP + QStringLiteral("[bar]") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP before '{' and after '}'
QTest::newRow("curly braces") << "foo{bar}baz" << QString(QStringLiteral("foo") + ZWSP + QStringLiteral("{bar}") + ZWSP + QStringLiteral("baz"));
// Should insert a ZWSP before '(' but not after ')' since it's the last char
QTest::newRow("ends with ')'") << "foo(bar)" << QString(QStringLiteral("foo") + ZWSP + QStringLiteral("(bar)"));
// Should insert a single ZWSP between the '_' and the '('
QTest::newRow("'_' followed by '('") << "foo_(bar)" << QString(QStringLiteral("foo_") + ZWSP + QStringLiteral("(bar)"));
// Should insert ZWSP's between the '_' and the '[', between the double
// '['s and the double ']'s, but not before and after 'bar'
QTest::newRow("'_' before double brackets") << "foo_[[bar]]"
<< QString(QStringLiteral("foo_") + ZWSP + QStringLiteral("[") + ZWSP + QStringLiteral("[bar]") + ZWSP
+ QStringLiteral("]"));
// Should only insert ZWSP's between the double '['s and the double ']'s
QTest::newRow("space before double brackets") << "foo [[bar]]"
<< QString(QStringLiteral("foo [") + ZWSP + QStringLiteral("[bar]") + ZWSP + QStringLiteral("]"));
// Shouldn't result in any additional breaks since the '(' is preceded
// by a space, and the ')' is followed by a space.
QTest::newRow("parens with spaces") << "foo (bar) baz"
<< "foo (bar) baz";
// Should insert a WJ (Word Joiner) before a single quote
QTest::newRow("single quote") << "foo'bar" << QString(QStringLiteral("foo") + WJ + QStringLiteral("'bar"));
// Should insert a ZWSP between sub-words, but not before nor after the word
QTest::newRow("camelCase") << "camelCase" << QString(QStringLiteral("camel") + ZWSP + QStringLiteral("Case"));
// Why limiting yourself to ASCII? More and more programming languages these days allow for Unicode identifiers.
QTest::newRow("camelCase international") << "приветМир" << QString(QStringLiteral("привет") + ZWSP + QStringLiteral("Мир"));
// Should insert a ZWSP between sub-words, but not before first (upper case) letter
QTest::newRow("PascalCase") << "PascalCase" << QString(QStringLiteral("Pascal") + ZWSP + QStringLiteral("Case"));
}
// Little helper function to make tests diagnostics more readable by humans
static QString replaceZwsp(const QString &string)
{
const QString replacement = QStringLiteral("<ZWSP>");
QString result;
result.reserve(string.length() + string.count(ZWSP) * replacement.length());
for (const auto i : string) {
if (i == ZWSP) {
result += replacement;
} else {
result += i;
}
}
return result;
}
void KStringHandlerTest::preProcessWrap()
{
QFETCH(QString, string);
QFETCH(QString, expected);
QCOMPARE(replaceZwsp(KStringHandler::preProcessWrap(string)), replaceZwsp(expected));
}
void KStringHandlerTest::logicalLength_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<int>("expected");
QTest::newRow("Latin") << "foo bar baz" << 11;
QTest::newRow("Chinese") << QString::fromUtf8("\xe4\xbd\xa0\xe5\xa5\xbd") << 4;
QTest::newRow("Japanese") << QString::fromUtf8("\xe9\x9d\x92\xe3\x81\x84\xe7\xa9\xba") << 6;
QTest::newRow("Korean") << QString::fromUtf8("\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4") << 6;
QTest::newRow("Mixed") << QString::fromUtf8("KDE\xe6\xa1\x8c\xe9\x9d\xa2") << 7;
}
void KStringHandlerTest::logicalLength()
{
QFETCH(QString, string);
QFETCH(int, expected);
QCOMPARE(KStringHandler::logicalLength(string), expected);
}
void KStringHandlerTest::lsqueeze_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<int>("length");
QTest::addColumn<QString>("expected");
QTest::newRow("kde_is_awesome") << "KDE is awesome" << 11 << "... awesome";
QTest::newRow("kde_is_really_awesome") << "KDE is really awesome" << 20 << "...is really awesome";
QTest::newRow("kde_is_really_awesome_full") << "KDE is really awesome" << 30 << "KDE is really awesome";
}
void KStringHandlerTest::lsqueeze()
{
QFETCH(QString, string);
QFETCH(int, length);
QFETCH(QString, expected);
QCOMPARE(KStringHandler::lsqueeze(string, length), expected);
}
void KStringHandlerTest::csqueeze_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<int>("length");
QTest::addColumn<QString>("expected");
QTest::newRow("kde_is_awesome") << "KDE is awesome" << 11 << "KDE ...some";
QTest::newRow("kde_is_really_awesome") << "KDE is really awesome" << 20 << "KDE is r... awesome";
QTest::newRow("kde_is_really_awesome_full") << "KDE is really awesome" << 30 << "KDE is really awesome";
}
void KStringHandlerTest::csqueeze()
{
QFETCH(QString, string);
QFETCH(int, length);
QFETCH(QString, expected);
QCOMPARE(KStringHandler::csqueeze(string, length), expected);
}
void KStringHandlerTest::rsqueeze_data()
{
QTest::addColumn<QString>("string");
QTest::addColumn<int>("length");
QTest::addColumn<QString>("expected");
QTest::newRow("kde_is_awesome") << "KDE is awesome" << 11 << "KDE is a...";
QTest::newRow("kde_is_really_awesome") << "KDE is really awesome" << 20 << "KDE is really awe...";
QTest::newRow("kde_is_really_awesome_full") << "KDE is really awesome" << 30 << "KDE is really awesome";
}
void KStringHandlerTest::rsqueeze()
{
QFETCH(QString, string);
QFETCH(int, length);
QFETCH(QString, expected);
QCOMPARE(KStringHandler::rsqueeze(string, length), expected);
}
#include "moc_kstringhandlertest.cpp"

View File

@@ -0,0 +1,31 @@
#ifndef KSTRINGHANDLERTEST_H
#define KSTRINGHANDLERTEST_H
#include <QObject>
class KStringHandlerTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void capwords();
void tagURLs();
void perlSplitTextSep();
void perlSplitRegexSep();
void obscure();
void preProcessWrap_data();
void preProcessWrap();
void logicalLength_data();
void logicalLength();
void lsqueeze();
void lsqueeze_data();
void csqueeze();
void csqueeze_data();
void rsqueeze();
void rsqueeze_data();
private:
static QString test;
};
#endif

View File

@@ -0,0 +1,50 @@
/*
SPDX-FileCopyrightText: 2005 Ingo Kloecker <kloecker@kde.org>
SPDX-FileCopyrightText: 2007 Allen Winter <winter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "../src/lib/text/ktexttohtml.h"
#include "../src/lib/text/ktexttohtml_p.h"
#include <QDebug>
#include <QTest>
#include <QUrl>
Q_DECLARE_METATYPE(KTextToHTML::Options)
class KTextToHTMLTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void benchHtmlConvert_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<KTextToHTML::Options>("options");
auto text = QStringLiteral("foo bar asdf :)").repeated(1000);
QTest::newRow("plain") << text << KTextToHTML::Options();
QTest::newRow("preserve-spaces") << text << KTextToHTML::Options(KTextToHTML::PreserveSpaces);
QTest::newRow("highlight-text") << text << KTextToHTML::Options(KTextToHTML::HighlightText);
QTest::newRow("replace-smileys") << text << KTextToHTML::Options(KTextToHTML::ReplaceSmileys);
QTest::newRow("preserve-spaces+highlight-text") << text << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText);
QTest::newRow("preserve-spaces+highlight-text+replace-smileys")
<< text << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText | KTextToHTML::ReplaceSmileys);
}
void benchHtmlConvert()
{
QFETCH(QString, text);
QFETCH(KTextToHTML::Options, options);
QBENCHMARK {
const QString html = KTextToHTML::convertToHtml(text, options);
Q_UNUSED(html);
}
}
};
QTEST_MAIN(KTextToHTMLTest)
#include "ktexttohtmlbenchmarktest.moc"

View File

@@ -0,0 +1,484 @@
/*
SPDX-FileCopyrightText: 2005 Ingo Kloecker <kloecker@kde.org>
SPDX-FileCopyrightText: 2007 Allen Winter <winter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ktexttohtmltest.h"
#include "kcoreaddons_debug.h"
#include "../src/lib/text/ktexttohtml.h"
#include "../src/lib/text/ktexttohtml_p.h"
#include <QDebug>
#include <QTest>
#include <QUrl>
QTEST_MAIN(KTextToHTMLTest)
Q_DECLARE_METATYPE(KTextToHTML::Options)
#ifndef Q_OS_WIN
void initLocale()
{
setenv("LC_ALL", "en_US.utf-8", 1);
}
Q_CONSTRUCTOR_FUNCTION(initLocale)
#endif
void KTextToHTMLTest::testGetEmailAddress()
{
// empty input
const QString emptyQString;
KTextToHTMLHelper ll1(emptyQString, 0);
QVERIFY(ll1.getEmailAddress().isEmpty());
// no '@' at scan position
KTextToHTMLHelper ll2(QStringLiteral("foo@bar.baz"), 0);
QVERIFY(ll2.getEmailAddress().isEmpty());
// '@' in local part
KTextToHTMLHelper ll3(QStringLiteral("foo@bar@bar.baz"), 7);
QVERIFY(ll3.getEmailAddress().isEmpty());
// empty local part
KTextToHTMLHelper ll4(QStringLiteral("@bar.baz"), 0);
QVERIFY(ll4.getEmailAddress().isEmpty());
KTextToHTMLHelper ll5(QStringLiteral(".@bar.baz"), 1);
QVERIFY(ll5.getEmailAddress().isEmpty());
KTextToHTMLHelper ll6(QStringLiteral(" @bar.baz"), 1);
QVERIFY(ll6.getEmailAddress().isEmpty());
KTextToHTMLHelper ll7(QStringLiteral(".!#$%&'*+-/=?^_`{|}~@bar.baz"), qstrlen(".!#$%&'*+-/=?^_`{|}~"));
QVERIFY(ll7.getEmailAddress().isEmpty());
// allowed special chars in local part of address
KTextToHTMLHelper ll8(QStringLiteral("a.!#$%&'*+-/=?^_`{|}~@bar.baz"), qstrlen("a.!#$%&'*+-/=?^_`{|}~"));
QCOMPARE(ll8.getEmailAddress(), QStringLiteral("a.!#$%&'*+-/=?^_`{|}~@bar.baz"));
// '@' in domain part
KTextToHTMLHelper ll9(QStringLiteral("foo@bar@bar.baz"), 3);
QVERIFY(ll9.getEmailAddress().isEmpty());
// domain part without dot
KTextToHTMLHelper lla(QStringLiteral("foo@bar"), 3);
QVERIFY(lla.getEmailAddress().isEmpty());
KTextToHTMLHelper llb(QStringLiteral("foo@bar."), 3);
QVERIFY(llb.getEmailAddress().isEmpty());
KTextToHTMLHelper llc(QStringLiteral(".foo@bar"), 4);
QVERIFY(llc.getEmailAddress().isEmpty());
KTextToHTMLHelper lld(QStringLiteral("foo@bar "), 3);
QVERIFY(lld.getEmailAddress().isEmpty());
KTextToHTMLHelper lle(QStringLiteral(" foo@bar"), 4);
QVERIFY(lle.getEmailAddress().isEmpty());
KTextToHTMLHelper llf(QStringLiteral("foo@bar-bar"), 3);
QVERIFY(llf.getEmailAddress().isEmpty());
// empty domain part
KTextToHTMLHelper llg(QStringLiteral("foo@"), 3);
QVERIFY(llg.getEmailAddress().isEmpty());
KTextToHTMLHelper llh(QStringLiteral("foo@."), 3);
QVERIFY(llh.getEmailAddress().isEmpty());
KTextToHTMLHelper lli(QStringLiteral("foo@-"), 3);
QVERIFY(lli.getEmailAddress().isEmpty());
// simple address
KTextToHTMLHelper llj(QStringLiteral("foo@bar.baz"), 3);
QCOMPARE(llj.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper llk(QStringLiteral("foo@bar.baz."), 3);
QCOMPARE(llk.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper lll(QStringLiteral(".foo@bar.baz"), 4);
QCOMPARE(lll.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper llm(QStringLiteral("foo@bar.baz-"), 3);
QCOMPARE(llm.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper lln(QStringLiteral("-foo@bar.baz"), 4);
QCOMPARE(lln.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper llo(QStringLiteral("foo@bar.baz "), 3);
QCOMPARE(llo.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper llp(QStringLiteral(" foo@bar.baz"), 4);
QCOMPARE(llp.getEmailAddress(), QStringLiteral("foo@bar.baz"));
KTextToHTMLHelper llq(QStringLiteral("foo@bar-bar.baz"), 3);
QCOMPARE(llq.getEmailAddress(), QStringLiteral("foo@bar-bar.baz"));
}
void KTextToHTMLTest::testGetUrl()
{
QStringList brackets;
brackets << QString() << QString(); // no brackets
brackets << QStringLiteral("<") << QStringLiteral(">");
brackets << QStringLiteral("[") << QStringLiteral("]");
brackets << QStringLiteral("\"") << QStringLiteral("\"");
brackets << QStringLiteral("<link>") << QStringLiteral("</link>");
for (int i = 0; i < brackets.count(); i += 2) {
testGetUrl2(brackets[i], brackets[i + 1]);
}
}
void KTextToHTMLTest::testGetUrl2(const QString &left, const QString &right)
{
QStringList schemas;
schemas << QStringLiteral("http://");
schemas << QStringLiteral("https://");
schemas << QStringLiteral("vnc://");
schemas << QStringLiteral("fish://");
schemas << QStringLiteral("ftp://");
schemas << QStringLiteral("ftps://");
schemas << QStringLiteral("sftp://");
schemas << QStringLiteral("smb://");
schemas << QStringLiteral("file://");
schemas << QStringLiteral("irc://");
schemas << QStringLiteral("ircs://");
QStringList urls;
urls << QStringLiteral("www.kde.org");
urls << QStringLiteral("user@www.kde.org");
urls << QStringLiteral("user:pass@www.kde.org");
urls << QStringLiteral("user:pass@www.kde.org:1234");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path?a=1");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path?a=1#anchor");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/\npath \n /long/ path \t ?a=1#anchor");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path/special(123)?a=1#anchor");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla");
urls << QStringLiteral("user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]");
urls << QStringLiteral("user:pass@www.kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]");
urls << QStringLiteral("user:pass@www.kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?") + QStringLiteral("\n\t \n\t a=1#anchor[bla]");
for (const QString &schema : std::as_const(schemas)) {
for (QString url : std::as_const(urls)) {
// by definition: if the URL is enclosed in brackets, the URL itself is not allowed
// to contain the closing bracket, as this would be detected as the end of the URL
if ((left.length() == 1) && (url.contains(right[0]))) {
continue;
}
// if the url contains a whitespace, it must be enclosed with brackets
if ((url.contains(QLatin1Char('\n')) || url.contains(QLatin1Char('\t')) || url.contains(QLatin1Char(' '))) && left.isEmpty()) {
continue;
}
QString test(left + schema + url + right);
KTextToHTMLHelper ll(test, left.length());
QString gotUrl = ll.getUrl();
// we want to have the url without whitespace
url.remove(QLatin1Char(' '));
url.remove(QLatin1Char('\n'));
url.remove(QLatin1Char('\t'));
bool ok = (gotUrl == (schema + url));
if (!ok) {
qCDebug(KCOREADDONS_DEBUG) << "got:" << gotUrl;
}
QVERIFY2(ok, qPrintable(test));
}
}
QStringList urlsWithoutSchema;
urlsWithoutSchema << QStringLiteral(".kde.org");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path?a=1");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path?a=1#anchor");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path/special(123)?a=1#anchor");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]");
urlsWithoutSchema << QStringLiteral(".kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?") + QStringLiteral("\n\t \n\t a=1#anchor[bla]");
QStringList starts;
starts << QStringLiteral("www") << QStringLiteral("ftp") << QStringLiteral("news:www");
for (const QString &start : std::as_const(starts)) {
for (QString url : std::as_const(urlsWithoutSchema)) {
// by definition: if the URL is enclosed in brackets, the URL itself is not allowed
// to contain the closing bracket, as this would be detected as the end of the URL
if ((left.length() == 1) && (url.contains(right[0]))) {
continue;
}
// if the url contains a whitespace, it must be enclosed with brackets
if ((url.contains(QLatin1Char('\n')) || url.contains(QLatin1Char('\t')) || url.contains(QLatin1Char(' '))) && left.isEmpty()) {
continue;
}
QString test(left + start + url + right);
KTextToHTMLHelper ll(test, left.length());
QString gotUrl = ll.getUrl();
// we want to have the url without whitespace
url.remove(QLatin1Char(' '));
url.remove(QLatin1Char('\n'));
url.remove(QLatin1Char('\t'));
bool ok = (gotUrl == (start + url));
if (!ok) {
qCDebug(KCOREADDONS_DEBUG) << "got:" << gotUrl;
}
QVERIFY2(ok, qPrintable(gotUrl));
}
}
// test max url length
QString url = QStringLiteral("https://www.kde.org/this/is/a_very_loooooong_url/test/test/test");
{
KTextToHTMLHelper ll(url, 0, 10);
QVERIFY(ll.getUrl().isEmpty()); // url too long
}
{
KTextToHTMLHelper ll(url, 0, url.length() - 1);
QVERIFY(ll.getUrl().isEmpty()); // url too long
}
{
KTextToHTMLHelper ll(url, 0, url.length());
QCOMPARE(ll.getUrl(), url);
}
{
KTextToHTMLHelper ll(url, 0, url.length() + 1);
QCOMPARE(ll.getUrl(), url);
}
// mailto
{
QString addr = QStringLiteral("mailto:test@kde.org");
QString test(left + addr + right);
KTextToHTMLHelper ll(test, left.length());
QString gotUrl = ll.getUrl();
bool ok = (gotUrl == addr);
if (!ok) {
qCDebug(KCOREADDONS_DEBUG) << "got:" << gotUrl;
}
QVERIFY2(ok, qPrintable(gotUrl));
}
}
void KTextToHTMLTest::testHtmlConvert_data()
{
QTest::addColumn<QString>("plainText");
QTest::addColumn<KTextToHTML::Options>("flags");
QTest::addColumn<QString>("htmlText");
// Linker error when using PreserveSpaces, therefore the hardcoded 0x01 or 0x09
// Test preserving whitespace correctly
QTest::newRow("") << " foo" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "&nbsp;foo";
QTest::newRow("") << " foo" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "&nbsp;&nbsp;foo";
QTest::newRow("") << " foo " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "&nbsp;&nbsp;foo&nbsp;&nbsp;";
QTest::newRow("") << " foo " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "&nbsp;&nbsp;foo&nbsp;";
QTest::newRow("") << "bla bla bla bla bla" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla bla bla bla bla";
QTest::newRow("") << "bla bla bla \n bla bla bla " << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "bla bla bla&nbsp;<br />\n&nbsp;&nbsp;bla bla bla&nbsp;";
QTest::newRow("") << "bla bla bla" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "bla bla&nbsp;&nbsp;bla";
QTest::newRow("") << " bla bla \n bla bla a\n bla bla " << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "&nbsp;bla bla&nbsp;<br />\n&nbsp;bla bla a<br />\n"
"&nbsp;&nbsp;bla bla&nbsp;";
// Test highlighting with *, / and _
QTest::newRow("") << "Ce paragraphe _contient_ des mots ou des _groupes de mots_ à mettre en"
" forme…"
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "Ce paragraphe <u>_contient_</u> des mots ou des"
" <u>_groupes de mots_</u> à mettre en forme…";
QTest::newRow("punctation-bug") << "Ce texte *a l'air* de _fonctionner_, à condition"
" dutiliser le guillemet ASCII."
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "Ce texte <b>*a l'air*</b> de <u>_fonctionner_</u>, à"
" condition dutiliser le guillemet ASCII.";
QTest::newRow("punctation-bug") << "Un répertoire /est/ un *dossier* où on peut mettre des"
" *fichiers*."
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "Un répertoire <i>/est/</i> un"
" <b>*dossier*</b> où on peut mettre des <b>*fichiers*</b>.";
QTest::newRow("punctation-bug") << "*BLA BLA BLA BLA*." << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "<b>BLA BLA BLA BLA</b>.";
QTest::newRow("") << "Je vais tenter de repérer des faux positif*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "Je vais tenter de repérer des faux positif*";
QTest::newRow("") << "*Ouais !* *Yes!*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "<b>*Ouais !*</b> <b>*Yes!*</b>";
QTest::newRow("multispace") << "*Ouais foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "<b>*Ouais foo*</b>";
QTest::newRow("multispace3") << "*Ouais: foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "<b>*Ouais: foo*</b>";
QTest::newRow("multi-") << "** Ouais: foo **" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "** Ouais:&nbsp;&nbsp;foo **";
QTest::newRow("multi-") << "*** Ouais: foo ***" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "*** Ouais:&nbsp;&nbsp;foo ***";
QTest::newRow("nohtmlversion") << "* Ouais: foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "* Ouais:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo *";
QTest::newRow("nohtmlversion2") << "*Ouais: foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "*Ouais:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo *";
QTest::newRow("nohtmlversion3") << "* Ouais: foo*" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "* Ouais:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo*";
QTest::newRow("nohtmlversion3") << "* Ouais: *ff sfsdf* foo *" << KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "* Ouais: <b>*ff sfsdf*</b> foo *";
QTest::newRow("") << "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file"
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText)
<< "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file";
// This test has problems with the encoding, apparently.
// QTest::newRow( "" ) << "*Ça fait plaisir de pouvoir utiliser des lettres accentuées dans du"
// " texte mis en forme*." << 0x09 << "<b>Ça fait plaisir de pouvoir"
// " utiliser des lettres accentuées dans du texte mis en forme</b>.";
// Bug reported by dfaure, the <hostname> would get lost
QTest::newRow("") << "QUrl url(\"http://strange<hostname>/\");" << KTextToHTML::Options(KTextToHTML::ReplaceSmileys | KTextToHTML::HighlightText)
<< "QUrl url(&quot;<a href=\"http://strange<hostname>/\">"
"http://strange&lt;hostname&gt;/</a>&quot;);";
// Bug: 211128 - plain text emails should not replace ampersand & with &amp;
QTest::newRow("bug211128") << "https://green-site/?Ticket=85&Page=next" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"https://green-site/?Ticket=85&Page=next\">"
"https://green-site/?Ticket=85&amp;Page=next</a>";
QTest::newRow("dotBeforeEnd") << "Look at this file: www.example.com/example.h" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "Look at this file: <a href=\"http://www.example.com/example.h\">"
"www.example.com/example.h</a>";
QTest::newRow("dotInMiddle") << "Look at this file: www.example.com/.bashrc" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "Look at this file: <a href=\"http://www.example.com/.bashrc\">"
"www.example.com/.bashrc</a>";
// A dot at the end of an URL is explicitly ignored
QTest::newRow("dotAtEnd") << "Look at this file: www.example.com/test.cpp." << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "Look at this file: <a href=\"http://www.example.com/test.cpp\">"
"www.example.com/test.cpp</a>.";
// Bug 313719 - URL in parenthesis
QTest::newRow("url-in-parenthesis-1") << "KDE (website https://www.kde.org)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "KDE (website <a href=\"https://www.kde.org\">https://www.kde.org</a>)";
QTest::newRow("url-in-parenthesis-2") << "KDE website (https://www.kde.org)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "KDE website (<a href=\"https://www.kde.org\">https://www.kde.org</a>)";
QTest::newRow("url-in-parenthesis-3") << "bla (https://www.kde.org - section 5.2)" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "bla (<a href=\"https://www.kde.org\">https://www.kde.org</a> - section 5.2)";
// Fix url as foo <<url> <url>> when we concatened them.
QTest::newRow("url-with-url")
<< "foo <https://www.kde.org/ <https://www.kde.org/>>" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "foo &lt;<a href=\"https://www.kde.org/ \">https://www.kde.org/ </a>&lt;<a href=\"https://www.kde.org/\">https://www.kde.org/</a>&gt;&gt;";
// Fix url exploit
QTest::newRow("url-exec-html") << "https://\"><!--" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "https://&quot;&gt;&lt;!--";
QTest::newRow("url-exec-html-2") << "https://192.168.1.1:\"><!--" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "https://192.168.1.1:&quot;&gt;&lt;!--";
QTest::newRow("url-exec-html-3") << "https://<IP>:\"><!--" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "https://&lt;IP&gt;:&quot;&gt;&lt;!--";
QTest::newRow("url-exec-html-4") << "https://<IP>:/\"><!--" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "https://&lt;IP&gt;:/&quot;&gt;&lt;!--";
QTest::newRow("url-exec-html-5") << "https://<IP>:/\"><script>alert(1);</script><!--" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "https://&lt;IP&gt;:/&quot;&gt;&lt;script&gt;alert(1);&lt;/script&gt;&lt;!--";
QTest::newRow("url-exec-html-6") << "https://<IP>:/\"><script>alert(1);</script><!--\nTest2" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "https://&lt;IP&gt;:/&quot;&gt;&lt;script&gt;alert(1);&lt;/script&gt;&lt;!--\nTest2";
QTest::newRow("url-with-ref-in-[") << "https://www.kde.org[1]" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"https://www.kde.org\">https://www.kde.org</a>[1]";
QTest::newRow("url-with-ref-in-[2") << "[http://www.example.org/][whatever]" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "[<a href=\"http://www.example.org/\">http://www.example.org/</a>][whatever]";
// Bug 346132
QTest::newRow("url-with-ref-in-<") << "http://www.foo.bar<http://foo.bar/>" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"http://www.foo.bar\">http://www.foo.bar</a>&lt;<a href=\"http://foo.bar/\">http://foo.bar/</a>&gt;";
QTest::newRow("url-with-ref-in-]") << "[Please visit our booth 24-25 http://example.com/]" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "[Please visit our booth 24-25 <a href=\"http://example.com/\">http://example.com/</a>]";
QTest::newRow("two url with space") << "http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"http://www.kde.org/standards/kcfg/1.0\">http://www.kde.org/standards/kcfg/1.0</a> <a "
"href=\"http://www.kde.org/\">http://www.kde.org/</a>";
// Bug kmail
QTest::newRow("two url with space-2")
<< "@@ -55,6 +55,10 @@ xsi:schemaLocation=\"http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/"
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "@@ -55,6 +55,10 @@ xsi:schemaLocation=&quot;<a href=\"http://www.kde.org/standards/kcfg/1.0\">http://www.kde.org/standards/kcfg/1.0</a> <a "
"href=\"http://www.kde.org/\">http://www.kde.org/</a>";
const auto opt = KTextToHTML::PreserveSpaces | KTextToHTML::ConvertPhoneNumbers;
// tel: urls
QTest::newRow("tel url compact") << "bla bla <tel:+491234567890> bla bla" << opt
<< "bla bla &lt;<a href=\"tel:+491234567890\">tel:+491234567890</a>&gt; bla bla";
QTest::newRow("tel url fancy") << "bla bla tel:+49-321-123456 bla bla" << opt << "bla bla <a href=\"tel:+49-321-123456\">tel:+49-321-123456</a> bla bla";
// negative tel: url tests
QTest::newRow("empty tel url") << "bla tel: blub" << opt << "bla tel: blub";
// phone numbers
QTest::newRow("tel compact international") << "call +49123456789, then hang up" << opt
<< "call <a href=\"tel:+49123456789\">+49123456789</a>, then hang up";
QTest::newRow("tel parenthesis/spaces international")
<< "phone:+33 (01) 12 34 56 78 blub" << opt << "phone:<a href=\"tel:+330112345678\">+33 (01) 12 34 56 78</a> blub";
QTest::newRow("tel dashes international") << "bla +44-321-1-234-567" << opt << "bla <a href=\"tel:+443211234567\">+44-321-1-234-567</a>";
QTest::newRow("tel dashes/spaces international") << "+1 123-456-7000 blub" << opt << "<a href=\"tel:+11234567000\">+1 123-456-7000</a> blub";
QTest::newRow("tel spaces international") << "bla +32 1 234 5678 blub" << opt << "bla <a href=\"tel:+3212345678\">+32 1 234 5678</a> blub";
QTest::newRow("tel slash domestic") << "bla 030/12345678 blub" << opt << "bla <a href=\"tel:03012345678\">030/12345678</a> blub";
QTest::newRow("tel slash/space domestic") << "Tel.: 089 / 12 34 56 78" << opt << "Tel.: <a href=\"tel:08912345678\">089 / 12 34 56 78</a>";
QTest::newRow("tel follow by parenthesis") << "Telefon: 0 18 05 / 12 23 46 (14 Cent/Min.*)" << opt
<< "Telefon: <a href=\"tel:01805122346\">0 18 05 / 12 23 46</a> (14 Cent/Min.*)";
QTest::newRow("tel space single digit at end") << "0123/123 456 7" << opt << "<a href=\"tel:01231234567\">0123/123 456 7</a>";
QTest::newRow("tel space around dash") << "bla +49 (0) 12 23 - 45 6000 blub" << opt
<< "bla <a href=\"tel:+4901223456000\">+49 (0) 12 23 - 45 6000</a> blub";
QTest::newRow("tel two numbers speparated by dash")
<< "bla +49 (0) 12 23 46 78 - +49 0123/123 456 78 blub" << opt
<< "bla <a href=\"tel:+49012234678\">+49 (0) 12 23 46 78</a> - <a href=\"tel:+49012312345678\">+49 0123/123 456 78</a> blub";
// negative tests for phone numbers
QTest::newRow("non-tel number") << "please send 1200 cakes" << opt << "please send 1200 cakes";
QTest::newRow("non-tel alpha-numeric") << "bla 1-123-456-ABCD blub" << opt << "bla 1-123-456-ABCD blub";
QTest::newRow("non-tel alpha prefix") << "ABCD0123-456-789" << opt << "ABCD0123-456-789";
QTest::newRow("non-tel date") << "bla 02/03/2019 blub" << opt << "bla 02/03/2019 blub";
QTest::newRow("non-tel too long") << "bla +012-4567890123456 blub" << opt << "bla +012-4567890123456 blub";
QTest::newRow("non-tel unbalanced") << "bla +012-456789(01 blub" << opt << "bla +012-456789(01 blub";
QTest::newRow("non-tel nested") << "bla +012-4(56(78)90)1 blub" << opt << "bla +012-4(56(78)90)1 blub";
QTest::newRow("tel extraction disabled") << "call +49123456789 now" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "call +49123456789 now";
QTest::newRow("bug-414360")
<< "https://www.openstreetmap.org/directions?engine=graphhopper_foot&route=44.85765%2C-0.55931%3B44.85713%2C-0.56117#map=18/44.85756/-0.56094"
<< KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a "
"href=\"https://www.openstreetmap.org/directions?engine=graphhopper_foot&route=44.85765%2C-0.55931%3B44.85713%2C-0.56117#map=18/44.85756/"
"-0.56094\">https://www.openstreetmap.org/directions?engine=graphhopper_foot&amp;route=44.85765%2C-0.55931%3B44.85713%2C-0.56117#map=18/44.85756/"
"-0.56094</a>";
// xmpp bug 422291
QTest::newRow("xmpp1") << "xmpp:username@server.tld" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"xmpp:username@server.tld\">xmpp:username@server.tld</a>";
QTest::newRow("xmpp2") << "xmpp:conversations@conference.siacs.eu" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"xmpp:conversations@conference.siacs.eu\">xmpp:conversations@conference.siacs.eu</a>";
QTest::newRow("xmpp3") << "xmpp:conversations@conference.siacs.eu?join" << KTextToHTML::Options(KTextToHTML::PreserveSpaces)
<< "<a href=\"xmpp:conversations@conference.siacs.eu?join\">xmpp:conversations@conference.siacs.eu?join</a>";
// Test news: only
QTest::newRow("news") << "news: " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "news:&nbsp;";
QTest::newRow("ftp") << "ftp: " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "ftp:&nbsp;";
QTest::newRow("mailto") << "mailto: " << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "mailto:&nbsp;";
QTest::newRow("empty") << "" << KTextToHTML::Options(KTextToHTML::PreserveSpaces) << "";
}
void KTextToHTMLTest::testHtmlConvert()
{
QFETCH(QString, plainText);
QFETCH(KTextToHTML::Options, flags);
QFETCH(QString, htmlText);
QEXPECT_FAIL("punctation-bug", "Linklocator does not properly detect punctation as boundaries", Continue);
const QString actualHtml = KTextToHTML::convertToHtml(plainText, flags);
QCOMPARE(actualHtml, htmlText);
}
#include "moc_ktexttohtmltest.cpp"

View File

@@ -0,0 +1,25 @@
/*
SPDX-FileCopyrightText: 2007 Allen Winter <winter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTEXTTOHTMLTEST_H
#define KTEXTTOHTMLTEST_H
#include <QObject>
class KTextToHTMLTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testGetEmailAddress();
void testGetUrl();
void testHtmlConvert();
void testHtmlConvert_data();
private:
void testGetUrl2(const QString &left, const QString &right);
};
#endif

View File

@@ -0,0 +1,162 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kurlmimedatatest.h"
#include <QMimeData>
#include <QTest>
#include <kurlmimedata.h>
QTEST_MAIN(KUrlMimeDataTest)
void KUrlMimeDataTest::testURLList()
{
QMimeData *mimeData = new QMimeData();
QVERIFY(!mimeData->hasUrls());
QList<QUrl> urls;
urls.append(QUrl(QLatin1String("https://www.kde.org")));
urls.append(QUrl(QLatin1String("http://wstephenson:secret@example.com/path")));
urls.append(QUrl(QLatin1String("file:///home/dfaure/konqtests/Mat%C3%A9riel")));
QMap<QString, QString> metaData;
metaData[QLatin1String("key")] = QLatin1String("value");
metaData[QLatin1String("key2")] = QLatin1String("value2");
KUrlMimeData::setUrls(QList<QUrl>(), urls, mimeData);
KUrlMimeData::setMetaData(metaData, mimeData);
QVERIFY(mimeData->hasUrls());
QVERIFY(mimeData->hasText());
QMap<QString, QString> decodedMetaData;
QList<QUrl> decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferKdeUrls, &decodedMetaData);
QVERIFY(!decodedURLs.isEmpty());
QList<QUrl> expectedUrls = urls;
expectedUrls[1] = QUrl(QLatin1String("http://wstephenson:secret@example.com/path")); // password kept, unlike in KDE4, but that's okay, it's not displayed
QCOMPARE(expectedUrls, decodedURLs);
const QList<QUrl> qurls = mimeData->urls();
QCOMPARE(qurls.count(), urls.count());
for (int i = 0; i < qurls.count(); ++i) {
QCOMPARE(qurls[i], decodedURLs[i]);
}
QVERIFY(!decodedMetaData.isEmpty());
QCOMPARE(decodedMetaData[QLatin1String("key")], QString::fromLatin1("value"));
QCOMPARE(decodedMetaData[QLatin1String("key2")], QString::fromLatin1("value2"));
delete mimeData;
}
void KUrlMimeDataTest::testOneURL()
{
QUrl oneURL(QLatin1String("file:///tmp"));
QList<QUrl> oneEltList;
oneEltList.append(oneURL);
QMimeData *mimeData = new QMimeData();
KUrlMimeData::setUrls(QList<QUrl>(), oneEltList, mimeData);
QVERIFY(mimeData->hasUrls());
QMap<QString, QString> decodedMetaData;
QList<QUrl> decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferKdeUrls, &decodedMetaData);
QVERIFY(!decodedURLs.isEmpty());
QCOMPARE(decodedURLs.count(), 1);
QCOMPARE(decodedURLs[0], oneURL);
QVERIFY(decodedMetaData.isEmpty());
delete mimeData;
}
void KUrlMimeDataTest::testFromQUrl()
{
QList<QUrl> qurls;
qurls.append(QUrl(QLatin1String("https://www.kde.org")));
qurls.append(QUrl(QLatin1String("file:///home/dfaure/konqtests/Mat%C3%A9riel")));
QMimeData *mimeData = new QMimeData();
KUrlMimeData::setUrls(QList<QUrl>(), qurls, mimeData);
QVERIFY(mimeData->hasUrls());
QMap<QString, QString> decodedMetaData;
QList<QUrl> decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferKdeUrls, &decodedMetaData);
QVERIFY(!decodedURLs.isEmpty());
QCOMPARE(decodedURLs.count(), 2);
QCOMPARE(decodedURLs[0], qurls[0]);
QCOMPARE(decodedURLs[1], qurls[1]);
QVERIFY(decodedMetaData.isEmpty());
delete mimeData;
}
void KUrlMimeDataTest::testMostLocalUrlList_data()
{
QTest::addColumn<bool>("withKdeUrls");
QTest::addColumn<bool>("withLocalUrls");
QTest::addColumn<bool>("expectedLocalUrls");
QTest::newRow("both") << true << true << false;
QTest::newRow("local_only") << false << true << true;
QTest::newRow("kde_only") << true << false << false;
}
void KUrlMimeDataTest::testMostLocalUrlList()
{
QFETCH(bool, withKdeUrls);
QFETCH(bool, withLocalUrls);
QFETCH(bool, expectedLocalUrls);
QMimeData *mimeData = new QMimeData();
QList<QUrl> urls;
urls.append(QUrl(QLatin1String("desktop:/foo")));
urls.append(QUrl(QLatin1String("desktop:/bar")));
QList<QUrl> localUrls;
localUrls.append(QUrl(QLatin1String("file:/home/dfaure/Desktop/foo")));
localUrls.append(QUrl(QLatin1String("file:/home/dfaure/Desktop/bar")));
if (withKdeUrls && withLocalUrls) {
KUrlMimeData::setUrls(urls, localUrls, mimeData);
} else if (withKdeUrls) {
KUrlMimeData::setUrls(urls, {}, mimeData);
} else if (withLocalUrls) {
KUrlMimeData::setUrls({}, localUrls, mimeData);
}
QVERIFY(mimeData->hasUrls());
QVERIFY(mimeData->hasText());
// The support for urls is done in hasText, a direct call to hasFormat will say false.
// QVERIFY(mimeData->hasFormat(QLatin1String("text/plain")));
// urlsFromMimeData decodes the real "kde" urls by default, if any
QList<QUrl> decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData);
QVERIFY(!decodedURLs.isEmpty());
if (expectedLocalUrls) {
QCOMPARE(decodedURLs, localUrls);
} else {
QCOMPARE(decodedURLs, urls);
}
// urlsFromMimeData can also be told to decode the "most local" urls
decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferLocalUrls);
QVERIFY(!decodedURLs.isEmpty());
if (withLocalUrls) {
QCOMPARE(decodedURLs, localUrls);
} else {
QCOMPARE(decodedURLs, urls);
}
// QMimeData decodes the "most local" urls
const QList<QUrl> qurls = mimeData->urls();
if (withLocalUrls) {
QCOMPARE(qurls.count(), localUrls.count());
for (int i = 0; i < qurls.count(); ++i) {
QCOMPARE(qurls[i], static_cast<QUrl>(localUrls[i]));
}
} else {
QCOMPARE(qurls.count(), 0);
}
delete mimeData;
}
#include "moc_kurlmimedatatest.cpp"

View File

@@ -0,0 +1,24 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KURLMIMEDATATEST_H
#define KURLMIMEDATATEST_H
#include <QObject>
class KUrlMimeDataTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testURLList();
void testOneURL();
void testFromQUrl();
void testMostLocalUrlList_data();
void testMostLocalUrlList();
};
#endif

View File

@@ -0,0 +1,265 @@
/*
SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QDebug>
#include <QTest>
#include "kcoreaddons_debug.h"
#include "kuser.h"
namespace QTest
{
template<>
char *toString(const KUserId &id)
{
return qstrdup(id.toString().toLocal8Bit().data());
}
template<>
char *toString(const KGroupId &id)
{
return qstrdup(id.toString().toLocal8Bit().data());
}
}
class KUserTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testKUser();
void testKUserGroup();
void testKUserId();
void testKGroupId();
};
static inline void printUserInfo(KUser user)
{
qCDebug(KCOREADDONS_DEBUG) << "Login name:" << user.loginName();
qCDebug(KCOREADDONS_DEBUG) << "Full name:" << user.property(KUser::FullName);
qCDebug(KCOREADDONS_DEBUG) << "User ID:" << user.userId().toString();
qCDebug(KCOREADDONS_DEBUG) << "Group ID:" << user.groupId().toString();
qCDebug(KCOREADDONS_DEBUG) << "Home dir:" << user.homeDir();
qCDebug(KCOREADDONS_DEBUG) << "Superuser:" << user.isSuperUser();
qCDebug(KCOREADDONS_DEBUG) << "Shell: " << user.shell();
qCDebug(KCOREADDONS_DEBUG) << "Face icon path:" << user.faceIconPath();
qCDebug(KCOREADDONS_DEBUG) << "Groups:" << user.groupNames();
qCDebug(KCOREADDONS_DEBUG);
}
void KUserTest::testKUser()
{
KUser user(KUser::UseRealUserID);
KUser effectiveUser(KUser::UseRealUserID);
QVERIFY(user.isValid());
QVERIFY(effectiveUser.isValid());
QCOMPARE(user, effectiveUser); // should be the same, no suid
QVERIFY(user.groupId().isValid());
QCOMPARE(user.groupId(), KGroupId::currentGroupId());
QVERIFY(!user.groups().isEmpty()); // user must be in at least one group
QVERIFY(!user.groupNames().isEmpty()); // user must be in at least one group
QCOMPARE(user.groups().size(), user.groupNames().size());
QStringList allUserNames = KUser::allUserNames();
QList<KUser> allUsers = KUser::allUsers();
QVERIFY(!allUserNames.isEmpty());
QVERIFY(!allUsers.isEmpty());
QCOMPARE(allUsers.size(), allUserNames.size());
// check that the limiting works
QCOMPARE(user.groups(1).size(), 1);
QCOMPARE(user.groupNames(1).size(), 1);
qCDebug(KCOREADDONS_DEBUG) << "All users: " << allUserNames;
// check that the limiting works
QCOMPARE(KUser::allUserNames(1).size(), 1);
QCOMPARE(KUser::allUsers(1).size(), 1);
// We can't test the KUser properties, since they differ on each system
// instead just print them all out, this can be verified by the person running the test
printUserInfo(user);
#if 0 // enable this if you think that KUser might not be working correctly
Q_FOREACH (const KUser &u, allUsers) {
printUserInfo(u);
}
#endif
// test operator==
KUser invalidKUser = KUser(KUserId());
QVERIFY(invalidKUser != invalidKUser); // invalid never equal
QVERIFY(invalidKUser != user);
QVERIFY(user != invalidKUser); // now test the other way around
QCOMPARE(user, user);
// make sure we don't crash when accessing properties of an invalid instance
QCOMPARE(invalidKUser.faceIconPath(), QString());
#if KCOREADDONS_ENABLE_DEPRECATED_SINCE(5, 0)
QCOMPARE(invalidKUser.fullName(), QString());
#endif
QCOMPARE(invalidKUser.groupId(), KGroupId());
invalidKUser.groupNames(); // could be empty, or "nogroup", so no checks here
invalidKUser.groups(); // same as above
QCOMPARE(invalidKUser.homeDir(), QString());
QCOMPARE(invalidKUser.isSuperUser(), false);
QCOMPARE(invalidKUser.loginName(), QString());
QCOMPARE(invalidKUser.shell(), QString());
QCOMPARE(invalidKUser.userId(), KUserId());
QCOMPARE(invalidKUser.userId(), KUserId());
QCOMPARE(invalidKUser.property(KUser::RoomNumber), QVariant());
}
void KUserTest::testKUserGroup()
{
KUserGroup group(KUser::UseRealUserID);
KUserGroup effectiveUser(KUser::UseEffectiveUID);
QVERIFY(group.isValid());
QVERIFY(effectiveUser.isValid());
QCOMPARE(group, effectiveUser); // should be the same, no suid
#ifdef Q_OS_WIN
// on Windows the special group "None" has no members (often the only group that exists)
if (group.name() != QLatin1String("None")) {
#else
{
#endif
QStringList groupUserNames = group.userNames();
QList<KUser> groupUsers = group.users();
QVERIFY(!groupUsers.isEmpty()); // group must have at least one user (the current user)
QVERIFY(!groupUserNames.isEmpty()); // group must have at least one user (the current user)
QCOMPARE(groupUsers.size(), groupUserNames.size());
// check that the limiting works
QCOMPARE(group.users(1).size(), 1);
QCOMPARE(group.userNames(1).size(), 1);
}
QStringList allGroupNames = KUserGroup::allGroupNames();
QList<KUserGroup> allGroups = KUserGroup::allGroups();
QVERIFY(!allGroupNames.isEmpty());
QVERIFY(!allGroups.isEmpty());
QCOMPARE(allGroups.size(), allGroupNames.size());
qCDebug(KCOREADDONS_DEBUG) << "All groups: " << allGroupNames;
// check that the limiting works
QCOMPARE(KUserGroup::allGroupNames(1).size(), 1);
QCOMPARE(KUserGroup::allGroups(1).size(), 1);
// We can't test the KUser properties, since they differ on each system
// instead just print them all out, this can be verified by the person running the test
qCDebug(KCOREADDONS_DEBUG).nospace() << "Current group: " << group.name() << ", group ID =" << group.groupId().toString()
<< ", members = " << group.userNames();
#if 0 // enable this if you think that KUser might not be working correctly
for (int i = 0; i < allGroups.size(); ++i) {
qDebug().nospace() << "Group " << i << ": name = " << allGroups[i].name()
<< ", group ID =" << allGroups[i].groupId().toString();
qDebug() << allGroups[i].name() << "members:" << allGroups[i].userNames();
}
#endif
// test operator==
KUserGroup invalidKUserGroup = KUserGroup(KGroupId());
QVERIFY(invalidKUserGroup != invalidKUserGroup); // invalid never equal
QVERIFY(invalidKUserGroup != group);
QVERIFY(group != invalidKUserGroup); // now test the other way around
QCOMPARE(group, group);
// make sure we don't crash when accessing an invalid KUserGroup
QCOMPARE(invalidKUserGroup.groupId(), KGroupId());
invalidKUserGroup.name(); // could be empty, or "nogroup", so no checks here
QCOMPARE(invalidKUserGroup.userNames(), QStringList());
QCOMPARE(invalidKUserGroup.users(), QList<KUser>());
}
void KUserTest::testKUserId()
{
// make sure KUser::currentUserId() and KUser::curretEffectiveUserId() work
KUserId currentUser = KUserId::currentUserId();
QVERIFY(currentUser.isValid());
KUserId currentEffectiveUser = KUserId::currentEffectiveUserId();
QVERIFY(currentEffectiveUser.isValid());
// these should be the same since this is not a setuid program
QCOMPARE(currentUser, currentEffectiveUser);
KUser kuser(currentUser);
// now get the same user from his name
QString userName = kuser.loginName();
qDebug("Current user: %s, id: %s", qPrintable(userName), qPrintable(currentUser.toString()));
QVERIFY(!userName.isEmpty());
KUserId currentUserFromStr = KUserId::fromName(userName);
QVERIFY(currentUserFromStr.isValid());
KUserId currentUserCopyFromKUser = kuser.userId();
QVERIFY(currentUserCopyFromKUser.isValid());
KUserId invalid;
QVERIFY(!invalid.isValid());
#ifdef Q_OS_WIN
KUserId invalid2(nullptr);
#else
KUserId invalid2(-1);
#endif
QVERIFY(!invalid2.isValid());
// I guess it is safe to assume no user with this name exists
KUserId invalid3 = KUserId::fromName(QStringLiteral("This_user_does_not_exist"));
QVERIFY(!invalid3.isValid());
// check comparison
QCOMPARE(invalid, KUserId());
QCOMPARE(invalid, invalid2);
QCOMPARE(invalid, invalid3);
QCOMPARE(currentUser, currentUserFromStr);
QCOMPARE(currentUser, currentEffectiveUser);
QCOMPARE(currentUser, currentUserCopyFromKUser);
QVERIFY(currentUser != invalid);
QVERIFY(currentUser != invalid2);
QVERIFY(currentUser != invalid3);
QVERIFY(invalid != currentUser);
// Copy constructor and assignment
KUserId currentUserCopy = currentUser;
QCOMPARE(currentUser, currentUserCopy);
QCOMPARE(currentUser, KUserId(currentUser));
QCOMPARE(currentEffectiveUser, KUserId(currentUser));
}
void KUserTest::testKGroupId()
{
// make sure KGroup::currentGroupId() and KGroup::curretEffectiveGroupId() work
KGroupId currentGroup = KGroupId::currentGroupId();
QVERIFY(currentGroup.isValid());
KGroupId currentEffectiveGroup = KGroupId::currentEffectiveGroupId();
QVERIFY(currentEffectiveGroup.isValid());
// these should be the same since this is not a setuid program
QCOMPARE(currentGroup, currentEffectiveGroup);
// now get the same Group from his name
KUserGroup kuserGroup(currentGroup);
QString groupName = kuserGroup.name();
qDebug("Current group: %s, id: %s", qPrintable(groupName), qPrintable(currentGroup.toString()));
QVERIFY(!groupName.isEmpty());
KGroupId currentGroupFromStr = KGroupId::fromName(groupName);
QVERIFY(currentGroupFromStr.isValid());
KGroupId currentGroupCopyFromKUserGroup = kuserGroup.groupId();
QVERIFY(currentGroupCopyFromKUserGroup.isValid());
KGroupId invalid;
QVERIFY(!invalid.isValid());
#ifdef Q_OS_WIN
KGroupId invalid2(nullptr);
#else
KGroupId invalid2(-1);
#endif
QVERIFY(!invalid2.isValid());
// I guess it is safe to assume no Group with this name exists
KGroupId invalid3 = KGroupId::fromName(QStringLiteral("This_Group_does_not_exist"));
QVERIFY(!invalid3.isValid());
// check comparison
QCOMPARE(invalid, KGroupId());
QCOMPARE(invalid, invalid2);
QCOMPARE(invalid, invalid3);
QCOMPARE(currentGroup, currentGroupFromStr);
QCOMPARE(currentGroup, currentEffectiveGroup);
QCOMPARE(currentGroup, currentGroupCopyFromKUserGroup);
QVERIFY(invalid != currentGroup);
QVERIFY(currentGroup != invalid);
QVERIFY(currentGroup != invalid2);
QVERIFY(currentGroup != invalid3);
// Copy constructor and assignment
KGroupId currentGroupCopy = currentGroup;
QCOMPARE(currentGroup, currentGroupCopy);
QCOMPARE(currentGroup, KGroupId(currentGroup));
QCOMPARE(currentEffectiveGroup, KGroupId(currentGroup));
}
QTEST_MAIN(KUserTest)
#include "kusertest.moc"

View File

@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "multiplugin.h"
#include "kcoreaddons_debug.h"
#include <QDebug>
#include <kexportplugin.h>
#include <kpluginfactory.h>
MultiPlugin1::MultiPlugin1(QObject *parent, const QVariantList &args)
: QObject(parent)
{
qCDebug(KCOREADDONS_DEBUG) << "MultiPlugin1" << args;
setObjectName(QLatin1String("MultiPlugin1"));
}
MultiPlugin2::MultiPlugin2(QObject *parent, const QVariantList &args)
: QObject(parent)
{
qCDebug(KCOREADDONS_DEBUG) << "MultiPlugin2" << args;
setObjectName(QLatin1String("MultiPlugin2"));
}
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89)
K_PLUGIN_FACTORY(MultiPluginFactory,
registerPlugin<MultiPlugin1>();
registerPlugin<MultiPlugin2>(QLatin1String("secondary")); // keyword
)
#else
K_PLUGIN_FACTORY(MultiPluginFactory, registerPlugin<MultiPlugin1>();)
#endif
#include "multiplugin.moc"
#include "moc_multiplugin.cpp"

View File

@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef MULTIPLUGIN_H
#define MULTIPLUGIN_H
#include <QObject>
class MultiPlugin1 : public QObject
{
Q_OBJECT
public:
MultiPlugin1(QObject *parent, const QVariantList &args);
};
class MultiPlugin2 : public QObject
{
Q_OBJECT
public:
MultiPlugin2(QObject *parent, const QVariantList &args);
};
#endif // MULTIPLUGIN_H

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <kpluginfactory.h>
class PluginWithoutMetaData : public QObject
{
Q_OBJECT
public:
PluginWithoutMetaData(const QObject *, const QVariantList &)
: QObject(){
};
};
K_PLUGIN_CLASS(PluginWithoutMetaData)
#include "pluginwithoutmetadata.moc"

View File

@@ -0,0 +1,15 @@
/*
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QObject>
#include <QtPlugin>
class MyQtPlugin : public QObject
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "somepluginid")
};
#include "qtplugin.moc"

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "kpluginfactory.h"
class StaticSimplePluginClass : public QObject
{
Q_OBJECT
public:
explicit StaticSimplePluginClass(QObject *, const QVariantList &)
{
}
};
K_PLUGIN_CLASS_WITH_JSON(StaticSimplePluginClass, "jsonplugin.json")
#include "statickpluginclass.moc"

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "kpluginfactory.h"
class OtherStaticSimplePluginClass : public QObject
{
Q_OBJECT
public:
explicit OtherStaticSimplePluginClass(QObject *, const QVariantList &)
{
}
};
K_PLUGIN_CLASS_WITH_JSON(OtherStaticSimplePluginClass, "jsonplugin.json")
#include "statickpluginclass_2.moc"

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "kpluginfactory.h"
class StaticPluginWithoutMetaData : public QObject
{
Q_OBJECT
public:
explicit StaticPluginWithoutMetaData(QObject *, const QVariantList &)
{
}
};
K_PLUGIN_CLASS(StaticPluginWithoutMetaData)
#include "staticpluginwithoutmetadata.moc"

View File

@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "unversionedplugin.h"
#include "kcoreaddons_debug.h"
#include <QDebug>
#include <kexportplugin.h>
#include <kpluginfactory.h>
UnversionedPlugin::UnversionedPlugin(QObject *parent, const QVariantList &args)
: QObject(parent)
{
qCDebug(KCOREADDONS_DEBUG) << "Created UnversionedPlugin with args" << args;
}
K_PLUGIN_FACTORY(UnversionedPluginFactory, registerPlugin<UnversionedPlugin>();)
#include "unversionedplugin.moc"
#include "moc_unversionedplugin.cpp"

View File

@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef UNVERSIONEDPLUGIN_H
#define UNVERSIONEDPLUGIN_H
#include <QObject>
class UnversionedPlugin : public QObject
{
Q_OBJECT
public:
UnversionedPlugin(QObject *parent, const QVariantList &args);
};
#endif // UNVERSIONEDPLUGIN_H

View File

@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "versionedplugin.h"
#include "kcoreaddons_debug.h"
#include <QDebug>
#include <kexportplugin.h>
#include <kpluginfactory.h>
VersionedPlugin::VersionedPlugin(QObject *parent, const QVariantList &args)
: QObject(parent)
{
qCDebug(KCOREADDONS_DEBUG) << "Created VersionedPlugin with args" << args;
}
K_PLUGIN_FACTORY(VersionedPluginFactory, registerPlugin<VersionedPlugin>();)
#if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 84)
K_EXPORT_PLUGIN_VERSION(5)
#endif
#include "versionedplugin.moc"
#include "moc_versionedplugin.cpp"

View File

@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2014 Alex Merry <alexmerry@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef VERSIONEDPLUGIN_H
#define VERSIONEDPLUGIN_H
#include <QObject>
class VersionedPlugin : public QObject
{
Q_OBJECT
public:
VersionedPlugin(QObject *parent, const QVariantList &args);
};
#endif // VERSIONEDPLUGIN_H