Bug 1432410 - Add tests in tree to make sure we don't regress with clang-tidy. draft
authorAndi-Bogdan Postelnicu <bpostelnicu@mozilla.com>
Fri, 06 Apr 2018 10:08:21 +0200
changeset 778366 465e5e783f10446f3aba49ea19c1eacb438513db
parent 778365 a444b9258c2215265d5ca013376335401968d446
push id105478
push userbmo:bpostelnicu@mozilla.com
push dateFri, 06 Apr 2018 08:09:09 +0000
bugs1432410
milestone61.0a1
Bug 1432410 - Add tests in tree to make sure we don't regress with clang-tidy. MozReview-Commit-ID: EP12O11lD3e
python/mozbuild/mozbuild/mach_commands.py
tools/clang-tidy/test/issues.json
tools/clang-tidy/test/test.cpp
--- 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