Files
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

556 lines
27 KiB
C++

/*
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.
*/
#include "testutils/testutils.h"
#include "libsync/common/filesystembase.h"
#include "libsync/common/utility.h"
#include "libsync/discoveryinfo.h"
#include "libsync/filesystem.h"
#ifdef Q_OS_WIN
// the lnk code requires some parts we usually no include
// also the include order is relevant
#undef WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlguid.h>
#include <shobjidl.h>
#include "common/utility_win.h"
#endif
#include <QTemporaryDir>
#include <QtTest>
using namespace Qt::Literals::StringLiterals;
using namespace std::chrono_literals;
using namespace OCC::Utility;
namespace OCC {
QSFERA_SYNC_EXPORT extern bool fsCasePreserving_override;
}
class TestUtility : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testOctetsToString()
{
QLocale::setDefault(QLocale(QStringLiteral("en")));
QCOMPARE(octetsToString(999), QString::fromLatin1("999 bytes"));
QCOMPARE(octetsToString(1024), QString::fromLatin1("1 kB"));
QCOMPARE(octetsToString(1364), QString::fromLatin1("1 kB"));
QCOMPARE(octetsToString(9110), QString::fromLatin1("9 kB"));
QCOMPARE(octetsToString(9910), QString::fromLatin1("10 kB"));
QCOMPARE(octetsToString(10240), QString::fromLatin1("10 kB"));
QCOMPARE(octetsToString(123456), QString::fromLatin1("121 kB"));
QCOMPARE(octetsToString(1234567), QString::fromLatin1("1.2 MB"));
QCOMPARE(octetsToString(12345678), QString::fromLatin1("11.8 MB"));
QCOMPARE(octetsToString(123456789), QString::fromLatin1("117.7 MB"));
QCOMPARE(octetsToString(1000LL * 1000 * 1000 * 5), QString::fromLatin1("4.66 GB"));
QCOMPARE(octetsToString(1), QString::fromLatin1("1 bytes"));
QCOMPARE(octetsToString(2), QString::fromLatin1("2 bytes"));
QCOMPARE(octetsToString(1024), QString::fromLatin1("1 kB"));
QCOMPARE(octetsToString(1024 * 1024), QString::fromLatin1("1.0 MB"));
QCOMPARE(octetsToString(1024LL * 1024 * 1024), QString::fromLatin1("1.00 GB"));
}
void testLaunchOnStartup()
{
QString postfix = QString::number(QRandomGenerator::global()->generate());
const QString appName = QStringLiteral("testLaunchOnStartup.%1").arg(postfix);
const QString guiName = QStringLiteral("LaunchOnStartup GUI Name");
QVERIFY(hasLaunchOnStartup(appName) == false);
setLaunchOnStartup(appName, guiName, true);
QVERIFY(hasLaunchOnStartup(appName) == true);
setLaunchOnStartup(appName, guiName, false);
QVERIFY(hasLaunchOnStartup(appName) == false);
}
void testDurationToDescriptiveString_data()
{
QTest::addColumn<bool>("useTranslation");
QTest::addColumn<std::chrono::milliseconds>("input");
QTest::addColumn<QString>("output1");
QTest::addColumn<QString>("output2");
const QDateTime current = QDateTime::currentDateTimeUtc();
QTest::newRow("0ms") << false << std::chrono::milliseconds(0ms) << QStringLiteral("0 second(s)") << QString();
QTest::newRow("5ms") << false << std::chrono::milliseconds(5ms) << QStringLiteral("0 second(s)") << QString();
QTest::newRow("1s") << false << std::chrono::milliseconds(1s) << QStringLiteral("1 second(s)") << QString();
QTest::newRow("1005ms") << false << std::chrono::milliseconds(1005ms) << QStringLiteral("1 second(s)") << QString();
QTest::newRow("56123ms") << false << std::chrono::milliseconds(56123ms) << QStringLiteral("56 second(s)") << QString();
QTest::newRow("90s") << false << std::chrono::milliseconds(90s) << QStringLiteral("2 minute(s)") << QStringLiteral("1 minute(s) 30 second(s)");
QTest::newRow("3h") << false << std::chrono::milliseconds(3h) << QStringLiteral("3 hour(s)") << QString();
QTest::newRow("3h + 20s") << false << std::chrono::milliseconds(3h + 20s) << QStringLiteral("3 hour(s)") << QString();
QTest::newRow("3h + 70s") << false << std::chrono::milliseconds(3h + 70s) << QStringLiteral("3 hour(s)") << QStringLiteral("3 hour(s) 1 minute(s)");
QTest::newRow("3h + 100s") << false << std::chrono::milliseconds(3h + 100s) << QStringLiteral("3 hour(s)") << QStringLiteral("3 hour(s) 2 minute(s)");
QTest::newRow("4years") << false << std::chrono::milliseconds(current.addYears(4).addMonths(5).addDays(2).addSecs(23 * 60 * 60) - current)
<< QStringLiteral("4 year(s)") << QStringLiteral("4 year(s) 5 month(s)");
QTest::newRow("2days") << false << std::chrono::milliseconds(current.addDays(2).addSecs(23 * 60 * 60) - current) << QStringLiteral("3 day(s)")
<< QStringLiteral("2 day(s) 23 hour(s)");
QTest::newRow("0ms translated") << true << std::chrono::milliseconds(0ms) << QStringLiteral("0 seconds") << QString();
QTest::newRow("5ms translated") << true << std::chrono::milliseconds(5ms) << QStringLiteral("0 seconds") << QString();
QTest::newRow("1s translated") << true << std::chrono::milliseconds(1s) << QStringLiteral("1 second") << QString();
QTest::newRow("1005ms translated") << true << std::chrono::milliseconds(1005ms) << QStringLiteral("1 second") << QString();
QTest::newRow("56123ms translated") << true << std::chrono::milliseconds(56123ms) << QStringLiteral("56 seconds") << QString();
QTest::newRow("90s translated") << true << std::chrono::milliseconds(90s) << QStringLiteral("2 minutes") << QStringLiteral("1 minute 30 seconds");
QTest::newRow("3h translated") << true << std::chrono::milliseconds(3h) << QStringLiteral("3 hours") << QString();
QTest::newRow("3h + 20s translated") << true << std::chrono::milliseconds(3h + 20s) << QStringLiteral("3 hours") << QString();
QTest::newRow("3h + 70s translated") << true << std::chrono::milliseconds(3h + 70s) << QStringLiteral("3 hours") << QStringLiteral("3 hours 1 minute");
QTest::newRow("3h + 100s translated") << true << std::chrono::milliseconds(3h + 100s) << QStringLiteral("3 hours")
<< QStringLiteral("3 hours 2 minutes");
QTest::newRow("4years translated") << true << std::chrono::milliseconds(current.addYears(4).addMonths(5).addDays(2).addSecs(23 * 60 * 60) - current)
<< QStringLiteral("4 years") << QStringLiteral("4 years 5 months");
QTest::newRow("2days translated") << true << std::chrono::milliseconds(current.addDays(2).addSecs(23 * 60 * 60) - current) << QStringLiteral("3 days")
<< QStringLiteral("2 days 23 hours");
}
void testDurationToDescriptiveString()
{
QFETCH(bool, useTranslation);
QFETCH(std::chrono::milliseconds, input);
QFETCH(QString, output1);
QFETCH(QString, output2);
QScopedPointer<QTranslator> translator;
if (useTranslation) {
translator.reset(new QTranslator(QCoreApplication::instance()));
QVERIFY(translator->load(QStringLiteral("desktop_en.ts"), QStringLiteral(":/client/translations/")));
QCoreApplication::instance()->installTranslator(translator.data());
}
QCOMPARE(durationToDescriptiveString1(input), output1);
QCOMPARE(durationToDescriptiveString2(input), output2.isEmpty() ? output1 : output2);
}
void testTimeAgo()
{
// Both times in same timezone
QDateTime d1 = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30+01:00"), Qt::ISODate);
QDateTime d2 = QDateTime::fromString(QStringLiteral("2015-01-23T09:20:30+01:00"), Qt::ISODate);
QString s = timeAgoInWords(d2, d1);
QCOMPARE(s, QLatin1String("1 day(s) ago"));
// Different timezones
QDateTime earlyTS = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30+01:00"), Qt::ISODate);
QDateTime laterTS = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30-01:00"), Qt::ISODate);
s = timeAgoInWords(earlyTS, laterTS);
QCOMPARE(s, QLatin1String("2 hour(s) ago"));
// 'Now' in whatever timezone
earlyTS = QDateTime::currentDateTime();
laterTS = earlyTS;
s = timeAgoInWords(earlyTS, laterTS );
QCOMPARE(s, QLatin1String("now"));
earlyTS = earlyTS.addSecs(-6);
s = timeAgoInWords(earlyTS, laterTS );
QCOMPARE(s, QLatin1String("less than a minute ago"));
}
void testFsCasePreserving()
{
QVERIFY(isMac() || isWindows() ? fsCasePreserving() : ! fsCasePreserving());
QScopedValueRollback<bool> scope(OCC::fsCasePreserving_override);
OCC::fsCasePreserving_override = 1;
QVERIFY(fsCasePreserving());
OCC::fsCasePreserving_override = 0;
QVERIFY(! fsCasePreserving());
}
void testFileNamesEqual()
{
QTemporaryDir dir;
QVERIFY(dir.isValid());
QDir dir2(dir.path());
QVERIFY(dir2.mkpath(QStringLiteral("1")));
if( !fsCasePreserving() ) {
QVERIFY(dir2.mkpath(QStringLiteral("TEST")));
}
QVERIFY(dir2.mkpath(QStringLiteral("test/TESTI")));
QVERIFY(dir2.mkpath(QStringLiteral("TESTI")));
QString a = dir.path();
QString b = dir.path();
QVERIFY(fileNamesEqual(a, b));
QVERIFY(fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/test"))); // both exist
QVERIFY(fileNamesEqual(a + QStringLiteral("/test/TESTI"), b + QStringLiteral("/test/../test/TESTI"))); // both exist
QScopedValueRollback<bool> scope(OCC::fsCasePreserving_override, true);
QVERIFY(fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/TEST"))); // both exist
QVERIFY(!fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/test/TESTI"))); // both are different
dir.remove();
}
void testIsChildOf_data()
{
QTest::addColumn<QString>("child");
QTest::addColumn<QString>("parent");
QTest::addColumn<OCC::FileSystem::ChildResults>("output");
QTest::addColumn<bool>("casePreserving");
int row = 0;
const auto add = [&row](const QString &child, const QString &parent, OCC::FileSystem::ChildResults result, bool casePreserving = true) {
const auto title =
QStringLiteral("Row: %1 CasePreserving %2: %3 is %4 child of %5")
.arg(QString::number(++row), casePreserving ? QStringLiteral("yes") : QStringLiteral("no"), child, QDebug::toString(result), parent);
QTest::addRow("%s", qUtf8Printable(title)) << child << parent << result << casePreserving;
};
add(QStringLiteral("/A/a"), QStringLiteral("/A"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("/A/a"), QStringLiteral("/A/a"), OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsEqual);
add(QStringLiteral("/A/a"), QStringLiteral("/A/a/"), OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsEqual);
add(QStringLiteral("/A/a/"), QStringLiteral("/A/a/"), OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsEqual);
add(QStringLiteral("/A/a/"), QStringLiteral("/A/a"), OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsEqual);
add(QStringLiteral("C:/A/a"), QStringLiteral("C:/A"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("C:/Aa"), QStringLiteral("C:/A"), OCC::FileSystem::ChildResult::IsNoChild);
add(QStringLiteral("A/a"), QStringLiteral("A"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("a/a"), QStringLiteral("A"), OCC::FileSystem::ChildResult::IsChild, true);
add(QStringLiteral("a/a"), QStringLiteral("A"), OCC::FileSystem::ChildResult::IsNoChild, false);
add(QStringLiteral("Aa"), QStringLiteral("A"), OCC::FileSystem::ChildResult::IsNoChild);
add(QStringLiteral("A/a"), QStringLiteral("A"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("A/a"), QStringLiteral("A/"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("ä/a"), QStringLiteral("ä/"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("Ä/è/a"), QStringLiteral("Ä/è/"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("Ä/a"), QStringLiteral("Ä/"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("https://foo/bar"), QStringLiteral("https://foo"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral("https://foo/bar"), QStringLiteral("http://foo"), OCC::FileSystem::ChildResult::IsNoChild);
add(QStringLiteral("https://foo/bar"), QStringLiteral("http://foo/foo"), OCC::FileSystem::ChildResult::IsNoChild);
add(QStringLiteral("https://foo/bar"), QString(), OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsParentEmpty);
#ifdef Q_OS_WIN
// QDir::cleanPath converts \\ only on Windows
add(QStringLiteral("C:/Program Files/test)"), QStringLiteral("C:/Program Files"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral(R"(C:\Program Files\test)"), QStringLiteral("C:/Program Files"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral(R"(C:\Program Files\test\)"), QStringLiteral("C:/Program Files\\"), OCC::FileSystem::ChildResult::IsChild);
add(QStringLiteral(R"(C:\Program Files\test\\\)"), QStringLiteral("C:/Program Files/test"),
OCC::FileSystem::ChildResult::IsChild | OCC::FileSystem::ChildResult::IsEqual);
#endif
}
void testIsChildOf()
{
const QScopedValueRollback<bool> rollback(OCC::fsCasePreserving_override);
QFETCH(QString, child);
QFETCH(QString, parent);
QFETCH(OCC::FileSystem::ChildResults, output);
QFETCH(bool, casePreserving);
OCC::fsCasePreserving_override = casePreserving;
QCOMPARE(OCC::FileSystem::isChildPathOf2(child, parent), output);
}
void testSanitizeForFileName_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
QTest::newRow("")
<< "foobar"
<< "foobar";
QTest::newRow("")
<< "a/b?c<d>e\\f:g*h|i\"j"
<< "abcdefghij";
QTest::newRow("")
<< QString::fromLatin1("a\x01 b\x1f c\x80 d\x9f")
<< "a b c d";
}
void testSanitizeForFileName()
{
QFETCH(QString, input);
QFETCH(QString, output);
QCOMPARE(sanitizeForFileName(input), output);
}
void testNormalizeEtag()
{
;
#define CHECK_NORMALIZE_ETAG(TEST, EXPECT) \
QCOMPARE(OCC::Utility::normalizeEtag(QStringLiteral(TEST)), QStringLiteral(EXPECT));
CHECK_NORMALIZE_ETAG("foo", "foo");
CHECK_NORMALIZE_ETAG("\"foo\"", "foo");
CHECK_NORMALIZE_ETAG("\"nar123\"", "nar123");
CHECK_NORMALIZE_ETAG("", "");
CHECK_NORMALIZE_ETAG("\"\"", "");
/* Test with -gzip (all combinaison) */
CHECK_NORMALIZE_ETAG("foo-gzip", "foo");
CHECK_NORMALIZE_ETAG("\"foo\"-gzip", "foo");
CHECK_NORMALIZE_ETAG("\"foo-gzip\"", "foo");
}
void testFileMetaData()
{
using namespace OCC::TestUtils;
using namespace OCC::FileSystem;
QTemporaryDir temp = createTempDir();
QFile tempFile(temp.filePath(QStringLiteral("testfile")));
QVERIFY(tempFile.open(QIODevice::WriteOnly));
QByteArray data(64, 'X');
QCOMPARE(tempFile.write(data), data.size());
tempFile.close();
const auto fn = tempFile.fileName();
const QString testKey = QStringLiteral("testKey");
const auto testValue(u"testValue"_s);
QVERIFY(!Tags::get(fn, testKey).has_value());
QVERIFY(Tags::set(fn, testKey, testValue));
QCOMPARE(Tags::get(fn, testKey).value(), testValue);
QVERIFY(Tags::remove(fn, testKey));
QVERIFY(!Tags::get(fn, testKey).has_value());
}
void testDirMetaData()
{
using namespace OCC::TestUtils;
using namespace OCC::FileSystem;
QTemporaryDir tempDir = createTempDir();
const auto fn = tempDir.path();
const QString testKey = QStringLiteral("testKey");
const QString testValue(u"testValue"_s);
QVERIFY(!Tags::get(fn, testKey).has_value());
QVERIFY(Tags::set(fn, testKey, testValue));
QCOMPARE(Tags::get(fn, testKey).value(), testValue);
QVERIFY(Tags::remove(fn, testKey));
QVERIFY(!Tags::get(fn, testKey).has_value());
}
#ifdef Q_OS_WIN
void testUtilityUnixTimeToLargeIntegerFiletime()
{
// Sun Jan 31 2038 23:00:00 GMT+0000
time_t y2k38 = 2148591600;
LARGE_INTEGER out;
OCC::Utility::UnixTimeToLargeIntegerFiletime(y2k38, &out);
// (2148591600 * 10000000) + 116444736000000000
QCOMPARE(out.QuadPart, 137930652000000000);
}
void initTestLNK()
{
// can be replaced with a private api cal in 6.9
// https://github.com/qt/qtbase/blob/a64bc279969b2cf8da6d0c07bf01683e57a8a30c/src/corelib/kernel/qfunctions_win.cpp#L48 in docker with the offscreen
// backend this will initialise com, if com is already initialised nothing will happen
HRESULT hres = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
// abort if failed
Q_ASSERT(SUCCEEDED(hres));
}
void testLNK()
{
auto mkLNK = [](const std::wstring &path, const std::filesystem::path &target) -> bool {
// https://learn.microsoft.com/en-us/windows/win32/shell/links?redirectedfrom=MSDN#Shellink_Creating_Shortcut
IShellLink *psl;
auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl);
if (SUCCEEDED(hres)) {
IPersistFile *ppf;
// Set the path to the shortcut target and add the description.
psl->SetPath(path.data());
psl->SetDescription(L"A test lnk");
// Query IShellLink for the IPersistFile interface, used for saving the
// shortcut in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf);
if (SUCCEEDED(hres)) {
hres = ppf->Save(target.native().data(), true);
if (SUCCEEDED(hres)) {
qDebug() << u"Created lnk" << target.native() << u"->" << path;
} else {
qCritical() << u"Failed to create lnk: Save" << OCC::Utility::formatWinError(hres);
}
ppf->Release();
} else {
qCritical() << u"Failed to create lnk: QueryInterface" << OCC::Utility::formatWinError(hres);
}
psl->Release();
} else {
qCritical() << u"Failed to create lnk: CoCreateInstance" << OCC::Utility::formatWinError(hres);
}
return SUCCEEDED(hres);
};
// the test relies on different change times for this binary and the lnk, sleep to be sure
QThread::currentThread()->sleep(1s);
const auto tmp = OCC::TestUtils::createTempDir();
{
// historically Qt handles llnk filles as symlinks and resolves them
// ensure w don't do the same
const QString qtTarget = tmp.filePath(u"test.lnk"_s);
const auto target = OCC::FileSystem::toFilesystemPath(qtTarget);
// we must not use toFilesystemPath as we would get a \\?\ path which is not supported with shortcuts
QVERIFY(mkLNK(qApp->applicationFilePath().toStdWString(), target));
auto entry = std::filesystem::directory_entry{target};
OCC::LocalInfo fileInfo(entry, ItemTypeFile);
const auto qFileInfo = QFileInfo(qtTarget);
const auto qFileInfoTarget = QFileInfo(qtTarget);
QVERIFY(qFileInfo.exists());
QVERIFY(entry.exists());
// fails as Qt resolves the path
QCOMPARE_NE(entry.file_size(), qFileInfo.size());
QCOMPARE_NE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), OCC::Utility::qDateTimeToTime_t(qFileInfo.metadataChangeTime()));
QCOMPARE(entry.file_size(), fileInfo.size());
QCOMPARE_NE(entry.file_size(), qFileInfoTarget.size());
QCOMPARE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), fileInfo.modtime());
}
{
const QString qtTarget = tmp.filePath(u"bad_test.lnk"_s);
const auto target = OCC::FileSystem::toFilesystemPath(qtTarget);
// create an invalid link
QVERIFY(mkLNK(L"", target));
auto entry = std::filesystem::directory_entry{target};
OCC::LocalInfo fileInfo(entry, ItemTypeFile);
const auto qFileInfo = QFileInfo(qtTarget);
QVERIFY(!qFileInfo.exists());
QVERIFY(entry.exists());
// Qt resolves the path, the target is invalid
QCOMPARE(0, qFileInfo.size());
QCOMPARE(0, OCC::Utility::qDateTimeToTime_t(qFileInfo.metadataChangeTime()));
QCOMPARE(entry.file_size(), fileInfo.size());
QCOMPARE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), fileInfo.modtime());
}
}
void cleanupTestLNK() { CoUninitialize(); }
#endif
void testFilesystem()
{
// validate that the different ways to access file metadata behave the same
auto entry = std::filesystem::directory_entry{OCC::FileSystem::toFilesystemPath(qApp->applicationFilePath())};
OCC::LocalInfo fileInfo(entry, ItemTypeFile);
QFileInfo qFileInfo(OCC::FileSystem::fromFilesystemPath(entry.path()));
QCOMPARE(entry.file_size(), fileInfo.size());
QCOMPARE(entry.file_size(), qFileInfo.size());
QCOMPARE(entry.file_size(), OCC::FileSystem::getSize(entry.path()));
QCOMPARE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), fileInfo.modtime());
QCOMPARE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), OCC::Utility::qDateTimeToTime_t(qFileInfo.metadataChangeTime()));
QCOMPARE(OCC::FileSystem::fileTimeToTime_t(entry.last_write_time()), OCC::FileSystem::getModTime(entry.path()));
auto inode = OCC::FileSystem::getInode(entry.path());
QVERIFY(inode.has_value());
QCOMPARE(fileInfo.inode(), inode.value());
}
void testCanonicalPath()
{
// we compare .native() for std::fiileystem::path, else Qt does actual file comparison
std::error_code ec;
// our build dir might be symlinked, ensure the input path is already canonical
auto path = OCC::FileSystem::fromFilesystemPath(std::filesystem::canonical(qApp->applicationFilePath().toStdString(), ec));
QVERIFY(ec.value() == 0);
QCOMPARE(OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native(), OCC::FileSystem::toFilesystemPath(path).native());
QCOMPARE(path, OCC::FileSystem::canonicalPath(path));
#ifdef Q_OS_WIN
path = u"C:/"_s;
QCOMPARE(path, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(path).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
path = u"C:"_s;
QCOMPARE("C:/"_L1, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(path).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test non-existing file, which relies on lexical normalization rather than actual canonical path
path = u"C:/fooo_bar"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(path, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(path).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
path = u"C:/fooo_bar/../foo/./../fooo_bar"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"C:/fooo_bar"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(
OCC::FileSystem::toFilesystemPath(u"C:/fooo_bar"_s).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test multiple consecutive slashes
path = u"C:///fooo_bar//test///file"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"C:/fooo_bar/test/file"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(u"C:/fooo_bar/test/file"_s).native(),
OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test trailing slashes
path = u"C:/fooo_bar///"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"C:/fooo_bar"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(
OCC::FileSystem::toFilesystemPath(u"C:/fooo_bar"_s).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test dot segments in middle of path
path = u"C:/fooo_bar/./test/./file"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"C:/fooo_bar/test/file"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(u"C:/fooo_bar/test/file"_s).native(),
OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test mixed slashes on Windows
path = u"C:\\fooo_bar/test\\file"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"C:/fooo_bar/test/file"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(u"C:/fooo_bar/test/file"_s).native(),
OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
// test UNC path
path = u"\\\\server\\share\\path"_s;
QCOMPARE(u"//server/share/path"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(u"//server/share/path"_s).native(),
OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
#else
// test non-existing file, which relies on lexical normalization rather than actual canonical path
path = u"/fooo_bar"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(path, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(path).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
path = u"/fooo_bar/../foo/./../fooo_bar"_s;
QVERIFY(!QFileInfo::exists(path));
QCOMPARE(u"/fooo_bar"_s, OCC::FileSystem::canonicalPath(path));
QCOMPARE(OCC::FileSystem::toFilesystemPath(u"/fooo_bar"_s).native(), OCC::FileSystem::canonicalPath(OCC::FileSystem::toFilesystemPath(path)).native());
#endif
}
};
QTEST_GUILESS_MAIN(TestUtility)
#include "testutility.moc"