Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,158 @@
set(KCOREADDONS_INTERNAL_SKIP_PLUGIN_INSTALLATION ON)
add_definitions(-DQT_FORCE_ASSERTS=1)
include(ECMAddTests)
include(ConfigureChecks.cmake) # configure checks for QFileSystemWatcher
include(../KF6CoreAddonsMacros.cmake)
find_package(Qt6Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT TARGET Qt6::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 Qt6::Core)
if(ENABLE_PCH)
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/pch.cpp)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/pch.cpp "/*empty file*/")
endif()
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/pch_tmp.h
"#pragma once\n"
"#include <QObject>\n"
"#include <QTest>\n"
"#include <QString>\n"
"#include <QDateTime>\n"
)
# avoid rebuilding if there was no change
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/pch_tmp.h" "${CMAKE_CURRENT_BINARY_DIR}/pch.h")
add_library(tests_pch STATIC ${CMAKE_CURRENT_BINARY_DIR}/pch.cpp)
target_link_libraries(tests_pch Qt6::Core Qt6::Test)
target_precompile_headers(tests_pch PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/pch.h)
endif()
configure_file(config-tests.h.in config-tests.h)
add_subdirectory(plugin)
macro(add_unit_test testname)
ecm_add_test(${testname}.cpp ${ARGN}
TEST_NAME ${testname}
LINK_LIBRARIES Qt6::Test KF6::CoreAddons autotests_static)
if(ENABLE_PCH)
target_precompile_headers(${testname} REUSE_FROM tests_pch)
endif()
endmacro()
add_unit_test(kaboutdatatest)
add_unit_test(kaboutdataapplicationdatatest)
add_unit_test(kautosavefiletest)
add_unit_test(kcompositejobtest)
add_unit_test(kformattest)
add_unit_test(kjobtest)
add_unit_test(kosreleasetest)
add_unit_test(krandomtest)
add_unit_test(kshareddatacachetest)
add_unit_test(kshelltest)
add_unit_test(kurlmimedatatest)
add_unit_test(kstringhandlertest)
add_unit_test(kmacroexpandertest)
add_unit_test(kusertest)
add_unit_test(kprocesslisttest)
add_unit_test(kfileutilstest)
add_unit_test(kfuzzymatchertest)
add_unit_test(knetworkmountstestcanonical)
add_unit_test(knetworkmountstestnoconfig)
add_unit_test(knetworkmountstestpaths)
add_unit_test(knetworkmountsteststatic)
add_unit_test(klibexectest)
add_unit_test(kmemoryinfotest)
add_unit_test(kruntimeplatformtest)
if (WIN32)
add_unit_test(klistopenfilesjobtest_win)
endif ()
if (UNIX)
add_unit_test(klistopenfilesjobtest_unix)
endif ()
if(NOT IOS)
add_unit_test(kprocesstest)
endif()
if(TARGET klistopenfilesjobtest_unix AND CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
find_package(Qt6Network ${REQUIRED_QT_VERSION} CONFIG QUIET)
target_link_libraries(klistopenfilesjobtest_unix Qt6::Network)
endif()
add_library(ktexttohtmlteststatic STATIC ${CMAKE_SOURCE_DIR}/src/lib/text/ktexttohtml.cpp ${CMAKE_SOURCE_DIR}/src/lib/text/kemoticonsparser.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 Qt6::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 ${CMAKE_SOURCE_DIR}/src/lib/text/kemoticonsparser.cpp)
target_link_libraries(ktexttohtmlbenchmarktest PUBLIC ktexttohtmlteststatic)
if(NOT IOS)
add_executable(kprocesstest_helper kprocesstest_helper.cpp)
target_link_libraries(kprocesstest_helper KF6::CoreAddons)
endif()
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_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} Qt6::Test KF6::CoreAddons autotests_static)
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} Qt6::Test KF6::CoreAddons autotests_static)
if(NOT WIN32)
target_link_libraries(${BACKEND_BENCHMARK_TARGET} Threads::Threads)
endif()
if(ENABLE_PCH AND NOT WIN32)
target_precompile_headers(${BACKEND_TEST_TARGET} REUSE_FROM tests_pch)
target_precompile_headers(${BACKEND_BENCHMARK_TARGET} REUSE_FROM tests_pch)
endif()
endforeach()
@@ -0,0 +1,9 @@
set(CMAKE_REQUIRED_LIBRARIES Qt6::Core)
check_cxx_source_compiles(
"#include <QtCore/QFileSystemWatcher>
int main()
{
QFileSystemWatcher *watcher = new QFileSystemWatcher();
delete watcher;
return 0;
}" HAVE_QFILESYSTEMWATCHER)
@@ -0,0 +1 @@
#cmakedefine01 ENABLE_BENCHMARKS
@@ -0,0 +1,22 @@
NAME="Name"
VERSION="100.5"
ID=theid
ID_LIKE="otherid otherotherid"
VERSION_CODENAME=versioncodename
VERSION_ID="500.1"
PRETTY_NAME="Vicuña — 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/"
@@ -0,0 +1,83 @@
/*
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();
};
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));
}
QTEST_MAIN(KAboutDataApplicationDataTest)
#include "kaboutdataapplicationdatatest.moc"
@@ -0,0 +1,413 @@
/*
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();
void testSetDesktopFileName();
void testCopying();
void testKAboutDataOrganizationDomain();
void testLicenseSPDXID();
void testLicenseOrLater();
void testProductName();
};
static const char AppName[] = "app";
static const char ProgramName[] = "ProgramName";
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));
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));
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);
aboutData.addAuthor(QStringLiteral("John"),
QStringLiteral("Stuff"),
QStringLiteral("john@john.com"),
QStringLiteral("john.com"),
QStringLiteral("ocsjohn"));
QCOMPARE(aboutData.authors().length(), 1);
QCOMPARE(aboutData.credits().length(), 0);
QCOMPARE(aboutData.authors().at(0).avatarUrl(), QUrl(QStringLiteral("https://store.kde.org/avatar/ocsjohn")));
// 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));
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 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(), QLatin1String(LicenseFileText));
}
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"
@@ -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"
@@ -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
@@ -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"
@@ -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
@@ -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"
@@ -0,0 +1,140 @@
/*
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::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);
}
}
@@ -0,0 +1,766 @@
/*
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 <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 = 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 spyDirty;
}
spyDirty.wait(50);
}
return 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 spyDeleted;
}
spyDeleted.wait(50);
}
return 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 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));
qCDebug(KCOREADDONS_DEBUG) << &watch;
// 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
qCDebug(KCOREADDONS_DEBUG) << &watch;
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 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);
qCDebug(KCOREADDONS_DEBUG) << &watch;
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
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
qCDebug(KCOREADDONS_DEBUG) << &watch;
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"
@@ -0,0 +1,94 @@
/*
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";
QTest::newRow("filename_with_numbers_parentheses") << "super.weird.filename(123).txt(54321)" << QStringList() << "super.weird.filename(123) (1).txt(54321)";
}
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"
@@ -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
@@ -0,0 +1,403 @@
/*
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 <QTimeZone>
#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, QTimeZone::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 frenchLocal = QLocale::French;
KFormat formatFrench(frenchLocal);
QCOMPARE(formatFrench.formatRelativeDateTime(testDateTime, QLocale::LongFormat), QStringLiteral("Dimanche 3 octobre 2021 at 05:33"));
}
QTEST_MAIN(KFormatTest)
#include "moc_kformattest.cpp"
@@ -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
@@ -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") << QStringLiteral("") << 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=") << QStringLiteral("")
<< 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)
{
QList<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<QList<Range>>("expectedRanges");
QTest::addColumn<bool>("matchingOnly");
QTest::newRow("Emtpy") << QStringLiteral("") << QStringLiteral("") << QList<Range>{} << true;
QTest::newRow("Hello") << QStringLiteral("Hlo") << QStringLiteral("Hello") << QList<Range>{{0, 1}, {3, 2}} << true;
QTest::newRow("lll") << QStringLiteral("lll") << QStringLiteral("SVisualLoggerLogsList") << QList<Range>{{7, 1}, {13, 1}, {17, 1}} << true;
QTest::newRow("Sort") << QStringLiteral("sort") << QStringLiteral("SorT") << QList<Range>{{0, 4}} << true;
QTest::newRow("Unmatching") << QStringLiteral("git") << QStringLiteral("gti") << QList<Range>{} << true;
QTest::newRow("UnmatchingWithAllMatches") << QStringLiteral("git") << QStringLiteral("gti") << QList<Range>{{0, 1}, {2, 1}} << false;
}
void KFuzzyMatcherTest::testMatchedRanges()
{
QFETCH(QString, pattern);
QFETCH(QString, string);
QFETCH(bool, matchingOnly);
using Range = QPair<int, int>;
QFETCH(QList<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"
@@ -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
@@ -0,0 +1,591 @@
/*
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 <QList>
#include <QMetaEnum>
#include <QSignalSpy>
#include <QTest>
#include <QTimer>
#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");
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);
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);
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);
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);
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);
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);
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);
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));
processedChanged_spy.clear();
totalChanged_spy.clear();
percentChanged_spy.clear();
/**
* Try again with Files as the progress unit
*/
testJob->setProgressUnit(KJob::Files);
testJob->setProcessedSize(16);
QCOMPARE(percentChanged_spy.size(), 0);
testJob->setTotalFiles(5);
QCOMPARE(percentChanged_spy.size(), 1);
QCOMPARE(percentChanged_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(0));
testJob->setProcessedFiles(2);
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<QList<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 << QList<Action>{a, b};
}
}
}
}
void KJobTest::testEmitAtMostOnce()
{
auto *const job = setupErrorResultFinished();
QSignalSpy destroyed_spy(job, &QObject::destroyed);
QFETCH(bool, autoDelete);
job->setAutoDelete(autoDelete);
QFETCH(QList<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::testElapseTime()
{
m_innerJob = new WaitJob();
QTimer::singleShot(10, m_innerJob, &WaitJob::makeItFinish);
QVERIFY(m_innerJob->exec());
QCOMPARE_GE(m_innerJob->elapsedTime(), 10);
QCOMPARE_LE(m_innerJob->elapsedTime(), 14);
}
void KJobTest::testElapseTimeSuspendResume()
{
m_innerJob = new WaitJob();
QSignalSpy suspended_spy(m_innerJob, &KJob::suspended);
QSignalSpy resume_spy(m_innerJob, &KJob::resumed);
QTimer::singleShot(5, m_innerJob, [this]() {
m_innerJob->suspend();
QCOMPARE_GE(m_innerJob->elapsedTime(), 5);
QCOMPARE_LE(m_innerJob->elapsedTime(), 8);
});
QTimer::singleShot(10, m_innerJob, &KJob::resume);
QTimer::singleShot(15, m_innerJob, [this]() {
m_innerJob->suspend();
QCOMPARE_GE(m_innerJob->elapsedTime(), 10);
QCOMPARE_LE(m_innerJob->elapsedTime(), 14);
});
QTimer::singleShot(20, m_innerJob, [this]() {
m_innerJob->resume();
m_innerJob->makeItFinish();
});
QVERIFY(m_innerJob->exec());
QCOMPARE_GE(m_innerJob->elapsedTime(), 10);
QCOMPARE_LE(m_innerJob->elapsedTime(), 14);
QCOMPARE(suspended_spy.count(), 2);
QCOMPARE(resume_spy.count(), 2);
}
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()
{
startElapsedTimer();
}
bool WaitJob::doResume()
{
return true;
}
bool WaitJob::doSuspend()
{
return true;
}
void WaitJob::makeItFinish()
{
emitResult();
}
void TestJobUiDelegate::connectJob(KJob *job)
{
QVERIFY(job->uiDelegate() != nullptr);
}
#include "moc_kjobtest.cpp"
@@ -0,0 +1,115 @@
/*
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 testElapseTime();
void testElapseTimeSuspendResume();
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;
bool doResume() override;
bool doSuspend() override;
public Q_SLOTS:
void makeItFinish();
};
#endif
@@ -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 <QFileInfo>
#include <QObject>
#include <QTest>
#include <KLibexec>
class KLibexecTest : public QObject
{
Q_OBJECT
const QString m_relative = QStringLiteral("fakeexec/kf6");
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"
@@ -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.1-or-later
*/
#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"
@@ -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.1-or-later
*/
#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
@@ -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.1-or-later
*/
#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"
@@ -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.1-or-later
*/
#ifndef KLISTOPENFILESJOBTEST_WIN_H
#define KLISTOPENFILESJOBTEST_WIN_H
#include <QObject>
class KListOpenFilesJobTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testNotSupported();
};
#endif
@@ -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(QStringLiteral("expanded"))
{
}
protected:
bool expandMacro(QChar ch, QStringList &ret) override
{
if (ch == QLatin1Char('m')) {
ret = QStringList(exp);
return true;
}
return false;
}
};
class MyWExpander : public KWordMacroExpander
{
QString exp;
public:
MyWExpander()
: KWordMacroExpander()
, exp(QStringLiteral("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 << QStringLiteral("Restaurant \"Chew It\"");
map.insert(QLatin1Char('n'), list);
list.clear();
list << QStringLiteral("element1") << QStringLiteral("'element2'");
map.insert(QLatin1Char('l'), list);
s = QStringLiteral("%% text %l text %n");
QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("% text element1 'element2' text Restaurant \"Chew It\""));
s = QStringLiteral("text \"%l %n\" text");
QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("text \"element1 'element2' Restaurant \"Chew It\"\" text"));
QHash<QChar, QString> map2;
map2.insert(QLatin1Char('a'), QStringLiteral("%n"));
map2.insert(QLatin1Char('f'), QStringLiteral("filename.txt"));
map2.insert(QLatin1Char('u'), QStringLiteral("https://www.kde.org/index.html"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant \"Chew It\""));
s = QStringLiteral("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(QStringLiteral("foo"), QStringLiteral("%n"));
smap.insert(QStringLiteral("file"), QStringLiteral("filename.txt"));
smap.insert(QStringLiteral("url"), QStringLiteral("https://www.kde.org/index.html"));
smap.insert(QStringLiteral("name"), QStringLiteral("Restaurant \"Chew It\""));
s = QStringLiteral("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 = QStringLiteral("%foo - %file - %url - %name");
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\""));
s = QStringLiteral("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 = QStringLiteral("%{foo} - %{file} - %{url} - %{name}");
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\""));
s = QStringLiteral("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 = QStringLiteral("Title: %{file} %{url");
QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: filename.txt %{url"));
s = QStringLiteral(" * 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 << QStringLiteral("Restaurant \"Chew It\"");
map.insert(QLatin1Char('n'), list);
list.clear();
list << QStringLiteral("element1") << QStringLiteral("'element2'") << QStringLiteral("\"element3\"");
map.insert(QLatin1Char('l'), list);
#ifdef Q_OS_WIN
s = QStringLiteral("text %l %n text");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map),
QLatin1String("text element1 'element2' \\^\"element3\\^\" \"Restaurant \"\\^\"\"Chew It\"\\^\" text"));
s = QStringLiteral("text \"%l %n\" text");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map),
QLatin1String("text \"element1 'element2' \"\\^\"\"element3\"\\^\"\" Restaurant \"\\^\"\"Chew It\"\\^\"\"\" text"));
#else
s = QStringLiteral("text %l %n text");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 ''\\''element2'\\''' '\"element3\"' 'Restaurant \"Chew It\"' text"));
s = QStringLiteral("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(QLatin1Char('a'), QStringLiteral("%n"));
map2.insert(QLatin1Char('f'), QStringLiteral("filename.txt"));
map2.insert(QLatin1Char('u'), QStringLiteral("https://www.kde.org/index.html"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant \"Chew It\""));
#ifdef Q_OS_WIN
s = QStringLiteral("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 = QStringLiteral("kedit --caption %n %f");
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant 'Chew It'"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
s = QStringLiteral("kedit --caption \"%n\" %f");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant \"Chew It\""));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \"\\^\"\"Chew It\"\\^\"\"\" filename.txt"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant %HOME%"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant %PERCENT_SIGN%HOME%PERCENT_SIGN%\" filename.txt"));
s = QStringLiteral("kedit c:\\%f");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit c:\\filename.txt"));
s = QStringLiteral("kedit \"c:\\%f\"");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\filename.txt\""));
map2.insert(QLatin1Char('f'), QStringLiteral("\"filename.txt\""));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\\\\"\\^\"\"filename.txt\"\\^\"\"\""));
map2.insert(QLatin1Char('f'), QStringLiteral("path\\"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\path\\\\\"\"\""));
#else
s = QStringLiteral("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 = QStringLiteral("kedit --caption %n %f");
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant 'Chew It'"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption 'Restaurant '\\''Chew It'\\''' filename.txt"));
s = QStringLiteral("kedit --caption \"%n\" %f");
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant \"Chew It\""));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\\"Chew It\\\"\" filename.txt"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant $HOME"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\$HOME\" filename.txt"));
map2.insert(QLatin1Char('n'), QStringLiteral("Restaurant `echo hello`"));
QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\`echo hello\\`\" filename.txt"));
s = QStringLiteral("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 = QStringLiteral("( 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 = QStringLiteral("subst %m but not %n equ %%");
mx1.expandMacros(s);
QCOMPARE(s, QLatin1String("subst expanded but not %n equ %"));
MyWExpander mx2;
s = QStringLiteral("subst %macro but not %not equ %%");
mx2.expandMacros(s);
QCOMPARE(s, QLatin1String("subst expanded but not %not equ %"));
}
QTEST_MAIN(KMacroExpanderTest)
#include "kmacroexpandertest.moc"
@@ -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"
@@ -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
@@ -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"
@@ -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
@@ -0,0 +1,138 @@
/*
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("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("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("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("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"
@@ -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
@@ -0,0 +1,156 @@
/*
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::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"
@@ -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
@@ -0,0 +1,107 @@
/*
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("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"
@@ -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
@@ -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("Vicuña — 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"
@@ -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.1-or-later
*/
#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"
@@ -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.1-or-later
*/
#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
@@ -0,0 +1,127 @@
/*
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
// not all distros have /bin/true, e.g. NixOS lacks it
if (!QFile::exists(QStringLiteral("/bin/true"))) {
QSKIP("This test needs /bin/true");
}
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"
@@ -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;
}
@@ -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"
@@ -0,0 +1,118 @@
/*
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 <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;
class KRandomTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void test_randomString();
void test_randomStringThreaded();
void test_shuffle();
};
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());
}
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);
QList<int> vector = {1, 2, 3, 4, 5};
const QList<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];
}
}
// Manually implemented to dispatch to child process if needed to support
// subtests
int main([[maybe_unused]] int argc, char *argv[])
{
binpath = argv[0];
KRandomTest randomTest;
return QTest::qExec(&randomTest);
}
#include "krandomtest.moc"
@@ -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{QStringLiteral("mobile"), QStringLiteral("bigscreen")};
QCOMPARE(KRuntimePlatform::runtimePlatform(), expected);
}
};
QTEST_GUILESS_MAIN(KRuntimePlatformTest)
#include "kruntimeplatformtest.moc"
@@ -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"
@@ -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"
@@ -0,0 +1,263 @@
#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()
{
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"));
// But if a string already contains whitespaces, no need to insert ZWSPs
QTest::newRow("camelCase with whitespaces") << "camelCase PascalCase"
<< "camelCase PascalCase";
// However, single quote and other tricks should still work.
QTest::newRow("single quote with whitespaces") << "foo(bar) PascalCase'camelCase"
<< QString(QStringLiteral("foo") + ZWSP + QStringLiteral("(bar) PascalCase") + WJ
+ QStringLiteral("'camelCase"));
}
// 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"
@@ -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
@@ -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"
@@ -0,0 +1,537 @@
/*
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]");
urls << QStringLiteral("en.wikipedia.org/wiki/%C3%98_(disambiguation)");
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);
}
#define s(x) QStringLiteral(x)
void KTextToHTMLTest::testEmoticons_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
QTest::newRow("empty") << QString() << QString();
QTest::newRow("trailing") << s("Hello :-)") << s("Hello 🙂");
QTest::newRow("embedded") << s("Hello :-) How are you?") << s("Hello 🙂 How are you?");
QTest::newRow("leading") << s(":-( Bye") << s("🙁 Bye");
QTest::newRow("embedded-html") << s("<b>:(</b>") << s("&lt;b&gt;:(&lt;/b&gt;");
QTest::newRow("html-attribute") << s("<img src=\"...\" title=\":-)\" />") << s("&lt;img src=&quot;...&quot; title=&quot;:-)&quot; /&gt;");
QTest::newRow("broken-1") << s(":))") << s("😆");
QTest::newRow("broken-4") << s(":D and :-D are not the same as :d and :-d") << s("😀 and 😀 are not the same as :d and :-d");
QTest::newRow("broken-5") << s("4d:D>:)F:/&gt;:-(:Pu:d9") << s("4d:D&gt;:)F:/&amp;gt;:-(:Pu:d9");
QTest::newRow("broken-6") << s("&lt;::pvar:: test=1&gt;") << s("&amp;lt;::pvar:: test=1&amp;gt;");
QTest::newRow("working-5") << s("(&amp;)") << s("(&amp;amp;)");
QTest::newRow("working-6") << s("Bla (&nbsp;)") << s("Bla (&amp;nbsp;)");
QTest::newRow("working-7") << s("a non-breaking space (&nbsp;) character") << s("a non-breaking space (&amp;nbsp;) character");
QTest::newRow("angle-bracket-1") << s(">:)") << s("😈");
QTest::newRow("angle-bracket-2") << s("<b>:)") << s("&lt;b&gt;:)");
}
void KTextToHTMLTest::testEmoticons()
{
QFETCH(QString, input);
QFETCH(QString, output);
QCOMPARE(KTextToHTML::convertToHtml(input, KTextToHTML::ReplaceSmileys | KTextToHTML::IgnoreUrls), output);
}
void KTextToHTMLTest::testEmoticonsNoReplace_data()
{
QTest::addColumn<QString>("input");
QTest::newRow("empty") << QString();
QTest::newRow("no-space-spearator") << s("Very happy! :-):-)");
QTest::newRow("broken-2") << s("In a sentence:practical example");
QTest::newRow("broken-8") << s("-+-[-:-(-:-)-:-]-+-");
QTest::newRow("broken-9") << s("::shrugs::");
QTest::newRow("broken-10") << s(":Ptesting:P");
QTest::newRow("working-1") << s(":):)");
QTest::newRow("working-4") << s("http://www.kde.org");
QTest::newRow("working-3") << s("End of sentence:p");
QTest::newRow("xmpp-1") << s("an xmpp emoticon (%)");
}
void KTextToHTMLTest::testEmoticonsNoReplace()
{
QFETCH(QString, input);
QCOMPARE(KTextToHTML::convertToHtml(input, KTextToHTML::ReplaceSmileys | KTextToHTML::IgnoreUrls), input);
}
#include "moc_ktexttohtmltest.cpp"
@@ -0,0 +1,29 @@
/*
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();
void testEmoticons_data();
void testEmoticons();
void testEmoticonsNoReplace_data();
void testEmoticonsNoReplace();
private:
void testGetUrl2(const QString &left, const QString &right);
};
#endif
@@ -0,0 +1,205 @@
/*
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>
#include <kurlmimedata_p.h>
#include <memory>
using namespace Qt::StringLiterals;
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;
}
#if HAVE_QTDBUS
void KUrlMimeDataTest::testSameProcessCopyPaste()
{
if (!KUrlMimeData::isDocumentsPortalAvailable()) {
QSKIP("Documents portal not available");
}
std::unique_ptr<QMimeData> mimeData(new QMimeData());
QList<QUrl> urls{QUrl(QStringLiteral("desktop:/foo")), QUrl(QStringLiteral("desktop:/bar"))};
KUrlMimeData::setUrls(urls, {}, mimeData.get());
mimeData->setData(QStringLiteral("application/vnd.portal.filetransfer"), QFile::encodeName(QStringLiteral("bad-transfer-id")));
// Should fail because the transfer id is invalid
QList<QUrl> decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData.get());
QVERIFY(decodedURLs.isEmpty());
// Should succeed because we put in the source id, so the portal is skipped
KUrlMimeData::setSourceId(mimeData.get());
decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData.get());
QVERIFY(!decodedURLs.isEmpty());
QCOMPARE(decodedURLs, urls);
// Should fail because we put in a bad source id, so the portal is not skipped
mimeData->setData(QStringLiteral("application/x-kde-source-id"), QStringLiteral("bad-source-id").toUtf8());
decodedURLs = KUrlMimeData::urlsFromMimeData(mimeData.get());
QVERIFY(decodedURLs.isEmpty());
}
#endif // HAVE_QTDBUS
void KUrlMimeDataTest::initTestCase()
{
#if HAVE_QTDBUS
qputenv("KCOREADDONS_FORCE_DOCUMENTS_PORTAL", "1"_ba);
#endif
}
#include "moc_kurlmimedatatest.cpp"
@@ -0,0 +1,29 @@
/*
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 "config-kdirwatch.h"
#include <QObject>
class KUrlMimeDataTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testURLList();
void testOneURL();
void testFromQUrl();
void testMostLocalUrlList_data();
void testMostLocalUrlList();
#if HAVE_QTDBUS
void testSameProcessCopyPaste();
#endif
void initTestCase();
};
#endif
@@ -0,0 +1,262 @@
/*
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());
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"
@@ -0,0 +1,58 @@
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS Widgets)
if (NOT TARGET Qt6::Widgets)
return()
endif()
add_library(plugin_classes plugins.cpp)
generate_export_header(plugin_classes)
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 KF6::CoreAddons autotests_static plugin_classes)
target_link_libraries(plugin_classes Qt6::Core)
kcoreaddons_add_plugin(qtplugin SOURCES qtplugin.cpp INSTALL_NAMESPACE "namespace")
ecm_mark_as_test(qtplugin)
target_link_libraries(qtplugin KF6::CoreAddons autotests_static)
kcoreaddons_add_plugin(pluginwithoutmetadata SOURCES pluginwithoutmetadata.cpp INSTALL_NAMESPACE "namespace")
ecm_mark_as_test(pluginwithoutmetadata)
target_link_libraries(pluginwithoutmetadata KF6::CoreAddons autotests_static)
kcoreaddons_add_plugin(widgetsplugin SOURCES widgetsplugin.cpp INSTALL_NAMESPACE "widgets")
ecm_mark_as_test(widgetsplugin)
target_link_libraries(widgetsplugin KF6::CoreAddons Qt6::Widgets)
add_library(org.kde.test MODULE qtplugin.cpp)
target_link_libraries(org.kde.test KF6::CoreAddons)
ecm_add_tests(
kpluginmetadatatest.cpp
kpluginfactorytest.cpp
${autotests_OPTIONAL_SRCS}
LINK_LIBRARIES Qt6::Test Qt6::Widgets KF6::CoreAddons autotests_static
)
if (NOT WIN32)
target_link_libraries(kpluginfactorytest plugin_classes)
endif()
kcoreaddons_add_plugin(static_jsonplugin_cmake_macro SOURCES statickpluginclass.cpp INSTALL_NAMESPACE "staticnamespace" STATIC)
target_link_libraries(static_jsonplugin_cmake_macro KF6::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 KF6::CoreAddons autotests_static)
kcoreaddons_add_plugin(static_plugin_without_metadata SOURCES staticpluginwithoutmetadata.cpp INSTALL_NAMESPACE "staticnamespace3" STATIC)
target_link_libraries(static_plugin_without_metadata KF6::CoreAddons autotests_static)
kcoreaddons_add_plugin(org.kde.test-staticplugin SOURCES statickpluginclass_3.cpp INSTALL_NAMESPACE "rdnstatic" STATIC)
target_link_libraries(org.kde.test-staticplugin KF6::CoreAddons autotests_static)
kcoreaddons_target_static_plugins(kpluginfactorytest NAMESPACE "staticnamespace")
kcoreaddons_target_static_plugins(kpluginfactorytest NAMESPACE "staticnamespace2")
kcoreaddons_target_static_plugins(kpluginmetadatatest NAMESPACE "staticnamespace")
kcoreaddons_target_static_plugins(kpluginmetadatatest NAMESPACE "staticnamespace2")
kcoreaddons_target_static_plugins(kpluginmetadatatest NAMESPACE "rdnstatic")
# Make the last one use the target by targets list instead of namespace
kcoreaddons_target_static_plugins(kpluginmetadatatest TARGETS static_plugin_without_metadata)
@@ -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"]
}
}
@@ -0,0 +1,6 @@
{
"KPlugin": {
"Description": "This is another plugin",
"MimeTypes": [ "text/html" ]
}
}
@@ -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
}
@@ -0,0 +1,29 @@
// 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"
#include "plugins.h"
class SimplePluginClass : public MyPlugin
{
Q_OBJECT
public:
explicit SimplePluginClass(QObject *parent, const QVariantList &args)
: MyPlugin(parent)
{
setProperty("arg", args.isEmpty() ? QVariant() : args.first());
}
};
class SimplePluginClass2 : public MyPlugin2
{
Q_OBJECT
public:
explicit SimplePluginClass2(QObject *parent)
: MyPlugin2(parent)
{
}
};
K_PLUGIN_FACTORY_WITH_JSON(MyFactory, "data/jsonplugin.json", registerPlugin<SimplePluginClass>(); registerPlugin<SimplePluginClass2>();)
#include "kpluginclass.moc"
@@ -0,0 +1,148 @@
/*
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>
#ifndef Q_OS_WIN
#include "plugins.h"
#endif
class KPluginFactoryTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testCreate()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
#ifndef Q_OS_WIN
KPluginMetaData data(QStringLiteral("namespace/jsonplugin_cmake_macro"));
QVERIFY(data.isValid());
KPluginFactory::Result<KPluginFactory> factoryResult = KPluginFactory::loadFactory(data);
auto factory = factoryResult.plugin;
QVERIFY(factory);
const QString testProp = QStringLiteral("testme");
QObject *obj = factory->create<MyPlugin>(this, {testProp});
QVERIFY(obj);
QCOMPARE(obj->metaObject()->className(), "SimplePluginClass");
QCOMPARE(obj->property("arg").toString(), testProp);
QObject *obj2 = factory->create<MyPlugin2>(this);
QVERIFY(obj2);
QCOMPARE(obj2->metaObject()->className(), "SimplePluginClass2");
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;
#endif
}
void testPluginWithoutMetaData()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
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 testCreateUsingUtilityMethods()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QStringLiteral("namespace/jsonplugin_cmake_macro")));
QVERIFY(result.plugin);
QCOMPARE(result.plugin->metaObject()->className(), "SimplePluginClass");
QVERIFY(result.errorString.isEmpty());
QCOMPARE(result.errorReason, KPluginFactory::NO_PLUGIN_ERROR);
delete result.plugin;
}
void testCreateUsingUtilityMethodsErrorHandling()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
{
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QFINDTESTDATA("data/jsonplugin.json")));
QVERIFY(!result.plugin);
QCOMPARE(result.errorReason, KPluginFactory::INVALID_PLUGIN);
}
{
// it is a valid plugin, but does not contain a KPluginFactory
QVERIFY(QPluginLoader(QStringLiteral("namespace/qtplugin")).instance());
auto result = KPluginFactory::instantiatePlugin<QObject>(KPluginMetaData(QStringLiteral("namespace/qtplugin")));
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("namespace/jsonplugin_cmake_macro")));
QVERIFY(!result.plugin);
QCOMPARE(result.errorReason, KPluginFactory::INVALID_KPLUGINFACTORY_INSTANTIATION);
QVERIFY(result.errorText.contains(QStringLiteral("KPluginFactoryTest")));
}
}
void testStaticPlugins()
{
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace"));
QCOMPARE(plugins.count(), 1);
auto factory = KPluginFactory::loadFactory(plugins.first()).plugin;
QCOMPARE(factory->metaObject()->className(), "static_jsonplugin_cmake_macro_factory");
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"));
}
void testInstantiateWidget()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
KPluginMetaData data(QStringLiteral("widgets/widgetsplugin"), KPluginMetaData::AllowEmptyMetaData);
QVERIFY(data.isValid());
auto factory = KPluginFactory::loadFactory(data).plugin;
QVERIFY(factory);
// This has two constructors, make sure we choose the first one that takes args
auto obj = factory->create<QWidget>(nullptr, QVariantList{true});
QVERIFY(obj);
QVERIFY(obj->property("firstarg").toBool());
delete obj;
}
};
QTEST_MAIN(KPluginFactoryTest)
#include "kpluginfactorytest.moc"
@@ -0,0 +1,549 @@
/*
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 <kpluginmetadata.h>
#include <QLocale>
#include <QLoggingCategory>
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;
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()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
QString location;
location = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).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("data/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("namespace/jsonplugin_cmake_macro")));
KPluginMetaData fromFullPath(location);
KPluginMetaData fromRelativePath(QStringLiteral("namespace/jsonplugin_cmake_macro"));
KPluginMetaData fromRawData(jsonDoc.object(), location);
auto description = QStringLiteral("This is a plugin");
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"
" \"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"
" }\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"));
QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time"));
QCOMPARE(m.category(), QStringLiteral("Date and Time"));
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/"));
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());
}
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()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
QTest::addColumn<QString>("inputAbsolute");
QTest::addColumn<QString>("pluginPath");
// 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("/namespace/jsonplugin_cmake_macro"));
QVERIFY2(!shlibLoader.fileName().isEmpty(), "Could not find jsonplugin_cmake_macro");
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 (path.endsWith(QLatin1String(".json"))) {
return KPluginMetaData::fromJsonFile(path);
} else {
return KPluginMetaData(path);
}
};
KPluginMetaData mdAbsolute = createMetaData(inputAbsolute);
QVERIFY(mdAbsolute.isValid());
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 = QStringLiteral("namespace/") + QFileInfo(inputAbsolute).baseName();
} else {
inputRelative = QDir::current().relativeFilePath(inputAbsolute);
}
QVERIFY2(QDir::isRelativePath(inputRelative), qPrintable(inputRelative));
KPluginMetaData mdRelative = createMetaData(inputRelative);
QVERIFY(mdRelative.isValid());
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));
// We should not be normalizing files that have not been openened, so both arguments should be unchanged.
KPluginMetaData mdFromJson1(json, pluginRelative);
KPluginMetaData mdFromJson2(json, inputRelative);
QCOMPARE(mdFromJson1.fileName(), pluginRelative);
QCOMPARE(mdFromJson2.fileName(), inputRelative);
}
void testFindPlugins()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
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("namespace"));
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); // ID is not the filename, it is set in the JSON metadata
QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin"));
QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin")); // ID is not the filename, it is set in the JSON metadata
// filter accepts none
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) {
return false;
});
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 0);
// filter accepts all
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) {
return true;
});
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
// mimetype filter. Only one match, jsonplugin2 is specific to text/html.
auto supportTextPlain = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QStringLiteral("text/plain"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextPlain);
QCOMPARE(plugins.size(), 1);
QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
// mimetype filter. Two matches, both support text/html, via inheritance.
auto supportTextHtml = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QStringLiteral("text/html"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextHtml);
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin"));
// mimetype filter with invalid mimetype
auto supportDoesNotExist = [](const KPluginMetaData &metaData) {
return metaData.supportsMimeType(QStringLiteral("does/not/exist"));
};
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportDoesNotExist);
QCOMPARE(plugins.size(), 0);
// by plugin invalid id
KPluginMetaData plugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("invalidid"));
QVERIFY(!plugin.isValid());
// absolute path, no filter
plugins = KPluginMetaData::findPlugins(QCoreApplication::applicationDirPath() + QStringLiteral("/namespace"));
std::sort(plugins.begin(), plugins.end(), sortPlugins);
QCOMPARE(plugins.size(), 2);
QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin"));
// This plugin has no explicit pluginId and will fall back to basename of file
const KPluginMetaData validPlugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("jsonplugin_cmake_macro"));
QVERIFY(validPlugin.isValid());
QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
}
void testStaticPlugins()
{
#if defined(QT_SHARED)
// in a static Qt build we can already have other built-in static plugins here
QCOMPARE(QPluginLoader::staticPlugins().count(), 0);
#endif
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()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
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(), 3);
for (auto plugin : plugins) {
QVERIFY(plugin.isValid());
if (plugin.pluginId() == QLatin1String("pluginwithoutmetadata")) {
QVERIFY(plugin.rawData().isEmpty());
} else if (plugin.pluginId() == QLatin1String("jsonplugin_cmake_macro") || plugin.pluginId() == QLatin1String("qtplugin")) {
QVERIFY(!plugin.rawData().isEmpty());
} else {
QVERIFY2(false, "should not be reachable");
}
}
const auto pluginInvalid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("pluginwithoutmetadata"));
const auto pluginValid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), //
QStringLiteral("pluginwithoutmetadata"),
KPluginMetaData::AllowEmptyMetaData);
QVERIFY(!pluginInvalid.isValid());
QVERIFY(pluginValid.isValid());
}
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()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
KPluginMetaData data(QStringLiteral("org.kde.test"));
QVERIFY(data.isValid());
QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test"));
}
// We can't use the plain pluginId as a classname, thus check if the replacement compiles and the original identifies it used for lookup
void testReverseDomanNotationStaticPlugin()
{
KPluginMetaData data = KPluginMetaData::findPluginById(QStringLiteral("rdnstatic"), QStringLiteral("org.kde.test-staticplugin"));
QVERIFY(data.isValid());
QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test-staticplugin"));
}
void testFindingPluginInAppDirFirst()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
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);
}
void testMetaDataQDebugOperator()
{
#if !defined(QT_SHARED)
QSKIP("Dynamic plugin loading not supported with a static Qt build");
#endif
const auto list = KPluginMetaData::findPlugins(QStringLiteral("namespace"));
qDebug() << list.first();
qDebug() << list;
qDebug() << (QList<KPluginMetaData>() << list << list << list);
}
void benchmarkFindPlugins()
{
QSKIP("Skipped by default");
int loopIterations = 10;
QBENCHMARK {
for (int i = 0; i < loopIterations; ++i) {
const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData);
Q_UNUSED(plugins)
}
}
QList<KPluginMetaData> plugins =
KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData);
QBENCHMARK {
for (int i = 0; i < loopIterations; ++i) {
plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData);
}
}
}
};
QTEST_MAIN(KPluginMetaDataTest)
#include "kpluginmetadatatest.moc"
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "plugins.h"
MyPlugin::MyPlugin(QObject *parent)
: QObject(parent)
{
}
MyPlugin::~MyPlugin() = default;
MyPlugin2::~MyPlugin2() = default;
MyPlugin2::MyPlugin2(QObject *parent)
: QObject(parent)
{
}
#include "moc_plugins.cpp"
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "plugin_classes_export.h"
#include <QObject>
class PLUGIN_CLASSES_EXPORT MyPlugin : public QObject
{
Q_OBJECT
public:
MyPlugin(QObject *parent);
~MyPlugin() override;
};
class PLUGIN_CLASSES_EXPORT MyPlugin2 : public QObject
{
Q_OBJECT
public:
MyPlugin2(QObject *parent);
~MyPlugin2() override;
};
@@ -0,0 +1,22 @@
/*
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:
// Add a default arg to make sure we do not get an ambiguity compiler error
PluginWithoutMetaData(const QObject *, const QVariantList &args = {})
: QObject()
{
Q_UNUSED(args)
};
};
K_PLUGIN_CLASS(PluginWithoutMetaData)
#include "pluginwithoutmetadata.moc"
@@ -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" FILE "data/jsonplugin2.json")
};
#include "qtplugin.moc"
@@ -0,0 +1,22 @@
// 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:
// Next to the assertion below, ensure that we have no ambiguity!
explicit StaticSimplePluginClass(QObject *parent, const KPluginMetaData &data = {})
: QObject(parent)
{
// We have added a default arg, but KPluginFactory should still provide the valid metadata instead of the default one
Q_ASSERT(data.isValid());
}
};
K_PLUGIN_CLASS_WITH_JSON(StaticSimplePluginClass, "data/jsonplugin.json")
#include "statickpluginclass.moc"
@@ -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 *)
{
}
};
K_PLUGIN_CLASS_WITH_JSON(OtherStaticSimplePluginClass, "data/jsonplugin.json")
#include "statickpluginclass_2.moc"
@@ -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 AnotherStaticSimplePluginClass : public QObject
{
Q_OBJECT
public:
explicit AnotherStaticSimplePluginClass(QObject *)
{
}
};
K_PLUGIN_CLASS_WITH_JSON(AnotherStaticSimplePluginClass, "data/jsonplugin.json")
#include "statickpluginclass_3.moc"
@@ -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 *)
{
}
};
K_PLUGIN_CLASS(StaticPluginWithoutMetaData)
#include "staticpluginwithoutmetadata.moc"
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include <QWidget>
#include <kpluginfactory.h>
class MyWidget : public QWidget
{
public:
explicit MyWidget(QWidget *parent, const KPluginMetaData &data)
: QWidget(parent)
{
Q_ASSERT(!data.fileName().isEmpty());
}
};
class MyWidgetArgs : public QWidget
{
public:
explicit MyWidgetArgs(QWidget *parent, const KPluginMetaData &data, const QVariantList &args)
: QWidget(parent)
{
Q_ASSERT(!data.fileName().isEmpty());
setProperty("firstarg", args.first());
}
};
class MyWidgetNoMetaData : public QWidget
{
public:
explicit MyWidgetNoMetaData(QWidget *parent)
: QWidget(parent)
{
}
};
// Ignore the duplicate registration, just make sure it compiles
K_PLUGIN_FACTORY(MyWidgetFactory, registerPlugin<MyWidgetArgs>(); registerPlugin<MyWidget>(); registerPlugin<MyWidgetNoMetaData>();)
#include "widgetsplugin.moc"