Bug 1432410 - Add tests in tree to make sure we don't regress with clang-tidy.
MozReview-Commit-ID: EP12O11lD3e
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1689,16 +1689,67 @@ class StaticAnalysis(MachCommandBase):
with StaticAnalysisOutputManager(self.log_manager, monitor, footer) as output:
rc = self.run_process(args=args, line_handler=output.on_line, cwd=cwd)
self.log(logging.WARNING, 'warning_summary',
{'count': len(monitor.warnings_db)},
'{count} warnings present.')
return rc
+ @StaticAnalysisSubCommand('static-analysis', 'autotest',
+ 'Run the checks using the helper tool')
+ @CommandArgument('--dump_results', '-d', default=False, action='store_true',
+ help='Try to autofix errors detected by clang-tidy checkers.')
+
+ def autotest(self, verbose=False, dump_results=False):
+ # If dump_results is True than we just wanna generate the issues.json, we will not
+ # force the re-dowloand from artifacts
+ self._set_log_level(verbose)
+
+ force = True
+ if dump_results:
+ force = False
+
+ # 1. Test to see if we can download clang-package from TaskCluster
+ rc = self._get_clang_tools(force=force, verbose=verbose)
+
+ if rc != 0:
+ return rc
+
+ # 2. Load the defaut checks from the yaml config
+ checks = self._get_checks()
+
+ # 3. Run clang-tidy on the test suit
+ test_cpp = mozpath.join(self.topsrcdir, "tools", "clang-tidy", "test", "test.cpp")
+
+ cmd = [
+ self._clang_tidy_path,
+ '-checks=%s' % checks,
+ test_cpp
+ ]
+
+ clang_output = subprocess.check_output(cmd).decode('utf-8')
+
+ print("{}".format(clang_output))
+ issues = self._parse_issues(clang_output)
+
+ if dump_results:
+ self._build_autotest_result(issues)
+ return 0
+
+ # 4. Read the pre-determined issues
+ baseline_issues = self._get_autotest_stored_issues()
+
+ # 5. Compare the two lists
+ if issues != baseline_issues:
+ return 1
+
+ return 0
+
+
@StaticAnalysisSubCommand('static-analysis', 'install',
'Install the static analysis helper tool')
@CommandArgument('source', nargs='?', type=str,
help='Where to fetch a local archive containing the static-analysis and format helper tool.'
'It will be installed in ~/.mozbuild/clang-tools/.'
'Can be omitted, in which case the latest clang-tools '
' helper for the platform would be automatically '
'detected and installed.')
@@ -1750,16 +1801,51 @@ class StaticAnalysis(MachCommandBase):
return rc
if path is None:
return self._run_clang_format_diff(self._clang_format_diff,
self._clang_format_path, show)
else:
return self._run_clang_format_path(self._clang_format_path, show, path)
+ def _build_autotest_result(self, issues):
+ with open(mozpath.join(self.topsrcdir, "tools", "clang-tidy", "test", "issues.json"), 'w') as f:
+ json.dump(issues, f)
+
+ def _get_autotest_stored_issues(self):
+ with open(mozpath.join(self.topsrcdir, "tools", "clang-tidy", "test", "issues.json")) as f:
+ return json.load(f)
+
+ def _parse_issues(self, clang_output):
+ '''
+ Parse clang-tidy output into structured issues
+ '''
+
+ # Limit clang output parsing to 'Enabled checks:'
+ end = re.search(r'^Enabled checks:\n', clang_output, re.MULTILINE)
+ if end is not None:
+ clang_output = clang_output[:end.start()-1]
+
+ # Sort headers by positions
+ regex_header = re.compile(
+ r'(.+):(\d+):(\d+): (warning|error|note): ([^\[\]\n]+)(?: \[([\.\w-]+)\])?$', re.MULTILINE)
+
+ something = regex_header.finditer(clang_output)
+ headers = sorted(
+ regex_header.finditer(clang_output),
+ key=lambda h: h.start()
+ )
+
+ issues = []
+ for _, header in enumerate(headers):
+ header_group = header.groups()
+ element = [header_group[3], header_group[4], header_group[5]]
+ issues.append(element)
+ return json.dumps(issues)
+
def _get_checks(self):
checks = '-*'
import yaml
with open(mozpath.join(self.topsrcdir, "tools", "clang-tidy", "config.yaml")) as f:
try:
config = yaml.load(f)
for item in config['clang_checkers']:
if item['publish']:
new file mode 100644
--- /dev/null
+++ b/tools/clang-tidy/test/issues.json
@@ -0,0 +1,1 @@
+"[[\"warning\", \"Value stored to 'a' during its initialization is never read\", \"clang-analyzer-deadcode.DeadStores\"], [\"note\", \"Value stored to 'a' during its initialization is never read\", null]]"
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/tools/clang-tidy/test/test.cpp
@@ -0,0 +1,84 @@
+#include <memory>
+#include <string>
+#include <iostream>
+#include <cmath>
+#include <vector>
+#include <map>
+#include <math.h>
+
+extern void g();
+void f() {
+ // readability-redundant-control-flow
+ g();
+ return;
+}
+
+extern const std::string& constReference();
+
+void fstring(std::string) {}
+
+int foo() {
+ // readability-uniqueptr-delete-release
+ std::unique_ptr<int> P;
+ delete P.release();
+
+ // readability-redundant-string-init
+ std::string a = "";
+
+ // readability-redundant-string-cstr
+ std::string tmp;
+ tmp.assign(a.c_str());
+
+ // readability-misleading-indentation
+ if (true)
+ if (true)
+ f();
+ else
+ f();
+
+ // readability-else-after-return
+ if (true)
+ return;
+ else
+ f();
+
+ // readability-container-size-empty
+ if (a.size())
+ f();
+
+ // performance-unnecessary-copy-initialization
+ const std::string UnnecessaryCopy = constReference();
+
+ // performance-type-promotion-in-math-fn
+ float af;
+ asin(0.f);
+ {
+ // performance-inefficient-vector-operation
+ std::vector<int> v;
+ int n = 100;
+ for (int i = 0; i < n; ++i) {
+ v.push_back(n);
+ }
+ }
+
+ {
+ // performance-inefficient-string-concatenation
+ std::string mystr1, mystr2;
+ auto myautostr1 = mystr1;
+ auto myautostr2 = mystr2;
+
+ for (int i = 0; i < 10; ++i) {
+ fstring(mystr1 + mystr2 + mystr1);
+ }
+ }
+ {
+ // performance-implicit-cast-in-loop
+ std::map<int, std::vector<std::string>> my_map;
+ for (const std::pair<int, std::vector<std::string>>& p : my_map) {
+ }
+ }
+}
+
+// performance-unnecessary-value-param
+void f(const std::string Value) {
+}
\ No newline at end of file