Updates for PIO Check (#3640)

* Update check tools to the latest versions

* Use language standard when exporting defines to check tools

* Buffer Cppcheck output to detect multiline messages

* Add new test for PIO Check

* Pass include paths to Clang-Tidy as individual compiler arguments

Clang-tidy doesn't support response files which can exceed command
length limitations on Windows

* Simplify tests for PIO Check

* Update history

* Sync changelog
This commit is contained in:
Valerii Koval
2020-08-25 21:19:21 +03:00
committed by GitHub
parent b9fe493336
commit 3e72f098fe
6 changed files with 101 additions and 50 deletions

View File

@ -82,6 +82,7 @@ PlatformIO Core 4
* Added `PlatformIO CLI Shell Completion <https://docs.platformio.org/page/core/userguide/system/completion/index.html>`__ for Fish, Zsh, Bash, and PowerShell (`issue #3435 <https://github.com/platformio/platformio-core/issues/3435>`_)
* Automatically build ``contrib-pysite`` package on a target machine when pre-built package is not compatible (`issue #3482 <https://github.com/platformio/platformio-core/issues/3482>`_)
* Fixed an issue on Windows when installing a library dependency from Git repository (`issue #2844 <https://github.com/platformio/platformio-core/issues/2844>`_, `issue #3328 <https://github.com/platformio/platformio-core/issues/3328>`_)
* Fixed an issue with PIO Check when a defect with multiline error message is not reported in verbose mode (`issue #3631 <https://github.com/platformio/platformio-core/issues/3631>`_)
4.3.3 (2020-04-28)
~~~~~~~~~~~~~~~~~~

View File

@ -51,9 +51,9 @@ __core_packages__ = {
"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__ = [

View File

@ -83,7 +83,9 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
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)

View File

@ -63,10 +63,7 @@ class ClangtidyCheckTool(CheckToolBase):
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 @@ class ClangtidyCheckTool(CheckToolBase):
continue
includes.append(inc)
cmd.append("--extra-arg=" + self._long_includes_hook(includes))
cmd.extend(["-I%s" % inc for inc in includes])
return cmd

View File

@ -24,6 +24,8 @@ from platformio.package.manager.core import get_core_package_dir
class CppcheckCheckTool(CheckToolBase):
def __init__(self, *args, **kwargs):
self._field_delimiter = "<&PIO&>"
self._buffer = ""
self.defect_fields = [
"severity",
"message",
@ -55,13 +57,15 @@ class CppcheckCheckTool(CheckToolBase):
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 @@ class CppcheckCheckTool(CheckToolBase):
self._bad_input = True
return None
self._buffer = ""
return DefectItem(**args)
def configure_command(
@ -103,13 +108,16 @@ class CppcheckCheckTool(CheckToolBase):
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")

View File

@ -61,6 +61,12 @@ int main() {
}
"""
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 @@ int main() {
)
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 @@ R21.4 text.
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 @@ int main() {
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 @@ board = teensy35
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 @@ check_tool = %s
"""
# 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 <stdlib.h>
void unused_function(int val){
@ -425,13 +439,13 @@ int main() {
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 @@ framework = arduino
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 <stdlib.h>
#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