Skip to content

Commit 69e67fc

Browse files
committed
Add file/dir name normalization test
Check that a file/directory name with NFC encoding on the server ends up with the same encoding on the client, and that a subsequent discovery+sync will not upload differently encoded files. Same for an NFD encoded file/directory name.
1 parent 604b671 commit 69e67fc

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

test/testlocaldiscovery.cpp

+77
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,83 @@ private Q_SLOTS:
238238
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/.foo")));
239239
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/bar")));
240240
}
241+
242+
void testNameNormalization_data()
243+
{
244+
QTest::addColumn<QString>("correct");
245+
QTest::addColumn<QString>("incorrect");
246+
247+
const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00};
248+
const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast<const char *>(a_umlaut_composed_bytes));
249+
const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D);
250+
251+
QTest::newRow("a_umlaut decomposed") << a_umlaut_decomposed << a_umlaut_composed;
252+
QTest::newRow("a_umlaut composed") << a_umlaut_composed << a_umlaut_decomposed;
253+
}
254+
255+
// Test that when a file/directory name on the remote is encoded in NFC, the local name is encoded
256+
// in the same way, and that a subsequent sync does not change anything. And the same for NFD.
257+
void testNameNormalization()
258+
{
259+
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
260+
QFETCH_GLOBAL(bool, filesAreDehydrated);
261+
262+
QFETCH(QString, correct);
263+
QFETCH(QString, incorrect);
264+
265+
// Create an empty remote folder
266+
FakeFolder fakeFolder({FileInfo{}}, vfsMode, filesAreDehydrated);
267+
OperationCounter counter(fakeFolder);
268+
269+
// Create a file with an a-umlout in the "correct" normalization:
270+
fakeFolder.remoteModifier().mkdir(QStringLiteral("P"));
271+
fakeFolder.remoteModifier().mkdir(QStringLiteral("P/A"));
272+
fakeFolder.remoteModifier().insert(QStringLiteral("P/A/") + correct);
273+
274+
// Same for a directory, holding a "normal" file:
275+
fakeFolder.remoteModifier().mkdir(QStringLiteral("P/B") + correct);
276+
fakeFolder.remoteModifier().insert(QStringLiteral("P/B") + correct + QStringLiteral("/b"));
277+
278+
LocalDiscoveryTracker tracker;
279+
connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted);
280+
connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished);
281+
282+
// First sync: discover that there are files/directories on the server that are not yet synced to the local end
283+
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
284+
285+
// Check that locally we have the file and the directory with the correct names:
286+
{
287+
auto localState = fakeFolder.currentLocalState();
288+
QVERIFY(localState.find(QStringLiteral("P/A/") + correct) != nullptr); // check if the file exists
289+
QVERIFY(localState.find(QStringLiteral("P/B") + correct + QStringLiteral("/b")) != nullptr); // check if the file exists
290+
}
291+
292+
counter.reset();
293+
294+
qDebug() << "*** MARK"; // Log marker to check if a PUT/DELETE shows up in the second sync
295+
296+
// Force a full local discovery on the next sync, which forces a walk of the (local) file system, reading back names (and file sizes/mtimes/etc.)...
297+
fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("P")});
298+
tracker.startSyncFullDiscovery();
299+
300+
// ... and start the second sync:
301+
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
302+
303+
// If the normalization of the file/directory name did not change, no rename/move/etc. should have been detected, so check that the client didn't issue
304+
// any of these operations:
305+
QCOMPARE(counter.nDELETE, 0);
306+
QCOMPARE(counter.nMOVE, 0);
307+
QCOMPARE(counter.nPUT, 0);
308+
309+
// Check that the remote names are unchanged, and that no "incorrect" names have been introduced:
310+
FileInfo &remoteState = fakeFolder.currentRemoteState();
311+
QVERIFY(remoteState.find(QStringLiteral("P/A/") + correct) != nullptr); // check if the file still exists in the original normalization
312+
QVERIFY(remoteState.find(QStringLiteral("P/A/") + incorrect) == nullptr); // there should NOT be a file with another normalization
313+
QVERIFY(remoteState.find(QStringLiteral("P/B") + correct + QStringLiteral("/b"))
314+
!= nullptr); // check if the directory still exists in the original normalization
315+
QVERIFY(remoteState.find(QStringLiteral("P/B") + incorrect + QStringLiteral("/b"))
316+
== nullptr); // there should NOT be a directory with another normalization
317+
}
241318
};
242319

243320
QTEST_GUILESS_MAIN(TestLocalDiscovery)

test/testutils/syncenginetestutils.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -710,14 +710,19 @@ inline char *printDbData(const FileInfo &fi)
710710
return QTest::toString(QStringLiteral("FileInfo with %1 files(%2)").arg(files.size()).arg(files.join(QStringLiteral(", "))));
711711
}
712712

713+
/**
714+
* @brief Utility class that count the number of GET/PUT/MOVE/DELETE operations during a sync.
715+
*
716+
* This can be used for subsequent syncs, but the counters need to be reset.
717+
*/
713718
struct OperationCounter
714719
{
715720
int nGET = 0;
716721
int nPUT = 0;
717722
int nMOVE = 0;
718723
int nDELETE = 0;
719724

720-
OperationCounter() {};
725+
OperationCounter() { }
721726
OperationCounter(const OperationCounter &) = delete;
722727
OperationCounter(OperationCounter &&) = delete;
723728
void operator=(OperationCounter const &) = delete;

0 commit comments

Comments
 (0)