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.
This commit is contained in:
Oliver Stöneberg 2023-12-01 16:34:14 +01:00 committed by GitHub
parent 4182f943aa
commit 3272a2bbe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 44 deletions

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -111,7 +111,7 @@ protected:
void printHelp() const;
private:
bool isCppcheckPremium() const;
static bool isCppcheckPremium();
template<typename T>
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<std::string> mPathNames;

View File

@ -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=<directory> 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) {

View File

@ -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<picojson::object>();
if (obj.count("productName") && obj["productName"].is<std::string>())
cppcheckCfgProductName = obj["productName"].get<std::string>();
if (obj.count("about") && obj["about"].is<std::string>())
cppcheckCfgAbout = obj["about"].get<std::string>();
if (obj.count("addons") && obj["addons"].is<picojson::array>()) {
for (const picojson::value &v : obj["addons"].get<picojson::array>()) {
{
const std::string& lastErr = picojson::get_last_error();
if (!lastErr.empty())
return "not a valid JSON - " + lastErr;
}
const picojson::object& obj = json.get<picojson::object>();
{
const picojson::object::const_iterator it = obj.find("productName");
if (it != obj.cend()) {
const auto& v = it->second;
if (!v.is<std::string>())
return "'productName' is not a string";
cppcheckCfgProductName = v.get<std::string>();
}
}
{
const picojson::object::const_iterator it = obj.find("about");
if (it != obj.cend()) {
const auto& v = it->second;
if (!v.is<std::string>())
return "'about' is not a string";
cppcheckCfgAbout = v.get<std::string>();
}
}
{
const picojson::object::const_iterator it = obj.find("addons");
if (it != obj.cend()) {
const auto& entry = it->second;
if (!entry.is<picojson::array>())
return "'addons' is not an array";
for (const picojson::value &v : entry.get<picojson::array>())
{
if (!v.is<std::string>())
return "'addons' array entry is not a string";
const std::string &s = v.get<std::string>();
if (!Path::isAbsolute(s))
addons.emplace(Path::getPathFromFilename(fileName) + s);
addons.emplace(Path::join(Path::getPathFromFilename(fileName), s));
else
addons.emplace(s);
}
}
if (obj.count("suppressions") && obj["suppressions"].is<picojson::array>()) {
for (const picojson::value &v : obj["suppressions"].get<picojson::array>())
nomsg.addSuppressionLine(v.get<std::string>());
}
{
const picojson::object::const_iterator it = obj.find("suppressions");
if (it != obj.cend()) {
const auto& entry = it->second;
if (!entry.is<picojson::array>())
return "'suppressions' is not an array";
for (const picojson::value &v : entry.get<picojson::array>())
{
if (!v.is<std::string>())
return "'suppressions' array entry is not a string";
const std::string &s = v.get<std::string>();
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<Severity>, SimpleEnableGroup<Checks>> &groups)

View File

@ -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<std::string> addons;

View File

@ -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.

View File

@ -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, "<?xml"));
ASSERT(startsWith(GET_REDIRECT_OUTPUT, ErrorMessage::getXMLHeader("")));
ASSERT(endsWith(GET_REDIRECT_OUTPUT, "</results>\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, "</results>\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)

View File

@ -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)