kdelibs/kio/tests/jobtest.cpp
Ivailo Monev 2eda09625a kio: testing build fix
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2024-06-04 03:17:56 +03:00

1324 lines
42 KiB
C++

/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "qtest_kde.h"
#include "jobtest.h"
#include <config.h>
#include <kurl.h>
#include <kio/netaccess.h>
#include <kio/previewjob.h>
#include <kdebug.h>
#include <klocale.h>
#include <kcmdlineargs.h>
#include <QtCore/QFileInfo>
#include <QtCore/QEventLoop>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QVariant>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <kprotocolinfo.h>
#include <kio/directorysizejob.h>
#include <kio/copyjob.h>
#include <kio/deletejob.h>
#include "kiotesthelper.h" // createTestFile etc.
QTEST_KDEMAIN( JobTest, NoGUI )
// The code comes partly from kdebase/kioslave/trash/testtrash.cpp
static QString otherTmpDir()
{
// This one needs to be on another partition
return "/tmp/jobtest/";
}
Q_DECLARE_METATYPE(KIO::Job*)
void JobTest::initTestCase()
{
s_referenceTimeStamp = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
// Start with a clean base dir
cleanupTestCase();
homeTmpDir(); // create it
if ( !QFile::exists( otherTmpDir() ) ) {
bool ok = QDir().mkdir( otherTmpDir() );
if ( !ok )
kFatal() << "Couldn't create " << otherTmpDir();
}
qRegisterMetaType<KJob*>("KJob*");
qRegisterMetaType<KIO::Job*>("KIO::Job*");
qRegisterMetaType<KUrl>("KUrl");
qRegisterMetaType<time_t>("time_t");
}
static void delDir(const QString& pathOrUrl) {
KIO::Job* job = KIO::del(KUrl(pathOrUrl), KIO::HideProgressInfo);
job->setUiDelegate(0);
KIO::NetAccess::synchronousRun(job, 0);
}
void JobTest::cleanupTestCase()
{
delDir( homeTmpDir() );
delDir( otherTmpDir() );
}
void JobTest::enterLoop()
{
QEventLoop eventLoop;
connect(this, SIGNAL(exitLoop()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
void JobTest::storedGet()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KUrl u( filePath );
m_result = -1;
KIO::StoredTransferJob* job = KIO::storedGet( u, KIO::HideProgressInfo );
QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong)));
QVERIFY(spyPercent.isValid());
job->setUiDelegate( 0 );
connect( job, SIGNAL(result(KJob*)),
this, SLOT(slotGetResult(KJob*)) );
enterLoop();
QCOMPARE( m_result, 0 ); // no error
QCOMPARE( m_data, QByteArray("Hello\0world", 11) );
QCOMPARE( m_data.size(), 11 );
QVERIFY(!spyPercent.isEmpty());
}
void JobTest::slotGetResult( KJob* job )
{
m_result = job->error();
m_data = static_cast<KIO::StoredTransferJob *>(job)->data();
emit exitLoop();
}
void JobTest::put()
{
const QString filePath = homeTmpDir() + "fileFromHome";
KUrl u(filePath);
KIO::TransferJob* job = KIO::put( u, 0600, KIO::Overwrite | KIO::HideProgressInfo );
QDateTime mtime = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds
job->setModificationTime(mtime);
job->setUiDelegate( 0 );
connect( job, SIGNAL(result(KJob*)),
this, SLOT(slotResult(KJob*)) );
connect( job, SIGNAL(dataReq(KIO::Job*,QByteArray&)),
this, SLOT(slotDataReq(KIO::Job*,QByteArray&)) );
m_result = -1;
m_dataReqCount = 0;
enterLoop();
QVERIFY( m_result == 0 ); // no error
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n"
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser ));
QCOMPARE(fileInfo.lastModified(), mtime);
}
void JobTest::slotDataReq( KIO::Job*, QByteArray& data )
{
// Really not the way you'd write a slotDataReq usually :)
switch(m_dataReqCount++) {
case 0:
data = "This is a test for ";
break;
case 1:
data = "KIO::put()\n";
break;
case 2:
data = QByteArray();
break;
}
}
void JobTest::slotResult( KJob* job )
{
m_result = job->error();
emit exitLoop();
}
void JobTest::storedPut()
{
const QString filePath = homeTmpDir() + "fileFromHome";
KUrl u(filePath);
QByteArray putData = "This is the put data";
KIO::TransferJob* job = KIO::storedPut( putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo );
QSignalSpy spyPercent(job, SIGNAL(percent(KJob*,ulong)));
QVERIFY(spyPercent.isValid());
QDateTime mtime = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds
job->setModificationTime(mtime);
job->setUiDelegate( 0 );
QVERIFY(job->exec());
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
QCOMPARE(fileInfo.size(), (long long)putData.size());
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser ));
QCOMPARE(fileInfo.lastModified(), mtime);
QVERIFY(!spyPercent.isEmpty());
}
////
void JobTest::copyLocalFile( const QString& src, const QString& dest )
{
const KUrl u( src );
const KUrl d( dest );
// copy the file with file_copy
KIO::Job* job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo );
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// check that the timestamp is the same (#24443)
// Note: this only works because of copy() in kio_file.
// The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime()
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
}
// cleanup and retry with KIO::copy()
QFile::remove( dest );
job = KIO::copy(u, d, KIO::HideProgressInfo );
QSignalSpy spyCopyingDone(job, SIGNAL(copyingDone(KIO::Job*,KUrl,KUrl,time_t,bool,bool)));
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// check that the timestamp is the same (#24443)
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
}
QCOMPARE(spyCopyingDone.count(), 1);
}
void JobTest::copyLocalDirectory( const QString& src, const QString& _dest, int flags )
{
QVERIFY( QFileInfo( src ).isDir() );
QVERIFY( QFileInfo( src + "/testfile" ).isFile() );
KUrl u;
u.setPath( src );
QString dest( _dest );
KUrl d;
d.setPath( dest );
if ( flags & AlreadyExists )
QVERIFY( QDir( dest ).exists() );
else
QVERIFY( !QDir( dest ).exists() );
KIO::Job* job = KIO::copy(u, d, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QDir( dest ).exists() );
QVERIFY( QFileInfo( dest ).isDir() );
QVERIFY( QFileInfo( dest + "/testfile" ).isFile() );
QVERIFY( QDir( src ).exists() ); // still there
if ( flags & AlreadyExists ) {
dest += '/' + u.fileName();
//kDebug() << "Expecting dest=" << dest;
}
// CopyJob::setNextDirAttribute isn't implemented for Windows currently.
{
// Check that the timestamp is the same (#24443)
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
}
// Do it again, with Overwrite.
// Use copyAs, we don't want a subdir inside d.
job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
// Do it again, without Overwrite (should fail).
job = KIO::copyAs(u, d, KIO::HideProgressInfo);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( !ok );
}
void JobTest::copyFileToSamePartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = homeTmpDir() + "fileFromHome_copied";
createTestFile( filePath );
copyLocalFile( filePath, dest );
}
void JobTest::copyDirectoryToSamePartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
copyLocalDirectory( src, dest );
}
void JobTest::copyDirectoryToExistingDirectory()
{
kDebug() ;
// just the same as copyDirectoryToSamePartition, but this time dest exists.
// So we get a subdir, "dirFromHome_copy/dirFromHome"
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
createTestDirectory( dest );
copyLocalDirectory( src, dest, AlreadyExists );
}
void JobTest::copyFileToOtherPartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = otherTmpDir() + "fileFromHome_copied";
createTestFile( filePath );
copyLocalFile( filePath, dest );
}
void JobTest::copyDirectoryToOtherPartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = otherTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
copyLocalDirectory( src, dest );
}
void JobTest::moveLocalFile( const QString& src, const QString& dest )
{
QVERIFY( QFile::exists( src ) );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
// move the file with file_move
KIO::Job* job = KIO::file_move(u, d, -1, KIO::HideProgressInfo);
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( !QFile::exists( src ) ); // not there anymore
// move it back with KIO::move()
job = KIO::move( d, u, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( !QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // it's back
}
static void moveLocalSymlink( const QString& src, const QString& dest )
{
KDE_struct_stat buf;
QVERIFY ( KDE_lstat( QFile::encodeName( src ), &buf ) == 0 );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
// move the symlink with move, NOT with file_move
KIO::Job* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
if ( !ok )
kWarning() << KIO::NetAccess::lastError();
QVERIFY( ok );
QVERIFY ( KDE_lstat( QFile::encodeName( dest ), &buf ) == 0 );
QVERIFY( !QFile::exists( src ) ); // not there anymore
// move it back with KIO::move()
job = KIO::move( d, u, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY ( KDE_lstat( QFile::encodeName( dest ), &buf ) != 0 ); // doesn't exist anymore
QVERIFY ( KDE_lstat( QFile::encodeName( src ), &buf ) == 0 ); // it's back
}
void JobTest::moveLocalDirectory( const QString& src, const QString& dest )
{
kDebug() << src << " " << dest;
QVERIFY( QDir( src ).exists() );
QVERIFY( QFileInfo( src ).isDir() );
QVERIFY( QFileInfo( src + "/testfile" ).isFile() );
QVERIFY( QFileInfo( src + "/testlink" ).isSymLink() );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::Job* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QDir( dest).exists() );
QVERIFY( QFileInfo( dest ).isDir() );
QVERIFY( QFileInfo( dest + "/testfile" ).isFile() );
QVERIFY( !QDir( src ).exists() ); // not there anymore
QVERIFY( QFileInfo( dest + "/testlink" ).isSymLink() );
}
void JobTest::moveFileToSamePartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = homeTmpDir() + "fileFromHome_moved";
createTestFile( filePath );
moveLocalFile( filePath, dest );
}
void JobTest::moveDirectoryToSamePartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_moved";
createTestDirectory( src );
moveLocalDirectory( src, dest );
}
void JobTest::moveFileToOtherPartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = otherTmpDir() + "fileFromHome_moved";
createTestFile( filePath );
moveLocalFile( filePath, dest );
}
void JobTest::moveSymlinkToOtherPartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "testlink";
const QString dest = otherTmpDir() + "testlink_moved";
createTestSymlink( filePath );
moveLocalSymlink( filePath, dest );
}
void JobTest::moveDirectoryToOtherPartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = otherTmpDir() + "dirFromHome_moved";
createTestDirectory( src );
moveLocalDirectory( src, dest );
}
void JobTest::moveFileNoPermissions()
{
kDebug() ;
const QString src = "/etc/passwd";
const QString dest = homeTmpDir() + "passwd";
QVERIFY( QFile::exists( src ) );
QVERIFY( QFileInfo( src ).isFile() );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::CopyJob* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 ); // no skip dialog, thanks
KIO::MetaData metaData;
bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
QVERIFY( !ok );
QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
// OK this is fishy. Just like mv(1), KIO's behavior depends on whether
// a direct rename(2) was used, or a full copy+del. In the first case
// there is no destination file created, but in the second case the
// destination file remains.
//QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) );
}
void JobTest::moveDirectoryNoPermissions()
{
kDebug() ;
// All of /etc is a bit much, so try to find something smaller:
QString src = "/etc/fonts";
if ( !QDir( src ).exists() )
src = "/etc";
const QString dest = homeTmpDir() + "mdnp";
QVERIFY( QDir( src ).exists() );
QVERIFY( QFileInfo( src ).isDir() );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::CopyJob* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 ); // no skip dialog, thanks
KIO::MetaData metaData;
bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
QVERIFY( !ok );
QCOMPARE( KIO::NetAccess::lastError(), (int)KIO::ERR_ACCESS_DENIED );
//QVERIFY( QDir( dest ).exists() ); // see moveFileNoPermissions
QVERIFY( QDir( src ).exists() );
}
void JobTest::listRecursive()
{
// Note: many other tests must have been run before since we rely on the files they created
const QString src = homeTmpDir();
// Add a symlink to a dir, to make sure we don't recurse into those
bool symlinkOk = symlink( "dirFromHome", QFile::encodeName( src + "/dirFromHome_link" ) ) == 0;
QVERIFY( symlinkOk );
KIO::ListJob* job = KIO::listRecursive( KUrl(src), KIO::HideProgressInfo );
job->setUiDelegate( 0 );
connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)) );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
m_names.sort();
QByteArray ref_names = QByteArray( ".,..,"
"dirFromHome,dirFromHome/testfile,"
"dirFromHome/testlink,"
"dirFromHome_copied,"
"dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile,"
"dirFromHome_copied/dirFromHome/testlink,"
"dirFromHome_copied/testfile,"
"dirFromHome_copied/testlink,dirFromHome_link,"
"fileFromHome,fileFromHome_copied");
const QString joinedNames = m_names.join( "," );
if (joinedNames.toLatin1() != ref_names) {
qDebug( "%s", qPrintable( joinedNames ) );
qDebug( "%s", ref_names.data() );
}
QCOMPARE( joinedNames.toLatin1(), ref_names );
}
void JobTest::listFile()
{
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::ListJob* job = KIO::listDir(KUrl(filePath), KIO::HideProgressInfo);
job->setUiDelegate( 0 );
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_IS_FILE));
// And list something that doesn't exist
const QString path = homeTmpDir() + "fileFromHomeDoesNotExist";
job = KIO::listDir(KUrl(path), KIO::HideProgressInfo);
job->setUiDelegate( 0 );
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_DOES_NOT_EXIST));
}
void JobTest::killJob()
{
const QString src = homeTmpDir();
KIO::ListJob* job = KIO::listDir( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
QPointer<KIO::ListJob> ptr(job);
job->setUiDelegate( 0 );
qApp->processEvents(); // let the job start, it's no fun otherwise
job->kill();
qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job
QVERIFY(ptr.isNull());
}
void JobTest::killJobBeforeStart()
{
const QString src = homeTmpDir();
KIO::Job* job = KIO::stat( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
QPointer<KIO::Job> ptr(job);
job->setUiDelegate( 0 );
job->kill();
qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job
QVERIFY(ptr.isNull());
qApp->processEvents(); // does KIO scheduler crash here? nope.
}
void JobTest::deleteJobBeforeStart() // #163171
{
const QString src = homeTmpDir();
KIO::Job* job = KIO::stat( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
job->setUiDelegate( 0 );
delete job;
qApp->processEvents(); // does KIO scheduler crash here?
}
void JobTest::directorySize()
{
// Note: many other tests must have been run before since we rely on the files they created
const QString src = homeTmpDir();
KIO::DirectorySizeJob* job = KIO::directorySize( KUrl(src) );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
kDebug() << "totalSize: " << job->totalSize();
kDebug() << "totalFiles: " << job->totalFiles();
kDebug() << "totalSubdirs: " << job->totalSubdirs();
QCOMPARE(job->totalFiles(), 8ULL); // see expected result in listRecursive() above
QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above
QVERIFY(job->totalSize() >= 325);
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
}
void JobTest::directorySizeError()
{
KIO::DirectorySizeJob* job = KIO::directorySize( KUrl("/I/Dont/Exist") );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( !ok );
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
}
void JobTest::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst )
{
for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
QString displayName = (*it).stringValue( KIO::UDSEntry::UDS_NAME );
//KUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL );
m_names.append( displayName );
}
}
#if 0 // old performance tests
class OldUDSAtom
{
public:
QString m_str;
long long m_long;
unsigned int m_uds;
};
typedef QList<OldUDSAtom> OldUDSEntry; // well it was a QValueList :)
static void fillOldUDSEntry( OldUDSEntry& entry, time_t now_time_t, const QString& nameStr )
{
OldUDSAtom atom;
atom.m_uds = KIO::UDSEntry::UDS_NAME;
atom.m_str = nameStr;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_SIZE;
atom.m_long = 123456ULL;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_MODIFICATION_TIME;
atom.m_long = now_time_t;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_ACCESS_TIME;
atom.m_long = now_time_t;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_ACCESS;
atom.m_long = 0644;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_USER;
atom.m_str = nameStr;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_GROUP;
atom.m_str = nameStr;
entry.append( atom );
}
// QHash or QMap? doesn't seem to make much difference.
typedef QHash<uint, QVariant> UDSEntryHV;
static void fillUDSEntryHV( UDSEntryHV& entry, const QDateTime& now, const QString& nameStr )
{
entry.reserve( 8 );
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
// we might need a method to make sure people use unsigned long long
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
// Which one is used depends on UDS_STRING vs UDS_LONG
struct UDSAtom4 // can't be a union due to qstring...
{
UDSAtom4() {} // for QHash or QMap
UDSAtom4( const QString& s ) : m_str( s ) {}
UDSAtom4( long long l ) : m_long( l ) {}
QString m_str;
long long m_long;
};
// Another possibility, to save on QVariant costs
typedef QHash<uint, UDSAtom4> UDSEntryHS; // hash+struct
static void fillQHashStructEntry( UDSEntryHS& entry, time_t now_time_t, const QString& nameStr )
{
entry.reserve( 8 );
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
// Let's see if QMap makes any difference
typedef QMap<uint, UDSAtom4> UDSEntryMS; // map+struct
static void fillQMapStructEntry( UDSEntryMS& entry, time_t now_time_t, const QString& nameStr )
{
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
void JobTest::newApiPerformance()
{
const QDateTime now = QDateTime::currentDateTime();
const time_t now_time_t = now.toTime_t();
// use 30000 for callgrind, at least 100 times that for timing-based
// use /10 times that in svn, so that jobtest doesn't last forever
const int iterations = 30000 /* * 100 */ / 10;
const int lookupIterations = 5 * iterations;
const QString nameStr = QString::fromLatin1( "name" );
/*
This is to compare the old list-of-lists API vs a QMap/QHash-based API
in terms of performance.
The number of atoms and their type map to what kio_file would put in
for any normal file.
The lookups are done for two atoms that are present, and for one that is not.
*/
/// Old API
{
qDebug( "Timing old api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
OldUDSEntry entry;
fillOldUDSEntry( entry, now_time_t, nameStr );
}
qDebug("Old API: slave code: %ld", time(0) - start);
OldUDSEntry entry;
fillOldUDSEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
OldUDSEntry::ConstIterator it2 = entry.constBegin();
for( ; it2 != entry.constEnd(); it2++ ) {
switch ((*it2).m_uds) {
case KIO::UDSEntry::UDS_NAME:
displayName = (*it2).m_str;
break;
case KIO::UDSEntry::UDS_URL:
url = (*it2).m_str;
break;
case KIO::UDSEntry::UDS_SIZE:
size = (*it2).m_long;
break;
}
}
}
qDebug("Old API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
///////// TEST CODE FOR FUTURE KIO API
////
{
qDebug( "Timing new QHash+QVariant api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryHV entry;
fillUDSEntryHV( entry, now, nameStr );
}
qDebug("QHash+QVariant API: slave code: %ld", time(0) - start);
UDSEntryHV entry;
fillUDSEntryHV( entry, now, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
// Normally the code would look like this, but let's change it to time it like the old api
/*
QString displayName = entry.value( KIO::UDSEntry::UDS_NAME ).toString();
KUrl url = entry.value( KIO::UDSEntry::UDS_URL ).toString();
KIO::filesize_t size = entry.value( KIO::UDSEntry::UDS_SIZE ).toULongLong();
*/
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).toString();
// For a field that might not be there
UDSEntryHV::const_iterator it = entry.constFind( KIO::UDSEntry::UDS_URL );
const UDSEntryHV::const_iterator end = entry.constEnd();
if ( it != end )
url = it.value().toString();
it = entry.constFind( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().toULongLong();
}
qDebug("QHash+QVariant API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
// ########### THE CHOSEN SOLUTION:
{
qDebug( "Timing new QHash+struct api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryHS entry;
fillQHashStructEntry( entry, now_time_t, nameStr );
}
qDebug("QHash+struct API: slave code: %ld", time(0) - start);
UDSEntryHS entry;
fillQHashStructEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).m_str;
// For a field that might not be there
UDSEntryHS::const_iterator it = entry.constFind( KIO::UDSEntry::UDS_URL );
const UDSEntryHS::const_iterator end = entry.constEnd();
if ( it != end )
url = it.value().m_str;
it = entry.constFind( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().m_long;
}
qDebug("QHash+struct API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
{
qDebug( "Timing new QMap+struct api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryMS entry;
fillQMapStructEntry( entry, now_time_t, nameStr );
}
qDebug("QMap+struct API: slave code: %ld", time(0) - start);
UDSEntryMS entry;
fillQMapStructEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).m_str;
// For a field that might not be there
UDSEntryMS::const_iterator it = entry.constFind( KIO::UDSEntry::UDS_URL );
const UDSEntryMS::const_iterator end = entry.constEnd();
if ( it != end )
url = it.value().m_str;
it = entry.constFind( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().m_long;
}
qDebug("QMap+struct API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
}
#endif
void JobTest::calculateRemainingSeconds()
{
unsigned int seconds = KIO::calculateRemainingSeconds( 2 * 86400 - 60, 0, 1 );
QCOMPARE( seconds, static_cast<unsigned int>( 2 * 86400 - 60 ) );
QString text = KIO::convertSeconds( seconds );
QCOMPARE( text, i18n( "23 hours and 59 minutes" ) );
seconds = KIO::calculateRemainingSeconds( 520, 20, 10 );
QCOMPARE( seconds, static_cast<unsigned int>( 50 ) );
text = KIO::convertSeconds( seconds );
QCOMPARE( text, i18n( "50 seconds" ) );
}
void JobTest::getInvalidUrl()
{
KUrl url("http:/strange<hostname>/");
QVERIFY(!url.isValid());
KIO::SimpleJob* job = KIO::get(url, KIO::HideProgressInfo);
QVERIFY(job != 0);
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( !ok ); // it should fail :)
}
void JobTest::deleteFile()
{
const QString dest = otherTmpDir() + "fileFromHome_copied";
QVERIFY(QFile::exists(dest));
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(dest));
}
void JobTest::deleteDirectory()
{
const QString dest = otherTmpDir() + "dirFromHome_copied";
if (!QFile::exists(dest))
createTestDirectory(dest);
// Let's put a few things in there to see if the recursive deletion works correctly
// A hidden file:
createTestFile(dest + "/.hidden");
// A broken symlink:
createTestSymlink(dest+"/broken_symlink");
// A symlink to a dir:
bool symlink_ok = symlink( KDESRCDIR, QFile::encodeName( dest + "/symlink_to_dir" ) ) == 0;
if ( !symlink_ok )
kFatal() << "couldn't create symlink: " << strerror( errno ) ;
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(dest));
}
void JobTest::deleteSymlink()
{
const QString src = homeTmpDir() + "dirFromHome";
createTestDirectory(src);
QVERIFY(QDir(src).exists());
const QString dest = homeTmpDir() + "/dirFromHome_link";
if (!QDir(dest).exists()) {
// Add a symlink to a dir, to make sure we don't recurse into those
bool symlinkOk = symlink(QFile::encodeName(src), QFile::encodeName(dest)) == 0;
QVERIFY( symlinkOk );
QVERIFY(QDir(dest).exists());
}
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QDir(dest).exists());
QVERIFY(QDir(src).exists());
}
void JobTest::deleteManyDirs()
{
const int numDirs = 50;
KUrl::List dirs;
for (int i = 0; i < numDirs; ++i) {
const QString dir = homeTmpDir() + "dir" + QString::number(i);
createTestDirectory(dir);
dirs << KUrl(dir);
}
QElapsedTimer dt;
dt.start();
KIO::Job* job = KIO::del(dirs, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
Q_FOREACH(const KUrl& dir, dirs) {
QVERIFY(!QDir(dir.path()).exists());
}
kDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds";
}
static void createManyFiles(const QString& baseDir, int numFiles)
{
for (int i = 0; i < numFiles; ++i) {
// create empty file
QFile f(baseDir + QString::number(i));
QVERIFY(f.open(QIODevice::WriteOnly));
}
}
void JobTest::deleteManyFilesIndependently()
{
QElapsedTimer dt;
dt.start();
const int numFiles = 100; // Use 1000 for performance testing
const QString baseDir = homeTmpDir();
createManyFiles(baseDir, numFiles);
for (int i = 0; i < numFiles; ++i) {
// delete each file independently. lots of jobs. this stress-tests kio scheduling.
const QString file = baseDir + QString::number(i);
QVERIFY(QFile::exists(file));
//kDebug() << file;
KIO::Job* job = KIO::del(KUrl(file), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(file));
}
kDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
}
void JobTest::deleteManyFilesTogether()
{
QElapsedTimer dt;
dt.start();
const int numFiles = 100; // Use 1000 for performance testing
const QString baseDir = homeTmpDir();
createManyFiles(baseDir, numFiles);
KUrl::List urls;
for (int i = 0; i < numFiles; ++i) {
const QString file = baseDir + QString::number(i);
QVERIFY(QFile::exists(file));
urls.append(KUrl(file));
}
//kDebug() << file;
KIO::Job* job = KIO::del(urls, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
kDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
}
void JobTest::rmdirEmpty()
{
const QString dir = homeTmpDir() + "dir";
QDir().mkdir(dir);
QVERIFY(QDir(dir).exists());
KIO::Job* job = KIO::rmdir(dir);
QVERIFY(job->exec());
QVERIFY(!QDir(dir).exists());
}
void JobTest::rmdirNotEmpty()
{
const QString dir = homeTmpDir() + "dir";
createTestDirectory(dir);
createTestDirectory(dir + "/subdir");
KIO::Job* job = KIO::rmdir(dir);
QVERIFY(!job->exec());
QVERIFY(QDir(dir).exists());
}
void JobTest::stat()
{
#if 1
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::StatJob* job = KIO::stat(filePath, KIO::HideProgressInfo);
QVERIFY(job);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
// TODO set setSide, setDetails
const KIO::UDSEntry& entry = job->statResult();
QVERIFY(!entry.isDir());
QVERIFY(!entry.isLink());
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString("fileFromHome"));
#else
// Testing stat over HTTP
KIO::StatJob* job = KIO::stat(KUrl("http://www.kde.org"), KIO::HideProgressInfo);
QVERIFY(job);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
// TODO set setSide, setDetails
const KIO::UDSEntry& entry = job->statResult();
QVERIFY(!entry.isDir());
QVERIFY(!entry.isLink());
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString());
#endif
}
void JobTest::moveFileDestAlreadyExists() // #157601
{
const QString file1 = homeTmpDir() + "fileFromHome";
createTestFile( file1 );
const QString file2 = homeTmpDir() + "anotherFile";
createTestFile( file2 );
const QString existingDest = otherTmpDir() + "fileFromHome";
createTestFile( existingDest );
KUrl::List urls; urls << KUrl(file1) << KUrl(file2);
KIO::CopyJob* job = KIO::move(urls, otherTmpDir(), KIO::HideProgressInfo);
job->setUiDelegate(0);
job->setAutoSkip(true);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(QFile::exists(file1)); // it was skipped
QVERIFY(!QFile::exists(file2)); // it was moved
}
void JobTest::moveDestAlreadyExistsAutoRename_data()
{
QTest::addColumn<bool>("samePartition");
QTest::addColumn<bool>("moveDirs");
QTest::newRow("files same partition") << true << false;
QTest::newRow("files other partition") << false << false;
QTest::newRow("dirs same partition") << true << true;
QTest::newRow("dirs other partition") << false << true;
}
void JobTest::moveDestAlreadyExistsAutoRename()
{
QFETCH(bool, samePartition);
QFETCH(bool, moveDirs);
QString dir;
if (samePartition) {
dir = homeTmpDir() + "dir/";
QVERIFY(QDir(dir).exists() || QDir().mkdir(dir));
} else {
dir = otherTmpDir();
}
moveDestAlreadyExistsAutoRename(dir, moveDirs);
if (samePartition) {
// cleanup
KIO::Job* job = KIO::del(dir, KIO::HideProgressInfo);
QVERIFY(job->exec());
QVERIFY(!QDir(dir).exists());
}
}
void JobTest::moveDestAlreadyExistsAutoRename(const QString& destDir, bool moveDirs) // #256650
{
const QString prefix = moveDirs ? "dir " : "file ";
QStringList sources;
const QString file1 = homeTmpDir() + prefix + "1";
const QString file2 = homeTmpDir() + prefix + "2";
const QString existingDest1 = destDir + prefix + "1";
const QString existingDest2 = destDir + prefix + "2";
sources << file1 << file2 << existingDest1 << existingDest2;
Q_FOREACH(const QString& source, sources) {
if (moveDirs)
QVERIFY(QDir().mkdir(source));
else
createTestFile(source);
}
KUrl::List urls; urls << KUrl(file1) << KUrl(file2);
KIO::CopyJob* job = KIO::move(urls, destDir, KIO::HideProgressInfo);
job->setUiDelegate(0);
job->setAutoRename(true);
//kDebug() << QDir(destDir).entryList();
bool ok = KIO::NetAccess::synchronousRun(job, 0);
kDebug() << QDir(destDir).entryList();
QVERIFY(ok);
QVERIFY(!QFileInfo(file1).exists()); // it was moved
QVERIFY(!QFileInfo(file2).exists()); // it was moved
QVERIFY(QFileInfo(existingDest1).exists());
QVERIFY(QFileInfo(existingDest2).exists());
const QString file3 = destDir + prefix + "3";
const QString file4 = destDir + prefix + "4";
QVERIFY(QFileInfo(file3).exists());
QVERIFY(QFileInfo(file4).exists());
if (moveDirs) {
QDir().rmdir(file1);
QDir().rmdir(file2);
QDir().rmdir(file3);
QDir().rmdir(file4);
} else {
QFile::remove(file1);
QFile::remove(file2);
QFile::remove(file3);
QFile::remove(file4);
}
}
void JobTest::moveAndOverwrite()
{
const QString sourceFile = homeTmpDir() + "fileFromHome";
createTestFile( sourceFile );
QString existingDest = otherTmpDir() + "fileFromHome";
createTestFile( existingDest );
KIO::FileCopyJob* job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
// Now same thing when the target is a symlink to the source
createTestFile( sourceFile );
createTestSymlink( existingDest, QFile::encodeName(sourceFile) );
QVERIFY(QFile::exists(existingDest));
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
// Now same thing when the target is a symlink to another file
createTestFile( sourceFile );
createTestFile( sourceFile + "2" );
createTestSymlink( existingDest, QFile::encodeName(sourceFile + "2") );
QVERIFY(QFile::exists(existingDest));
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
// Now same thing when the target is a _broken_ symlink
createTestFile( sourceFile );
createTestSymlink( existingDest );
QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken...
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
}
void JobTest::moveOverSymlinkToSelf() // #169547
{
const QString sourceFile = homeTmpDir() + "fileFromHome";
createTestFile( sourceFile );
const QString existingDest = homeTmpDir() + "testlink";
createTestSymlink( existingDest, QFile::encodeName(sourceFile) );
QVERIFY(QFile::exists(existingDest));
KIO::CopyJob* job = KIO::move(KUrl(sourceFile), KUrl(existingDest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(!ok);
QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES!
QVERIFY(QFile::exists(sourceFile)); // it not moved
}
#include "moc_jobtest.cpp"