aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cppeditor/cpptoolstestcase.cpp
blob: 0c986588c548c75ea2582a04aaba6e9397d0520e (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
// Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0#include"cpptoolstestcase.h"#include"baseeditordocumentparser.h"#include"baseeditordocumentprocessor.h"#include"cppeditorwidget.h"#include"cppmodelmanager.h"#include"cppworkingcopy.h"#include"editordocumenthandle.h"#include"projectinfo.h"#include <coreplugin/editormanager/editormanager.h>#include <extensionsystem/pluginmanager.h>#include <extensionsystem/pluginspec.h>#include <cplusplus/CppDocument.h>#include <projectexplorer/buildsystem.h>#include <projectexplorer/project.h>#include <projectexplorer/projectexplorer.h>#include <projectexplorer/projectmanager.h>#include <texteditor/codeassist/iassistproposal.h>#include <texteditor/codeassist/iassistproposalmodel.h>#include <texteditor/icodestylepreferences.h>#include <texteditor/storagesettings.h>#include <texteditor/syntaxhighlighter.h>#include <texteditor/tabsettings.h>#include <texteditor/texteditor.h>#include <texteditor/texteditorsettings.h>#include <utils/environment.h>#include <utils/fileutils.h>#include <utils/hostosinfo.h>#include <utils/qtcassert.h>#include <utils/temporarydirectory.h>#include <QtTest>using namespace ProjectExplorer;using namespace Utils;namespaceCppEditor::Internal::Tests {boolisClangFormatPresent(){using namespace ExtensionSystem;returnUtils::contains(PluginManager::plugins(), [](const PluginSpec *plugin) {return plugin->id() =="clangformat"&& plugin->isEffectivelyEnabled();});};CppTestDocument::CppTestDocument(const QByteArray &fileName,const QByteArray &source,char cursorMarker):m_fileName(QString::fromUtf8(fileName)),m_source(QString::fromUtf8(source)),m_cursorMarker(cursorMarker),m_targetCursorPosition(m_source.indexOf(QLatin1Char('$'))),m_selectionStartMarker(QLatin1Char(m_cursorMarker) +QLatin1String("{start}")),m_selectionEndMarker(QLatin1Char(m_cursorMarker) +QLatin1String("{end}")){// Try to find selection markersconst int selectionStartIndex = m_source.indexOf(m_selectionStartMarker);const int selectionEndIndex = m_source.indexOf(m_selectionEndMarker);const bool bothSelectionMarkersFound = selectionStartIndex != -1&& selectionEndIndex != -1;const bool noneSelectionMarkersFounds = selectionStartIndex == -1&& selectionEndIndex == -1;QTC_ASSERT(bothSelectionMarkersFound || noneSelectionMarkersFounds,return);if(selectionStartIndex != -1) { m_cursorPosition = selectionEndIndex; m_anchorPosition = selectionStartIndex;// No selection markers found, so check for simple cursorMarker}else{ m_cursorPosition = m_source.indexOf(QLatin1Char(cursorMarker));}if(m_cursorPosition != -1|| m_targetCursorPosition != -1)QVERIFY(m_cursorPosition != m_targetCursorPosition);} TestDocumentPtr CppTestDocument::create(const QByteArray &source,const QByteArray &fileName){const TestDocumentPtr doc(newCppTestDocument(fileName, source)); doc->removeMarkers();return doc;} TestDocumentPtr CppTestDocument::create(const QByteArray &fileName,const QByteArray &source,const QByteArray &expectedSource){const TestDocumentPtr doc(newCppTestDocument(fileName, source)); doc->m_expectedSource =QString::fromUtf8(expectedSource); doc->removeMarkers();return doc;} FilePath CppTestDocument::filePath()const{if(!m_baseDirectory.isEmpty())returnFilePath::fromString(QDir::cleanPath(m_baseDirectory +'/'+ m_fileName));if(!QFileInfo(m_fileName).isAbsolute())returnFilePath::fromString(TemporaryDirectory::masterDirectoryPath() +'/'+ m_fileName);returnFilePath::fromString(m_fileName);}boolCppTestDocument::writeToDisk()const{returnCppEditor::Tests::TestCase::writeFile(filePath(), m_source.toUtf8());}voidCppTestDocument::removeMarkers(){// Remove selection markersif(m_anchorPosition != -1) {if(m_anchorPosition < m_cursorPosition) { m_source.remove(m_anchorPosition, m_selectionStartMarker.size()); m_cursorPosition -= m_selectionStartMarker.size(); m_source.remove(m_cursorPosition, m_selectionEndMarker.size());}else{ m_source.remove(m_cursorPosition, m_selectionEndMarker.size()); m_anchorPosition -= m_selectionEndMarker.size(); m_source.remove(m_anchorPosition, m_selectionStartMarker.size());}// Remove simple cursor marker}else if(m_cursorPosition != -1|| m_targetCursorPosition != -1) {if(m_cursorPosition > m_targetCursorPosition) { m_source.remove(m_cursorPosition,1);if(m_targetCursorPosition != -1) { m_source.remove(m_targetCursorPosition,1);--m_cursorPosition;}}else{ m_source.remove(m_targetCursorPosition,1);if(m_cursorPosition != -1) { m_source.remove(m_cursorPosition,1);--m_targetCursorPosition;}}}const int cursorPositionInExpectedSource = m_expectedSource.indexOf(QLatin1Char(m_cursorMarker));if(cursorPositionInExpectedSource > -1) m_expectedSource.remove(cursorPositionInExpectedSource,1);}VerifyCleanCppModelManager::VerifyCleanCppModelManager(){QVERIFY(isClean());}VerifyCleanCppModelManager::~VerifyCleanCppModelManager() {QVERIFY(isClean());}#define RETURN_FALSE_IF_NOT(check) if (!(check)) return false;boolVerifyCleanCppModelManager::isClean(bool testOnlyForCleanedProjects){RETURN_FALSE_IF_NOT(CppModelManager::projectInfos().isEmpty());RETURN_FALSE_IF_NOT(CppModelManager::headerPaths().isEmpty());RETURN_FALSE_IF_NOT(CppModelManager::definedMacros().isEmpty());RETURN_FALSE_IF_NOT(CppModelManager::projectFiles().isEmpty());if(!testOnlyForCleanedProjects) {RETURN_FALSE_IF_NOT(CppModelManager::snapshot().isEmpty());RETURN_FALSE_IF_NOT(CppModelManager::workingCopy().size() ==1);RETURN_FALSE_IF_NOT(CppModelManager::workingCopy().get(CppModelManager::configurationFileName()));}return true;}#undef RETURN_FALSE_IF_NOT}// namespace CppEditor::Internal::TestsnamespaceCppEditor::Tests {static boolcloseEditorsWithoutGarbageCollectorInvocation(const QList<Core::IEditor *> &editors){CppModelManager::enableGarbageCollector(false);const bool closeEditorsSucceeded =Core::EditorManager::closeEditors(editors,false);CppModelManager::enableGarbageCollector(true);return closeEditorsSucceeded;}static boolsnapshotContains(constCPlusPlus::Snapshot &snapshot,const QSet<FilePath> &filePaths){for(const FilePath &filePath : filePaths) {if(!snapshot.contains(filePath)) {qWarning() <<"Missing file in snapshot:"<<qPrintable(filePath.toUrlishString());return false;}}return true;}TestCase::TestCase(bool runGarbageCollector):m_succeededSoFar(false),m_runGarbageCollector(runGarbageCollector){if(m_runGarbageCollector)QVERIFY(garbageCollectGlobalSnapshot()); m_succeededSoFar =true;}TestCase::~TestCase(){QVERIFY(closeEditorsWithoutGarbageCollectorInvocation(m_editorsToClose));QCoreApplication::processEvents();if(m_runGarbageCollector)QVERIFY(garbageCollectGlobalSnapshot());}boolTestCase::succeededSoFar()const{return m_succeededSoFar;}boolTestCase::openCppEditor(const FilePath &filePath,TextEditor::BaseTextEditor **editor, CppEditorWidget **editorWidget){if(constauto e =dynamic_cast<TextEditor::BaseTextEditor *>(Core::EditorManager::openEditor(filePath))) {if(editor) {*editor = e;TextEditor::StorageSettings s = e->textDocument()->storageSettings(); s.m_addFinalNewLine =false; e->textDocument()->setStorageSettings(s);TextEditor::TabSettings ts =TextEditor::TextEditorSettings::codeStyle()->tabSettings(); ts.m_autoDetect =false; e->textDocument()->setTabSettings(ts);}if(!QTest::qWaitFor([e] {return e->editorWidget()->textDocument()->syntaxHighlighter()->syntaxHighlighterUpToDate();},5000))return false;if(editorWidget) {if(CppEditorWidget *w =dynamic_cast<CppEditorWidget *>(e->editorWidget())) {*editorWidget = w;return true;}else{return false;// no or wrong widget}}else{return true;// ok since no widget requested}}else{return false;// no or wrong editor}}CPlusPlus::Snapshot TestCase::globalSnapshot(){returnCppModelManager::snapshot();}boolTestCase::garbageCollectGlobalSnapshot(){CppModelManager::GC();returnglobalSnapshot().isEmpty();}static boolwaitForProcessedEditorDocument_internal(CppEditorDocumentHandle *editorDocument,int timeOutInMs){QTC_ASSERT(editorDocument,return false); QElapsedTimer timer; timer.start(); forever {if(!editorDocument->processor()->isParserRunning())return true;if(timer.elapsed() > timeOutInMs)return false;QCoreApplication::processEvents();QThread::msleep(20);}}boolTestCase::waitForProcessedEditorDocument(const FilePath &filePath,int timeOutInMs){auto*editorDocument =CppModelManager::cppEditorDocument(filePath);returnwaitForProcessedEditorDocument_internal(editorDocument, timeOutInMs);}CPlusPlus::Document::Ptr TestCase::waitForRehighlightedSemanticDocument( CppEditorWidget *editorWidget,int timeoutInMs){ QElapsedTimer timer; timer.start();while(!editorWidget->isSemanticInfoValid()) {if(timer.elapsed() >= timeoutInMs)return{};QCoreApplication::processEvents();QThread::msleep(20);}return editorWidget->semanticInfo().doc;}boolTestCase::parseFiles(const QSet<FilePath> &filePaths){CppModelManager::updateSourceFiles(filePaths).waitForFinished();QCoreApplication::processEvents();constCPlusPlus::Snapshot snapshot =globalSnapshot();if(snapshot.isEmpty()) {qWarning("After parsing: snapshot is empty.");return false;}if(!snapshotContains(snapshot, filePaths)) {qWarning("After parsing: snapshot does not contain all expected files.");return false;}return true;}boolTestCase::parseFiles(const QString &filePath){returnparseFiles({FilePath::fromString(filePath)});}voidTestCase::closeEditorAtEndOfTestCase(Core::IEditor *editor){if(editor && !m_editorsToClose.contains(editor)) m_editorsToClose.append(editor);}boolTestCase::closeEditorWithoutGarbageCollectorInvocation(Core::IEditor *editor){returncloseEditorsWithoutGarbageCollectorInvocation({editor});}CPlusPlus::Document::Ptr TestCase::waitForFileInGlobalSnapshot(const FilePath &filePath,int timeOutInMs){constauto documents =waitForFilesInGlobalSnapshot({filePath}, timeOutInMs);return documents.isEmpty() ?CPlusPlus::Document::Ptr() : documents.first();} QList<CPlusPlus::Document::Ptr>TestCase::waitForFilesInGlobalSnapshot(const FilePaths &filePaths,int timeOutInMs){ QElapsedTimer t; t.start(); QList<CPlusPlus::Document::Ptr> result;for(const FilePath &filePath : filePaths) { forever {if(CPlusPlus::Document::Ptr document =globalSnapshot().document(filePath)) { result.append(document);break;}if(t.elapsed() > timeOutInMs)return{};QCoreApplication::processEvents();}}return result;}boolTestCase::waitUntilProjectIsFullyOpened(Project *project,int timeOutInMs){if(!project)return false;returnQTest::qWaitFor([project]() {returnactiveBuildSystemForActiveProject()&& !activeBuildSystemForActiveProject()->isParsing()&&CppModelManager::projectInfo(project);}, timeOutInMs);}boolTestCase::writeFile(const FilePath &filePath,const QByteArray &contents){Utils::FileSaver saver(filePath);if(!saver.write(contents) || !saver.finalize()) {qWarning() <<"Failed to write file to disk:"<<qPrintable(filePath.toUserOutput());return false;}return true;}ProjectOpenerAndCloser::ProjectOpenerAndCloser(){QVERIFY(!ProjectManager::hasProjects());}ProjectOpenerAndCloser::~ProjectOpenerAndCloser(){if(m_openProjects.isEmpty())return;bool hasGcFinished =false;auto connection =QObject::connect(CppModelManager::instance(), &CppModelManager::gcFinished,[&hasGcFinished] { hasGcFinished =true; });for(Project *project :std::as_const(m_openProjects))ProjectExplorerPlugin::unloadProject(project); QElapsedTimer t; t.start();while(!hasGcFinished && t.elapsed() <=30000)QCoreApplication::processEvents();QObject::disconnect(connection);}ProjectInfo::ConstPtr ProjectOpenerAndCloser::open(const FilePath &projectFile,bool configureAsExampleProject, Kit *kit){ OpenProjectResult result =ProjectExplorerPlugin::openProject(projectFile);if(!result) {qWarning() << result.errorMessage() << result.alreadyOpen();return{};} Project *project = result.project();if(configureAsExampleProject) project->configureAsExampleProject(kit);if(TestCase::waitUntilProjectIsFullyOpened(project)) { m_openProjects.append(project);returnCppModelManager::projectInfo(project);}return{};}TemporaryDir::TemporaryDir():m_temporaryDir("qtcreator-tests-XXXXXX"),m_isValid(m_temporaryDir.isValid()){} FilePath TemporaryDir::createFile(const QByteArray &relativePath,const QByteArray &contents){const QString relativePathString =QString::fromUtf8(relativePath);if(relativePathString.isEmpty() ||QFileInfo(relativePathString).isAbsolute())return{};const FilePath filePath = m_temporaryDir.filePath(relativePathString);if(!TestCase::writeFile(filePath, contents))return{};return filePath;}static boolcopyRecursively(const QString &sourceDirPath,const QString &targetDirPath, QString *error){auto copyHelper = [](const FilePath &sourcePath,const FilePath &targetPath, QString *error) ->bool{if(!sourcePath.copyFile(targetPath)) {if(error) {*error =QString::fromLatin1("copyRecursively() failed:\"%1\"to\"%2\".").arg(sourcePath.toUserOutput(), targetPath.toUserOutput());}return false;}// Copied files from Qt resources are read-only. Make them writable// so that their parent directory can be removed without warnings.return targetPath.setPermissions(targetPath.permissions() |QFile::WriteUser);};returnUtils::FileUtils::copyRecursively(Utils::FilePath::fromString(sourceDirPath),Utils::FilePath::fromString(targetDirPath), error, copyHelper);}TemporaryCopiedDir::TemporaryCopiedDir(const QString &sourceDirPath){if(!m_isValid)return;if(sourceDirPath.isEmpty())return; QFileInfo fi(sourceDirPath);if(!fi.exists() || !fi.isReadable()) { m_isValid =false;return;} QString errorMessage;if(!copyRecursively(sourceDirPath,path(), &errorMessage)) {qWarning() <<qPrintable(errorMessage); m_isValid =false;}} FilePath TemporaryCopiedDir::absolutePath(const QString &relativePath)const{return m_temporaryDir.filePath(relativePath);}intclangdIndexingTimeout(){bool isConversionOk =false;const int intervalAsInt =qtcEnvironmentVariableIntValue("QTC_CLANGD_INDEXING_TIMEOUT",&isConversionOk);if(!isConversionOk)returnUtils::HostOsInfo::isWindowsHost() ?20000:10000;return intervalAsInt;}SourceFilesRefreshGuard::SourceFilesRefreshGuard(){connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed,this, [this] { m_refreshed =true;});}boolSourceFilesRefreshGuard::wait(){for(int i =0; i <10&& !m_refreshed; ++i) {CppEditor::Tests::waitForSignalOrTimeout(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed,1000);}return m_refreshed;}}// namespace CppEditor::Tests
close