From 3272a2bbe7a3e462a225eb98652479eebe6d9472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 1 Dec 2023 16:34:14 +0100 Subject: [PATCH] greatly improved `Settings::loadCppcheckCfg()` error handling (#5712) This also fixes the issue that `cppcheck.cfg` is no longer being loaded from executable path. That was introduced by #5704. --- .github/workflows/cppcheck-premium.yml | 2 +- Makefile | 4 +- cli/cmdlineparser.cpp | 32 +++++-- cli/cmdlineparser.h | 4 +- gui/mainwindow.cpp | 11 ++- lib/settings.cpp | 93 ++++++++++++++----- lib/settings.h | 2 +- releasenotes.txt | 1 + test/testcmdlineparser.cpp | 49 +++++++++- test/testsettings.cpp | 124 +++++++++++++++++++++++++ 10 files changed, 278 insertions(+), 44 deletions(-) diff --git a/.github/workflows/cppcheck-premium.yml b/.github/workflows/cppcheck-premium.yml index 11d6f3b8c..2f9863d5e 100644 --- a/.github/workflows/cppcheck-premium.yml +++ b/.github/workflows/cppcheck-premium.yml @@ -40,4 +40,4 @@ jobs: - name: Check run: | cppcheckpremium-${{ env.PREMIUM_VERSION }}/premiumaddon --check-loc-license cppcheck.lic > cppcheck-premium-loc - cppcheckpremium-${{ env.PREMIUM_VERSION }}/cppcheck -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2008 --premium=cert-c++-2016 --error-exitcode=1 lib + cppcheckpremium-${{ env.PREMIUM_VERSION }}/cppcheck -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2008 --premium=cert-c++-2016 --inline-suppr --error-exitcode=1 lib diff --git a/Makefile b/Makefile index 09c40c0e2..9153bf4b3 100644 --- a/Makefile +++ b/Makefile @@ -728,7 +728,7 @@ test/testclangimport.o: test/testclangimport.cpp lib/addoninfo.h lib/check.h lib test/testclass.o: test/testclass.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkclass.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testclass.cpp -test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcmdlineparser.cpp test/testcolor.o: test/testcolor.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h @@ -809,7 +809,7 @@ test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprocessexecutor.cpp -test/testsettings.o: test/testsettings.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h +test/testsettings.o: test/testsettings.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsettings.cpp test/testsimplifytemplate.o: test/testsimplifytemplate.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 1eb8d2fb3..dc8a90063 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -282,6 +282,8 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) // TODO: error out on all missing given files/paths CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const argv[]) { + mSettings.exename = Path::getCurrentExecutablePath(argv[0]); + if (argc <= 1) { printHelp(); return Result::Exit; @@ -307,7 +309,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a // print all possible error messages.. if (std::strcmp(argv[i], "--errorlist") == 0) { - mSettings.loadCppcheckCfg(); + if (!loadCppcheckCfg()) + return Result::Fail; { XMLErrorMessagesLogger xmlLogger; std::cout << ErrorMessage::getXMLHeader(mSettings.cppcheckCfgProductName); @@ -324,7 +327,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } if (std::strcmp(argv[i], "--version") == 0) { - mSettings.loadCppcheckCfg(); + if (!loadCppcheckCfg()) + return Result::Fail; if (!mSettings.cppcheckCfgProductName.empty()) { mLogger.printRaw(mSettings.cppcheckCfgProductName); } else { @@ -343,8 +347,6 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a ImportProject project; - mSettings.exename = Path::getCurrentExecutablePath(argv[0]); - for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { // User define @@ -1203,7 +1205,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } } - mSettings.loadCppcheckCfg(); + if (!loadCppcheckCfg()) + return Result::Fail; // Default template format.. if (mSettings.templateFormat.empty()) { @@ -1609,10 +1612,10 @@ void CmdLineParser::printHelp() const mLogger.printRaw(oss.str()); } -bool CmdLineParser::isCppcheckPremium() const { - if (mSettings.cppcheckCfgProductName.empty()) - mSettings.loadCppcheckCfg(); - return startsWith(mSettings.cppcheckCfgProductName, "Cppcheck Premium"); +bool CmdLineParser::isCppcheckPremium() { + Settings settings; + settings.loadCppcheckCfg(); // TODO: how to handle errors? + return startsWith(settings.cppcheckCfgProductName, "Cppcheck Premium"); } bool CmdLineParser::tryLoadLibrary(Library& destination, const std::string& basepath, const char* filename) @@ -1701,3 +1704,14 @@ bool CmdLineParser::loadAddons(Settings& settings) } return result; } + +bool CmdLineParser::loadCppcheckCfg() +{ + const std::string cfgErr = mSettings.loadCppcheckCfg(); + if (!cfgErr.empty()) { + mLogger.printError("could not load cppcheck.cfg - " + cfgErr); + return false; + } + return true; +} + diff --git a/cli/cmdlineparser.h b/cli/cmdlineparser.h index db3bb9638..739d2b21b 100644 --- a/cli/cmdlineparser.h +++ b/cli/cmdlineparser.h @@ -111,7 +111,7 @@ protected: void printHelp() const; private: - bool isCppcheckPremium() const; + static bool isCppcheckPremium(); template bool parseNumberArg(const char* const arg, std::size_t offset, T& num, bool mustBePositive = false) @@ -150,6 +150,8 @@ private: */ bool loadAddons(Settings& settings); + bool loadCppcheckCfg(); + CmdLineLogger &mLogger; std::vector mPathNames; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 2835f8983..3be305ee5 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -119,7 +119,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : { Settings tempSettings; tempSettings.exename = QCoreApplication::applicationFilePath().toStdString(); - tempSettings.loadCppcheckCfg(); + tempSettings.loadCppcheckCfg(); // TODO: how to handle error? mCppcheckCfgProductName = QString::fromStdString(tempSettings.cppcheckCfgProductName); mCppcheckCfgAbout = QString::fromStdString(tempSettings.cppcheckCfgAbout); } @@ -903,8 +903,7 @@ bool MainWindow::tryLoadLibrary(Library *library, const QString& filename) return true; } -Settings MainWindow::getCppcheckSettings() -{ +Settings MainWindow::getCppcheckSettings() { saveSettings(); // Save settings Settings result; @@ -915,7 +914,11 @@ Settings MainWindow::getCppcheckSettings() if (!std) QMessageBox::critical(this, tr("Error"), tr("Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir= at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured.").arg("std.cfg")); - result.loadCppcheckCfg(); + { + const QString cfgErr = QString::fromStdString(result.loadCppcheckCfg()); + if (!cfgErr.isEmpty()) + QMessageBox::critical(this, tr("Error"), tr("Failed to load %1 - %2").arg("cppcheck.cfg").arg(cfgErr)); + } // If project file loaded, read settings from it if (mProjectFile) { diff --git a/lib/settings.cpp b/lib/settings.cpp index eb1e9847c..54c96f672 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -40,40 +40,87 @@ Settings::Settings() setCheckLevelNormal(); } -// TODO: report error when the config is invalid -void Settings::loadCppcheckCfg() +std::string Settings::loadCppcheckCfg() { - std::string fileName = Path::getPathFromFilename(exename) + "cppcheck.cfg"; + static const std::string cfgFilename = "cppcheck.cfg"; + std::string fileName; #ifdef FILESDIR - if (Path::isFile(FILESDIR "/cppcheck.cfg")) - fileName = FILESDIR "/cppcheck.cfg"; + if (Path::isFile(Path::join(FILESDIR, cfgFilename))) + fileName = Path::join(FILESDIR, cfgFilename); #endif + // cppcheck-suppress knownConditionTrueFalse + if (fileName.empty()) { + fileName = Path::getPathFromFilename(exename) + cfgFilename; + if (!Path::isFile(fileName)) + return ""; + } std::ifstream fin(fileName); if (!fin.is_open()) - return; + return "could not open file"; picojson::value json; fin >> json; - if (!picojson::get_last_error().empty()) - return; - picojson::object obj = json.get(); - if (obj.count("productName") && obj["productName"].is()) - cppcheckCfgProductName = obj["productName"].get(); - if (obj.count("about") && obj["about"].is()) - cppcheckCfgAbout = obj["about"].get(); - if (obj.count("addons") && obj["addons"].is()) { - for (const picojson::value &v : obj["addons"].get()) { - const std::string &s = v.get(); - if (!Path::isAbsolute(s)) - addons.emplace(Path::getPathFromFilename(fileName) + s); - else - addons.emplace(s); + { + const std::string& lastErr = picojson::get_last_error(); + if (!lastErr.empty()) + return "not a valid JSON - " + lastErr; + } + const picojson::object& obj = json.get(); + { + const picojson::object::const_iterator it = obj.find("productName"); + if (it != obj.cend()) { + const auto& v = it->second; + if (!v.is()) + return "'productName' is not a string"; + cppcheckCfgProductName = v.get(); } } - if (obj.count("suppressions") && obj["suppressions"].is()) { - for (const picojson::value &v : obj["suppressions"].get()) - nomsg.addSuppressionLine(v.get()); + { + const picojson::object::const_iterator it = obj.find("about"); + if (it != obj.cend()) { + const auto& v = it->second; + if (!v.is()) + return "'about' is not a string"; + cppcheckCfgAbout = v.get(); + } } + { + const picojson::object::const_iterator it = obj.find("addons"); + if (it != obj.cend()) { + const auto& entry = it->second; + if (!entry.is()) + return "'addons' is not an array"; + for (const picojson::value &v : entry.get()) + { + if (!v.is()) + return "'addons' array entry is not a string"; + const std::string &s = v.get(); + if (!Path::isAbsolute(s)) + addons.emplace(Path::join(Path::getPathFromFilename(fileName), s)); + else + addons.emplace(s); + } + } + } + { + const picojson::object::const_iterator it = obj.find("suppressions"); + if (it != obj.cend()) { + const auto& entry = it->second; + if (!entry.is()) + return "'suppressions' is not an array"; + for (const picojson::value &v : entry.get()) + { + if (!v.is()) + return "'suppressions' array entry is not a string"; + const std::string &s = v.get(); + const std::string err = nomsg.addSuppressionLine(s); + if (!err.empty()) + return "could not parse suppression '" + s + "' - " + err; + } + } + } + + return ""; } std::string Settings::parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups) diff --git a/lib/settings.h b/lib/settings.h index 96317f8b4..6833d8569 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -99,7 +99,7 @@ private: public: Settings(); - void loadCppcheckCfg(); + std::string loadCppcheckCfg(); /** @brief addons, either filename of python/json file or json data */ std::unordered_set addons; diff --git a/releasenotes.txt b/releasenotes.txt index 6144ba869..5e9b0d6db 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -32,3 +32,4 @@ Other: - You can suppress all warnings where macro is used using "-macro" - fixed CMake build with UBSAN and GCC - Added command-line options "--fsigned-char" and "--funsigned-char" to control the signess of the "char" type. This overrides previously specified "--platform" options and is overrides by following ones. +- An error is now reported when the "cppcheck.cfg" is invalid. The CLI version will also exit with a failure in that case. diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 955a55751..d5998c078 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -22,6 +22,7 @@ #include "cppcheckexecutor.h" #include "errortypes.h" #include "helpers.h" +#include "path.h" #include "platform.h" #include "redirect.h" #include "settings.h" @@ -125,6 +126,7 @@ private: TEST_CASE(version); TEST_CASE(versionWithCfg); TEST_CASE(versionExclusive); + TEST_CASE(versionWithInvalidCfg); TEST_CASE(onefile); TEST_CASE(onepath); TEST_CASE(optionwithoutfile); @@ -268,7 +270,9 @@ private: TEST_CASE(showtimeEmpty); TEST_CASE(showtimeInvalid); TEST_CASE(errorlist); + TEST_CASE(errorlistWithCfg); TEST_CASE(errorlistExclusive); + TEST_CASE(errorlistWithInvalidCfg); TEST_CASE(ignorepathsnopath); #if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING) TEST_CASE(exceptionhandling); @@ -360,6 +364,8 @@ private: TEST_CASE(cppcheckBuildDirExistent); TEST_CASE(cppcheckBuildDirNonExistent); TEST_CASE(cppcheckBuildDirEmpty); + + TEST_CASE(invalidCppcheckCfg); } void nooptions() { @@ -412,7 +418,7 @@ private: void versionWithCfg() { REDIRECT; - ScopedFile file("cppcheck.cfg", + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), "{\n" "\"productName\": \"The Product\"" "}\n"); @@ -433,6 +439,16 @@ private: ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } + void versionWithInvalidCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "--version"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + void onefile() { REDIRECT; const char * const argv[] = {"cppcheck", "file.cpp"}; @@ -1690,11 +1706,19 @@ private: const char * const argv[] = {"cppcheck", "--errorlist"}; ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); ASSERT_EQUALS("", logger->str()); // empty since it is logged via ErrorLogger - ASSERT(startsWith(GET_REDIRECT_OUTPUT, "\n")); } - // TODO: test --errorlist with product name + void errorlistWithCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + R"({"productName": "The Product"}\n)"); + const char * const argv[] = {"cppcheck", "--errorlist"}; + ASSERT_EQUALS(CmdLineParser::Result::Exit, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("", logger->str()); // empty since it is logged via ErrorLogger + ASSERT(startsWith(GET_REDIRECT_OUTPUT, ErrorMessage::getXMLHeader("The Product"))); + } void errorlistExclusive() { REDIRECT; @@ -1705,6 +1729,16 @@ private: ASSERT(endsWith(GET_REDIRECT_OUTPUT, "\n")); } + void errorlistWithInvalidCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "--errorlist"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + void ignorepathsnopath() { REDIRECT; const char * const argv[] = {"cppcheck", "-i"}; @@ -2309,6 +2343,15 @@ private: ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); ASSERT_EQUALS("cppcheck: error: Directory '' specified by --cppcheck-build-dir argument has to be existent.\n", logger->str()); } + + void invalidCppcheckCfg() { + REDIRECT; + ScopedFile file(Path::join(Path::getPathFromFilename(Path::getCurrentExecutablePath("")), "cppcheck.cfg"), + "{\n"); + const char * const argv[] = {"cppcheck", "test.cpp"}; + ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + } }; REGISTER_TEST(TestCmdlineParser) diff --git a/test/testsettings.cpp b/test/testsettings.cpp index 03cc6e7a6..c35983008 100644 --- a/test/testsettings.cpp +++ b/test/testsettings.cpp @@ -20,6 +20,7 @@ #include "errortypes.h" #include "settings.h" #include "fixture.h" +#include "helpers.h" class TestSettings : public TestFixture { public: @@ -28,6 +29,7 @@ public: private: void run() override { TEST_CASE(simpleEnableGroup); + TEST_CASE(loadCppcheckCfg); } void simpleEnableGroup() const { @@ -85,6 +87,128 @@ private: ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); } + + void loadCppcheckCfg() + { + { + Settings s; + ASSERT_EQUALS("", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + "{}\n"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + "{\n"); + ASSERT_EQUALS("not a valid JSON - syntax error at line 1 near: ", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": ""}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("", s.cppcheckCfgProductName); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": "product"}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("product", s.cppcheckCfgProductName); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"productName": 1}\n)"); + ASSERT_EQUALS("'productName' is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": ""}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("", s.cppcheckCfgAbout); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": "about"}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS("about", s.cppcheckCfgAbout); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"about": 1}\n)"); + ASSERT_EQUALS("'about' is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": []}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(0, s.addons.size()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": 1}\n)"); + ASSERT_EQUALS("'addons' is not an array", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": ["addon"]}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(1, s.addons.size()); + ASSERT_EQUALS("addon", *s.addons.cbegin()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": [1]}\n)"); + ASSERT_EQUALS("'addons' array entry is not a string", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"addons": []}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(0, s.addons.size()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": 1}\n)"); + ASSERT_EQUALS("'suppressions' is not an array", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": ["id"]}\n)"); + ASSERT_EQUALS("", s.loadCppcheckCfg()); + ASSERT_EQUALS(1, s.nomsg.getSuppressions().size()); + ASSERT_EQUALS("id", s.nomsg.getSuppressions().cbegin()->errorId); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": [""]}\n)"); + ASSERT_EQUALS("could not parse suppression '' - Failed to add suppression. No id.", s.loadCppcheckCfg()); + } + { + Settings s; + ScopedFile file("cppcheck.cfg", + R"({"suppressions": [1]}\n)"); + ASSERT_EQUALS("'suppressions' array entry is not a string", s.loadCppcheckCfg()); + } + + // TODO: test with FILESDIR + } }; REGISTER_TEST(TestSettings)