From 6277bdbff6cb0af84653558cb384d6d542550471 Mon Sep 17 00:00:00 2001 From: Knut Petter Svendsen Date: Wed, 20 Nov 2013 11:59:34 +0100 Subject: ClearCase: Refactor and add tests Refactored code to make it more testable, and added tests. When running WITH_TESTS the TestCases will make the plugin fake ClearTool such that ClearTool is not needed to run the current tests. Change-Id: I49b50a667309cf337a07ef20dabb4801c680700d Reviewed-by: Orgad Shaneh --- src/plugins/clearcase/clearcasesync.cpp | 237 +++++++++++++++++++++++--------- 1 file changed, 170 insertions(+), 67 deletions(-) (limited to 'src/plugins/clearcase/clearcasesync.cpp') diff --git a/src/plugins/clearcase/clearcasesync.cpp b/src/plugins/clearcase/clearcasesync.cpp index ac965eec93..969e7e91e2 100644 --- a/src/plugins/clearcase/clearcasesync.cpp +++ b/src/plugins/clearcase/clearcasesync.cpp @@ -34,6 +34,12 @@ #include #include #include +#include + +#ifdef WITH_TESTS +#include +#include +#endif namespace ClearCase { namespace Internal { @@ -44,60 +50,120 @@ ClearCaseSync::ClearCaseSync(ClearCasePlugin *plugin, QSharedPointer { } +QStringList ClearCaseSync::updateStatusHotFiles(const QString &viewRoot, + const bool isDynamic, int &total) +{ + QStringList hotFiles; + // find all files whose permissions changed OR hijacked files + // (might have become checked out) + const StatusMap::Iterator send = m_statusMap->end(); + for (StatusMap::Iterator it = m_statusMap->begin(); it != send; ++it) { + const QFileInfo fi(viewRoot, it.key()); + const bool permChanged = it.value().permissions != fi.permissions(); + if (permChanged || it.value().status == FileStatus::Hijacked) { + hotFiles.append(it.key()); + it.value().status = FileStatus::Unknown; + ++total; + } else if (isDynamic && !fi.isWritable()) { // assume a read only file is checked in + it.value().status = FileStatus::CheckedIn; + ++total; + } + } + return hotFiles; +} + +void ClearCaseSync::updateStatus(const QDir &viewRootDir, const bool isDynamic, + const QStringList &files) +{ + foreach (const QString &file, files) { + if (isDynamic) { // assume a read only file is checked in + const QFileInfo fi(viewRootDir, file); + if (!fi.isWritable()) + m_plugin->setStatus(fi.absoluteFilePath(), FileStatus::CheckedIn, false); + } else { + m_plugin->setStatus(viewRootDir.absoluteFilePath(file), FileStatus::Unknown, false); + } + } +} + +void ClearCaseSync::processLine(const QDir &viewRootDir, const QString &buffer) +{ + const int atatpos = buffer.indexOf(QLatin1String("@@")); + if (atatpos == -1) + return; + + // find first whitespace. anything before that is not interesting + const int wspos = buffer.indexOf(QRegExp(QLatin1String("\\s"))); + const QString absFile = + viewRootDir.absoluteFilePath( + QDir::fromNativeSeparators(buffer.left(atatpos))); + QTC_CHECK(QFile(absFile).exists()); + + QString ccState; + const QRegExp reState(QLatin1String("^\\s*\\[[^\\]]*\\]")); // [hijacked]; [loaded but missing] + if (reState.indexIn(buffer, wspos + 1, QRegExp::CaretAtOffset) != -1) { + ccState = reState.cap(); + if (ccState.indexOf(QLatin1String("hijacked")) != -1) + m_plugin->setStatus(absFile, FileStatus::Hijacked, true); + else if (ccState.indexOf(QLatin1String("loaded but missing")) != -1) + m_plugin->setStatus(absFile, FileStatus::Missing, false); + } + else if (buffer.lastIndexOf(QLatin1String("CHECKEDOUT"), wspos) != -1) + m_plugin->setStatus(absFile, FileStatus::CheckedOut, true); + // don't care about checked-in files not listed in project + else if (m_statusMap->contains(absFile)) + m_plugin->setStatus(absFile, FileStatus::CheckedIn, true); +} + +void ClearCaseSync::updateTotalFilesCount(const QString view, ClearCaseSettings settings, + const int processed) +{ + settings = m_plugin->settings(); // Might have changed while task was running + settings.totalFiles[view] = processed; + m_plugin->setSettings(settings); +} + +void ClearCaseSync::updateStatusForNotManagedFiles(const QStringList &files) +{ + foreach (const QString &file, files) { + QString absFile = QFileInfo(file).absoluteFilePath(); + if (!m_statusMap->contains(absFile)) + m_plugin->setStatus(absFile, FileStatus::NotManaged, false); + } +} + void ClearCaseSync::run(QFutureInterface &future, QStringList &files) { ClearCaseSettings settings = m_plugin->settings(); + if (settings.disableIndexer) + return; + const QString program = settings.ccBinaryPath; if (program.isEmpty()) return; - int total = files.size(); - const bool hot = (total < 10); + int totalFileCount = files.size(); + const bool hot = (totalFileCount < 10); int processed = 0; QString view = m_plugin->currentView(); if (view.isEmpty()) emit updateStreamAndView(); if (!hot) - total = settings.totalFiles.value(view, total); + totalFileCount = settings.totalFiles.value(view, totalFileCount); // refresh activities list if (m_plugin->isUcm()) m_plugin->refreshActivities(); - if (settings.disableIndexer) - return; - const bool isDynamic = m_plugin->isDynamic(); const QString viewRoot = m_plugin->viewRoot(); const QDir viewRootDir(viewRoot); QStringList args(QLatin1String("ls")); if (hot) { - // find all files whose permissions changed OR hijacked files - // (might have become checked out) - const StatusMap::Iterator send = m_statusMap->end(); - for (StatusMap::Iterator it = m_statusMap->begin(); it != send; ++it) { - const QFileInfo fi(viewRoot, it.key()); - const bool permChanged = it.value().permissions != fi.permissions(); - if (permChanged || it.value().status == FileStatus::Hijacked) { - files.append(it.key()); - it.value().status = FileStatus::Unknown; - ++total; - } else if (isDynamic && !fi.isWritable()) { // assume a read only file is checked in - it.value().status = FileStatus::CheckedIn; - ++total; - } - } + files << updateStatusHotFiles(viewRoot, isDynamic, totalFileCount); args << files; } else { - foreach (const QString &file, files) { - if (isDynamic) { // assume a read only file is checked in - const QFileInfo fi(viewRootDir, file); - if (!fi.isWritable()) - m_plugin->setStatus(fi.absoluteFilePath(), FileStatus::CheckedIn, false); - } else { - m_plugin->setStatus(viewRootDir.absoluteFilePath(file), FileStatus::Unknown, false); - } - } + updateStatus(viewRootDir, isDynamic, files); args << QLatin1String("-recurse"); QStringList vobs; @@ -111,7 +177,7 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) // adding 1 for initial sync in which total is not accurate, to prevent finishing // (we don't want it to become green) - future.setProgressRange(0, total + 1); + future.setProgressRange(0, totalFileCount + 1); QProcess process; process.setWorkingDirectory(viewRoot); @@ -124,55 +190,92 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) process.bytesAvailable() && !future.isCanceled()) { const QString line = QString::fromLocal8Bit(process.readLine().constData()); - buffer += line; if (buffer.endsWith(QLatin1Char('\n')) || process.atEnd()) { - const int atatpos = buffer.indexOf(QLatin1String("@@")); - if (atatpos != -1) { // probably managed file - // find first whitespace. anything before that is not interesting - const int wspos = buffer.indexOf(QRegExp(QLatin1String("\\s"))); - const QString absFile = - viewRootDir.absoluteFilePath( - QDir::fromNativeSeparators(buffer.left(atatpos))); - - QString ccState; - const QRegExp reState(QLatin1String("^\\s*\\[[^\\]]*\\]")); // [hijacked]; [loaded but missing] - if (reState.indexIn(buffer, wspos + 1, QRegExp::CaretAtOffset) != -1) { - ccState = reState.cap(); - if (ccState.indexOf(QLatin1String("hijacked")) != -1) - m_plugin->setStatus(absFile, FileStatus::Hijacked, true); - else if (ccState.indexOf(QLatin1String("loaded but missing")) != -1) - m_plugin->setStatus(absFile, FileStatus::Missing, false); - } - else if (buffer.lastIndexOf(QLatin1String("CHECKEDOUT"), wspos) != -1) - m_plugin->setStatus(absFile, FileStatus::CheckedOut, true); - // don't care about checked-in files not listed in project - else if (m_statusMap->contains(absFile)) - m_plugin->setStatus(absFile, FileStatus::CheckedIn, true); - } + processLine(viewRootDir, buffer); buffer.clear(); - future.setProgressValue(qMin(total, ++processed)); + future.setProgressValue(qMin(totalFileCount, ++processed)); } } } if (!future.isCanceled()) { - foreach (const QString &file, files) { - QString absFile = QFileInfo(file).absoluteFilePath(); - if (!m_statusMap->contains(absFile)) - m_plugin->setStatus(absFile, FileStatus::NotManaged, false); - } - future.setProgressValue(total + 1); - if (!hot) { - settings = m_plugin->settings(); // Might have changed while task was running - settings.totalFiles[view] = processed; - m_plugin->setSettings(settings); - } + updateStatusForNotManagedFiles(files); + future.setProgressValue(totalFileCount + 1); + if (!hot) + updateTotalFilesCount(view, settings, processed); } + if (process.state() == QProcess::Running) process.kill(); + process.waitForFinished(); } +#ifdef WITH_TESTS +namespace { +class TempFile +{ +public: + TempFile(const QString &fileName) + : m_fileName(fileName) + { + Utils::FileSaver srcSaver(fileName); + srcSaver.write(QByteArray()); + srcSaver.finalize(); + + } + + QString fileName() const { return m_fileName; } + + ~TempFile() + { + QVERIFY(QFile::remove(m_fileName)); + } + +private: + const QString m_fileName; +}; +} + +void ClearCaseSync::verifyParseStatus(const QString &fileName, + const QString &cleartoolLsLine, + const FileStatus::Status status) +{ + QCOMPARE(m_statusMap->count(), 0); + processLine(QDir(QLatin1String("/")), cleartoolLsLine); + + if (status == FileStatus::CheckedIn) { + // The algorithm doesn't store checked in files in the index, unless it was there already + QCOMPARE(m_statusMap->count(), 0); + QCOMPARE(m_statusMap->contains(fileName), false); + m_plugin->setStatus(fileName, FileStatus::Unknown, false); + processLine(QDir(QLatin1String("/")), cleartoolLsLine); + } + + QCOMPARE(m_statusMap->count(), 1); + QCOMPARE(m_statusMap->contains(fileName), true); + QCOMPARE(m_statusMap->value(fileName).status, status); + + QCOMPARE(m_statusMap->contains(QLatin1String(("notexisting"))), false); +} + +void ClearCaseSync::verifyFileNotManaged() +{ + QCOMPARE(m_statusMap->count(), 0); + TempFile temp(QDir::currentPath() + QLatin1String("/notmanaged.cpp")); + const QString fileName = temp.fileName(); + + updateStatusForNotManagedFiles(QStringList(fileName)); + + QCOMPARE(m_statusMap->count(), 1); + + QCOMPARE(m_statusMap->contains(fileName), true); + QCOMPARE(m_statusMap->value(fileName).status, FileStatus::NotManaged); +} + +#endif + + } // namespace Internal } // namespace ClearCase -- cgit v1.2.1 From 0bb76dc984f022ddc7508f783d67d5fb82d6df98 Mon Sep 17 00:00:00 2001 From: Knut Petter Svendsen Date: Thu, 5 Dec 2013 13:30:20 +0100 Subject: ClearCase: Improved performance for indexing dynamic views For dynamic views we only at initial sync/indexing check for checked out files. When a file is opened it will be reindexed if needed. The very time consuming recursive listing of all vobs is not needed as for snapshot views. Change-Id: I83d4ab70efdd311b6f3239ab45569c6d1810e10f Reviewed-by: Orgad Shaneh --- src/plugins/clearcase/clearcasesync.cpp | 172 ++++++++++++++++++++++++++------ 1 file changed, 139 insertions(+), 33 deletions(-) (limited to 'src/plugins/clearcase/clearcasesync.cpp') diff --git a/src/plugins/clearcase/clearcasesync.cpp b/src/plugins/clearcase/clearcasesync.cpp index 969e7e91e2..769c4707bd 100644 --- a/src/plugins/clearcase/clearcasesync.cpp +++ b/src/plugins/clearcase/clearcasesync.cpp @@ -50,8 +50,7 @@ ClearCaseSync::ClearCaseSync(ClearCasePlugin *plugin, QSharedPointer { } -QStringList ClearCaseSync::updateStatusHotFiles(const QString &viewRoot, - const bool isDynamic, int &total) +QStringList ClearCaseSync::updateStatusHotFiles(const QString &viewRoot, int &total) { QStringList hotFiles; // find all files whose permissions changed OR hijacked files @@ -64,31 +63,31 @@ QStringList ClearCaseSync::updateStatusHotFiles(const QString &viewRoot, hotFiles.append(it.key()); it.value().status = FileStatus::Unknown; ++total; - } else if (isDynamic && !fi.isWritable()) { // assume a read only file is checked in - it.value().status = FileStatus::CheckedIn; - ++total; } } return hotFiles; } -void ClearCaseSync::updateStatus(const QDir &viewRootDir, const bool isDynamic, +// Set status for all files to unknown until we're done indexing +void ClearCaseSync::invalidateStatus(const QDir &viewRootDir, const QStringList &files) { foreach (const QString &file, files) { - if (isDynamic) { // assume a read only file is checked in - const QFileInfo fi(viewRootDir, file); - if (!fi.isWritable()) - m_plugin->setStatus(fi.absoluteFilePath(), FileStatus::CheckedIn, false); - } else { m_plugin->setStatus(viewRootDir.absoluteFilePath(file), FileStatus::Unknown, false); - } } } -void ClearCaseSync::processLine(const QDir &viewRootDir, const QString &buffer) +void ClearCaseSync::invalidateStatusAllFiles() +{ + const StatusMap::ConstIterator send = m_statusMap->end(); + for (StatusMap::ConstIterator it = m_statusMap->begin(); it != send; ++it) + m_plugin->setStatus(it.key(), FileStatus::Unknown, false); +} + +void ClearCaseSync::processCleartoolLsLine(const QDir &viewRootDir, const QString &buffer) { const int atatpos = buffer.indexOf(QLatin1String("@@")); + if (atatpos == -1) return; @@ -98,6 +97,7 @@ void ClearCaseSync::processLine(const QDir &viewRootDir, const QString &buffer) viewRootDir.absoluteFilePath( QDir::fromNativeSeparators(buffer.left(atatpos))); QTC_CHECK(QFile(absFile).exists()); + QTC_CHECK(!absFile.isEmpty()); QString ccState; const QRegExp reState(QLatin1String("^\\s*\\[[^\\]]*\\]")); // [hijacked]; [loaded but missing] @@ -132,38 +132,26 @@ void ClearCaseSync::updateStatusForNotManagedFiles(const QStringList &files) } } -void ClearCaseSync::run(QFutureInterface &future, QStringList &files) +void ClearCaseSync::syncSnapshotView(QFutureInterface &future, QStringList &files, + const ClearCaseSettings &settings) { - ClearCaseSettings settings = m_plugin->settings(); - if (settings.disableIndexer) - return; + QString view = m_plugin->currentView(); - const QString program = settings.ccBinaryPath; - if (program.isEmpty()) - return; int totalFileCount = files.size(); const bool hot = (totalFileCount < 10); int processed = 0; - QString view = m_plugin->currentView(); - if (view.isEmpty()) - emit updateStreamAndView(); if (!hot) totalFileCount = settings.totalFiles.value(view, totalFileCount); - // refresh activities list - if (m_plugin->isUcm()) - m_plugin->refreshActivities(); - - const bool isDynamic = m_plugin->isDynamic(); const QString viewRoot = m_plugin->viewRoot(); const QDir viewRootDir(viewRoot); QStringList args(QLatin1String("ls")); if (hot) { - files << updateStatusHotFiles(viewRoot, isDynamic, totalFileCount); + files << updateStatusHotFiles(viewRoot, totalFileCount); args << files; } else { - updateStatus(viewRootDir, isDynamic, files); + invalidateStatus(viewRootDir, files); args << QLatin1String("-recurse"); QStringList vobs; @@ -181,6 +169,8 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) QProcess process; process.setWorkingDirectory(viewRoot); + const QString program = settings.ccBinaryPath; + process.start(program, args); if (!process.waitForStarted()) return; @@ -192,7 +182,7 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) const QString line = QString::fromLocal8Bit(process.readLine().constData()); buffer += line; if (buffer.endsWith(QLatin1Char('\n')) || process.atEnd()) { - processLine(viewRootDir, buffer); + processCleartoolLsLine(viewRootDir, buffer); buffer.clear(); future.setProgressValue(qMin(totalFileCount, ++processed)); } @@ -212,6 +202,82 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) process.waitForFinished(); } +void ClearCaseSync::processCleartoolLscheckoutLine(const QString &buffer) +{ + QString absFile = buffer.trimmed(); + m_plugin->setStatus(absFile, FileStatus::CheckedOut, true); +} + +/// +/// Update the file status for dynamic views. +/// +void ClearCaseSync::syncDynamicView(QFutureInterface &future, + const ClearCaseSettings& settings) +{ + // Always invalidate status for all files + invalidateStatusAllFiles(); + + QStringList args(QLatin1String("lscheckout")); + args << QLatin1String("-avobs") + << QLatin1String("-me") + << QLatin1String("-cview") + << QLatin1String("-s"); + + const QString viewRoot = m_plugin->viewRoot(); + + QProcess process; + process.setWorkingDirectory(viewRoot); + + const QString program = settings.ccBinaryPath; + process.start(program, args); + if (!process.waitForStarted()) + return; + + QString buffer; + int processed = 0; + while (process.waitForReadyRead() && !future.isCanceled()) { + while (process.state() == QProcess::Running && + process.bytesAvailable() && !future.isCanceled()) { + const QString line = QString::fromLocal8Bit(process.readLine().constData()); + buffer += line; + if (buffer.endsWith(QLatin1Char('\n')) || process.atEnd()) { + processCleartoolLscheckoutLine(buffer); + buffer.clear(); + future.setProgressValue(++processed); + } + } + } + + if (process.state() == QProcess::Running) + process.kill(); + + process.waitForFinished(); +} + +void ClearCaseSync::run(QFutureInterface &future, QStringList &files) +{ + ClearCaseSettings settings = m_plugin->settings(); + if (settings.disableIndexer) + return; + + const QString program = settings.ccBinaryPath; + if (program.isEmpty()) + return; + + // refresh activities list + if (m_plugin->isUcm()) + m_plugin->refreshActivities(); + + QString view = m_plugin->currentView(); + if (view.isEmpty()) + emit updateStreamAndView(); + + if (m_plugin->isDynamic()) + syncDynamicView(future, settings); + else + syncSnapshotView(future, files, settings); +} + #ifdef WITH_TESTS namespace { class TempFile @@ -243,14 +309,14 @@ void ClearCaseSync::verifyParseStatus(const QString &fileName, const FileStatus::Status status) { QCOMPARE(m_statusMap->count(), 0); - processLine(QDir(QLatin1String("/")), cleartoolLsLine); + processCleartoolLsLine(QDir(QLatin1String("/")), cleartoolLsLine); if (status == FileStatus::CheckedIn) { // The algorithm doesn't store checked in files in the index, unless it was there already QCOMPARE(m_statusMap->count(), 0); QCOMPARE(m_statusMap->contains(fileName), false); m_plugin->setStatus(fileName, FileStatus::Unknown, false); - processLine(QDir(QLatin1String("/")), cleartoolLsLine); + processCleartoolLsLine(QDir(QLatin1String("/")), cleartoolLsLine); } QCOMPARE(m_statusMap->count(), 1); @@ -274,6 +340,46 @@ void ClearCaseSync::verifyFileNotManaged() QCOMPARE(m_statusMap->value(fileName).status, FileStatus::NotManaged); } +void ClearCaseSync::verifyFileCheckedOutDynamicView() +{ + QCOMPARE(m_statusMap->count(), 0); + + QString fileName(QLatin1String("/hello.C")); + processCleartoolLscheckoutLine(fileName); + + QCOMPARE(m_statusMap->count(), 1); + + QVERIFY(m_statusMap->contains(fileName)); + QCOMPARE(m_statusMap->value(fileName).status, FileStatus::CheckedOut); + + QVERIFY(!m_statusMap->contains(QLatin1String(("notexisting")))); +} + +void ClearCaseSync::verifyFileCheckedInDynamicView() +{ + QCOMPARE(m_statusMap->count(), 0); + + QString fileName(QLatin1String("/hello.C")); + + // checked in files are not kept in the index + QCOMPARE(m_statusMap->count(), 0); + QCOMPARE(m_statusMap->contains(fileName), false); +} + +void ClearCaseSync::verifyFileNotManagedDynamicView() +{ + QCOMPARE(m_statusMap->count(), 0); + TempFile temp(QDir::currentPath() + QLatin1String("/notmanaged.cpp")); + const QString fileName = temp.fileName(); + + updateStatusForNotManagedFiles(QStringList(fileName)); + + QCOMPARE(m_statusMap->count(), 1); + + QVERIFY(m_statusMap->contains(fileName)); + QCOMPARE(m_statusMap->value(fileName).status, FileStatus::NotManaged); +} + #endif -- cgit v1.2.1