diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 07ca4e77ed2..2b4c9ba654f 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -113,13 +113,15 @@ CheckThread::CheckThread(ThreadResult &result, int threadIndex) void CheckThread::setSettings(const Settings &settings, std::shared_ptr supprs) { mFiles.clear(); + mFileSettings.clear(); mSettings = settings; // this is a copy mSuppressions = std::move(supprs); } -void CheckThread::analyseWholeProgram(const std::list &files, const std::string& ctuInfo) +void CheckThread::analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo) { mFiles = files; + mFileSettings = fileSettings; mAnalyseWholeProgram = true; mCtuInfo = ctuInfo; start(); @@ -136,8 +138,9 @@ void CheckThread::run() std::string ctuInfo; ctuInfo.swap(mCtuInfo); qDebug() << "Whole program analysis"; - cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, {}, ctuInfo); + cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, mFileSettings, ctuInfo); mFiles.clear(); + mFileSettings.clear(); emit done(); return; } diff --git a/gui/checkthread.h b/gui/checkthread.h index a630ef7fc13..74eb292e6e2 100644 --- a/gui/checkthread.h +++ b/gui/checkthread.h @@ -69,10 +69,11 @@ class CheckThread : public QThread { /** * @brief Run whole program analysis - * @param files All files - * @param ctuInfo Ctu info for addons + * @param files All files + * @param fileSettings All files of the project + * @param ctuInfo Ctu info for addons */ - void analyseWholeProgram(const std::list &files, const std::string& ctuInfo); + void analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo); void setAddonsAndTools(const QStringList &addonsAndTools) { mAddonsAndTools = addonsAndTools; @@ -153,6 +154,7 @@ class CheckThread : public QThread { bool isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const; std::list mFiles; + std::list mFileSettings; bool mAnalyseWholeProgram{}; std::string mCtuInfo; QStringList mAddonsAndTools; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 3ed37eb7b25..5ddb790b77b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -59,6 +59,7 @@ #include #include +#include #include #include #include @@ -96,6 +97,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +120,18 @@ static QString fromNativePath(const QString& p) { #endif } +// The files selected in the results view are not necessarily spelled the same way as the +// project or files.txt spells them, compare them as absolute paths +static QString comparablePath(const QString& path) { + return QDir::cleanPath(QFileInfo(path).absoluteFilePath()); +} + +static QSet comparablePaths(const QStringList& paths) { + QSet ret; + std::transform(paths.cbegin(), paths.cend(), std::inserter(ret, ret.end()), comparablePath); + return ret; +} + template> static T toStdStringList(const QStringList& stringList) { T ret; @@ -568,21 +582,31 @@ void MainWindow::saveSettings() const mUI->mResults->saveSettings(mSettings); } -void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& recheckFiles) +void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { Settings checkSettings; auto supprs = std::make_shared(); if (!getCppcheckSettings(checkSettings, *supprs)) return; - // filter requested files - if (!recheckFiles.isEmpty()) { + // Filter the selected files + if (recheck == Recheck::Selected) { + const QSet selected = comparablePaths(selectedFiles); p.fileSettings.remove_if([&](const FileSettings& fs) { - return !recheckFiles.contains(QString::fromStdString(fs.filename())); + return !selected.contains(comparablePath(QString::fromStdString(fs.filename()))); }); + if (p.fileSettings.empty()) { + QMessageBox msg(QMessageBox::Information, + "Cppcheck", + tr("None of the selected files is part of the project, nothing to analyze."), + QMessageBox::Ok, + this); + msg.exec(); + return; + } } - clearResults(); + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; if (mProjectFile) { @@ -647,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig) +void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { if (files.isEmpty()) return; @@ -657,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c if (!getCppcheckSettings(checkSettings, *supprs)) return; - clearResults(); + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; FileList pathList; @@ -669,7 +693,6 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c } QStringList fileNames = pathList.getFileList(); - mUI->mResults->clear(true); mUI->mResults->setResultsSource(ResultsTree::ResultsSource::Analysis); mThread->clearFiles(); @@ -1379,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) return; if (mProjectFile) { - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); - analyzeProject(mProjectFile, files); return; } @@ -1393,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) if (!getCppcheckSettings(checkSettings, *supprs)) return; - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); + clearResultsForAnalysis(checkSettings.buildDir, files, Recheck::Selected); mCurrentDirectory = mUI->mResults->getCheckDirectory(); FileList pathList; @@ -1451,7 +1466,7 @@ void MainWindow::reAnalyze(bool all) void MainWindow::clearResults() { if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) { - QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir()); + QDir dir(QDir(QFileInfo(mProjectFile->getFilename()).absolutePath()).filePath(mProjectFile->getBuildDir())); for (const QString& f: dir.entryList(QDir::Files)) { if (!f.endsWith("files.txt")) { static const QRegularExpression rx("^.*.s[0-9]+$"); @@ -1465,6 +1480,42 @@ void MainWindow::clearResults() enableResultsButtons(); } +void MainWindow::clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck) +{ + switch (recheck) { + case Recheck::None: + clearResults(); + break; + case Recheck::Selected: + // Only the selected files are analyzed, the results of the other files are kept + mUI->mResults->clear(false); + for (const QString& f : selectedFiles) + mUI->mResults->clearRecheckFile(f); + enableResultsButtons(); + break; + case Recheck::Cached: { + // Every file reports its results again, either analyzed or replayed from the analyzer + // information. Remove the analyzer information of the selected files so that they really + // are analyzed again, also by the clang tools that keep their results next to it. + const QSet selected = comparablePaths(selectedFiles); + QDir dir(QString::fromStdString(buildDir)); + std::ifstream filesTxt(buildDir + "/files.txt"); + std::string line; + while (std::getline(filesTxt, line)) { + AnalyzerInformation::Info info; + if (!info.parse(line) || !selected.contains(comparablePath(QString::fromStdString(info.sourceFile)))) + continue; + const QString afile = QString::fromStdString(info.afile); + for (const QString& f : dir.entryList({afile, afile + ".*"}, QDir::Files)) + dir.remove(f); + } + mUI->mResults->clear(true); + enableResultsButtons(); + break; + } + } +} + void MainWindow::openResults() { if (mUI->mResults->hasResults()) { @@ -1863,10 +1914,17 @@ bool MainWindow::loadLastResults() return true; } -void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, const bool checkLib, const bool checkConfig) +void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, const bool checkLib, const bool checkConfig) { Settings::terminate(false); + // With a build directory the whole project is analyzed: the analyzer information cache skips + // the unchanged files, and files.txt stays complete so whole program analysis sees every file. + // Without a build directory there is no cache, so only the selected files are analyzed. + Recheck recheck = Recheck::None; + if (!selectedFiles.isEmpty()) + recheck = projectFile->getBuildDir().isEmpty() ? Recheck::Selected : Recheck::Cached; + QFileInfo inf(projectFile->getFilename()); const QString& rootpath = projectFile->getRootPath(); @@ -1968,11 +2026,11 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis msg.exec(); return; } - doAnalyzeProject(p, checkLib, checkConfig, recheckFiles); // TODO: avoid copy + doAnalyzeProject(p, checkLib, checkConfig, selectedFiles, recheck); // TODO: avoid copy return; } - QStringList paths = recheckFiles.isEmpty() ? projectFile->getCheckPaths() : recheckFiles; + QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths(); // If paths not given then check the root path (which may be the project // file's location, see above). This is to keep the compatibility with @@ -1981,7 +2039,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis if (paths.isEmpty()) { paths << mCurrentDirectory; } - doAnalyzeFiles(paths, checkLib, checkConfig); + doAnalyzeFiles(paths, checkLib, checkConfig, selectedFiles, recheck); } void MainWindow::newProjectFile() diff --git a/gui/mainwindow.h b/gui/mainwindow.h index c654a2c94f0..a92476a8c20 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -249,6 +250,19 @@ private slots: void changeReportType(); private: + /** @brief How an analysis treats the files selected for a recheck */ + enum class Recheck : std::uint8_t { + /** Not a recheck, everything is analyzed from scratch */ + None, + /** Only the selected files are analyzed, the results of the other files are kept */ + Selected, + /** + * Everything is analyzed. The analyzer information in the build directory is kept, + * except for the selected files, so the unchanged files are not analyzed again. + */ + Cached + }; + bool isCppcheckPremium() const; /** Get filename for last results */ @@ -266,11 +280,11 @@ private slots: /** * @brief Analyze the project. * @param projectFile Pointer to the project to analyze. - * @param recheckFiles files to recheck, empty => check all files + * @param selectedFiles files to recheck, empty => check all files * @param checkLib Flag to indicate if the library should be checked. * @param checkConfig Flag to indicate if the configuration should be checked. */ - void analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, bool checkLib = false, bool checkConfig = false); + void analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, bool checkLib = false, bool checkConfig = false); /** * @brief Set current language @@ -308,8 +322,10 @@ private slots: * @param p imported project * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& recheckFiles = QStringList()); + void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); /** * @brief Analyze all files specified in parameter files @@ -317,8 +333,18 @@ private slots: * @param files List of files and/or directories to analyze * @param checkLib Flag to indicate if library should be checked * @param checkConfig Flag to indicate if the configuration should be checked. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked + */ + void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); + + /** + * @brief Clear the results that a new analysis replaces + * @param buildDir Build directory of the analysis + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false); + void clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, Recheck recheck); /** * @brief Get our default cppcheck settings and read project file. diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index ed8b3c7412e..2c99ecf3ed1 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -246,7 +246,7 @@ bool ResultsTree::addErrorItem(const ErrorItem& errorItem) const QString s = errorItem.toString(); if (mErrorList.contains(s)) return false; - mErrorList.append(s); + mErrorList.insert(s); QSharedPointer errorItemPtr{new ErrorItem(errorItem)}; @@ -427,7 +427,7 @@ void ResultsTree::clear(const QString &filename) if (stripped == fileItem->text() || filename == fileItem->errorItem->file0) { - mErrorList.removeAll(fileItem->errorItem->toString()); + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); break; } @@ -445,7 +445,7 @@ void ResultsTree::clearRecheckFile(const QString &filename) QString storedfile = fileItem->getErrorPathItem().file; storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile); if (actualfile == storedfile) { - mErrorList.removeAll(fileItem->errorItem->toString()); + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); break; } @@ -1132,6 +1132,15 @@ void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const } } +void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem) +{ + for (int i{0}; i < fileItem->rowCount(); ++i) { + const auto *errorItem = dynamic_cast(fileItem->child(i, COLUMN_FILE)); + if (errorItem && errorItem->errorItem) + mErrorList.remove(errorItem->errorItem->toString()); + } +} + void ResultsTree::updateFromOldReport(const QString &filename) { showColumn(COLUMN_SINCE_DATE); @@ -1194,7 +1203,15 @@ void ResultsTree::updateSettings(bool showFullPath, void ResultsTree::setCheckDirectory(const QString &dir) { + if (mCheckPath == dir) + return; + mCheckPath = dir; + + // A partial recheck keeps results whose displayed paths are relative to the check path, so + // they are recalculated. Not without a check path, QDir("") would turn absolute paths into "../..". + if (!mCheckPath.isEmpty()) + refreshFilePaths(); } const QString& ResultsTree::getCheckDirectory() const diff --git a/gui/resultstree.h b/gui/resultstree.h index 23fe5567ed9..c829c21fc6a 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -356,6 +357,12 @@ protected slots: */ void saveErrors(Report *report, const ResultItem *fileItem) const; + /** + * @brief Remove all errors of a file item from the duplicate-check list + * @param fileItem Item whose errors to remove + */ + void removeFileErrorsFromErrorList(const ResultItem *fileItem); + /** * @brief Convert a severity string to a icon filename * @@ -566,8 +573,8 @@ protected slots: QStringList mHiddenMessageId; - // List of existing errors so we can avoid duplicates - QStringList mErrorList; + // Set of existing errors so we can avoid duplicates + QSet mErrorList; QItemSelectionModel* mSelectionModel{}; ThreadHandler *mThread{}; diff --git a/gui/test/resultstree/testresultstree.cpp b/gui/test/resultstree/testresultstree.cpp index 31520f3e6d9..e2e40517f69 100644 --- a/gui/test/resultstree/testresultstree.cpp +++ b/gui/test/resultstree/testresultstree.cpp @@ -265,6 +265,31 @@ void TestResultsTree::resultsInSameFile() const } } +void TestResultsTree::clearRecheckFile() const +{ + // #14893 - rechecking a file removes its results, the results of the other files are kept + ResultsTree tree(nullptr); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); + + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QCOMPARE(model->rowCount(), 2); + + tree.clearRecheckFile("file1.c"); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->item(0,0)->text(), "file2.c"); + + // the recheck reports the results of the rechecked file again, they must not be treated as duplicates + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QCOMPARE(model->rowCount(), 2); + + // the results of the other files are still known + QVERIFY(!tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); +} + void TestResultsTree::testReportType() const { TestReport report("{id},{classification},{guideline}"); diff --git a/gui/test/resultstree/testresultstree.h b/gui/test/resultstree/testresultstree.h index add0390c456..dcce568b4ba 100644 --- a/gui/test/resultstree/testresultstree.h +++ b/gui/test/resultstree/testresultstree.h @@ -26,6 +26,7 @@ private slots: void duplicateResults() const; void multiLineResult() const; void resultsInSameFile() const; + void clearRecheckFile() const; void testReportType() const; void testReportTypeIcon() const; void testGetGuidelineError() const; diff --git a/gui/threadhandler.cpp b/gui/threadhandler.cpp index 3f89d58b558..577dcdc3375 100644 --- a/gui/threadhandler.cpp +++ b/gui/threadhandler.cpp @@ -21,6 +21,7 @@ #include "checkthread.h" #include "common.h" #include "filesettings.h" +#include "importproject.h" #include "resultsview.h" #include "settings.h" #include "utils.h" @@ -53,6 +54,7 @@ ThreadHandler::~ThreadHandler() void ThreadHandler::clearFiles() { mLastFiles.clear(); + mLastFileSettings.clear(); mResults.clearFiles(); mAnalyseWholeProgram = false; mCtuInfo.clear(); @@ -63,6 +65,7 @@ void ThreadHandler::clearFiles() void ThreadHandler::setFiles(std::list files) { mLastFiles = files; + mLastFileSettings.clear(); mResults.setFiles(std::move(files)); } @@ -70,6 +73,7 @@ void ThreadHandler::setProject(const ImportProject &prj) { mResults.setProject(prj); mLastFiles.clear(); + mLastFileSettings = prj.fileSettings; } void ThreadHandler::setCheckFiles(bool all) @@ -190,7 +194,7 @@ void ThreadHandler::threadDone() createThreads(1); mRunningThreadCount = 1; setupCheckThread(*mThreads[0]); - mThreads[0]->analyseWholeProgram(mLastFiles, mCtuInfo); + mThreads[0]->analyseWholeProgram(mLastFiles, mLastFileSettings, mCtuInfo); mAnalyseWholeProgram = false; mCtuInfo.clear(); return; diff --git a/gui/threadhandler.h b/gui/threadhandler.h index 17f011c32ac..879da0db8da 100644 --- a/gui/threadhandler.h +++ b/gui/threadhandler.h @@ -20,6 +20,7 @@ #ifndef THREADHANDLER_H #define THREADHANDLER_H +#include "filesettings.h" #include "settings.h" #include "suppressions.h" #include "threadresult.h" @@ -238,6 +239,12 @@ protected slots: */ std::list mLastFiles; + /** + * @brief Files of the project checked last time (used by the whole program analysis) + * + */ + std::list mLastFileSettings; + /** @brief date and time when current checking started */ QDateTime mCheckStartTime; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index aa8d82c27fa..3bdc6875e63 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -21,7 +21,6 @@ #include "errorlogger.h" #include "filesettings.h" #include "path.h" -#include "utils.h" #include #include @@ -197,12 +196,13 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId) { + const std::string simplifiedSourcefile = Path::simplifyPath(sourcefile); std::string line; while (std::getline(filesTxt,line)) { AnalyzerInformation::Info filesTxtInfo; if (!filesTxtInfo.parse(line)) continue; // TODO: report error? - if (endsWith(sourcefile, filesTxtInfo.sourceFile) && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) + if (filesTxtInfo.sourceFile == simplifiedSourcefile && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) return filesTxtInfo.afile; } return ""; diff --git a/test/testanalyzerinformation.cpp b/test/testanalyzerinformation.cpp index 2174f7d4676..0356630686c 100644 --- a/test/testanalyzerinformation.cpp +++ b/test/testanalyzerinformation.cpp @@ -53,6 +53,41 @@ class TestAnalyzerInformation : public TestFixture { ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "file1.c", "", 0)); f.str(filesTxt); ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "./file1.c", "", 0)); + + // only an exact match is used - "executor.cpp" is a suffix of "processexecutor.cpp" + // so both used to resolve to the same analyzer info file + { + constexpr char suffixTxt[] = "executor.a1:::executor.cpp\n" + "processexecutor.a2:::processexecutor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("executor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "executor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("processexecutor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "processexecutor.cpp", "", 0)); + } + + // the same, with the exact match on the first line + { + constexpr char suffixTxt[] = "processexecutor.a1:::processexecutor.cpp\n" + "executor.a2:::executor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("processexecutor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "processexecutor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("executor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "executor.cpp", "", 0)); + } + + // a path that only ends with the path in files.txt is not a match + { + constexpr char relTxt[] = "a.a1:::src/a.c\n"; + std::istringstream f1(relTxt); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "/home/proj/src/a.c", "", 0)); + } + + // no match at all + { + constexpr char noneTxt[] = "a.a1:::a.c\n"; + std::istringstream f1(noneTxt); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "b.c", "", 0)); + } } void getAnalyzerInfoFile() const {