├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── LICENSE ├── README.rst ├── junit_conversor ├── __about__.py ├── __init__.py └── __main__.py ├── requirements_ci.txt ├── requirements_dev.txt ├── setup.py ├── tests ├── __init__.py ├── flake8_example_results │ ├── failed_flake8.txt │ ├── failed_flake8_with_invalid_lines.txt │ └── valid_flake8.txt ├── output │ └── .gitkeep └── tests.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | include = junit_conversor/* 4 | omit = junit_conversor/__about__.py,junit_conversor/__main__.py 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | tests/output/* 3 | toxresults 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | install: 3 | - pip install coveralls 4 | - pip install tox 5 | script: 6 | - tox 7 | after_script: 8 | - mkdir -p build/logs 9 | - mv toxresults/* build/logs/ 10 | env: 11 | - TOXENV=py26 12 | - TOXENV=py27 13 | - TOXENV=py34 14 | - TOXENV=flake8 15 | after_success: 16 | - coveralls 17 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Credits 2 | ======= 3 | 4 | flake8-junit-report is written and maintained by `Carlos Goce 5 | `. 6 | 7 | Contributors 8 | ------------ 9 | 10 | - `Jeroen Op 't Eynde ` 11 | - `Marko Tibold ` 12 | - `Miguel González ` 13 | 14 | Please add yourself here alphabetically when you submit your first pull request. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Initios Desarrollos S.L. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | flake8-junit-report 2 | =================== 3 | Simple tool that converts a flake8 file to junit format. 4 | Use it on your CI server to see the flake8 failures with ease. 5 | 6 | .. image:: https://img.shields.io/badge/LICENSE-BSD%203--Clause-brightgreen.svg 7 | .. image:: https://readthedocs.org/projects/flake8-junit-report/badge/?version=latest 8 | :target: https://readthedocs.org/projects/flake8-junit-report/?badge=latest 9 | :alt: Documentation Status 10 | 11 | 12 | .. list-table:: 13 | 14 | * - Master 15 | - .. image:: https://travis-ci.org/initios/flake8-junit-report.svg?branch=master 16 | :target: https://travis-ci.org/initios/flake8-junit-report 17 | - .. image:: https://coveralls.io/repos/initios/flake8-junit-report/badge.svg?branch=master 18 | :target: https://coveralls.io/r/initios/flake8-junit-report?branch=master 19 | * - Develop 20 | - .. image:: https://travis-ci.org/initios/flake8-junit-report.svg?branch=develop 21 | :target: https://travis-ci.org/initios/flake8-junit-report 22 | - .. image:: https://coveralls.io/repos/initios/flake8-junit-report/badge.svg?branch=develop 23 | :target: https://coveralls.io/r/initios/flake8-junit-report?branch=develop 24 | 25 | Usage 26 | ----- 27 | Create your flake8 file as usual: 28 | 29 | .. code:: shell-session 30 | 31 | $ flake8 --output-file flake8.txt 32 | 33 | Convert it to JUnit format: 34 | 35 | .. code:: shell-session 36 | 37 | $ flake8_junit flake8.txt flake8_junit.xml 38 | 39 | Running the tests 40 | ----------------- 41 | 42 | .. code:: shell-session 43 | 44 | $ pip install -r requirements_dev.txt 45 | $ tox 46 | 47 | 48 | Changelog 49 | --------- 50 | 51 | 2016-05-11 **2.1.0** 52 | 53 | - Change setuptools `scripts` to `console scripts`. 54 | Now it should works on Windows 55 | 56 | 2016-02-13 57 | 58 | - Fix bug 59 | - Add version 2.0.1 60 | 61 | 62 | 2016-01-05 63 | 64 | - Flake8 file now is always generated, even when there are no errors 65 | 66 | 67 | 2015-07-28 68 | 69 | - Add version 2.x.x 70 | - Rolled back python cli to vanilla python 71 | - Renamed binary to flake8_junit 72 | 73 | 74 | ------------- 75 | 76 | `CONTRIBUTORS `_ 77 | -------------------------------------------------------------------------------- /junit_conversor/__about__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'flake8-junit-report' 2 | __summary__ = 'Simple tool that converts a flake8 file to junit format' 3 | __version__ = '2.1.0' 4 | __license__ = 'BSD 3-Clause License' 5 | __uri__ = 'https://github.com/initios/flake8-junit-report' 6 | __author__ = 'Carlos Goce' 7 | __email__ = 'carlosgoce@gmail.com' 8 | -------------------------------------------------------------------------------- /junit_conversor/__init__.py: -------------------------------------------------------------------------------- 1 | import xml.etree.cElementTree as ET 2 | import sys 3 | from collections import defaultdict 4 | 5 | 6 | def _parse(file_name): 7 | lines = tuple(open(file_name, 'r')) 8 | parsed = defaultdict(list) 9 | 10 | for line in lines: 11 | splitted = line.split(":", 3) 12 | 13 | # Skip invalid lines 14 | if len(splitted) == 4: 15 | error = { 16 | 'file': splitted[0].strip(), 17 | 'line': splitted[1].strip(), 18 | 'col': splitted[2].strip(), 19 | 'detail': splitted[3].strip(), 20 | 'code': splitted[3].strip()[:4] 21 | } 22 | 23 | parsed[error['file']].append(error) 24 | 25 | return dict(parsed) 26 | 27 | 28 | def _convert(origin, destination): 29 | parsed = _parse(origin) 30 | 31 | testsuite = ET.Element("testsuite") 32 | testsuite.attrib["errors"] = str(len(parsed)) 33 | testsuite.attrib["failures"] = "0" 34 | testsuite.attrib["name"] = "flake8" 35 | testsuite.attrib["tests"] = str(len(parsed)) or "1" 36 | testsuite.attrib["time"] = "1" 37 | 38 | for file_name, errors in parsed.items(): 39 | testcase = ET.SubElement(testsuite, "testcase", name=file_name) 40 | 41 | for error in errors: 42 | kargs = { 43 | "file": error['file'], 44 | "line": error['line'], 45 | "col": error['col'], 46 | "message": error['detail'], 47 | "type": "flake8 %s" % error['code'] 48 | } 49 | 50 | text = "{0}:{1} {2}".format(error['line'], error['col'], error['detail']) 51 | 52 | ET.SubElement(testcase, "failure", **kargs).text = text 53 | 54 | tree = ET.ElementTree(testsuite) 55 | 56 | if (2, 6) == sys.version_info[:2]: # py26 57 | tree.write(destination, encoding='utf-8') 58 | else: 59 | tree.write(destination, encoding='utf-8', xml_declaration=True) 60 | -------------------------------------------------------------------------------- /junit_conversor/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from . import _convert 4 | 5 | 6 | def main(): 7 | flake8_file, destination_file = sys.argv[1:] 8 | _convert(flake8_file, destination_file) 9 | sys.stdout.write("File %s was created successfully" % destination_file) 10 | sys.exit(0) 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /requirements_ci.txt: -------------------------------------------------------------------------------- 1 | -r requirements_dev.txt 2 | flake8-junit-report==2.0.0 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | flake8==2.4.0 2 | nose==1.3.4 3 | nose-cov==1.6 4 | scripttest==1.3 5 | tox==2.1.1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2015 Initios Desarrollos 3 | # 4 | # All rights reserved 5 | 6 | import os 7 | import setuptools 8 | 9 | 10 | base_dir = os.path.dirname(__file__) 11 | 12 | about = {} 13 | with open(os.path.join(base_dir, 'junit_conversor', '__about__.py')) as f: 14 | exec(f.read(), about) 15 | 16 | with open(os.path.join(base_dir, 'README.rst')) as readme: 17 | long_description = readme.read() 18 | 19 | # allow setup.py to be run from any path 20 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 21 | 22 | setuptools.setup( 23 | name=about['__title__'], 24 | version=about['__version__'], 25 | 26 | description=about['__summary__'], 27 | long_description=long_description, 28 | license=about['__license__'], 29 | url=about['__uri__'], 30 | 31 | author=about['__author__'], 32 | author_email=about['__email__'], 33 | 34 | classifiers=[ 35 | 'Framework :: Flake8', 36 | 'Programming Language :: Python', 37 | 'Programming Language :: Python :: 2.6', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Development Status :: 5 - Production/Stable', 41 | 'License :: OSI Approved :: BSD License', 42 | ], 43 | 44 | packages=[ 45 | 'junit_conversor', 46 | ], 47 | include_package_data=True, 48 | entry_points={ 49 | 'console_scripts': [ 50 | 'flake8_junit = junit_conversor.__main__:main', 51 | ], 52 | } 53 | ) 54 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/initios/flake8-junit-report/b422c130e635baca4dbf8d6e05ad3439c04aaf71/tests/__init__.py -------------------------------------------------------------------------------- /tests/flake8_example_results/failed_flake8.txt: -------------------------------------------------------------------------------- 1 | tests/subject/__init__.py:1:1: F401 'os' imported but unused 2 | tests/subject/__init__.py:3:1: E302 expected 2 blank lines, found 1 3 | tests/subject/example.py:4:1: E302 expected 2 blank lines, found 1 4 | tests/subject/example.py:16:22: E203 whitespace before ':' 5 | -------------------------------------------------------------------------------- /tests/flake8_example_results/failed_flake8_with_invalid_lines.txt: -------------------------------------------------------------------------------- 1 | tests/subject/__init__.py:1:1: F401 'os' imported but unused 2 | tests/subject/__init__.py:3:1: E302 expected 2 blank lines, found 1 3 | invalid line 4 | tests/subject/example.py:4:1: E302 expected 2 blank lines, found 1 5 | -------------------------------------------------------------------------------- /tests/flake8_example_results/valid_flake8.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/initios/flake8-junit-report/b422c130e635baca4dbf8d6e05ad3439c04aaf71/tests/flake8_example_results/valid_flake8.txt -------------------------------------------------------------------------------- /tests/output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/initios/flake8-junit-report/b422c130e635baca4dbf8d6e05ad3439c04aaf71/tests/output/.gitkeep -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import xml.dom.minidom 4 | 5 | from junit_conversor import _parse, _convert 6 | 7 | 8 | current_dir = os.path.dirname(os.path.realpath(__file__)) 9 | project_root = os.path.join(current_dir, os.pardir) 10 | output_dir = os.path.join(current_dir, 'output') 11 | example_files_dir = os.path.join(current_dir, 'flake8_example_results') 12 | failed_flake8 = os.path.join(example_files_dir, 'failed_flake8.txt') 13 | failed_flake8_with_invalid_lines = os.path.join(example_files_dir, 'failed_flake8_with_invalid_lines.txt') 14 | valid_flake8 = os.path.join(example_files_dir, 'valid_flake8.txt') 15 | 16 | junit_conversor_cli = os.path.join(current_dir, os.pardir, 'bin', 'junit_conversor') 17 | 18 | 19 | class TestCase(unittest.TestCase): 20 | def assertXmlIsValid(self, xml_file): 21 | try: 22 | with open(xml_file) as f: 23 | content = f.read() 24 | 25 | xml.dom.minidom.parseString(content) 26 | except xml.parsers.expat.ExpatError: 27 | raise Exception('The specified file is not a valid XML (%s)' 28 | % content[0:30]) 29 | 30 | def assertFileExist(self, file_name): 31 | self.assertTrue(os.path.exists(file_name), 'File %s does not exist' % file_name) 32 | 33 | def assertFileDoesNotExist(self, file_name): 34 | self.assertFalse(os.path.exists(file_name), 'File %s exist' % file_name) 35 | 36 | 37 | class ParseTest(TestCase): 38 | def test_should_parse_a_flake8_file_with_errors(self): 39 | parsed = _parse(failed_flake8) 40 | 41 | self.assertEqual(parsed, { 42 | "tests/subject/__init__.py": [ 43 | {"file": "tests/subject/__init__.py", "line": "1", "col": "1", "detail": "F401 'os' imported but unused", "code": "F401"}, 44 | {"file": "tests/subject/__init__.py", "line": "3", "col": "1", "detail": "E302 expected 2 blank lines, found 1", "code": "E302"}, 45 | ], 46 | "tests/subject/example.py": [ 47 | {"file": "tests/subject/example.py", "line": "4", "col": "1", "detail": "E302 expected 2 blank lines, found 1", "code": "E302"}, 48 | {"file": "tests/subject/example.py", "line": "16", "col": "22", "detail": "E203 whitespace before ':'", "code": "E203"}, 49 | ] 50 | }) 51 | 52 | def test_should_return_an_empty_dict_when_parsing_a_flake8_success_file(self): 53 | self.assertEqual({}, _parse(valid_flake8)) 54 | 55 | def test_should_skip_invalid_lines(self): 56 | parsed = _parse(failed_flake8_with_invalid_lines) 57 | 58 | self.assertEqual(parsed, { 59 | "tests/subject/__init__.py": [ 60 | {"file": "tests/subject/__init__.py", "line": "1", "col": "1", "detail": "F401 'os' imported but unused", "code": "F401"}, 61 | {"file": "tests/subject/__init__.py", "line": "3", "col": "1", "detail": "E302 expected 2 blank lines, found 1", "code": "E302"}, 62 | ], 63 | "tests/subject/example.py": [ 64 | {"file": "tests/subject/example.py", "line": "4", "col": "1", "detail": "E302 expected 2 blank lines, found 1", "code": "E302"}, 65 | ] 66 | }) 67 | 68 | 69 | class ConvertTest(TestCase): 70 | def setUp(self): 71 | self.destination = os.path.join(output_dir, 'junit.xml') 72 | 73 | try: 74 | os.remove(self.destination) 75 | except OSError: 76 | pass 77 | 78 | def test_should_convert_a_file_with_flake8_errors_to_junit_xml(self): 79 | _convert(failed_flake8, self.destination) 80 | 81 | self.assertFileExist(self.destination) 82 | self.assertXmlIsValid(self.destination) 83 | 84 | def test_should_create_a_file_even_when_there_are_no_errors(self): 85 | _convert(valid_flake8, self.destination) 86 | self.assertFileExist(self.destination) 87 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist=True 3 | envlist=py26,py27,py34,flake8 4 | 5 | [testenv] 6 | deps=-rrequirements_dev.txt 7 | commands= 8 | nosetests --with-cov --cov-config .coveragerc 9 | 10 | [testenv:flake8] 11 | deps=-rrequirements_ci.txt 12 | whitelist_externals= 13 | cat 14 | rm 15 | mkdir 16 | commands = 17 | rm -rf {toxinidir}/toxresults 18 | mkdir {toxinidir}/toxresults 19 | -flake8 junit_conversor --output-file={toxinidir}/toxresults/flake8-failures.txt 20 | -flake8_junit {toxinidir}/toxresults/flake8-failures.txt {toxinidir}/toxresults/flake8-failures.xml 21 | -cat {toxinidir}/toxresults/flake8-failures.txt 22 | --------------------------------------------------------------------------------