├── .circleci └── config.yml ├── .github ├── CODEOWNERS └── ISSUE_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.rst ├── setup.cfg ├── setup.py ├── src └── codacy │ ├── __init__.py │ ├── __main__.py │ └── reporter.py ├── tests ├── coverage-merge │ ├── cobertura.3.xml │ ├── cobertura.4.xml │ └── coverage-merge.json ├── coverage3 │ ├── cobertura.xml │ └── coverage.json ├── coverage4 │ ├── cobertura.xml │ └── coverage.json ├── filepath │ ├── cobertura.xml.tpl │ └── coverage.json └── tests.py ├── tox.ini └── version.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/circleci-working-dir 5 | docker: 6 | - image: circleci/python:2.7.13 7 | # - image: circleci/python:3.6.2 8 | steps: 9 | - checkout 10 | - run: 11 | command: | 12 | sudo python -m pip install --upgrade pip setuptools wheel 13 | sudo python -m pip install --upgrade pycodestyle pyflakes coverage tox 14 | sudo python -m pip install --upgrade virtualenv==12.0.2 15 | - run: 16 | command: | 17 | tox -e py27,py350,pycodestyle,pyflakes,coverage 18 | sudo python setup.py develop 19 | - deploy: 20 | name: Push coverage 21 | command: | 22 | if [ "${CIRCLE_BRANCH}" == "master" ]; then 23 | tox -e upload_coverage 24 | fi 25 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lolgab @ljmf00 @andreaTP @rtfpessoa @bmbferreira @DReigada @pedrocodacy 2 | 3 | *.yml @h314to @paulopontesm 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Are you looking for help? 2 | 3 | This is an issue tracker, used to manage and track the development of this [Codacy](https://www.codacy.com/) project. 4 | 5 | It is not a platform support system. If think your problem is related with our platform at https://www.codacy.com/, please contact us through our [contact form](https://www.codacy.com/contact) or our internal chat application, visible after you login on the bottom right corner. 6 | 7 | Keep in mind that this issue tracker is for specific problems of this project. 8 | 9 | ### Python Version 10 | 11 | 12 | ### Operating System (Ubuntu 15.10 / MacOS 10.10 / Windows 10) 13 | 14 | Use `uname -a` if on Linux. 15 | 16 | ### Library Dependencies 17 | 18 | If this is an issue that involves integration with another system, include the exact version and OS of the other system, including any intermediate drivers or APIs i.e. if you connect to a PostgreSQL database, include both the version / OS of PostgreSQL and the JDBC driver version used to connect to the database. 19 | 20 | ### Expected Behavior 21 | 22 | Please describe the expected behavior of the issue, starting from the first action. 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 28 | ### Actual Behavior 29 | 30 | Please provide a description of what actually happens, working from the same starting point. 31 | 32 | Be descriptive: "it doesn't work" does not describe what the behavior actually is -- instead, say "when sending the coverage with the command (...) it returns the output error (...)" 33 | 34 | 1. 35 | 2. 36 | 3. 37 | 38 | ### Reproducible Test Case 39 | 40 | Please provide a some information on how to reproduce the bug. A PR with a failing test would be awesome, if possible. 41 | 42 | If the issue is more complex or requires configuration, please provide a link to a project on Github/Codacy that reproduces the issue. 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 61 | 62 | *.iml 63 | 64 | ## Directory-based project format: 65 | .idea/ 66 | # if you remove the above rule, at least ignore the following: 67 | 68 | # User-specific stuff: 69 | # .idea/workspace.xml 70 | # .idea/tasks.xml 71 | # .idea/dictionaries 72 | 73 | # Sensitive or high-churn files: 74 | # .idea/dataSources.ids 75 | # .idea/dataSources.xml 76 | # .idea/sqlDataSources.xml 77 | # .idea/dynamic.xml 78 | # .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | # .idea/gradle.xml 82 | # .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | # .idea/mongoSettings.xml 86 | 87 | ## File-based project format: 88 | *.ipr 89 | *.iws 90 | 91 | ## Plugin-specific files: 92 | 93 | # IntelliJ 94 | /out/ 95 | 96 | # mpeltonen/sbt-idea plugin 97 | .idea_modules/ 98 | 99 | # JIRA plugin 100 | atlassian-ide-plugin.xml 101 | 102 | # Crashlytics plugin (for Android Studio and IntelliJ) 103 | com_crashlytics_export_strings.xml 104 | crashlytics.properties 105 | crashlytics-build.properties 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Ryan Shipp 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Deprecated 2 | ====================== 3 | 4 | This repository is no longer maintained. As an alternative, check `codacy-coverage-reporter `_ to send your test coverage results to your Codacy dashboard. 5 | 6 | python-codacy-coverage 7 | ====================== 8 | 9 | Credits to Ryan for creating this! Python coverage reporter for Codacy https://www.codacy.com 10 | 11 | .. image:: https://api.codacy.com/project/badge/grade/3a8cf06a9db94d0ab3d55e0357bc8f9d 12 | :target: https://www.codacy.com/app/Codacy/python-codacy-coverage 13 | :alt: Codacy Badge 14 | .. image:: https://api.codacy.com/project/badge/coverage/3a8cf06a9db94d0ab3d55e0357bc8f9d 15 | :target: https://www.codacy.com/app/Codacy/python-codacy-coverage 16 | :alt: Codacy Badge 17 | .. image:: https://circleci.com/gh/codacy/python-codacy-coverage.png?style=shield&circle-token=:circle-token 18 | :target: https://circleci.com/gh/codacy/python-codacy-coverage 19 | :alt: Build Status 20 | .. image:: https://badge.fury.io/py/codacy-coverage.svg 21 | :target: https://badge.fury.io/py/codacy-coverage 22 | :alt: PyPI version 23 | 24 | Setup 25 | ----- 26 | 27 | Codacy assumes that coverage is previously configured for your project. 28 | 29 | To generate the required coverage XML file, calculate coverage for your project as normal, by running: 30 | 31 | ``coverage xml`` 32 | 33 | Install codacy-coverage 34 | ~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | You can install the coverage reporter by running: 37 | 38 | ``pip install codacy-coverage`` 39 | 40 | Updating Codacy 41 | --------------- 42 | 43 | To update Codacy, you will need your project API token. You can create the token in `Project -> Settings -> Integrations -> Add Integration -> Project API` 44 | 45 | Then set it in your terminal, replacing %Project_Token% with your own token: 46 | 47 | ``export CODACY_PROJECT_TOKEN=%Project_Token%`` 48 | 49 | **Enterprise only** (Skip this step if you are using https://www.codacy.com) 50 | 51 | To send coverage in the enterprise version you should: 52 | 53 | ``export CODACY_API_BASE_URL=:16006`` 54 | 55 | **Upload Coverage** 56 | 57 | Next, simply run the Codacy reporter. It will find the current commit and send all details to your project dashboard: 58 | 59 | ``python-codacy-coverage -r coverage.xml`` 60 | 61 | Note: You should keep your API token well **protected**, as it grants owner permissions to your projects. 62 | 63 | Troubleshoot 64 | --------------- 65 | 66 | If you are using any CI that does not have .git information, you can specify the commit with -c and the clone directory with -d. For example if you are using AppVeyor you can: 67 | 68 | ``python-codacy-coverage -c $APPVEYOR_REPO_COMMIT -d $APPVEYOR_BUILD_FOLDER -r coverage.xml`` 69 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | 7 | [pycodestyle] 8 | max_line_length = 120 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Always prefer setuptools over distutils 2 | from setuptools import setup, find_packages 3 | # To use a consistent encoding 4 | from codecs import open 5 | from os import path 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | # Get the long description from the relevant file 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | # Default version 14 | __version__ = '1.3.0' 15 | 16 | # Get the correct version from file 17 | try: 18 | import version 19 | __version__ = version.__version__ 20 | except ImportError: 21 | pass 22 | 23 | setup( 24 | name='codacy-coverage', 25 | 26 | version=__version__, 27 | 28 | description='Codacy coverage reporter for Python', 29 | long_description=long_description, 30 | 31 | url='https://github.com/codacy/python-codacy-coverage', 32 | 33 | author='Codacy', 34 | author_email='team@codacy.com', 35 | 36 | license='MIT', 37 | 38 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 39 | classifiers=[ 40 | 'Development Status :: 5 - Production/Stable', 41 | 42 | 'Intended Audience :: Developers', 43 | 'Topic :: Software Development :: Build Tools', 44 | 45 | 'License :: OSI Approved :: MIT License', 46 | 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | ], 50 | 51 | keywords='development coverage', 52 | 53 | packages=find_packages('src'), 54 | package_dir={'': 'src'}, include_package_data=True, 55 | 56 | install_requires=['requests>=2.9.1'], 57 | 58 | extras_require={ 59 | 'dev': ['check-manifest'], 60 | 'test': ['nosetests', 'coverage'], 61 | }, 62 | 63 | entry_points={ 64 | 'console_scripts': [ 65 | 'python-codacy-coverage=codacy:main', 66 | ], 67 | }, 68 | test_suite='tests' 69 | ) 70 | -------------------------------------------------------------------------------- /src/codacy/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from . import reporter 3 | 4 | 5 | def main(): 6 | return reporter.run() 7 | -------------------------------------------------------------------------------- /src/codacy/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import 3 | 4 | import sys 5 | from . import __name__ 6 | from .reporter import run 7 | 8 | sys.exit(run(__name__)) 9 | -------------------------------------------------------------------------------- /src/codacy/reporter.py: -------------------------------------------------------------------------------- 1 | """Codacy coverage reporter for Python""" 2 | 3 | import argparse 4 | import contextlib 5 | import json 6 | import logging 7 | import os 8 | from xml.dom import minidom 9 | 10 | import requests 11 | from requests.packages.urllib3 import util as urllib3_util 12 | 13 | logging.basicConfig(level=logging.INFO, 14 | format='%(asctime)s - %(levelname)s - %(message)s') 15 | 16 | CODACY_BASE_API_URL = os.getenv('CODACY_API_BASE_URL', 'https://api.codacy.com') 17 | URL = CODACY_BASE_API_URL + '/2.0/coverage/{commit}/python' 18 | DEFAULT_REPORT_FILE = 'coverage.xml' 19 | MAX_RETRIES = 3 20 | BAD_REQUEST = 400 21 | 22 | 23 | class _Retry(urllib3_util.Retry): 24 | def is_forced_retry(self, method, status_code): 25 | return status_code >= BAD_REQUEST 26 | 27 | 28 | @contextlib.contextmanager 29 | def _request_session(): 30 | retry = _Retry(total=MAX_RETRIES, raise_on_redirect=False) 31 | session = requests.Session() 32 | session.mount("https://", requests.adapters.HTTPAdapter(max_retries=retry)) 33 | with session: 34 | yield session 35 | 36 | 37 | def get_git_revision_hash(): 38 | import subprocess 39 | 40 | return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode("utf-8").strip() 41 | 42 | 43 | def get_git_directory(): 44 | import subprocess 45 | 46 | return subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode("utf-8").strip() 47 | 48 | 49 | def generate_filename(sources, filename, git_directory): 50 | def strip_prefix(line, prefix): 51 | if line.startswith(prefix): 52 | return line[len(prefix):] 53 | else: 54 | return line 55 | 56 | for source in sources: 57 | if os.path.isfile(source + "/" + filename): 58 | return strip_prefix(source, git_directory).strip("/") + "/" + filename.strip("/") 59 | 60 | logging.debug("File not found: " + filename) 61 | return filename 62 | 63 | 64 | def merge_and_round_reports(report_list): 65 | """Merges together several report structures from parse_report_file (and rounds all values)""" 66 | 67 | if len(report_list) == 1: 68 | final_report = report_list[0] 69 | else: 70 | final_report = { 71 | 'language': "python", 72 | 'fileReports': [] 73 | } 74 | 75 | total_lines = 0 76 | for report in report_list: 77 | # First, merge together detailed report structures 78 | # This assumes no overlap 79 | # TODO: What should we do if there is a file listed multiple times? 80 | final_report['fileReports'] += report['fileReports'] 81 | total_lines += report['codeLines'] 82 | 83 | # Coverage weighted average (by number of lines of code) of all files 84 | average_sum = 0 85 | for file_entry in final_report['fileReports']: 86 | average_sum += file_entry['total'] * file_entry['codeLines'] 87 | 88 | final_report['total'] = average_sum / total_lines 89 | final_report['codeLines'] = total_lines 90 | 91 | # Round all total values 92 | for file_entry in final_report['fileReports']: 93 | file_entry['total'] = int(file_entry['total']) 94 | final_report['total'] = int(final_report['total']) 95 | 96 | return final_report 97 | 98 | 99 | def parse_report_file(report_file, git_directory): 100 | """Parse XML file and POST it to the Codacy API 101 | :param report_file: 102 | """ 103 | 104 | # Convert decimal string to decimal percent value 105 | def percent(s): 106 | return float(s) * 100 107 | 108 | # Parse the XML into the format expected by the API 109 | report_xml = minidom.parse(report_file) 110 | 111 | report = { 112 | 'language': "python", 113 | 'total': percent(report_xml.getElementsByTagName('coverage')[0].attributes['line-rate'].value), 114 | 'fileReports': [], 115 | } 116 | 117 | sources = [x.firstChild.nodeValue for x in report_xml.getElementsByTagName('source')] 118 | # replace windows style seperator with linux style seperator 119 | for i in range(len(sources)): 120 | sources[i] = sources[i].replace("\\", "/") 121 | classes = report_xml.getElementsByTagName('class') 122 | total_lines = 0 123 | for cls in classes: 124 | lines = cls.getElementsByTagName('line') 125 | total_lines += len(lines) 126 | file_report = { 127 | 'filename': generate_filename(sources, cls.attributes['filename'].value, git_directory), 128 | 'total': percent(cls.attributes['line-rate'].value), 129 | 'codeLines': len(lines), 130 | 'coverage': {}, 131 | } 132 | for line in lines: 133 | hits = int(line.attributes['hits'].value) 134 | file_report['coverage'][line.attributes['number'].value] = hits 135 | report['fileReports'] += [file_report] 136 | 137 | report['codeLines'] = total_lines 138 | 139 | return report 140 | 141 | 142 | def upload_report(report, token, commit): 143 | """Try to send the data, raise an exception if we fail""" 144 | url = URL.format(commit=commit) 145 | data = json.dumps(report) 146 | headers = { 147 | "project_token": token, 148 | "Content-Type": "application/json" 149 | } 150 | 151 | logging.debug(data) 152 | 153 | with _request_session() as session: 154 | r = session.post(url, data=data, headers=headers, allow_redirects=True) 155 | 156 | logging.debug(r.content) 157 | r.raise_for_status() 158 | 159 | response = json.loads(r.text) 160 | 161 | try: 162 | logging.info(response['success']) 163 | except KeyError: 164 | logging.error(response['error']) 165 | 166 | 167 | def run(prog=None): 168 | parser = argparse.ArgumentParser(prog=prog, description='Codacy coverage reporter for Python.') 169 | parser.add_argument("-r", "--report", help="coverage report file", 170 | default=[], type=str, 171 | action='append') 172 | parser.add_argument("-c", "--commit", type=str, help="git commit hash") 173 | parser.add_argument("-t", "--token", type=str, help="Codacy project token") 174 | parser.add_argument("-d", "--directory", type=str, help="git top level directory") 175 | parser.add_argument("-v", "--verbose", help="show debug information", action="store_true") 176 | 177 | args = parser.parse_args() 178 | 179 | if args.verbose: 180 | logging.Logger.setLevel(logging.getLogger(), logging.DEBUG) 181 | 182 | if args.token: 183 | codacy_project_token = args.token 184 | else: 185 | codacy_project_token = os.getenv('CODACY_PROJECT_TOKEN') 186 | if not codacy_project_token: 187 | logging.error("environment variable CODACY_PROJECT_TOKEN is not defined.") 188 | exit(1) 189 | 190 | if not args.commit: 191 | args.commit = get_git_revision_hash() 192 | 193 | if not args.report: 194 | args.report.append(DEFAULT_REPORT_FILE) 195 | 196 | if args.directory: 197 | git_directory = args.directory 198 | else: 199 | git_directory = get_git_directory() 200 | git_directory.replace("\\", "/") 201 | 202 | # Explictly check ALL files before parsing any 203 | for rfile in args.report: 204 | if not os.path.isfile(rfile): 205 | logging.error("Coverage report " + rfile + " not found.") 206 | exit(1) 207 | 208 | reports = [] 209 | for rfile in args.report: 210 | logging.info("Parsing report file %s...", rfile) 211 | reports.append(parse_report_file(rfile, git_directory)) 212 | 213 | report = merge_and_round_reports(reports) 214 | 215 | logging.info("Uploading report...") 216 | upload_report(report, codacy_project_token, args.commit) 217 | -------------------------------------------------------------------------------- /tests/coverage-merge/cobertura.3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/coverage-merge/cobertura.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /Users/rafaelcortes/Documents/qamine/python-codacycov 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /tests/coverage-merge/coverage-merge.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 49, 3 | "codeLines": 74, 4 | "fileReports": [ 5 | { 6 | "total": 75, 7 | "codeLines": 4, 8 | "coverage": { 9 | "4": 1, 10 | "5": 1, 11 | "6": 0, 12 | "7": 2 13 | }, 14 | "filename": "src/test/resources/TestSourceFile1.scala" 15 | }, 16 | { 17 | "total": 66, 18 | "codeLines": 3, 19 | "coverage": { 20 | "5": 0, 21 | "9": 1, 22 | "10": 1 23 | }, 24 | "filename": "src/test/resources/TestSourceFile2.scala" 25 | }, 26 | { 27 | "total": 20, 28 | "codeLines": 5, 29 | "coverage": { 30 | "1": 0, 31 | "2": 1, 32 | "3": 0, 33 | "4": 0, 34 | "7": 0 35 | }, 36 | "filename": "src/test/resources/TestSourceFile3.scala" 37 | }, 38 | { 39 | "total": 66, 40 | "codeLines": 3, 41 | "coverage": { 42 | "1": 1, 43 | "4": 1, 44 | "5": 0 45 | }, 46 | "filename": "src/codacy/__init__.py" 47 | }, 48 | { 49 | "total": 49, 50 | "codeLines": 59, 51 | "coverage": { 52 | "50": 1, 53 | "60": 1, 54 | "80": 1, 55 | "52": 1, 56 | "26": 1, 57 | "20": 1, 58 | "49": 1, 59 | "44": 1, 60 | "42": 1, 61 | "43": 1, 62 | "3": 1, 63 | "5": 1, 64 | "4": 1, 65 | "7": 1, 66 | "6": 1, 67 | "9": 1, 68 | "11": 1, 69 | "15": 1, 70 | "14": 1, 71 | "17": 1, 72 | "16": 1, 73 | "55": 1, 74 | "54": 1, 75 | "31": 1, 76 | "30": 1, 77 | "51": 1, 78 | "36": 1, 79 | "34": 1, 80 | "57": 1, 81 | "21": 0, 82 | "23": 0, 83 | "62": 0, 84 | "63": 0, 85 | "64": 0, 86 | "69": 0, 87 | "71": 0, 88 | "73": 0, 89 | "74": 0, 90 | "76": 0, 91 | "77": 0, 92 | "81": 0, 93 | "82": 0, 94 | "83": 0, 95 | "84": 0, 96 | "86": 0, 97 | "88": 0, 98 | "89": 0, 99 | "91": 0, 100 | "92": 0, 101 | "93": 0, 102 | "95": 0, 103 | "96": 0, 104 | "98": 0, 105 | "99": 0, 106 | "100": 0, 107 | "102": 0, 108 | "103": 0, 109 | "105": 0, 110 | "106": 0 111 | }, 112 | "filename": "src/codacy/reporter.py" 113 | } 114 | ], 115 | "language": "python" 116 | } 117 | -------------------------------------------------------------------------------- /tests/coverage3/cobertura.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/coverage3/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 50, 3 | "codeLines": 12, 4 | "fileReports": [ 5 | { 6 | "total": 75, 7 | "codeLines": 4, 8 | "coverage": { 9 | "4": 1, 10 | "5": 1, 11 | "6": 0, 12 | "7": 2 13 | }, 14 | "filename": "src/test/resources/TestSourceFile1.scala" 15 | }, 16 | { 17 | "total": 66, 18 | "codeLines": 3, 19 | "coverage": { 20 | "5": 0, 21 | "9": 1, 22 | "10": 1 23 | }, 24 | "filename": "src/test/resources/TestSourceFile2.scala" 25 | }, 26 | { 27 | "total": 20, 28 | "codeLines": 5, 29 | "coverage": { 30 | "1": 0, 31 | "2": 1, 32 | "3": 0, 33 | "4": 0, 34 | "7": 0 35 | }, 36 | "filename": "src/test/resources/TestSourceFile3.scala" 37 | } 38 | ], 39 | "language": "python" 40 | } 41 | -------------------------------------------------------------------------------- /tests/coverage4/cobertura.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /Users/rafaelcortes/Documents/qamine/python-codacycov 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /tests/coverage4/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 50, 3 | "codeLines": 62, 4 | "fileReports": [ 5 | { 6 | "total": 66, 7 | "codeLines": 3, 8 | "coverage": { 9 | "1": 1, 10 | "4": 1 11 | }, 12 | "filename": "src/codacy/__init__.py" 13 | }, 14 | { 15 | "total": 49, 16 | "codeLines": 59, 17 | "coverage": { 18 | "50": 1, 19 | "60": 1, 20 | "80": 1, 21 | "52": 1, 22 | "26": 1, 23 | "20": 1, 24 | "49": 1, 25 | "44": 1, 26 | "42": 1, 27 | "43": 1, 28 | "3": 1, 29 | "5": 1, 30 | "4": 1, 31 | "7": 1, 32 | "6": 1, 33 | "9": 1, 34 | "11": 1, 35 | "15": 1, 36 | "14": 1, 37 | "17": 1, 38 | "16": 1, 39 | "55": 1, 40 | "54": 1, 41 | "31": 1, 42 | "30": 1, 43 | "51": 1, 44 | "36": 1, 45 | "34": 1, 46 | "57": 1, 47 | "21": 0, 48 | "23": 0, 49 | "62": 0, 50 | "63": 0, 51 | "64": 0, 52 | "69": 0, 53 | "71": 0, 54 | "73": 0, 55 | "74": 0, 56 | "76": 0, 57 | "77": 0, 58 | "81": 0, 59 | "82": 0, 60 | "83": 0, 61 | "84": 0, 62 | "86": 0, 63 | "88": 0, 64 | "89": 0, 65 | "91": 0, 66 | "92": 0, 67 | "93": 0, 68 | "95": 0, 69 | "96": 0, 70 | "98": 0, 71 | "99": 0, 72 | "100": 0, 73 | "102": 0, 74 | "103": 0, 75 | "105": 0, 76 | "106": 0 77 | }, 78 | "filename": "src/codacy/reporter.py" 79 | } 80 | ], 81 | "language": "python" 82 | } -------------------------------------------------------------------------------- /tests/filepath/cobertura.xml.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $1/src/codacy 7 | $1/src/codacy 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /tests/filepath/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 50, 3 | "codeLines": 62, 4 | "fileReports": [ 5 | { 6 | "total": 66, 7 | "codeLines": 3, 8 | "coverage": { 9 | "1": 1, 10 | "4": 1, 11 | "5": 0 12 | }, 13 | "filename": "__init__.py" 14 | }, 15 | { 16 | "total": 49, 17 | "codeLines": 59, 18 | "coverage": { 19 | "50": 1, 20 | "60": 1, 21 | "80": 1, 22 | "52": 1, 23 | "26": 1, 24 | "20": 1, 25 | "49": 1, 26 | "44": 1, 27 | "42": 1, 28 | "43": 1, 29 | "3": 1, 30 | "5": 1, 31 | "4": 1, 32 | "7": 1, 33 | "6": 1, 34 | "9": 1, 35 | "11": 1, 36 | "15": 1, 37 | "14": 1, 38 | "17": 1, 39 | "16": 1, 40 | "55": 1, 41 | "54": 1, 42 | "31": 1, 43 | "30": 1, 44 | "51": 1, 45 | "36": 1, 46 | "34": 1, 47 | "57": 1, 48 | "21": 0, 49 | "23": 0, 50 | "62": 0, 51 | "63": 0, 52 | "64": 0, 53 | "69": 0, 54 | "71": 0, 55 | "73": 0, 56 | "74": 0, 57 | "76": 0, 58 | "77": 0, 59 | "81": 0, 60 | "82": 0, 61 | "83": 0, 62 | "84": 0, 63 | "86": 0, 64 | "88": 0, 65 | "89": 0, 66 | "91": 0, 67 | "92": 0, 68 | "93": 0, 69 | "95": 0, 70 | "96": 0, 71 | "98": 0, 72 | "99": 0, 73 | "100": 0, 74 | "102": 0, 75 | "103": 0, 76 | "105": 0, 77 | "106": 0 78 | }, 79 | "filename": "reporter.py" 80 | } 81 | ], 82 | "language": "python" 83 | } -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import unittest 4 | 5 | import codacy.reporter 6 | 7 | HERE = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | 10 | def _file_location(*args): 11 | return os.path.join(HERE, *args) 12 | 13 | 14 | class ReporterTests(unittest.TestCase): 15 | def compare_parse_result(self, generated, expected_filename): 16 | def file_get_contents(filename): 17 | with open(filename) as f: 18 | return f.read() 19 | 20 | def to_utf8(d): 21 | if type(d) is dict: 22 | result = {} 23 | for key, value in d.items(): 24 | result[to_utf8(key)] = to_utf8(value) 25 | elif type(d) is unicode: 26 | return d.encode('utf8') 27 | else: 28 | return d 29 | 30 | json_content = file_get_contents(expected_filename) 31 | expected = json.loads(json_content) 32 | 33 | self.assertEqual(to_utf8(generated), to_utf8(expected)) 34 | 35 | def test_parser_coverage3(self): 36 | self.maxDiff = None 37 | 38 | parsed = codacy.reporter.parse_report_file( 39 | _file_location('coverage3', 'cobertura.xml'), '') 40 | 41 | rounded = codacy.reporter.merge_and_round_reports([parsed]) 42 | 43 | self.compare_parse_result(rounded, 44 | _file_location('coverage3', 'coverage.json')) 45 | 46 | def test_parser_coverage4(self): 47 | self.maxDiff = None 48 | 49 | parsed = codacy.reporter.parse_report_file( 50 | _file_location('coverage4', 'cobertura.xml'), '') 51 | 52 | rounded = codacy.reporter.merge_and_round_reports([parsed]) 53 | 54 | self.compare_parse_result(rounded, 55 | _file_location('coverage4', 'coverage.json')) 56 | 57 | def test_parser_git_filepath(self): 58 | self.maxDiff = None 59 | 60 | parsed = codacy.reporter.parse_report_file( 61 | _file_location('filepath', 'cobertura.xml.tpl'), '') 62 | 63 | rounded = codacy.reporter.merge_and_round_reports([parsed]) 64 | 65 | self.compare_parse_result(rounded, 66 | _file_location('filepath', 'coverage.json')) 67 | 68 | def test_merge(self): 69 | self.maxDiff = None 70 | 71 | generated3 = codacy.reporter.parse_report_file( 72 | _file_location('coverage-merge', 'cobertura.3.xml'), '') 73 | generated4 = codacy.reporter.parse_report_file( 74 | _file_location('coverage-merge', 'cobertura.4.xml'), '') 75 | 76 | result = codacy.reporter.merge_and_round_reports([generated3, generated4]) 77 | 78 | self.compare_parse_result(result, _file_location('coverage-merge', 'coverage-merge.json')) 79 | 80 | 81 | if __name__ == '__main__': 82 | unittest.main() 83 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py350, pycodestyle, pyflakes 3 | 4 | [testenv] 5 | commands = {envpython} -m unittest discover -s tests -p "*test*" 6 | deps = requests 7 | usedevelop = true 8 | 9 | [testenv:pycodestyle] 10 | deps = 11 | pycodestyle 12 | commands = 13 | {envpython} -m pycodestyle src 14 | 15 | [testenv:pyflakes] 16 | deps = 17 | pyflakes 18 | commands = 19 | {envpython} -m pyflakes src 20 | 21 | [testenv:coverage] 22 | deps = coverage 23 | commands = 24 | {envpython} -m coverage run --source src/codacy -m unittest discover -p "*test*" -s tests 25 | {envpython} -m coverage xml 26 | 27 | [testenv:upload_coverage] 28 | deps = coverage 29 | passenv = CODACY_PROJECT_TOKEN 30 | commands = 31 | python-codacy-coverage -r coverage.xml 32 | 33 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.3.11' 2 | --------------------------------------------------------------------------------