add more spacing
[personal-kdebase.git] / runtime / kioslave / trash / tests / testtrash.cpp
blob02bb579ad2f3e3b2c89e0dd7d9ee13414de835c3
1 /* This file is part of the KDE project
2 Copyright (C) 2004 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include <qtest_kde.h>
22 #include "kio_trash.h"
23 #include "testtrash.h"
24 #include <ktemporaryfile.h>
26 #include <kurl.h>
27 #include <klocale.h>
28 #include <kapplication.h>
29 #include <kio/netaccess.h>
30 #include <kio/job.h>
31 #include <kio/copyjob.h>
32 #include <kio/deletejob.h>
33 #include <kdebug.h>
34 #include <kcmdlineargs.h>
35 #include <kconfiggroup.h>
37 #include <QDir>
38 #include <QFileInfo>
39 #include <QVector>
40 #include <kjobuidelegate.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45 #include <kfileitem.h>
46 #include <kstandarddirs.h>
47 #include <kio/chmodjob.h>
48 #include <kio/directorysizejob.h>
50 // There are two ways to test encoding things:
51 // * with utf8 filenames
52 // * with latin1 filenames -- not sure this still works.
54 #define UTF8TEST 1
57 QString TestTrash::homeTmpDir() const
59 return QDir::homePath() + "/.kde/testtrash/";
62 QString TestTrash::readOnlyDirPath() const
64 return homeTmpDir() + QString( "readonly" );
67 QString TestTrash::otherTmpDir() const
69 // This one needs to be on another partition
70 return "/tmp/testtrash/";
73 QString TestTrash::utf8FileName() const
75 return QString( "test" ) + QChar( 0x2153 ); // "1/3" character, not part of latin1
78 QString TestTrash::umlautFileName() const
80 return QString( "umlaut" ) + QChar( 0xEB );
83 static void removeFile( const QString& trashDir, const QString& fileName )
85 QDir dir;
86 dir.remove( trashDir + fileName );
87 QVERIFY( !QDir( trashDir + fileName ).exists() );
90 static void removeDir( const QString& trashDir, const QString& dirName )
92 QDir dir;
93 dir.rmdir( trashDir + dirName );
94 QVERIFY( !QDir( trashDir + dirName ).exists() );
97 static void removeDirRecursive( const QString& dir )
99 if ( QFile::exists( dir ) ) {
101 // Make it work even with readonly dirs, like trashReadOnlyDirFromHome() creates
102 KUrl u = KUrl::fromPath( dir );
103 KFileItem fileItem( u, "inode/directory", KFileItem::Unknown );
104 KFileItemList fileItemList;
105 fileItemList.append( fileItem );
106 KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0200, 0200, QString(), QString(), true /*recursive*/, KIO::HideProgressInfo );
107 KIO::NetAccess::synchronousRun( chmodJob, 0 );
109 KIO::Job* delJob = KIO::del(u, KIO::HideProgressInfo);
110 if (!KIO::NetAccess::synchronousRun(delJob, 0))
111 kFatal() << "Couldn't delete " << dir ;
115 void TestTrash::initTestCase()
117 #ifdef UTF8TEST
118 // Assume utf8 system
119 setenv( "LC_ALL", "en_GB.utf-8", 1 );
120 setenv( "KDE_UTF8_FILENAMES", "true", 1 );
121 #else
122 // Ensure a known QFile::encodeName behavior for trashUtf8FileFromHome
123 // However this assume your $HOME doesn't use characters from other locales...
124 setenv( "LC_ALL", "en_GB.ISO-8859-1", 1 );
125 unsetenv( "KDE_UTF8_FILENAMES" );
126 #endif
128 setenv( "KDE_FORK_SLAVES", "yes", true );
131 m_trashDir = KGlobal::dirs()->localxdgdatadir() + "Trash";
132 kDebug() << "setup: using trash directory " << m_trashDir;
134 // Look for another writable partition than $HOME (not mandatory)
135 TrashImpl impl;
136 impl.init();
138 TrashImpl::TrashDirMap trashDirs = impl.trashDirectories();
139 TrashImpl::TrashDirMap topDirs = impl.topDirectories();
140 bool foundTrashDir = false;
141 m_otherPartitionId = 0;
142 m_tmpIsWritablePartition = false;
143 m_tmpTrashId = -1;
144 QVector<int> writableTopDirs;
145 for ( TrashImpl::TrashDirMap::ConstIterator it = trashDirs.begin(); it != trashDirs.end() ; ++it ) {
146 if ( it.key() == 0 ) {
147 QVERIFY( it.value() == m_trashDir );
148 QVERIFY( topDirs.find( 0 ) == topDirs.end() );
149 foundTrashDir = true;
150 } else {
151 QVERIFY( topDirs.find( it.key() ) != topDirs.end() );
152 const QString topdir = topDirs[it.key()];
153 if ( QFileInfo( topdir ).isWritable() ) {
154 writableTopDirs.append( it.key() );
155 if ( topdir == "/tmp/" ) {
156 m_tmpIsWritablePartition = true;
157 m_tmpTrashId = it.key();
158 kDebug() << "/tmp is on its own partition (trashid=" << m_tmpTrashId << "), some tests will be skipped";
159 removeFile( it.value(), "/info/fileFromOther.trashinfo" );
160 removeFile( it.value(), "/files/fileFromOther" );
161 removeFile( it.value(), "/info/symlinkFromOther.trashinfo" );
162 removeFile( it.value(), "/files/symlinkFromOther" );
163 removeFile( it.value(), "/info/trashDirFromOther.trashinfo" );
164 removeFile( it.value(), "/files/trashDirFromOther/testfile" );
165 removeDir( it.value(), "/files/trashDirFromOther" );
170 for ( QVector<int>::const_iterator it = writableTopDirs.begin(); it != writableTopDirs.end(); ++it ) {
171 const QString topdir = topDirs[ *it ];
172 const QString trashdir = trashDirs[ *it ];
173 QVERIFY( !topdir.isEmpty() );
174 QVERIFY( !trashDirs.isEmpty() );
175 if ( topdir != "/tmp/" || // we'd prefer not to use /tmp here, to separate the tests
176 ( writableTopDirs.count() > 1 ) ) // but well, if we have no choice, take it
178 m_otherPartitionTopDir = topdir;
179 m_otherPartitionTrashDir = trashdir;
180 m_otherPartitionId = *it;
181 kDebug() << "OK, found another writable partition: topDir=" << m_otherPartitionTopDir
182 << " trashDir=" << m_otherPartitionTrashDir << " id=" << m_otherPartitionId << endl;
183 break;
186 // Check that m_trashDir got listed
187 QVERIFY( foundTrashDir );
188 if ( m_otherPartitionTrashDir.isEmpty() )
189 kWarning() << "No writable partition other than $HOME found, some tests will be skipped" ;
191 // Start with a clean base dir
192 removeDirRecursive( homeTmpDir() );
193 removeDirRecursive( otherTmpDir() );
195 QDir dir; // TT: why not a static method?
196 bool ok = dir.mkdir( homeTmpDir() );
197 if ( !ok )
198 kFatal() << "Couldn't create " << homeTmpDir() ;
199 ok = dir.mkdir( otherTmpDir() );
200 if ( !ok )
201 kFatal() << "Couldn't create " << otherTmpDir() ;
203 // Start with a clean trash too
204 removeDirRecursive( m_trashDir );
207 void TestTrash::urlTestFile()
209 const KUrl url = TrashImpl::makeURL( 1, "fileId", QString() );
210 QCOMPARE( url.url(), QString( "trash:/1-fileId" ) );
212 int trashId;
213 QString fileId;
214 QString relativePath;
215 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
216 QVERIFY( ok );
217 QCOMPARE( QString::number( trashId ), QString::fromLatin1( "1" ) );
218 QCOMPARE( fileId, QString::fromLatin1( "fileId" ) );
219 QCOMPARE( relativePath, QString() );
222 void TestTrash::urlTestDirectory()
224 const KUrl url = TrashImpl::makeURL( 1, "fileId", "subfile" );
225 QCOMPARE( url.url(), QString::fromLatin1( "trash:/1-fileId/subfile" ) );
227 int trashId;
228 QString fileId;
229 QString relativePath;
230 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
231 QVERIFY( ok );
232 QCOMPARE( trashId, 1 );
233 QCOMPARE( fileId, QString::fromLatin1( "fileId" ) );
234 QCOMPARE( relativePath, QString::fromLatin1( "subfile" ) );
237 void TestTrash::urlTestSubDirectory()
239 const KUrl url = TrashImpl::makeURL( 1, "fileId", "subfile/foobar" );
240 QCOMPARE( url.url(), QString::fromLatin1( "trash:/1-fileId/subfile/foobar" ) );
242 int trashId;
243 QString fileId;
244 QString relativePath;
245 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
246 QVERIFY( ok );
247 QCOMPARE( trashId, 1 );
248 QCOMPARE( fileId, QString::fromLatin1( "fileId" ) );
249 QCOMPARE( relativePath, QString::fromLatin1( "subfile/foobar" ) );
252 static void checkInfoFile( const QString& infoPath, const QString& origFilePath )
254 kDebug() << infoPath;
255 QFileInfo info( infoPath );
256 QVERIFY( info.exists() );
257 QVERIFY( info.isFile() );
258 KConfig infoFile( info.absoluteFilePath() );
259 KConfigGroup group = infoFile.group( "Trash Info" );
260 if ( !group.exists() )
261 kFatal() << "no Trash Info group in " << info.absoluteFilePath() ;
262 const QString origPath = group.readEntry( "Path" );
263 QVERIFY( !origPath.isEmpty() );
264 QVERIFY( origPath == QUrl::toPercentEncoding( origFilePath, "/" ) );
265 if (origFilePath.contains(QChar(0x2153)) || origFilePath.contains('%') || origFilePath.contains("umlaut")) {
266 QVERIFY(origPath.contains('%'));
267 } else {
268 QVERIFY(!origPath.contains('%'));
270 const QString date = group.readEntry( "DeletionDate" );
271 QVERIFY( !date.isEmpty() );
272 QVERIFY( date.contains( "T" ) );
275 static void createTestFile( const QString& path )
277 QFile f( path );
278 if ( !f.open( QIODevice::WriteOnly ) )
279 kFatal() << "Can't create " << path ;
280 f.write( "Hello world\n", 12 );
281 f.close();
282 QVERIFY( QFile::exists( path ) );
285 void TestTrash::trashFile( const QString& origFilePath, const QString& fileId )
287 // setup
288 if ( !QFile::exists( origFilePath ) )
289 createTestFile( origFilePath );
290 KUrl u;
291 u.setPath( origFilePath );
293 // test
294 KIO::Job* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
295 QMap<QString, QString> metaData;
296 bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
297 if ( !ok )
298 kError() << "moving " << u << " to trash failed with error " << KIO::NetAccess::lastError() << " " << KIO::NetAccess::lastErrorString() << endl;
299 QVERIFY( ok );
300 if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
301 kDebug() << " TESTS SKIPPED";
302 } else {
303 checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
305 QFileInfo files( m_trashDir + "/files/" + fileId );
306 QVERIFY( files.isFile() );
307 QVERIFY( files.size() == 12 );
310 // coolo suggests testing that the original file is actually gone, too :)
311 QVERIFY( !QFile::exists( origFilePath ) );
313 QVERIFY( !metaData.isEmpty() );
314 bool found = false;
315 QMap<QString, QString>::ConstIterator it = metaData.begin();
316 for ( ; it != metaData.end() ; ++it ) {
317 if ( it.key().startsWith( "trashURL" ) ) {
318 const QString origPath = it.key().mid( 9 );
319 KUrl trashURL( it.value() );
320 kDebug() << trashURL;
321 QVERIFY( !trashURL.isEmpty() );
322 QVERIFY( trashURL.protocol() == "trash" );
323 int trashId = 0;
324 if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition )
325 trashId = m_tmpTrashId;
326 QVERIFY( trashURL.path() == "/" + QString::number( trashId ) + '-' + fileId );
327 found = true;
330 QVERIFY( found );
333 void TestTrash::trashFileFromHome()
335 const QString fileName = "fileFromHome";
336 trashFile( homeTmpDir() + fileName, fileName );
338 // Do it again, check that we got a different id
339 trashFile( homeTmpDir() + fileName, fileName + "_1" );
342 void TestTrash::trashPercentFileFromHome()
344 const QString fileName = "file%2f";
345 trashFile( homeTmpDir() + fileName, fileName );
348 void TestTrash::trashUtf8FileFromHome()
350 #ifdef UTF8TEST
351 const QString fileName = utf8FileName();
352 trashFile( homeTmpDir() + fileName, fileName );
353 #endif
356 void TestTrash::trashUmlautFileFromHome()
358 const QString fileName = umlautFileName();
359 trashFile( homeTmpDir() + fileName, fileName );
362 void TestTrash::testTrashNotEmpty()
364 KConfig cfg( "trashrc", KConfig::SimpleConfig );
365 const KConfigGroup group = cfg.group( "Status" );
366 QVERIFY( group.exists() );
367 QVERIFY( group.readEntry( "Empty", true ) == false );
370 void TestTrash::trashFileFromOther()
372 const QString fileName = "fileFromOther";
373 trashFile( otherTmpDir() + fileName, fileName );
376 void TestTrash::trashFileIntoOtherPartition()
378 if ( m_otherPartitionTrashDir.isEmpty() ) {
379 kDebug() << " - SKIPPED";
380 return;
382 const QString fileName = "testtrash-file";
383 const QString origFilePath = m_otherPartitionTopDir + fileName;
384 const QString fileId = fileName;
385 // cleanup
386 QFile::remove( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo" );
387 QFile::remove( m_otherPartitionTrashDir + "/files/" + fileId );
389 // setup
390 if ( !QFile::exists( origFilePath ) )
391 createTestFile( origFilePath );
392 KUrl u;
393 u.setPath( origFilePath );
395 // test
396 KIO::Job* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
397 QMap<QString, QString> metaData;
398 bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
399 QVERIFY( ok );
400 // Note that the Path stored in the info file is relative, on other partitions (#95652)
401 checkInfoFile( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo", fileName );
403 QFileInfo files( m_otherPartitionTrashDir + "/files/" + fileId );
404 QVERIFY( files.isFile() );
405 QVERIFY( files.size() == 12 );
407 // coolo suggests testing that the original file is actually gone, too :)
408 QVERIFY( !QFile::exists( origFilePath ) );
410 QVERIFY( !metaData.isEmpty() );
411 bool found = false;
412 QMap<QString, QString>::ConstIterator it = metaData.begin();
413 for ( ; it != metaData.end() ; ++it ) {
414 if ( it.key().startsWith( "trashURL" ) ) {
415 const QString origPath = it.key().mid( 9 );
416 KUrl trashURL( it.value() );
417 kDebug() << trashURL;
418 QVERIFY( !trashURL.isEmpty() );
419 QVERIFY( trashURL.protocol() == "trash" );
420 QVERIFY( trashURL.path() == QString( "/%1-%2" ).arg( m_otherPartitionId ).arg( fileId ) );
421 found = true;
424 QVERIFY( found );
427 void TestTrash::trashFileOwnedByRoot()
429 KUrl u( "/etc/passwd" );
430 const QString fileId = "passwd";
432 KIO::CopyJob* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
433 job->setUiDelegate(0); // no skip dialog, thanks
434 QMap<QString, QString> metaData;
435 bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
436 QVERIFY( !ok );
437 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
438 const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
439 QVERIFY( !QFile::exists( infoPath ) );
441 QFileInfo files( m_trashDir + "/files/" + fileId );
442 QVERIFY( !files.exists() );
444 QVERIFY( QFile::exists( u.path() ) );
447 void TestTrash::trashSymlink( const QString& origFilePath, const QString& fileId, bool broken )
449 // setup
450 const char* target = broken ? "/nonexistent" : "/tmp";
451 bool ok = ::symlink( target, QFile::encodeName( origFilePath ) ) == 0;
452 QVERIFY( ok );
453 KUrl u;
454 u.setPath( origFilePath );
456 // test
457 KIO::Job* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
458 ok = job->exec();
459 QVERIFY( ok );
460 if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
461 kDebug() << " TESTS SKIPPED";
462 return;
464 checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
466 QFileInfo files( m_trashDir + "/files/" + fileId );
467 QVERIFY( files.isSymLink() );
468 QVERIFY( files.readLink() == QFile::decodeName( target ) );
469 QVERIFY( !QFile::exists( origFilePath ) );
472 void TestTrash::trashSymlinkFromHome()
474 const QString fileName = "symlinkFromHome";
475 trashSymlink( homeTmpDir() + fileName, fileName, false );
478 void TestTrash::trashSymlinkFromOther()
480 const QString fileName = "symlinkFromOther";
481 trashSymlink( otherTmpDir() + fileName, fileName, false );
484 void TestTrash::trashBrokenSymlinkFromHome()
486 const QString fileName = "brokenSymlinkFromHome";
487 trashSymlink( homeTmpDir() + fileName, fileName, true );
490 void TestTrash::trashDirectory( const QString& origPath, const QString& fileId )
492 kDebug() << fileId;
493 // setup
494 if ( !QFileInfo( origPath ).exists() ) {
495 QDir dir;
496 bool ok = dir.mkdir( origPath );
497 QVERIFY( ok );
499 createTestFile( origPath + "/testfile" );
500 KUrl u; u.setPath( origPath );
502 // test
503 KIO::Job* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
504 QVERIFY( job->exec() );
505 if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
506 kDebug() << " TESTS SKIPPED";
507 return;
509 checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origPath );
511 QFileInfo filesDir( m_trashDir + "/files/" + fileId );
512 QVERIFY( filesDir.isDir() );
513 QFileInfo files( m_trashDir + "/files/" + fileId + "/testfile" );
514 QVERIFY( files.exists() );
515 QVERIFY( files.isFile() );
516 QVERIFY( files.size() == 12 );
517 QVERIFY( !QFile::exists( origPath ) );
520 void TestTrash::trashDirectoryFromHome()
522 QString dirName = "trashDirFromHome";
523 trashDirectory( homeTmpDir() + dirName, dirName );
524 // Do it again, check that we got a different id
525 trashDirectory( homeTmpDir() + dirName, dirName + "_1" );
528 void TestTrash::trashReadOnlyDirFromHome()
530 const QString dirName = readOnlyDirPath();
531 QDir dir;
532 bool ok = dir.mkdir( dirName );
533 QVERIFY( ok );
534 // #130780
535 const QString subDirPath = dirName + "/readonly_subdir";
536 ok = dir.mkdir( subDirPath );
537 QVERIFY( ok );
538 createTestFile( subDirPath + "/testfile_in_subdir" );
539 ::chmod( QFile::encodeName( subDirPath ), 0500 );
541 trashDirectory( dirName, "readonly" );
544 void TestTrash::trashDirectoryFromOther()
546 QString dirName = "trashDirFromOther";
547 trashDirectory( otherTmpDir() + dirName, dirName );
550 void TestTrash::tryRenameInsideTrash()
552 kDebug() << " with file_move";
553 KIO::Job* job = KIO::file_move( KUrl("trash:/0-tryRenameInsideTrash"), KUrl("trash:/foobar"), -1, KIO::HideProgressInfo );
554 bool worked = KIO::NetAccess::synchronousRun( job, 0 );
555 QVERIFY( !worked );
556 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
558 kDebug() << " with move";
559 job = KIO::move( KUrl("trash:/0-tryRenameInsideTrash"), KUrl("trash:/foobar"), KIO::HideProgressInfo );
560 worked = KIO::NetAccess::synchronousRun( job, 0 );
561 QVERIFY( !worked );
562 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
565 void TestTrash::delRootFile()
567 // test deleting a trashed file
568 KIO::Job* delJob = KIO::del(KUrl("trash:/0-fileFromHome"), KIO::HideProgressInfo);
569 bool ok = KIO::NetAccess::synchronousRun(delJob, 0);
570 QVERIFY( ok );
572 QFileInfo file( m_trashDir + "/files/fileFromHome" );
573 QVERIFY( !file.exists() );
574 QFileInfo info( m_trashDir + "/info/fileFromHome.trashinfo" );
575 QVERIFY( !info.exists() );
577 // trash it again, we might need it later
578 const QString fileName = "fileFromHome";
579 trashFile( homeTmpDir() + fileName, fileName );
582 void TestTrash::delFileInDirectory()
584 // test deleting a file inside a trashed directory -> not allowed
585 KIO::Job* delJob = KIO::del(KUrl("trash:/0-trashDirFromHome/testfile"), KIO::HideProgressInfo);
586 bool ok = KIO::NetAccess::synchronousRun(delJob, 0);
587 QVERIFY( !ok );
588 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
590 QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
591 QVERIFY( dir.exists() );
592 QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
593 QVERIFY( file.exists() );
594 QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
595 QVERIFY( info.exists() );
598 void TestTrash::delDirectory()
600 // test deleting a trashed directory
601 KIO::Job* delJob = KIO::del(KUrl("trash:/0-trashDirFromHome"), KIO::HideProgressInfo);
602 bool ok = KIO::NetAccess::synchronousRun(delJob, 0);
603 QVERIFY( ok );
605 QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
606 QVERIFY( !dir.exists() );
607 QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
608 QVERIFY( !file.exists() );
609 QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
610 QVERIFY( !info.exists() );
612 // trash it again, we'll need it later
613 QString dirName = "trashDirFromHome";
614 trashDirectory( homeTmpDir() + dirName, dirName );
617 // KIO::NetAccess::stat() doesn't set HideProgressInfo - but it's not much work to do it ourselves:
618 static bool MyNetAccess_stat(const KUrl& url, KIO::UDSEntry& entry)
620 KIO::StatJob * statJob = KIO::stat( url, KIO::HideProgressInfo );
621 bool ok = KIO::NetAccess::synchronousRun(statJob, 0);
622 if (ok)
623 entry = statJob->statResult();
624 return ok;
626 static bool MyNetAccess_exists(const KUrl& url)
628 KIO::UDSEntry dummy;
629 return MyNetAccess_stat(url, dummy);
632 void TestTrash::statRoot()
634 KUrl url( "trash:/" );
635 KIO::UDSEntry entry;
636 bool ok = MyNetAccess_stat( url, entry );
637 QVERIFY( ok );
638 KFileItem item( entry, url );
639 QVERIFY( item.isDir() );
640 QVERIFY( !item.isLink() );
641 QVERIFY( item.isReadable() );
642 QVERIFY( item.isWritable() );
643 QVERIFY( !item.isHidden() );
644 QVERIFY( item.name() == "." );
647 void TestTrash::statFileInRoot()
649 KUrl url( "trash:/0-fileFromHome" );
650 KIO::UDSEntry entry;
651 bool ok = MyNetAccess_stat( url, entry );
652 QVERIFY( ok );
653 KFileItem item( entry, url );
654 QVERIFY( item.isFile() );
655 QVERIFY( !item.isDir() );
656 QVERIFY( !item.isLink() );
657 QVERIFY( item.isReadable() );
658 QVERIFY( !item.isWritable() );
659 QVERIFY( !item.isHidden() );
660 QVERIFY( item.text() == "fileFromHome" );
663 void TestTrash::statDirectoryInRoot()
665 KUrl url( "trash:/0-trashDirFromHome" );
666 KIO::UDSEntry entry;
667 bool ok = MyNetAccess_stat( url, entry );
668 QVERIFY( ok );
669 KFileItem item( entry, url );
670 QVERIFY( item.isDir() );
671 QVERIFY( !item.isLink() );
672 QVERIFY( item.isReadable() );
673 QVERIFY( !item.isWritable() );
674 QVERIFY( !item.isHidden() );
675 QVERIFY( item.text() == "trashDirFromHome" );
678 void TestTrash::statSymlinkInRoot()
680 KUrl url( "trash:/0-symlinkFromHome" );
681 KIO::UDSEntry entry;
682 bool ok = MyNetAccess_stat( url, entry );
683 QVERIFY( ok );
684 KFileItem item( entry, url );
685 QVERIFY( item.isLink() );
686 QVERIFY( item.linkDest() == "/tmp" );
687 QVERIFY( item.isReadable() );
688 QVERIFY( !item.isWritable() );
689 QVERIFY( !item.isHidden() );
690 QVERIFY( item.text() == "symlinkFromHome" );
693 void TestTrash::statFileInDirectory()
695 KUrl url( "trash:/0-trashDirFromHome/testfile" );
696 KIO::UDSEntry entry;
697 bool ok = MyNetAccess_stat( url, entry );
698 QVERIFY( ok );
699 KFileItem item( entry, url );
700 QVERIFY( item.isFile() );
701 QVERIFY( !item.isLink() );
702 QVERIFY( item.isReadable() );
703 QVERIFY( !item.isWritable() );
704 QVERIFY( !item.isHidden() );
705 QVERIFY( item.text() == "testfile" );
708 void TestTrash::copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
710 KUrl src( "trash:/0-" + fileId );
711 if ( !relativePath.isEmpty() )
712 src.addPath( relativePath );
713 KUrl dest;
714 dest.setPath( destPath );
716 QVERIFY(MyNetAccess_exists(src));
718 // A dnd would use copy(), but we use copyAs to ensure the final filename
719 //kDebug() << "copyAs:" << src << " -> " << dest;
720 KIO::Job* job = KIO::copyAs( src, dest, KIO::HideProgressInfo );
721 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
722 QVERIFY( ok );
723 QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
724 QVERIFY( QFile::exists(infoFile) );
726 QFileInfo filesItem( m_trashDir + "/files/" + fileId );
727 QVERIFY( filesItem.exists() );
729 QVERIFY( QFile::exists(destPath) );
732 void TestTrash::copyFileFromTrash()
734 // To test case of already-existing destination, uncomment this.
735 // This brings up the "rename" dialog though, so it can't be fully automated
736 #if 0
737 const QString destPath = otherTmpDir() + "fileFromHome_copied";
738 copyFromTrash( "fileFromHome", destPath );
739 QVERIFY( QFileInfo( destPath ).isFile() );
740 QVERIFY( QFileInfo( destPath ).size() == 12 );
741 #endif
744 void TestTrash::copyFileInDirectoryFromTrash()
746 const QString destPath = otherTmpDir() + "testfile_copied";
747 copyFromTrash( "trashDirFromHome", destPath, "testfile" );
748 QVERIFY( QFileInfo( destPath ).isFile() );
749 QVERIFY( QFileInfo( destPath ).size() == 12 );
752 void TestTrash::copyDirectoryFromTrash()
754 const QString destPath = otherTmpDir() + "trashDirFromHome_copied";
755 copyFromTrash( "trashDirFromHome", destPath );
756 QVERIFY( QFileInfo( destPath ).isDir() );
759 void TestTrash::copySymlinkFromTrash()
761 const QString destPath = otherTmpDir() + "symlinkFromHome_copied";
762 copyFromTrash( "symlinkFromHome", destPath );
763 QVERIFY( QFileInfo( destPath ).isSymLink() );
766 void TestTrash::moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
768 KUrl src( "trash:/0-" + fileId );
769 if ( !relativePath.isEmpty() )
770 src.addPath( relativePath );
771 KUrl dest;
772 dest.setPath( destPath );
774 QVERIFY(MyNetAccess_exists(src));
776 // A dnd would use move(), but we use moveAs to ensure the final filename
777 KIO::Job* job = KIO::moveAs( src, dest, KIO::HideProgressInfo );
778 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
779 QVERIFY( ok );
780 QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
781 QVERIFY( !QFile::exists( infoFile ) );
783 QFileInfo filesItem( m_trashDir + "/files/" + fileId );
784 QVERIFY( !filesItem.exists() );
786 QVERIFY( QFile::exists( destPath ) );
789 void TestTrash::moveFileFromTrash()
791 const QString destPath = otherTmpDir() + "fileFromHome_restored";
792 moveFromTrash( "fileFromHome", destPath );
793 QVERIFY( QFileInfo( destPath ).isFile() );
794 QVERIFY( QFileInfo( destPath ).size() == 12 );
796 // trash it again for later
797 const QString fileName = "fileFromHome";
798 trashFile( homeTmpDir() + fileName, fileName );
801 void TestTrash::moveFileInDirectoryFromTrash()
803 const QString destPath = otherTmpDir() + "testfile_restored";
804 copyFromTrash( "trashDirFromHome", destPath, "testfile" );
805 QVERIFY( QFileInfo( destPath ).isFile() );
806 QVERIFY( QFileInfo( destPath ).size() == 12 );
809 void TestTrash::moveDirectoryFromTrash()
811 const QString destPath = otherTmpDir() + "trashDirFromHome_restored";
812 moveFromTrash( "trashDirFromHome", destPath );
813 QVERIFY( QFileInfo( destPath ).isDir() );
815 // trash it again, we'll need it later
816 QString dirName = "trashDirFromHome";
817 trashDirectory( homeTmpDir() + dirName, dirName );
820 void TestTrash::trashDirectoryOwnedByRoot()
822 KUrl u;
823 if ( QFile::exists( "/etc/cups" ) )
824 u.setPath( "/etc/cups" );
825 else if ( QFile::exists( "/boot" ) )
826 u.setPath( "/boot" );
827 else
828 u.setPath( "/etc" );
829 const QString fileId = u.path();
830 kDebug() << "fileId=" << fileId;
832 KIO::CopyJob* job = KIO::move( u, KUrl("trash:/"), KIO::HideProgressInfo );
833 job->setUiDelegate(0); // no skip dialog, thanks
834 QMap<QString, QString> metaData;
835 bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
836 QVERIFY( !ok );
837 const int err = KIO::NetAccess::lastError();
838 QVERIFY( err == KIO::ERR_ACCESS_DENIED
839 || err == KIO::ERR_CANNOT_OPEN_FOR_READING );
841 const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
842 QVERIFY( !QFile::exists( infoPath ) );
844 QFileInfo files( m_trashDir + "/files/" + fileId );
845 QVERIFY( !files.exists() );
847 QVERIFY( QFile::exists( u.path() ) );
850 void TestTrash::moveSymlinkFromTrash()
852 const QString destPath = otherTmpDir() + "symlinkFromHome_restored";
853 moveFromTrash( "symlinkFromHome", destPath );
854 QVERIFY( QFileInfo( destPath ).isSymLink() );
857 void TestTrash::getFile()
859 const QString fileId = "fileFromHome_1";
860 const KUrl url = TrashImpl::makeURL( 0, fileId, QString() );
862 KTemporaryFile tmpFile;
863 QVERIFY(tmpFile.open());
864 const QString tmpFilePath = tmpFile.fileName();
866 KIO::Job* getJob = KIO::file_copy(url, KUrl(tmpFilePath), -1, KIO::Overwrite | KIO::HideProgressInfo);
867 bool ok = KIO::NetAccess::synchronousRun(getJob, 0);
868 if (!ok) {
869 kDebug() << getJob->errorString();
871 QVERIFY( ok );
872 // Don't use tmpFile.close()+tmpFile.open() here, the size would still be 0 in the QTemporaryFile object
873 // (due to the use of fstat on the old fd). Arguably a bug (I even have a testcase), but probably
874 // not fixable without breaking the security of QTemporaryFile...
875 QFile reader(tmpFilePath);
876 QVERIFY(reader.open(QIODevice::ReadOnly));
877 QByteArray str = reader.readAll();
878 QCOMPARE(str, QByteArray("Hello world\n"));
881 void TestTrash::restoreFile()
883 const QString fileId = "fileFromHome_1";
884 const KUrl url = TrashImpl::makeURL( 0, fileId, QString() );
885 const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
886 const QString filesItem( m_trashDir + "/files/" + fileId );
888 QVERIFY( QFile::exists( infoFile ) );
889 QVERIFY( QFile::exists( filesItem ) );
891 QByteArray packedArgs;
892 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
893 stream << (int)3 << url;
894 KIO::Job* job = KIO::special( url, packedArgs, KIO::HideProgressInfo );
895 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
896 QVERIFY( ok );
898 QVERIFY( !QFile::exists( infoFile ) );
899 QVERIFY( !QFile::exists( filesItem ) );
901 const QString destPath = homeTmpDir() + "fileFromHome";
902 QVERIFY( QFile::exists( destPath ) );
905 void TestTrash::restoreFileFromSubDir()
907 const QString fileId = "trashDirFromHome_1/testfile";
908 QVERIFY( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
910 const KUrl url = TrashImpl::makeURL( 0, fileId, QString() );
911 const QString infoFile( m_trashDir + "/info/trashDirFromHome_1.trashinfo" );
912 const QString filesItem( m_trashDir + "/files/trashDirFromHome_1/testfile" );
914 QVERIFY( QFile::exists( infoFile ) );
915 QVERIFY( QFile::exists( filesItem ) );
917 QByteArray packedArgs;
918 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
919 stream << (int)3 << url;
920 KIO::Job* job = KIO::special( url, packedArgs, KIO::HideProgressInfo );
921 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
922 QVERIFY( !ok );
923 // dest dir doesn't exist -> error message
924 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
926 // check that nothing happened
927 QVERIFY( QFile::exists( infoFile ) );
928 QVERIFY( QFile::exists( filesItem ) );
929 QVERIFY( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
932 void TestTrash::restoreFileToDeletedDirectory()
934 // Ensure we'll get "fileFromHome" as fileId
935 removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
936 removeFile( m_trashDir, "/files/fileFromHome" );
937 trashFileFromHome();
938 // Delete orig dir
939 KIO::Job* delJob = KIO::del(KUrl(homeTmpDir()), KIO::HideProgressInfo);
940 bool delOK = KIO::NetAccess::synchronousRun(delJob, 0);
941 QVERIFY( delOK );
943 const QString fileId = "fileFromHome";
944 const KUrl url = TrashImpl::makeURL( 0, fileId, QString() );
945 const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
946 const QString filesItem( m_trashDir + "/files/" + fileId );
948 QVERIFY( QFile::exists( infoFile ) );
949 QVERIFY( QFile::exists( filesItem ) );
951 QByteArray packedArgs;
952 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
953 stream << (int)3 << url;
954 KIO::Job* job = KIO::special( url, packedArgs, KIO::HideProgressInfo );
955 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
956 QVERIFY( !ok );
957 // dest dir doesn't exist -> error message
958 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
960 // check that nothing happened
961 QVERIFY( QFile::exists( infoFile ) );
962 QVERIFY( QFile::exists( filesItem ) );
964 const QString destPath = homeTmpDir() + "fileFromHome";
965 QVERIFY( !QFile::exists( destPath ) );
968 void TestTrash::listRootDir()
970 m_entryCount = 0;
971 m_listResult.clear();
972 KIO::ListJob* job = KIO::listDir( KUrl("trash:/"), KIO::HideProgressInfo );
973 connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
974 SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
975 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
976 QVERIFY( ok );
977 kDebug() << "listDir done - m_entryCount=" << m_entryCount;
978 QVERIFY( m_entryCount > 1 );
980 kDebug() << m_listResult;
981 QVERIFY( m_listResult.contains( "." ) == 1 ); // found it, and only once
984 void TestTrash::listRecursiveRootDir()
986 m_entryCount = 0;
987 m_listResult.clear();
988 KIO::ListJob* job = KIO::listRecursive( KUrl("trash:/"), KIO::HideProgressInfo );
989 connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
990 SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
991 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
992 QVERIFY( ok );
993 kDebug() << "listDir done - m_entryCount=" << m_entryCount;
994 QVERIFY( m_entryCount > 1 );
996 kDebug() << m_listResult;
997 QVERIFY( m_listResult.count( "." ) == 1 ); // found it, and only once
1000 void TestTrash::listSubDir()
1002 m_entryCount = 0;
1003 m_listResult.clear();
1004 KIO::ListJob* job = KIO::listDir( KUrl("trash:/0-trashDirFromHome"), KIO::HideProgressInfo );
1005 connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
1006 SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
1007 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
1008 QVERIFY( ok );
1009 kDebug() << "listDir done - m_entryCount=" << m_entryCount;
1010 QVERIFY( m_entryCount == 2 );
1012 kDebug() << m_listResult;
1013 QVERIFY( m_listResult.count( "." ) == 1 ); // found it, and only once
1014 QVERIFY( m_listResult.count( "testfile" ) == 1 ); // found it, and only once
1017 void TestTrash::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst )
1019 for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
1020 const KIO::UDSEntry& entry (*it);
1021 QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
1022 KUrl url = entry.stringValue( KIO::UDSEntry::UDS_URL );
1023 kDebug() << displayName << " " << url;
1024 if ( !url.isEmpty() ) {
1025 QVERIFY( url.protocol() == "trash" );
1027 m_listResult << displayName;
1029 m_entryCount += lst.count();
1032 void TestTrash::emptyTrash()
1034 // ## Even though we use a custom XDG_DATA_HOME value, emptying the
1035 // trash would still empty the other trash directories in other partitions.
1036 // So we can't activate this test by default.
1037 #if 0
1039 // To make this test standalone
1040 trashFileFromHome();
1042 // #167051: orphaned files
1043 createTestFile( m_trashDir + "/files/testfile_nometadata" );
1045 QByteArray packedArgs;
1046 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
1047 stream << (int)1;
1048 KIO::Job* job = KIO::special( KUrl( "trash:/" ), packedArgs, KIO::HideProgressInfo );
1049 bool ok = KIO::NetAccess::synchronousRun( job, 0 );
1050 QVERIFY( ok );
1052 KConfig cfg( "trashrc", KConfig::SimpleConfig );
1053 QVERIFY( cfg.hasGroup( "Status" ) );
1054 QVERIFY( cfg.group("Status").readEntry( "Empty", false ) == true );
1056 QVERIFY( !QFile::exists( m_trashDir + "/files/fileFromHome" ) );
1057 QVERIFY( !QFile::exists( m_trashDir + "/files/readonly" ) );
1058 QVERIFY( !QFile::exists( m_trashDir + "/info/readonly.trashinfo" ) );
1059 QVERIFY(QDir(m_trashDir + "/info").entryList(QDir::NoDotAndDotDot|QDir::AllEntries).isEmpty());
1060 QVERIFY(QDir(m_trashDir + "/files").entryList(QDir::NoDotAndDotDot|QDir::AllEntries).isEmpty());
1062 #else
1063 kDebug() << " : SKIPPED";
1064 #endif
1067 void TestTrash::testTrashSize()
1069 KIO::DirectorySizeJob* job = KIO::directorySize(KUrl("trash:/"));
1070 QVERIFY(job->exec());
1071 QVERIFY(job->totalSize() < 1000000000 /*1GB*/); // #157023
1074 static void checkIcon( const KUrl& url, const QString& expectedIcon )
1076 QString icon = KMimeType::iconNameForUrl( url );
1077 QCOMPARE( icon, expectedIcon );
1080 void TestTrash::testIcons()
1082 checkIcon( KUrl("trash:/"), "user-trash-full" ); // #100321
1083 checkIcon( KUrl("trash:/foo/"), "inode-directory" );
1086 QTEST_KDEMAIN(TestTrash, NoGUI)
1088 #include "testtrash.moc"