Bug 1273634 - [mozlint] Add a treeherder formatter, r?jgraham draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Sat, 28 May 2016 23:38:30 -0400
changeset 374048 f3504acb27a58255d2cfb39ea79f0fa4f4e95dbb
parent 374047 b8ea68ce81ef951dd5b4f5ae0256207e6d832eeb
child 374049 e76904071cb3e4fdffbba677cf2774ff7acd5e45
push id19910
push userahalberstadt@mozilla.com
push dateWed, 01 Jun 2016 18:16:04 +0000
reviewersjgraham
bugs1273634, 1276486
milestone49.0a1
Bug 1273634 - [mozlint] Add a treeherder formatter, r?jgraham This is a really simple and ugly formatter that is compatible with treeherder's error highlighting mechanism. It is designed to be identical to the current eslint output on treeherder: https://dxr.mozilla.org/mozilla-central/rev/4d63dde701b47b8661ab7990f197b6b60e543839/tools/lint/eslint-formatter.js Eventually eslint will also use this and we can remove that file. Once bug 1276486 is fixed, we can make this look a little nicer. But for now it gets the job done. MozReview-Commit-ID: CwfWPcwWFxF
python/mozlint/mozlint/formatters/__init__.py
python/mozlint/mozlint/formatters/treeherder.py
python/mozlint/mozlint/roller.py
python/mozlint/test/test_formatters.py
tools/lint/flake8.lint
--- a/python/mozlint/mozlint/formatters/__init__.py
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -1,23 +1,25 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import json
 
 from ..result import ResultEncoder
 from .stylish import StylishFormatter
+from .treeherder import TreeherderFormatter
 
 
 class JSONFormatter(object):
     def __call__(self, results):
         return json.dumps(results, cls=ResultEncoder)
 
 
 all_formatters = {
     'json': JSONFormatter,
     'stylish': StylishFormatter,
+    'treeherder': TreeherderFormatter,
 }
 
 
 def get(name, **fmtargs):
     return all_formatters[name](**fmtargs)
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/formatters/treeherder.py
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import unicode_literals
+
+from ..result import ResultContainer
+
+
+class TreeherderFormatter(object):
+    """Formatter for treeherder friendly output.
+
+    This formatter looks ugly, but prints output such that
+    treeherder is able to highlight the errors and warnings.
+    This is a stop-gap until bug 1276486 is fixed.
+    """
+    fmt = "TEST-UNEXPECTED-{level} | {path}:{lineno}:{column} | {message} ({rule})"
+
+    def __call__(self, result):
+        message = []
+        for path, errors in sorted(result.iteritems()):
+            for err in errors:
+                assert isinstance(err, ResultContainer)
+
+                d = {s: getattr(err, s) for s in err.__slots__}
+                d['level'] = d['level'].upper()
+                d['rule'] = d['rule'] or d['linter']
+                message.append(self.fmt.format(**d))
+
+        return "\n".join(message)
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -37,17 +37,17 @@ def _run_linters(queue, paths, **lintarg
         # Ideally we would pass the entire LINTER definition as an argument
         # to the worker instead of re-parsing it. But passing a function from
         # a dynamically created module (with imp) does not seem to be possible
         # with multiprocessing on Windows.
         linter = parse(linter_path)
         func = supported_types[linter['type']]
         res = func(paths, linter, **lintargs) or []
 
-        if isinstance(res, basestring):
+        if not isinstance(res, (list, tuple)):
             continue
 
         for r in res:
             results[r.path].append(r)
 
 
 def _run_worker(*args, **lintargs):
     try:
--- a/python/mozlint/test/test_formatters.py
+++ b/python/mozlint/test/test_formatters.py
@@ -63,16 +63,26 @@ d/e/f.txt
   4:2  warning  oh no bar  bar-not-allowed (bar)
 
 \u2716 3 problems (2 errors, 1 warning)
 """.strip()
 
         fmt = formatters.get('stylish', disable_colors=True)
         self.assertEqual(expected, fmt(self.results))
 
+    def test_treeherder_formatter(self):
+        expected = """
+TEST-UNEXPECTED-ERROR | a/b/c.txt:1:1 | oh no foo (foo)
+TEST-UNEXPECTED-ERROR | a/b/c.txt:4:1 | oh no baz (baz)
+TEST-UNEXPECTED-WARNING | d/e/f.txt:4:2 | oh no bar (bar-not-allowed)
+""".strip()
+
+        fmt = formatters.get('treeherder')
+        self.assertEqual(expected, fmt(self.results))
+
     def test_json_formatter(self):
         fmt = formatters.get('json')
         formatted = json.loads(fmt(self.results))
 
         self.assertEqual(set(formatted.keys()), set(self.results.keys()))
 
         slots = ResultContainer.__slots__
         for errors in formatted.values():
--- a/tools/lint/flake8.lint
+++ b/tools/lint/flake8.lint
@@ -69,17 +69,17 @@ def lint(files, **lintargs):
     if not binary:
         try:
             binary = which.which('flake8')
         except which.WhichError:
             pass
 
     if not binary:
         print(FLAKE8_NOT_FOUND)
-        return 1
+        return []
 
     cmdargs = [
         binary,
         '--format', '{"path":"%(path)s","lineno":%(row)s,'
                     '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
     ]
 
     exclude = lintargs.get('exclude')