diff --git a/HISTORY.rst b/HISTORY.rst index 108cc259a8..24963d3e4e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -82,6 +82,7 @@ PlatformIO Core 4 * Added `PlatformIO CLI Shell Completion `__ for Fish, Zsh, Bash, and PowerShell (`issue #3435 `_) * Automatically build ``contrib-pysite`` package on a target machine when pre-built package is not compatible (`issue #3482 `_) * Fixed an issue on Windows when installing a library dependency from Git repository (`issue #2844 `_, `issue #3328 `_) +* Fixed an issue with PIO Check when a defect with multiline error message is not reported in verbose mode (`issue #3631 `_) 4.3.3 (2020-04-28) ~~~~~~~~~~~~~~~~~~ diff --git a/platformio/__init__.py b/platformio/__init__.py index 071e87bf10..0d8d3f17fb 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -51,9 +51,9 @@ "contrib-pysite": "~2.%d%d.0" % (sys.version_info.major, sys.version_info.minor), "tool-unity": "~1.20500.0", "tool-scons": "~2.20501.7" if sys.version_info.major == 2 else "~4.40001.0", - "tool-cppcheck": "~1.190.0", + "tool-cppcheck": "~1.210.0", "tool-clangtidy": "~1.100000.0", - "tool-pvs-studio": "~7.7.0", + "tool-pvs-studio": "~7.8.0", } __check_internet_hosts__ = [ diff --git a/platformio/commands/check/tools/base.py b/platformio/commands/check/tools/base.py index d873810d42..dc9f476fc4 100644 --- a/platformio/commands/check/tools/base.py +++ b/platformio/commands/check/tools/base.py @@ -83,7 +83,9 @@ def _extract_defines(language, includes_file): cmd = "echo | %s -x %s %s %s -dM -E -" % ( self.cc_path, language, - " ".join([f for f in build_flags if f.startswith(("-m", "-f"))]), + " ".join( + [f for f in build_flags if f.startswith(("-m", "-f", "-std"))] + ), includes_file, ) result = proc.exec_command(cmd, shell=True) diff --git a/platformio/commands/check/tools/clangtidy.py b/platformio/commands/check/tools/clangtidy.py index 05be67b4ab..06f3ff7616 100644 --- a/platformio/commands/check/tools/clangtidy.py +++ b/platformio/commands/check/tools/clangtidy.py @@ -63,10 +63,7 @@ def configure_command(self): for scope in project_files: src_files.extend(project_files[scope]) - cmd.extend(flags) - cmd.extend(src_files) - cmd.append("--") - + cmd.extend(flags + src_files + ["--"]) cmd.extend( ["-D%s" % d for d in self.cpp_defines + self.toolchain_defines["c++"]] ) @@ -79,6 +76,6 @@ def configure_command(self): continue includes.append(inc) - cmd.append("--extra-arg=" + self._long_includes_hook(includes)) + cmd.extend(["-I%s" % inc for inc in includes]) return cmd diff --git a/platformio/commands/check/tools/cppcheck.py b/platformio/commands/check/tools/cppcheck.py index 931b16edd9..b38bb8d69f 100644 --- a/platformio/commands/check/tools/cppcheck.py +++ b/platformio/commands/check/tools/cppcheck.py @@ -24,6 +24,8 @@ class CppcheckCheckTool(CheckToolBase): def __init__(self, *args, **kwargs): + self._field_delimiter = "<&PIO&>" + self._buffer = "" self.defect_fields = [ "severity", "message", @@ -55,13 +57,15 @@ def tool_output_filter(self, line): return line def parse_defect(self, raw_line): - if "<&PIO&>" not in raw_line or any( - f not in raw_line for f in self.defect_fields - ): + if self._field_delimiter not in raw_line: + return None + + self._buffer += raw_line + if any(f not in self._buffer for f in self.defect_fields): return None args = dict() - for field in raw_line.split("<&PIO&>"): + for field in self._buffer.split(self._field_delimiter): field = field.strip().replace('"', "") name, value = field.split("=", 1) args[name] = value @@ -94,6 +98,7 @@ def parse_defect(self, raw_line): self._bad_input = True return None + self._buffer = "" return DefectItem(**args) def configure_command( @@ -103,13 +108,16 @@ def configure_command( cmd = [ tool_path, + "--addon-python=%s" % proc.get_pythonexe_path(), "--error-exitcode=1", "--verbose" if self.options.get("verbose") else "--quiet", ] cmd.append( '--template="%s"' - % "<&PIO&>".join(["{0}={{{0}}}".format(f) for f in self.defect_fields]) + % self._field_delimiter.join( + ["{0}={{{0}}}".format(f) for f in self.defect_fields] + ) ) flags = self.get_flags("cppcheck") diff --git a/tests/commands/test_check.py b/tests/commands/test_check.py index 596c0f297a..6d4c6878b6 100644 --- a/tests/commands/test_check.py +++ b/tests/commands/test_check.py @@ -61,6 +61,12 @@ } """ + +PVS_STUDIO_FREE_LICENSE_HEADER = """ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com +""" + EXPECTED_ERRORS = 4 EXPECTED_WARNINGS = 1 EXPECTED_STYLE = 1 @@ -87,19 +93,21 @@ def count_defects(output): return error, warning, style -def test_check_cli_output(clirunner, check_dir): +def test_check_cli_output(clirunner, validate_cliresult, check_dir): result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir)]) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) - assert result.exit_code == 0 assert errors + warnings + style == EXPECTED_DEFECTS -def test_check_json_output(clirunner, check_dir): +def test_check_json_output(clirunner, validate_cliresult, check_dir): result = clirunner.invoke( cmd_check, ["--project-dir", str(check_dir), "--json-output"] ) + validate_cliresult(result) + output = json.loads(result.stdout.strip()) assert isinstance(output, list) @@ -114,14 +122,24 @@ def test_check_tool_defines_passed(clirunner, check_dir): assert "__GNUC__" in output -def test_check_severity_threshold(clirunner, check_dir): +def test_check_language_standard_definition_passed(clirunner, tmpdir): + config = DEFAULT_CONFIG + "\nbuild_flags = -std=c++17" + tmpdir.join("platformio.ini").write(config) + tmpdir.mkdir("src").join("main.cpp").write(TEST_CODE) + result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir), "-v"]) + + assert "__cplusplus=201703L" in result.output + assert "--std=c++17" in result.output + + +def test_check_severity_threshold(clirunner, validate_cliresult, check_dir): result = clirunner.invoke( cmd_check, ["--project-dir", str(check_dir), "--severity=high"] ) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) - assert result.exit_code == 0 assert errors == EXPECTED_ERRORS assert warnings == 0 assert style == 0 @@ -129,10 +147,9 @@ def test_check_severity_threshold(clirunner, check_dir): def test_check_includes_passed(clirunner, check_dir): result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir), "--verbose"]) - output = result.output inc_count = 0 - for l in output.split("\n"): + for l in result.output.split("\n"): if l.startswith("Includes:"): inc_count = l.count("-I") @@ -140,18 +157,18 @@ def test_check_includes_passed(clirunner, check_dir): assert inc_count > 1 -def test_check_silent_mode(clirunner, check_dir): +def test_check_silent_mode(clirunner, validate_cliresult, check_dir): result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir), "--silent"]) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) - assert result.exit_code == 0 assert errors == EXPECTED_ERRORS assert warnings == 0 assert style == 0 -def test_check_custom_pattern_absolute_path(clirunner, tmpdir_factory): +def test_check_custom_pattern_absolute_path(clirunner, validate_cliresult, tmpdir_factory): project_dir = tmpdir_factory.mktemp("project") project_dir.join("platformio.ini").write(DEFAULT_CONFIG) @@ -161,16 +178,16 @@ def test_check_custom_pattern_absolute_path(clirunner, tmpdir_factory): result = clirunner.invoke( cmd_check, ["--project-dir", str(project_dir), "--pattern=" + str(check_dir)] ) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) - assert result.exit_code == 0 assert errors == EXPECTED_ERRORS assert warnings == EXPECTED_WARNINGS assert style == EXPECTED_STYLE -def test_check_custom_pattern_relative_path(clirunner, tmpdir_factory): +def test_check_custom_pattern_relative_path(clirunner, validate_cliresult, tmpdir_factory): tmpdir = tmpdir_factory.mktemp("project") tmpdir.join("platformio.ini").write(DEFAULT_CONFIG) @@ -180,10 +197,10 @@ def test_check_custom_pattern_relative_path(clirunner, tmpdir_factory): result = clirunner.invoke( cmd_check, ["--project-dir", str(tmpdir), "--pattern=app", "--pattern=prj"] ) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) - assert result.exit_code == 0 assert errors + warnings + style == EXPECTED_DEFECTS * 2 @@ -214,7 +231,7 @@ def test_check_bad_flag_passed(clirunner, check_dir): assert style == 0 -def test_check_success_if_no_errors(clirunner, tmpdir): +def test_check_success_if_no_errors(clirunner, validate_cliresult, tmpdir): tmpdir.join("platformio.ini").write(DEFAULT_CONFIG) tmpdir.mkdir("src").join("main.c").write( """ @@ -232,26 +249,28 @@ def test_check_success_if_no_errors(clirunner, tmpdir): ) result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) + validate_cliresult(result) errors, warnings, style = count_defects(result.output) assert "[PASSED]" in result.output - assert result.exit_code == 0 assert errors == 0 assert warnings == 1 assert style == 1 -def test_check_individual_flags_passed(clirunner, tmpdir): +def test_check_individual_flags_passed(clirunner, validate_cliresult, tmpdir): config = DEFAULT_CONFIG + "\ncheck_tool = cppcheck, clangtidy, pvs-studio" config += """\ncheck_flags = cppcheck: --std=c++11 clangtidy: --fix-errors pvs-studio: --analysis-mode=4 """ + tmpdir.join("platformio.ini").write(config) - tmpdir.mkdir("src").join("main.cpp").write(TEST_CODE) + tmpdir.mkdir("src").join("main.cpp").write(PVS_STUDIO_FREE_LICENSE_HEADER + TEST_CODE) result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir), "-v"]) + validate_cliresult(result) clang_flags_found = cppcheck_flags_found = pvs_flags_found = False for l in result.output.split("\n"): @@ -269,7 +288,7 @@ def test_check_individual_flags_passed(clirunner, tmpdir): assert pvs_flags_found -def test_check_cppcheck_misra_addon(clirunner, check_dir): +def test_check_cppcheck_misra_addon(clirunner, validate_cliresult, check_dir): check_dir.join("misra.json").write( """ { @@ -309,12 +328,12 @@ def test_check_cppcheck_misra_addon(clirunner, check_dir): cmd_check, ["--project-dir", str(check_dir), "--flags=--addon=misra.json"] ) - assert result.exit_code == 0 + validate_cliresult(result) assert "R21.3 Found MISRA defect" in result.output assert not isfile(join(str(check_dir), "src", "main.cpp.dump")) -def test_check_fails_on_defects_only_with_flag(clirunner, tmpdir): +def test_check_fails_on_defects_only_with_flag(clirunner, validate_cliresult, tmpdir): config = DEFAULT_CONFIG + "\ncheck_tool = cppcheck, clangtidy" tmpdir.join("platformio.ini").write(config) tmpdir.mkdir("src").join("main.cpp").write(TEST_CODE) @@ -325,11 +344,13 @@ def test_check_fails_on_defects_only_with_flag(clirunner, tmpdir): cmd_check, ["--project-dir", str(tmpdir), "--fail-on-defect=high"] ) - assert default_result.exit_code == 0 + validate_cliresult(default_result) assert result_with_flag.exit_code != 0 -def test_check_fails_on_defects_only_on_specified_level(clirunner, tmpdir): +def test_check_fails_on_defects_only_on_specified_level( + clirunner, validate_cliresult, tmpdir +): config = DEFAULT_CONFIG + "\ncheck_tool = cppcheck, clangtidy" tmpdir.join("platformio.ini").write(config) tmpdir.mkdir("src").join("main.c").write( @@ -350,12 +371,12 @@ def test_check_fails_on_defects_only_on_specified_level(clirunner, tmpdir): high_result = clirunner.invoke( cmd_check, ["--project-dir", str(tmpdir), "--fail-on-defect=high"] ) + validate_cliresult(high_result) low_result = clirunner.invoke( cmd_check, ["--project-dir", str(tmpdir), "--fail-on-defect=low"] ) - assert high_result.exit_code == 0 assert low_result.exit_code != 0 @@ -367,15 +388,9 @@ def test_check_pvs_studio_free_license(clirunner, tmpdir): framework = arduino check_tool = pvs-studio """ - code = ( - """// This is an open source non-commercial project. Dear PVS-Studio, please check it. -// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com -""" - + TEST_CODE - ) tmpdir.join("platformio.ini").write(config) - tmpdir.mkdir("src").join("main.c").write(code) + tmpdir.mkdir("src").join("main.c").write(PVS_STUDIO_FREE_LICENSE_HEADER + TEST_CODE) result = clirunner.invoke( cmd_check, ["--project-dir", str(tmpdir), "--fail-on-defect=high", "-v"] @@ -399,8 +414,7 @@ def test_check_embedded_platform_all_tools(clirunner, validate_cliresult, tmpdir """ # tmpdir.join("platformio.ini").write(config) tmpdir.mkdir("src").join("main.c").write( - """// This is an open source non-commercial project. Dear PVS-Studio, please check it. -// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com + PVS_STUDIO_FREE_LICENSE_HEADER + """ #include void unused_function(int val){ @@ -425,13 +439,13 @@ def test_check_embedded_platform_all_tools(clirunner, validate_cliresult, tmpdir result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) validate_cliresult(result) defects = sum(count_defects(result.output)) - assert result.exit_code == 0 and defects > 0, "Failed %s with %s" % ( + assert defects > 0, "Failed %s with %s" % ( framework, tool, ) -def test_check_skip_includes_from_packages(clirunner, tmpdir): +def test_check_skip_includes_from_packages(clirunner, validate_cliresult, tmpdir): config = """ [env:test] platform = nordicnrf52 @@ -445,13 +459,42 @@ def test_check_skip_includes_from_packages(clirunner, tmpdir): result = clirunner.invoke( cmd_check, ["--project-dir", str(tmpdir), "--skip-packages", "-v"] ) - - output = result.output + validate_cliresult(result) project_path = fs.to_unix_path(str(tmpdir)) - for l in output.split("\n"): + for l in result.output.split("\n"): if not l.startswith("Includes:"): continue for inc in l.split(" "): if inc.startswith("-I") and project_path not in inc: pytest.fail("Detected an include path from packages: " + inc) + + +def test_check_multiline_error(clirunner, tmpdir_factory): + project_dir = tmpdir_factory.mktemp("project") + project_dir.join("platformio.ini").write(DEFAULT_CONFIG) + + project_dir.mkdir("include").join("main.h").write( + """ +#error This is a multiline error message \\ +that should be correctly reported \\ +in both default and verbose modes. +""" + ) + + project_dir.mkdir("src").join("main.c").write( + """ +#include +#include "main.h" + +int main() {} +""" + ) + + result = clirunner.invoke(cmd_check, ["--project-dir", str(project_dir)]) + errors, _, _ = count_defects(result.output) + + result = clirunner.invoke(cmd_check, ["--project-dir", str(project_dir), "-v"]) + verbose_errors, _, _ = count_defects(result.output) + + assert verbose_errors == errors == 1