mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-03 15:55:27 +08:00
qt 6.5.1 original
This commit is contained in:
27
tests/auto/corelib/io/qlockfile/CMakeLists.txt
Normal file
27
tests/auto/corelib/io/qlockfile/CMakeLists.txt
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#####################################################################
|
||||
## tst_qlockfile Test:
|
||||
#####################################################################
|
||||
|
||||
qt_internal_add_test(tst_qlockfile
|
||||
SOURCES
|
||||
tst_qlockfile.cpp
|
||||
LIBRARIES
|
||||
Qt::Concurrent
|
||||
Qt::CorePrivate
|
||||
)
|
||||
|
||||
## Scopes:
|
||||
#####################################################################
|
||||
|
||||
qt_internal_extend_target(tst_qlockfile CONDITION WIN32
|
||||
LIBRARIES
|
||||
advapi32
|
||||
)
|
||||
add_subdirectory(qlockfiletesthelper)
|
||||
|
||||
if(QT_FEATURE_process AND NOT ANDROID)
|
||||
add_dependencies(tst_qlockfile qlockfile_test_helper)
|
||||
endif()
|
@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#####################################################################
|
||||
## qlockfile_test_helper Binary:
|
||||
#####################################################################
|
||||
|
||||
qt_internal_add_test_helper(qlockfile_test_helper
|
||||
OVERRIDE_OUTPUT_DIRECTORY
|
||||
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
SOURCES
|
||||
qlockfile_test_helper.cpp
|
||||
)
|
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
#include <QLockFile>
|
||||
#include <QThread>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc <= 1)
|
||||
return -1;
|
||||
|
||||
const QString lockName = QString::fromLocal8Bit(argv[1]);
|
||||
|
||||
QString option;
|
||||
if (argc > 2)
|
||||
option = QString::fromLocal8Bit(argv[2]);
|
||||
|
||||
if (option == "-uncleanexit") {
|
||||
QLockFile lockFile(lockName);
|
||||
lockFile.lock();
|
||||
// exit on purpose, so that the lock remains!
|
||||
_exit(0);
|
||||
} else if (option == "-busy") {
|
||||
QLockFile lockFile(lockName);
|
||||
lockFile.lock();
|
||||
QThread::msleep(500);
|
||||
return 0;
|
||||
} else {
|
||||
QLockFile lockFile(lockName);
|
||||
if (lockFile.isLocked()) // cannot happen, before calling lock or tryLock
|
||||
return QLockFile::UnknownError;
|
||||
|
||||
lockFile.tryLock();
|
||||
return lockFile.error();
|
||||
}
|
||||
}
|
660
tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
Normal file
660
tests/auto/corelib/io/qlockfile/tst_qlockfile.cpp
Normal file
@ -0,0 +1,660 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
|
||||
#include <QTest>
|
||||
#include <QtConcurrentRun>
|
||||
#if QT_CONFIG(process)
|
||||
#include <QProcess>
|
||||
#endif
|
||||
#include <QSemaphore>
|
||||
#include <QFutureSynchronizer>
|
||||
|
||||
#include <qlockfile.h>
|
||||
#include <qtemporarydir.h>
|
||||
#include <qsysinfo.h>
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#elif defined(Q_OS_WIN)
|
||||
# include <qt_windows.h>
|
||||
# include <QOperatingSystemVersion>
|
||||
#endif
|
||||
|
||||
#include <private/qlockfile_p.h> // for getLockFileHandle()
|
||||
|
||||
class tst_QLockFile : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void lockUnlock();
|
||||
void lockOutOtherProcess();
|
||||
void lockOutOtherThread();
|
||||
void raceWithOtherThread();
|
||||
void waitForLock_data();
|
||||
void waitForLock();
|
||||
void staleLockFromCrashedProcess_data();
|
||||
void staleLockFromCrashedProcess();
|
||||
void staleLockFromCrashedProcessReusedPid();
|
||||
void staleShortLockFromBusyProcess();
|
||||
void staleLongLockFromBusyProcess();
|
||||
void staleLockRace();
|
||||
void noPermissions();
|
||||
void noPermissionsWindows();
|
||||
void corruptedLockFile();
|
||||
void corruptedLockFileInTheFuture();
|
||||
void hostnameChange();
|
||||
void differentMachines();
|
||||
void reboot();
|
||||
|
||||
private:
|
||||
static bool overwriteLineInLockFile(QFile &f, int line, const QString &newLine);
|
||||
static bool overwritePidInLockFile(const QString &filePath, qint64 pid);
|
||||
|
||||
public:
|
||||
QString m_helperApp;
|
||||
QTemporaryDir dir;
|
||||
};
|
||||
|
||||
void tst_QLockFile::initTestCase()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
QSKIP("This test requires deploying and running external console applications");
|
||||
#elif !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
|
||||
// chdir to our testdata path and execute helper apps relative to that.
|
||||
QString testdata_dir = QFileInfo(QFINDTESTDATA("qlockfiletesthelper")).absolutePath();
|
||||
QVERIFY2(QDir::setCurrent(testdata_dir), qPrintable("Could not chdir to " + testdata_dir));
|
||||
m_helperApp = "qlockfiletesthelper/qlockfile_test_helper";
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
void tst_QLockFile::lockUnlock()
|
||||
{
|
||||
const QString fileName = dir.path() + "/lock1";
|
||||
QVERIFY(!QFile(fileName).exists());
|
||||
QLockFile lockFile(fileName);
|
||||
QCOMPARE(lockFile.fileName(), fileName);
|
||||
QVERIFY(lockFile.lock());
|
||||
QVERIFY(lockFile.isLocked());
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
|
||||
QVERIFY(QFile::exists(fileName));
|
||||
|
||||
// Recursive locking is not allowed
|
||||
// (can't test lock() here, it would wait forever)
|
||||
QVERIFY(!lockFile.tryLock());
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
|
||||
qint64 pid;
|
||||
QString hostname, appname;
|
||||
QVERIFY(lockFile.getLockInfo(&pid, &hostname, &appname));
|
||||
QCOMPARE(pid, QCoreApplication::applicationPid());
|
||||
QCOMPARE(appname, qAppName());
|
||||
QVERIFY(!lockFile.tryLock(200));
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
|
||||
|
||||
// Unlock deletes the lock file
|
||||
lockFile.unlock();
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
|
||||
QVERIFY(!lockFile.isLocked());
|
||||
QVERIFY(!QFile::exists(fileName));
|
||||
}
|
||||
|
||||
void tst_QLockFile::lockOutOtherProcess()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
// Lock
|
||||
const QString fileName = dir.path() + "/lockOtherProcess";
|
||||
QLockFile lockFile(fileName);
|
||||
QVERIFY(lockFile.lock());
|
||||
|
||||
// Other process can't acquire lock
|
||||
QProcess proc;
|
||||
proc.start(m_helperApp, QStringList() << fileName);
|
||||
QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
|
||||
QVERIFY(proc.waitForFinished());
|
||||
QCOMPARE(proc.exitCode(), int(QLockFile::LockFailedError));
|
||||
|
||||
// Unlock
|
||||
lockFile.unlock();
|
||||
QVERIFY(!QFile::exists(fileName));
|
||||
|
||||
// Other process can now acquire lock
|
||||
int ret = QProcess::execute(m_helperApp, QStringList() << fileName);
|
||||
QCOMPARE(ret, int(QLockFile::NoError));
|
||||
// Lock doesn't survive process though (on clean exit)
|
||||
QVERIFY(!QFile::exists(fileName));
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
static QLockFile::LockError tryLockFromThread(const QString &fileName)
|
||||
{
|
||||
QLockFile lockInThread(fileName);
|
||||
lockInThread.tryLock();
|
||||
return lockInThread.error();
|
||||
}
|
||||
|
||||
void tst_QLockFile::lockOutOtherThread()
|
||||
{
|
||||
const QString fileName = dir.path() + "/lockOtherThread";
|
||||
QLockFile lockFile(fileName);
|
||||
QVERIFY(lockFile.lock());
|
||||
|
||||
// Other thread can't acquire lock
|
||||
auto ret = QtConcurrent::run(tryLockFromThread, fileName);
|
||||
QCOMPARE(ret.result(), QLockFile::LockFailedError);
|
||||
|
||||
lockFile.unlock();
|
||||
|
||||
// Now other thread can acquire lock
|
||||
auto ret2 = QtConcurrent::run(tryLockFromThread, fileName);
|
||||
QCOMPARE(ret2.result(), QLockFile::NoError);
|
||||
}
|
||||
|
||||
static QLockFile::LockError lockFromThread(const QString &fileName)
|
||||
{
|
||||
QLockFile lockInThread(fileName);
|
||||
lockInThread.lock();
|
||||
return lockInThread.error();
|
||||
}
|
||||
|
||||
// QTBUG-38853, best way to trigger it was to add a QThread::sleep(1) in QLockFilePrivate::getLockInfo() after the first readLine.
|
||||
// Then (on Windows), the QFile::remove() in unlock() (called by the first thread who got the lock, in the destructor)
|
||||
// would fail due to the existing reader on the file. Fixed by checking the return value of QFile::remove() in unlock().
|
||||
void tst_QLockFile::raceWithOtherThread()
|
||||
{
|
||||
const QString fileName = dir.path() + "/raceWithOtherThread";
|
||||
auto ret = QtConcurrent::run(lockFromThread, fileName);
|
||||
auto ret2 = QtConcurrent::run(lockFromThread, fileName);
|
||||
QCOMPARE(ret.result(), QLockFile::NoError);
|
||||
QCOMPARE(ret2.result(), QLockFile::NoError);
|
||||
}
|
||||
|
||||
static bool lockFromThreadAndWait(const QString &fileName, int sleepMs, QSemaphore *semThreadReady, QSemaphore *semMainThreadDone)
|
||||
{
|
||||
QLockFile lockFile(fileName);
|
||||
if (!lockFile.lock()) {
|
||||
qWarning() << "Locking failed" << lockFile.error();
|
||||
return false;
|
||||
}
|
||||
semThreadReady->release();
|
||||
QThread::msleep(sleepMs);
|
||||
semMainThreadDone->acquire();
|
||||
lockFile.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
void tst_QLockFile::waitForLock_data()
|
||||
{
|
||||
QTest::addColumn<int>("testNumber");
|
||||
QTest::addColumn<int>("threadSleepMs");
|
||||
QTest::addColumn<bool>("releaseEarly");
|
||||
QTest::addColumn<int>("tryLockTimeout");
|
||||
QTest::addColumn<bool>("expectedResult");
|
||||
|
||||
int tn = 0; // test number
|
||||
QTest::newRow("wait_forever_succeeds") << ++tn << 500 << true << -1 << true;
|
||||
QTest::newRow("wait_longer_succeeds") << ++tn << 500 << true << 1000 << true;
|
||||
QTest::newRow("wait_zero_fails") << ++tn << 500 << false << 0 << false;
|
||||
QTest::newRow("wait_not_enough_fails") << ++tn << 500 << false << 100 << false;
|
||||
}
|
||||
|
||||
void tst_QLockFile::waitForLock()
|
||||
{
|
||||
QFETCH(int, testNumber);
|
||||
QFETCH(int, threadSleepMs);
|
||||
QFETCH(bool, releaseEarly);
|
||||
QFETCH(int, tryLockTimeout);
|
||||
QFETCH(bool, expectedResult);
|
||||
|
||||
const QString fileName = dir.path() + "/waitForLock" + QString::number(testNumber);
|
||||
QLockFile lockFile(fileName);
|
||||
QSemaphore semThreadReady, semMainThreadDone;
|
||||
// Lock file from a thread
|
||||
auto ret = QtConcurrent::run(lockFromThreadAndWait, fileName, threadSleepMs, &semThreadReady, &semMainThreadDone);
|
||||
semThreadReady.acquire();
|
||||
|
||||
if (releaseEarly) // let the thread release the lock after threadSleepMs
|
||||
semMainThreadDone.release();
|
||||
|
||||
QCOMPARE(lockFile.tryLock(tryLockTimeout), expectedResult);
|
||||
if (expectedResult)
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::NoError));
|
||||
else
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::LockFailedError));
|
||||
|
||||
if (!releaseEarly) // only let the thread release the lock now
|
||||
semMainThreadDone.release();
|
||||
|
||||
QVERIFY(ret.result()); // waits for the thread to finish
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleLockFromCrashedProcess_data()
|
||||
{
|
||||
QTest::addColumn<int>("staleLockTime");
|
||||
|
||||
// Test both use cases for QLockFile, should make no difference here.
|
||||
QTest::newRow("short") << 30000;
|
||||
QTest::newRow("long") << 0;
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleLockFromCrashedProcess()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
QFETCH(int, staleLockTime);
|
||||
const QString fileName = dir.path() + "/staleLockFromCrashedProcess";
|
||||
|
||||
int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-uncleanexit");
|
||||
QCOMPARE(ret, int(QLockFile::NoError));
|
||||
QTRY_VERIFY(QFile::exists(fileName));
|
||||
|
||||
QLockFile secondLock(fileName);
|
||||
secondLock.setStaleLockTime(staleLockTime);
|
||||
// tryLock detects and removes the stale lock (since the PID is dead)
|
||||
#ifdef Q_OS_WIN
|
||||
// It can take a bit of time on Windows, though.
|
||||
QVERIFY(secondLock.tryLock(30000));
|
||||
#else
|
||||
QVERIFY(secondLock.tryLock());
|
||||
#endif
|
||||
QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleLockFromCrashedProcessReusedPid()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#elif defined(QT_PLATFORM_UIKIT)
|
||||
QSKIP("We cannot retrieve information about other processes on this platform.");
|
||||
#else
|
||||
const QString fileName = dir.path() + "/staleLockFromCrashedProcessReusedPid";
|
||||
|
||||
int ret = QProcess::execute(m_helperApp, QStringList() << fileName << "-uncleanexit");
|
||||
QCOMPARE(ret, int(QLockFile::NoError));
|
||||
QVERIFY(QFile::exists(fileName));
|
||||
QVERIFY(overwritePidInLockFile(fileName, QCoreApplication::applicationPid()));
|
||||
|
||||
QLockFile secondLock(fileName);
|
||||
qint64 pid = 0;
|
||||
QVERIFY(secondLock.getLockInfo(&pid, 0, 0));
|
||||
QCOMPARE(pid, QCoreApplication::applicationPid());
|
||||
secondLock.setStaleLockTime(0);
|
||||
QVERIFY(secondLock.tryLock());
|
||||
QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleShortLockFromBusyProcess()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
const QString fileName = dir.path() + "/staleLockFromBusyProcess";
|
||||
|
||||
QProcess proc;
|
||||
proc.start(m_helperApp, QStringList() << fileName << "-busy");
|
||||
QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
|
||||
QTRY_VERIFY(QFile::exists(fileName));
|
||||
|
||||
QLockFile secondLock(fileName);
|
||||
QVERIFY(!secondLock.tryLock()); // held by other process
|
||||
QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
|
||||
qint64 pid;
|
||||
QString hostname, appname;
|
||||
QTRY_VERIFY(secondLock.getLockInfo(&pid, &hostname, &appname));
|
||||
#ifdef Q_OS_UNIX
|
||||
QCOMPARE(pid, proc.processId());
|
||||
#endif
|
||||
|
||||
secondLock.setStaleLockTime(100);
|
||||
QTest::qSleep(100); // make the lock stale
|
||||
// We can't "steal" (delete+recreate) a lock file from a running process
|
||||
// until the file descriptor is closed.
|
||||
QVERIFY(!secondLock.tryLock());
|
||||
|
||||
proc.waitForFinished();
|
||||
QVERIFY(secondLock.tryLock());
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleLongLockFromBusyProcess()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
const QString fileName = dir.path() + "/staleLockFromBusyProcess";
|
||||
|
||||
QProcess proc;
|
||||
proc.start(m_helperApp, QStringList() << fileName << "-busy");
|
||||
QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
|
||||
QTRY_VERIFY(QFile::exists(fileName));
|
||||
|
||||
QLockFile secondLock(fileName);
|
||||
secondLock.setStaleLockTime(0);
|
||||
QVERIFY(!secondLock.tryLock(100)); // never stale
|
||||
QCOMPARE(int(secondLock.error()), int(QLockFile::LockFailedError));
|
||||
qint64 pid;
|
||||
QTRY_VERIFY(secondLock.getLockInfo(&pid, NULL, NULL));
|
||||
QVERIFY(pid > 0);
|
||||
|
||||
// As long as the other process is running, we can't remove the lock file
|
||||
QVERIFY(!secondLock.removeStaleLockFile());
|
||||
|
||||
proc.waitForFinished();
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
static QString tryStaleLockFromThread(const QString &fileName)
|
||||
{
|
||||
QLockFile lockInThread(fileName + ".lock");
|
||||
lockInThread.setStaleLockTime(1000);
|
||||
if (!lockInThread.lock())
|
||||
return "Error locking: " + QString::number(lockInThread.error());
|
||||
|
||||
// The concurrent use of the file below (write, read, delete) is protected by the lock file above.
|
||||
// (provided that it doesn't become stale due to this operation taking too long)
|
||||
QFile theFile(fileName);
|
||||
if (!theFile.open(QIODevice::WriteOnly))
|
||||
return "Couldn't open for write";
|
||||
theFile.write("Hello world");
|
||||
theFile.flush();
|
||||
theFile.close();
|
||||
QFile reader(fileName);
|
||||
if (!reader.open(QIODevice::ReadOnly))
|
||||
return "Couldn't open for read";
|
||||
const QByteArray read = reader.readAll();
|
||||
if (read != "Hello world")
|
||||
return "File didn't have the expected contents:" + read;
|
||||
reader.remove();
|
||||
return QString();
|
||||
}
|
||||
|
||||
void tst_QLockFile::staleLockRace()
|
||||
{
|
||||
#if !QT_CONFIG(process)
|
||||
QSKIP("This test requires QProcess support");
|
||||
#else
|
||||
// Multiple threads notice a stale lock at the same time
|
||||
// Only one thread should delete it, otherwise a race will ensue
|
||||
const QString fileName = dir.path() + "/sharedFile";
|
||||
const QString lockName = fileName + ".lock";
|
||||
int ret = QProcess::execute(m_helperApp, QStringList() << lockName << "-uncleanexit");
|
||||
QCOMPARE(ret, int(QLockFile::NoError));
|
||||
QTRY_VERIFY(QFile::exists(lockName));
|
||||
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(10);
|
||||
QFutureSynchronizer<QString> synchronizer;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
synchronizer.addFuture(QtConcurrent::run(tryStaleLockFromThread, fileName));
|
||||
synchronizer.waitForFinished();
|
||||
foreach (const QFuture<QString> &future, synchronizer.futures())
|
||||
QVERIFY2(future.result().isEmpty(), qPrintable(future.result()));
|
||||
#endif // QT_CONFIG(process)
|
||||
}
|
||||
|
||||
void tst_QLockFile::noPermissions()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
// A readonly directory still allows us to create files, on Windows.
|
||||
QSKIP("No permission testing on Windows");
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
|
||||
if (::geteuid() == 0)
|
||||
QSKIP("Test is not applicable with root privileges");
|
||||
#endif
|
||||
// Restore permissions so that the QTemporaryDir cleanup can happen
|
||||
class PermissionRestorer
|
||||
{
|
||||
QString m_path;
|
||||
public:
|
||||
PermissionRestorer(const QString& path)
|
||||
: m_path(path)
|
||||
{}
|
||||
|
||||
~PermissionRestorer()
|
||||
{
|
||||
QFile file(m_path);
|
||||
file.setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner));
|
||||
}
|
||||
};
|
||||
|
||||
const QString fileName = dir.path() + "/staleLock";
|
||||
QFile dirAsFile(dir.path()); // I have to use QFile to change a dir's permissions...
|
||||
QVERIFY2(dirAsFile.setPermissions(QFile::Permissions{}), qPrintable(dir.path())); // no permissions
|
||||
PermissionRestorer permissionRestorer(dir.path());
|
||||
|
||||
QLockFile lockFile(fileName);
|
||||
QVERIFY(!lockFile.lock());
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
|
||||
}
|
||||
|
||||
enum ProcessProperty {
|
||||
ElevatedProcess = 0x1,
|
||||
VirtualStore = 0x2
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(ProcessProperties, ProcessProperty)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessProperties)
|
||||
|
||||
static inline ProcessProperties processProperties()
|
||||
{
|
||||
ProcessProperties result;
|
||||
#if defined(Q_OS_WIN)
|
||||
HANDLE processToken = NULL;
|
||||
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &processToken)) {
|
||||
DWORD elevation; // struct containing a DWORD, not present in some MinGW headers.
|
||||
DWORD cbSize = sizeof(elevation);
|
||||
if (GetTokenInformation(processToken, TokenElevation, &elevation, cbSize, &cbSize)
|
||||
&& elevation) {
|
||||
result |= ElevatedProcess;
|
||||
}
|
||||
// Check for UAC virtualization (compatibility mode for old software
|
||||
// allowing it to write to system folders by mirroring them under
|
||||
// "\Users\...\AppData\Local\VirtualStore\", which is typically the case
|
||||
// for MinGW).
|
||||
DWORD virtualStoreEnabled = 0;
|
||||
cbSize = sizeof(virtualStoreEnabled);
|
||||
if (GetTokenInformation(processToken, TokenVirtualizationEnabled, &virtualStoreEnabled, cbSize, &cbSize)
|
||||
&& virtualStoreEnabled) {
|
||||
result |= VirtualStore;
|
||||
}
|
||||
CloseHandle(processToken);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void tst_QLockFile::noPermissionsWindows()
|
||||
{
|
||||
// Windows: Do the permissions test in a system directory in which
|
||||
// files cannot be created.
|
||||
#if !defined(Q_OS_WIN)
|
||||
QSKIP("This test is for desktop Windows only");
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows7)
|
||||
QSKIP("This test requires at least Windows 7");
|
||||
#endif
|
||||
if (const int p = processProperties()) {
|
||||
const QByteArray message = "This test cannot be run (properties=0x"
|
||||
+ QByteArray::number(p, 16) + ')';
|
||||
QSKIP(message.constData());
|
||||
}
|
||||
|
||||
const QString fileName = QFile::decodeName(qgetenv("ProgramFiles"))
|
||||
+ QLatin1Char('/') + QCoreApplication::applicationName()
|
||||
+ QDateTime::currentDateTime().toString(QStringLiteral("yyMMddhhmm"));
|
||||
QLockFile lockFile(fileName);
|
||||
QVERIFY(!lockFile.lock());
|
||||
QCOMPARE(int(lockFile.error()), int(QLockFile::PermissionError));
|
||||
}
|
||||
|
||||
void tst_QLockFile::corruptedLockFile()
|
||||
{
|
||||
const QString fileName = dir.path() + "/corruptedLockFile";
|
||||
|
||||
{
|
||||
// Create a empty file. Typically the result of a computer crash or hard disk full.
|
||||
QFile file(fileName);
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
}
|
||||
|
||||
QLockFile secondLock(fileName);
|
||||
secondLock.setStaleLockTime(100);
|
||||
QVERIFY(secondLock.tryLock(10000));
|
||||
QCOMPARE(int(secondLock.error()), int(QLockFile::NoError));
|
||||
}
|
||||
|
||||
void tst_QLockFile::corruptedLockFileInTheFuture()
|
||||
{
|
||||
#if !defined(Q_OS_UNIX)
|
||||
QSKIP("This tests needs utimes");
|
||||
#else
|
||||
// This test is the same as the previous one, but the corruption was so there is a corrupted
|
||||
// .rmlock whose timestamp is in the future
|
||||
|
||||
const QString fileName = dir.path() + "/corruptedLockFile.rmlock";
|
||||
|
||||
{
|
||||
QFile file(fileName);
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
}
|
||||
|
||||
struct timeval times[2];
|
||||
gettimeofday(times, 0);
|
||||
times[1].tv_sec = (times[0].tv_sec += 600);
|
||||
times[1].tv_usec = times[0].tv_usec;
|
||||
utimes(fileName.toLocal8Bit(), times);
|
||||
|
||||
QTest::ignoreMessage(QtInfoMsg, "QLockFile: Lock file '" + fileName.toUtf8() + "' has a modification time in the future");
|
||||
corruptedLockFile();
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QLockFile::hostnameChange()
|
||||
{
|
||||
const QByteArray hostid = QSysInfo::machineUniqueId();
|
||||
if (hostid.isEmpty())
|
||||
QSKIP("Could not get a unique host ID on this machine");
|
||||
|
||||
QString lockFile = dir.path() + "/hostnameChangeLock";
|
||||
QLockFile lock1(lockFile);
|
||||
QVERIFY(lock1.lock());
|
||||
|
||||
{
|
||||
// now modify it
|
||||
QFile f;
|
||||
QVERIFY(f.open(QLockFilePrivate::getLockFileHandle(&lock1),
|
||||
QIODevice::ReadWrite | QIODevice::Text,
|
||||
QFile::DontCloseHandle));
|
||||
QVERIFY(overwriteLineInLockFile(f, 3, "this is not a hostname"));
|
||||
}
|
||||
|
||||
{
|
||||
// we should fail to lock
|
||||
QLockFile lock2(lockFile);
|
||||
QVERIFY(!lock2.tryLock(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QLockFile::differentMachines()
|
||||
{
|
||||
const QByteArray hostid = QSysInfo::machineUniqueId();
|
||||
if (hostid.isEmpty())
|
||||
QSKIP("Could not get a unique host ID on this machine");
|
||||
|
||||
QString lockFile = dir.path() + "/differentMachinesLock";
|
||||
QLockFile lock1(lockFile);
|
||||
QVERIFY(lock1.lock());
|
||||
|
||||
{
|
||||
// now modify it
|
||||
QFile f;
|
||||
QVERIFY(f.open(QLockFilePrivate::getLockFileHandle(&lock1),
|
||||
QIODevice::ReadWrite | QIODevice::Text,
|
||||
QFile::DontCloseHandle));
|
||||
QVERIFY(overwriteLineInLockFile(f, 1, QT_STRINGIFY(INT_MAX)));
|
||||
QVERIFY(overwriteLineInLockFile(f, 4, "this is not a UUID"));
|
||||
}
|
||||
|
||||
{
|
||||
// we should fail to lock
|
||||
QLockFile lock2(lockFile);
|
||||
QVERIFY(!lock2.tryLock(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QLockFile::reboot()
|
||||
{
|
||||
const QByteArray bootid = QSysInfo::bootUniqueId();
|
||||
if (bootid.isEmpty())
|
||||
QSKIP("Could not get a unique boot ID on this machine");
|
||||
|
||||
// create a lock so we can get its contents
|
||||
QString lockFile = dir.path() + "/rebootLock";
|
||||
QLockFile lock1(lockFile);
|
||||
QVERIFY(lock1.lock());
|
||||
|
||||
QFile f(lockFile);
|
||||
QVERIFY(f.open(QFile::ReadOnly | QFile::Text));
|
||||
auto lines = f.readAll().split('\n');
|
||||
f.close();
|
||||
|
||||
lock1.unlock();
|
||||
|
||||
// now recreate the file simulating a reboot
|
||||
QVERIFY(f.open(QFile::WriteOnly | QFile::Text));
|
||||
lines[4] = "this is not a UUID";
|
||||
f.write(lines.join('\n'));
|
||||
f.close();
|
||||
|
||||
// we should succeed in locking
|
||||
QVERIFY(lock1.tryLock(0));
|
||||
}
|
||||
|
||||
bool tst_QLockFile::overwritePidInLockFile(const QString &filePath, qint64 pid)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QFile::ReadWrite | QFile::Text)) {
|
||||
qErrnoWarning("Cannot open %s", qPrintable(filePath));
|
||||
return false;
|
||||
}
|
||||
return overwriteLineInLockFile(f, 1, QString::number(pid));
|
||||
}
|
||||
|
||||
bool tst_QLockFile::overwriteLineInLockFile(QFile &f, int line, const QString &newLine)
|
||||
{
|
||||
f.seek(0);
|
||||
QByteArray buf = f.readAll();
|
||||
QStringList lines = QString::fromUtf8(buf).split('\n');
|
||||
if (lines.size() < 3 && lines.size() < line - 1) {
|
||||
qWarning("Unexpected lockfile content.");
|
||||
return false;
|
||||
}
|
||||
lines[line - 1] = newLine;
|
||||
f.seek(0);
|
||||
buf = lines.join('\n').toUtf8();
|
||||
f.resize(buf.size());
|
||||
return f.write(buf) == buf.size();
|
||||
}
|
||||
|
||||
struct LockFileUsageInGlobalDtor
|
||||
{
|
||||
~LockFileUsageInGlobalDtor() {
|
||||
QLockFile lockFile(QDir::currentPath() + "/lastlock");
|
||||
QVERIFY(lockFile.lock());
|
||||
QVERIFY(lockFile.isLocked());
|
||||
}
|
||||
};
|
||||
LockFileUsageInGlobalDtor s_instance;
|
||||
|
||||
QTEST_MAIN(tst_QLockFile)
|
||||
#include "tst_qlockfile.moc"
|
Reference in New Issue
Block a user