diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..491deae0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 00000000..154d8a07 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tests/requirements.txt + - name: Test + run: | + make reinstall + make test + - name: Upload coverage reports to Codecov + run: | + codecov + env: # Or as an environment variable + super_secret: ${{ secrets.CODECOV_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 099c2c5a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: python -notifications: - email: false -sudo: false -python: - - 2.6 - - 2.7 - - pypy - - 3.3 - - 3.4 - - pypy3 -matrix: - include: - - python: 2.7 - dist: trusty - sudo: required - virtualenv: - system_site_packages: true - addons: - apt: - packages: - - python-requests - - python-coverage - - python-mock - -install: - - pip install -r tests/requirements.txt - - python setup.py install -script: - - py.test tests/test.py --cov=codecov -after_success: - - codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index 2355cbcf..d5eb65e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,146 @@ +### `2.1.12` + +#### Fixes +- #322 Add Cirrus CI + +#### Dependencies and Misc +- #311 Bump coverage from 5.3 to 5.3.1 +- #312 Bump coverage from 5.3.1 to 5.4 +- #314 Bump coverage from 5.4 to 5.5 +- #320 Upgrade to GitHub-native Dependabot + +### `2.1.11` + +#### Fixes +- #305 Added option to disable printing of gcov-out +- #308 Handle exceptions that don't have a returncode + +#### Dependencies and Misc +- #301 Update to Python 3.9 + +### `2.1.10` + +#### Fixes +- [#148](https://github.com/codecov/codecov-python/pull/148) Output elapsed time with S3 upload +- [#153](https://github.com/codecov/codecov-python/pull/153) Improve error reporting in the "try_run" function and correctly include original command output in the error message +- [#295](https://github.com/codecov/codecov-python/pull/295) Added sleep between upload retries. +- [#297](https://github.com/codecov/codecov-python/pull/297) Ignore emacs lisp files +- [#298](https://github.com/codecov/codecov-python/pull/298) Fix error try_to_run using | without shell=True (fix #284) + +#### Dependencies and Misc +- [#290](https://github.com/codecov/codecov-python/pull/290) Bump coverage from 4.5.4 to 5.2.1 +- [#291](https://github.com/codecov/codecov-python/pull/291) Update python versions +- [#292](https://github.com/codecov/codecov-python/pull/292) Add license scan report and status +- [#294](https://github.com/codecov/codecov-python/pull/294) Update README with accurate links +- [#296](https://github.com/codecov/codecov-python/pull/296) Bump coverage from 5.2.1 to 5.3 + +### `2.1.9` + +- [#289](https://github.com/codecov/codecov-python/pull/289)Remove token restriction as it is changed server-side + +### `2.1.8` + +- [#285](https://github.com/codecov/codecov-python/pull/285)Add support for CODECOV_FLAGS +- [#276](https://github.com/codecov/codecov-python/pull/276)Add ability to specify number of upload retries + +### `2.1.7` + +- [#279](https://github.com/codecov/codecov-python/pull/279) Fix pinned coverage version + +### `2.1.6` + +- [#275](https://github.com/codecov/codecov-python/pull/275) Fix GitHub Actions implementation + +### `2.1.5` + +- [#273](https://github.com/codecov/codecov-python/pull/273) Implement retries on Codecov API calls +- [#265](https://github.com/codecov/codecov-python/pull/265) Add GitHub Actions CI detection +- [#267](https://github.com/codecov/codecov-python/pull/267) Add CODECOV_NAME as default for name + +### `2.1.4` + +- [#260](https://github.com/codecov/codecov-python/pull/260) Enforce black formatting +- [#169](https://github.com/codecov/codecov-python/pull/169) Fix command line quoting on Windows +- [#216](https://github.com/codecov/codecov-python/pull/216) Fix GitLab CI project directory detection on Windows +- [#264](https://github.com/codecov/codecov-python/pull/264) Fix GitLab CI post version 9 +- [#262](https://github.com/codecov/codecov-python/pull/262) Check text for NoneType on writes +- [#266](https://github.com/codecov/codecov-python/pull/266) Include the cacert in the PUT call when uploading to S3 +- [#263](https://github.com/codecov/codecov-python/pull/263) Fixed gcov not being found in certain instances + +### `2.1.3` + +- Fix find command not working on Windows +- Add support for gzipping reports +- Dynamic syncing of version + +### `2.1.1` + +- Fix command when neither hg or git are not available + +### `2.1.0` + +- Remove x-amz-acl header +- Reformat with Black + +### `2.0.22` + +- Cleaning TOC generation + +### `2.0.21` + +- fixed string issues + +### `2.0.20` + +- fixed broken subprocess handling + +### `2.0.19` + +- fixed broken subprocess handling + +### `2.0.18` + +- fixed broken subprocess handling + +### `2.0.17` + +- fixed reported command injection vulnerability. + +### `2.0.16` + +- fixed reported command injection vulnerability. + +### `2.0.15` + +- add `-X s3` to disable direct to S3 uploading + +### `2.0.14` + +- fixed coverage combine + +### `2.0.13` + +- fix encoding issues + +### `2.0.12` + +- revert merge commit fix, back to old way + +### `2.0.11` + +- fix merge commit when it's a pull request +- remove snapci, business closed +- skip vendor directories for gcov parsing +- run coverage combine not merge +- fix report encoding + +### `2.0.10` + +- fix uploading when reports contain characters outside of latin-1 +- remove reduced_redundancy header from + ### `2.0.7` + - Add `--name/-n` to cli - Add support for Jenkins Blue - Fix environment variable joining @@ -10,12 +152,15 @@ - Support yaml:token and yaml:slug ### `2.0.5` + - Use `%20` for encoding spaces [appveyor] https://github.com/codecov/codecov-python/pull/66 ### `2.0.4` + - fix detecting merge commits on all CI, not just Travis ### `2.0.3` + - add `-F` to flagging uploads [new feature] - fixed some reports ascii chars - added `--pr` flag for manually specifing pulls @@ -29,17 +174,20 @@ - hide token from stdout ### `1.6.4` + - fix gitlab project directory - fallback on git branch/commit - fix using gcov_exec ### `1.6.0` + - depreciate `--no-fail` now a default - add `--required` to fail the build if Codecov fails - added `--cacerts` for enterprise customers - added fix reports http://bit.ly/1O4eBpt ### `1.5.0` + - fix retreiving mercurial commit - add support for swift/xcode7 profdata - now uploading direct-to-s3 to improve product performance @@ -47,9 +195,11 @@ - fix drone.io commit number, which is not a full 40 sha. ### `1.4.1` + - added `--no-fail` to prevent failing builds when missing configuration or Codecov errors ### `1.4.0` + - Ignore other known bad files/paths - Added test suite to test against example repositories - Using `coverage xml -i` to ignore No source for code errors @@ -57,36 +207,45 @@ - Added `gcov` processing, see `codecov --help` for more info. ### `1.3.1` + - Ignore other known bad files/paths - Fix issue with decoding files in py3+ ### `1.3.0` + - Refactor project to be a global uploader for more reports ### `1.2.3` + - Remove `test-results.xml`, not a coverage file - Add CircleCI container numbers ### `1.2.2` + - bring back client-side pre-processing for jacoco (they can crush) ### `1.2.1` + - accept any file ending in `coverge.xml` ### `1.2.0` + - accept `nosetests.xml` and `test-results.xml` files - no longer do client side pre-processing, upload raw - capture SEMAPHORE_CURRENT_THREAD ### `1.1.13` + - added --build arg for advanced usage ### `1.1.10` + - fix package for 2.6 on windows - fix showing `--help` when called in non-git backed repo - fix AppVeyor public repos ### `1.1.8` + - support GitLab CI Runner - added rollbar to help bugs if presented - added more filepath matching @@ -94,32 +253,39 @@ - added Shippable ci ### `1.1.7` + - support for D lang added, special thanks to @ColdenCullen - Wercker CI supported by @Robpol86 - fixed Drone build number ### `1.1.6` + - fix semaphore commit revision number - preprocess reports from xml ### `1.1.5` + - search for all `lcov|gcov` files -- depreciate `--min-coverage`, use Github Status Update feature +- depreciate `--min-coverage`, use GitHub Status Update feature - pre-process xml => json ### `1.1.4` + - added support for pyhton 2.6 by @Robpol86 - added AppVeyor support by @Robpol86 ### `1.1.3` + - added more ignore paths ### `1.1.2` + - search for `lcov.info` files - pause for `.1` before checking for min-coverage - accept `--env` variables which are stored in front-end for build specs ### `1.1.1` + - build python coverage xml only when no reports found to fix overriding reports - now defaulting output to **plain text**. Use `--json` to return json results - added `jacocoTestReport.xml` to search list diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..21e9b214 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include CHANGELOG.md +include LICENSE diff --git a/Makefile b/Makefile index c59bacfa..fae2de1e 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,18 @@ deploy: - git tag -a v$(shell python -c "import codecov;print codecov.version;") -m "" - git push origin v$(shell python -c "import codecov;print codecov.version;") - python setup.py sdist bdist_wheel upload + git tag -a v$(python -c 'import codecov;print(codecov.version)') -m "" + git push origin v$(python -c 'import codecov;print(codecov.version)') + python setup.py sdist bdist_wheel bdist_egg + python -m twine upload dist/* reinstall: pip uninstall -y codecov python setup.py install test: - py.test tests/test.py + py.test tests/test.py --cov=codecov + +format: + black . -v -t py38 --check --diff compare: hub compare $(shell git tag --sort=refname | tail -1)...master diff --git a/README.md b/README.md index 44564bb4..27531afd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,20 @@ -Codecov Global Python Uploader [![codecov.io](https://codecov.io/github/codecov/codecov-python/coverage.svg?branch=master)](https://codecov.io/github/codecov/codecov-python) +🚨🚨 Deprecation Notice 🚨🚨 + +This uploader is being deprecated by the Codecov team. We recommend migrating to our [new uploader](https://docs.codecov.com/docs/codecov-uploader) as soon as possible to prevent any lapses in coverage. [The new uploader is open source](https://github.com/codecov/uploader), and we highly encourage submitting Issues and Pull Requests. + +You can visit our [migration guide](https://docs.codecov.com/docs/deprecated-uploader-migration-guide#python-uploader) for help moving to our new uploader, and our blog post to learn more about our [deprecation plan](https://about.codecov.io/blog/codecov-uploader-deprecation-plan/) + +**On February 1, 2022 this uploader will be completely deprecated and will no longer be able to upload coverage to Codecov.** + +# Codecov Global Python Uploader + +[![codecov.io](https://codecov.io/github/codecov/codecov-python/coverage.svg?branch=master)](https://codecov.io/github/codecov/codecov-python) +![PyPI](https://img.shields.io/pypi/v/codecov) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcodecov%2Fcodecov-python.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fcodecov%2Fcodecov-python?ref=badge_shield) ======= -| [https://codecov.io/][1] | [@codecov][2] | [hello@codecov.io][3] | -| ------------------------ | ------------- | --------------------- | +| [Support][1] | [Documentation][2] | [Community Boards][3] | [Twitter][4] | +| ------------ | ------------------ | --------------------- | ------------ | Find coverage reports for all the [languages below](#languages), gather them and submit them to Codecov. @@ -14,11 +27,11 @@ Find coverage reports for all the [languages below](#languages), gather them and ## Usage ```sh -pip install --user codecov && codecov -t the-repository-upload-token +pip install --user codecov && codecov -t ``` or ```sh -conda install -c conda-forge codecov && codecov -t the-repository-upload-token +conda install -c conda-forge codecov && codecov -t ``` > `--user` argument not needed for Python projects. [See example here](https://github.com/codecov/example-python). @@ -35,7 +48,7 @@ Just please make sure to pass all the necessary environment variables through: ``` [testenv] -passenv = TOXENV CI TRAVIS TRAVIS_* +passenv = TOXENV CI TRAVIS TRAVIS_* CODECOV_* deps = codecov>=1.4.0 commands = codecov -e TOXENV ``` @@ -74,33 +87,42 @@ after_success: ## CI Providers -| Company | Supported | Token Required | -| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| [Travis CI](https://travis-ci.org/) | Yes [![Build Status](https://secure.travis-ci.org/codecov/codecov-python.svg?branch=master)](http://travis-ci.org/codecov/codecov-python) | Private only | -| [CircleCI](https://circleci.com/) | Yes | Private only | -| [Codeship](https://codeship.com/) | Yes | Public & Private | -| [Jenkins](https://jenkins-ci.org/) | Yes | Public & Private | -| [Semaphore](https://semaphoreci.com/) | Yes | Public & Private | -| [Drone.io](https://drone.io/) | Yes | Public & Private | -| [AppVeyor](http://www.appveyor.com/) | Yes [![Build status](https://ci.appveyor.com/api/projects/status/sw18lsj7786bw806/branch/master?svg=true)](https://ci.appveyor.com/project/stevepeak/codecov-python/branch/master) | Private only | -| [Wercker](http://wercker.com/) | Yes | Public & Private | -| [Magnum CI](https://magnum-ci.com/) | Yes | Public & Private | -| [Shippable](http://www.shippable.com/) | Yes | Public & Private | -| [Gitlab CI](https://about.gitlab.com/gitlab-ci/) | Yes | Public & Private | -| [Snap CI](https://snap-ci.com/) | Yes | Public & Private | -| git / mercurial | Yes (as a fallback) | Public & Private | -| [Buildbot](http://buildbot.net/) | `coming soon` [buildbot/buildbot#1671](https://github.com/buildbot/buildbot/pull/1671) | | -| [Bamboo](https://www.atlassian.com/software/bamboo) | `coming soon` | | -| [Solano Labs](https://www.solanolabs.com/) | `coming soon` | | - -> Using **Travis CI**? Uploader is compatible with `sudo: false` which can speed up your builds. :+1: - - - -[1]: https://codecov.io/ -[2]: https://twitter.com/codecov -[3]: mailto:hello@codecov.io +| Company | Supported | Token Required | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| [AppVeyor](https://www.appveyor.com/) | Yes [![Build status](https://ci.appveyor.com/api/projects/status/sw18lsj7786bw806/branch/master?svg=true)](https://ci.appveyor.com/project/stevepeak/codecov-python/branch/master) | Private only | +| [Bamboo](https://www.atlassian.com/software/bamboo) | `coming soon` | | +| [Buildbot](https://buildbot.net/) | `coming soon` [buildbot/buildbot#1671](https://github.com/buildbot/buildbot/pull/1671) | | +| [CircleCI](https://circleci.com/) | Yes | Private only | +| [Codeship](https://codeship.com/) | Yes | Public & Private | +| [Drone.io](https://drone.io/) | Yes | Public & Private | +| [GitHub Actions](https://github.com/features/actions) | Yes [![Build status](https://github.com/codecov/codecov-python/workflows/Python%20package/badge.svg?branch=master)](https://github.com/codecov/codecov-python/actions?query=workflow%3A%22Python+package%22) | Public & Private | +| [Gitlab CI](https://about.gitlab.com/gitlab-ci/) | Yes | Public & Private | +| [Jenkins](https://jenkins-ci.org/) | Yes | Public & Private | +| [Magnum CI](https://magnum-ci.com/) | Yes | Public & Private | +| [Semaphore](https://semaphoreci.com/) | Yes | Public & Private | +| [Shippable](https://www.shippable.com/) | Yes | Public & Private | +| [Solano Labs](https://www.solanolabs.com/) | `coming soon` | | +| [Travis CI](https://travis-ci.org/) | Yes [![Build Status](https://secure.travis-ci.org/codecov/codecov-python.svg?branch=master)](https://travis-ci.org/codecov/codecov-python) | Private only | +| [Wercker](http://wercker.com/) | Yes | Public & Private | +| [Cirrus CI](https://cirrus-ci.org/) | Yes | Private only | +| Git / Mercurial | Yes (as a fallback) | Public & Private | + + +## Troubleshooting + +If you're seeing an **HTTP 400 error when uploading reports to S3**, make sure you've updated to at least version 2.1.3. + + + +[1]: https://codecov.io/support/ +[2]: https://docs.codecov.io/ +[3]: https://community.codecov.io/ +[4]: https://twitter.com/codecov ## Copyright -> Copyright 2014-2017 codecov +> Copyright 2014-2022 codecov + + +## License +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcodecov%2Fcodecov-python.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fcodecov%2Fcodecov-python?ref=badge_large) diff --git a/appveyor.yml b/appveyor.yml index a4c6b26e..88cac480 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,35 +13,37 @@ environment: # a later point release. - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_VERSION: "2.7.x" # currently 2.7.15 PYTHON_ARCH: "32" - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_VERSION: "2.7.x" # currently 2.7.15 PYTHON_ARCH: "64" - - PYTHON: "C:\\Python33" - PYTHON_VERSION: "3.3.x" # currently 3.3.5 + - PYTHON: "C:\\Python34" + PYTHON_VERSION: "3.4.x" # currently 3.4.4 PYTHON_ARCH: "32" - - PYTHON: "C:\\Python33-x64" - PYTHON_VERSION: "3.3.x" # currently 3.3.5 + - PYTHON: "C:\\Python34-x64" + PYTHON_VERSION: "3.4.x" # currently 3.4.4 PYTHON_ARCH: "64" - - PYTHON: "C:\\Python34" - PYTHON_VERSION: "3.4.x" # currently 3.4.3 + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.x" # currently 3.6.6 PYTHON_ARCH: "32" - - PYTHON: "C:\\Python34-x64" - PYTHON_VERSION: "3.4.x" # currently 3.4.3 + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" # currently 3.6.6 PYTHON_ARCH: "64" - # Also test Python 2.6.6 not pre-installed - - - PYTHON: "C:\\Python266" - PYTHON_VERSION: "2.6.6" + - PYTHON: "C:\\Python37" + PYTHON_VERSION: "3.7.x" # currently 3.7.1 PYTHON_ARCH: "32" + - PYTHON: "C:\\Python37-x64" + PYTHON_VERSION: "3.7.x" # currently 3.7.1 + PYTHON_ARCH: "64" + install: # Download the Appveyor Python build accessories into subdirectory .\appveyor - mkdir appveyor diff --git a/codecov/__init__.py b/codecov/__init__.py index cedb314b..99109edf 100644 --- a/codecov/__init__.py +++ b/codecov/__init__.py @@ -6,147 +6,194 @@ import glob import requests import argparse +import fnmatch +import zlib from time import sleep from json import loads +from .__version__ import ( + __author__, + __author_email__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) + try: from urllib.parse import urlencode except ImportError: # pragma: no cover from urllib import urlencode +quote = None +if sys.platform == "win32": # pragma: no cover + try: + # https://github.com/python/cpython/blob/3.7/Lib/subprocess.py#L174-L175 + from subprocess import list2cmdline + + def quote(arg): + return list2cmdline([arg]) + + except ImportError: + pass + +if quote is None: + try: + from shlex import quote + except ImportError: # pragma: no cover + from pipes import quote + import subprocess # https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning -try: - import logging - logging.captureWarnings(True) -except: - # not py2.6 compatible - pass +import logging +logging.captureWarnings(True) -version = VERSION = __version__ = '2.0.9' + +version = __version__ COLOR = True -remove_token = re.compile(r'token=[^\&]+').sub - -is_merge_commit = re.compile(r'^Merge\s\w{40}\sinto\s\w{40}$') - -ignored_path = re.compile(r'(/vendor)|' - r'(/js/generated/coverage)|' - r'(/__pycache__)|' - r'(/coverage/instrumented)|' - r'(/build/lib)|' - r'(/htmlcov)|' - r'(/node_modules)|' - r'(/\.yarn-cache)|' - r'(\.egg-info)|' - r'(/\.git)|' - r'(/\.hg)|' - r'(/\.tox)|' - r'(/\.?v?(irtual)?envs?)', re.I).search - -ignored_report = re.compile('.*(' - r'(/\.coverage.*)|' - r'(\.coveragerc)|' - r'(\.egg)|' - r'(\.gif)|' - r'(\.ini)|' - r'(\.less)|' - r'(\.jpeg)|' - r'(\.jpg)|' - r'(\.md)|' - r'(\.png)|' - r'(\.p?sql)|' - r'(\.whl)|' - r'(\.cpp)|' - r'(\.pyc?)|' - r'(\.cfg)|' - r'(\.class)|' - r'(\.js)|' - r'(\.html)|' - r'(\.sh)|' - r'(\.tar\.gz)|' - r'(\.yml)|' - r'(\.xcconfig)|' - r'(\.data)|' - r'(coverage\.db)|' - r'(\.?codecov\.yml)|' - r'(coverage\.jade)|' - r'(include\.lst)|' - r'(inputFiles\.lst)|' - r'(createdFiles\.lst)|' - r'(scoverage\.measurements\..*)|' - r'(test_.*_coverage\.txt)|' - r'(conftest_.*\.c\.gcov)' - ')$', re.I).match - -is_report = re.compile('.*(' - r'([^/]*coverage[^/]*)|' - r'(\.gcov)|' - r'(\.lcov)|' - r'(\.lst)|' - r'(clover\.xml)|' - r'(cobertura\.xml)|' - r'(coverage-final\.json)|' - r'(coverage-summary\.json)|' - r'(gcov\.info)|' - r'(([^/]*\.)?codecov\.[^/]*)|' - r'(jacoco[^/]*\.xml)|' - r'(lcov\.info)|' - r'(luacov\.report\.out)|' - r'(nosetests\.xml)|' - r'(report\.xml)' - ')$', re.I).match +is_merge_commit = re.compile(r"^Merge\s\w{40}\sinto\s\w{40}$") + +remove_token = re.compile(r"token=[^\&]+").sub + + +def sanitize_arg(replacement, arg): + return re.sub(r"[\&]+", replacement, arg, 0, re.MULTILINE) + + +ignored_path = re.compile( + r"(/vendor)|" + r"(/js/generated/coverage)|" + r"(/__pycache__)|" + r"(/coverage/instrumented)|" + r"(/build/lib)|" + r"(/htmlcov)|" + r"(/node_modules)|" + r"(/\.yarn-cache)|" + r"(\.egg-info)|" + r"(/\.git)|" + r"(/\.hg)|" + r"(/\.tox)|" + r"(/\.?v?(irtual)?envs?)", + re.I, +).search + +ignored_report = re.compile( + ".*(" + r"(/\.coverage.*)|" + r"(\.coveragerc)|" + r"(\.egg)|" + r"(\.el)|" + r"(\.gif)|" + r"(\.ini)|" + r"(\.less)|" + r"(\.jpeg)|" + r"(\.jpg)|" + r"(\.md)|" + r"(\.png)|" + r"(\.p?sql)|" + r"(\.whl)|" + r"(\.cpp)|" + r"(\.pyc?)|" + r"(\.cfg)|" + r"(\.class)|" + r"(\.js)|" + r"(\.html)|" + r"(\.sh)|" + r"(\.tar\.gz)|" + r"(\.yml)|" + r"(\.xcconfig)|" + r"(\.data)|" + r"(coverage\.db)|" + r"(\.?codecov\.yml)|" + r"(coverage\.jade)|" + r"(include\.lst)|" + r"(inputFiles\.lst)|" + r"(createdFiles\.lst)|" + r"(scoverage\.measurements\..*)|" + r"(test_.*_coverage\.txt)|" + r"(conftest_.*\.c\.gcov)" + ")$", + re.I, +).match + +is_report = re.compile( + ".*(" + r"([^/]*coverage[^/]*)|" + r"(\.gcov)|" + r"(\.lcov)|" + r"(\.lst)|" + r"(clover\.xml)|" + r"(cobertura\.xml)|" + r"(coverage-final\.json)|" + r"(coverage-summary\.json)|" + r"(gcov\.info)|" + r"(([^/]*\.)?codecov\.[^/]*)|" + r"(jacoco[^/]*\.xml)|" + r"(lcov\.info)|" + r"(luacov\.report\.out)|" + r"(nosetests\.xml)|" + r"(report\.xml)" + ")$", + re.I, +).match opj = os.path.join # for faster access def write(text, color=None): global COLOR - if COLOR: - text = text.replace('==>', '\033[90m==>\033[0m') - text = text.replace(' +', ' \033[32m+\033[0m') - text = text.replace('XX>', '\033[31mXX>\033[0m') - if text[:6] == 'Error:': - text = '\033[41mError:\033[0m\033[91m%s\033[0m' % text[6:] - elif text[:4] == 'Tip:': - text = '\033[42mTip:\033[0m\033[32m%s\033[0m' % text[4:] - elif text.strip()[:4] == 'http': - text = '\033[92m%s\033[0m' % text - elif text[:7] == 'Codecov': - text = """ + if text and COLOR: + text = text.replace("==>", "\033[90m==>\033[0m") + text = text.replace(" +", " \033[32m+\033[0m") + text = text.replace("XX>", "\033[31mXX>\033[0m") + if text[:6] == "Error:": + text = "\033[41mError:\033[0m\033[91m%s\033[0m" % text[6:] + elif text[:4] == "Tip:": + text = "\033[42mTip:\033[0m\033[32m%s\033[0m" % text[4:] + elif text.strip()[:4] == "http": + text = "\033[92m%s\033[0m" % text + elif text[:7] == "Codecov": + text = ( + """ _____ _ / ____| | | | | ___ __| | ___ ___ _____ __ | | / _ \ / _ |/ _ \/ __/ _ \ \ / / | |___| (_) | (_| | __/ (_| (_) \ V / \_____\___/ \____|\___|\___\___/ \_/ - %s\n""" % text.split(' ')[1] - elif color == 'red': - text = '\033[91m%s\033[0m' % text - elif color == 'green': - text = '\033[92m%s\033[0m' % text + %s\n""" + % text.split(" ")[1] + ) + elif color == "red": + text = "\033[91m%s\033[0m" % text + elif color == "green": + text = "\033[92m%s\033[0m" % text - sys.stdout.write(text + '\n') + if text: + sys.stdout.write(text + "\n") def fopen(path): try: if sys.version_info < (3, 0): - with open(path, 'r') as f: + with open(path, "r") as f: return f.read() else: try: - with open(path, 'r', encoding='utf8') as f: + with open(path, "r", encoding="utf-8") as f: return f.read() except UnicodeDecodeError: - with open(path, 'r', encoding='ISO-8859-1') as f: + with open(path, "r", encoding="ISO-8859-1") as f: return f.read() except Exception as e: # on none of that works. just print the issue and continue - write(' - Ignored: ' + str(e)) + write(" - Ignored: " + str(e)) def read(filepath): @@ -154,36 +201,61 @@ def read(filepath): report = fopen(filepath) if report is None: return - write(' + %s bytes=%d' % (filepath, os.path.getsize(filepath))) - return '# path=' + filepath + '\n' + report + write(" + %s bytes=%d" % (filepath, os.path.getsize(filepath))) + return "# path=" + filepath + "\n" + report except Exception as e: # Ex: No such file or directory, skip them - write(' - Ignored: ' + str(e)) + write(" - Ignored: " + str(e)) def check_output(cmd, **popen_args): from subprocess import Popen, PIPE, CalledProcessError + process = Popen(cmd, stdout=PIPE, **popen_args) output, _ = process.communicate() if process.returncode: - raise CalledProcessError(process.returncode, cmd) + raise CalledProcessError(process.returncode, cmd, output) else: assert process.returncode == 0 - return output.decode('utf-8') + return output.decode("utf-8") + + +def try_to_run(cmd, shell=False, cwd=None): + try: + return check_output(cmd, shell=shell, cwd=cwd) + except Exception as e: + write( + " Error running `%s`: returncode=%s, output=%s" + % ( + cmd, + str(getattr(e, "returncode", None)), + str(getattr(e, "output", str(e))), + ) + ) + return None + +def run_python_coverage(args): + """Run the Python coverage tool -def try_to_run(cmd): + If it's importable in this Python, launch it using 'python -m'. + Otherwise, look it up on PATH like any other command. + """ try: - return check_output(cmd, shell=True) - except subprocess.CalledProcessError as e: - write(' Error running `%s`: %s' % (cmd, str(getattr(e, 'output', str(e))))) + import coverage + except ImportError: + # Coverage is not installed on this Python. Hope it's on PATH. + try_to_run(["coverage"] + args, shell=False) + else: + # Coverage is installed on this Python. Run it as a module. + try_to_run([sys.executable, "-m", "coverage"] + args, shell=False) def remove_non_ascii(data): try: - return data.decode('utf8') + '' + return data.decode("utf8") + "" except: - return ''.join([i if ord(i) < 128 else '' for i in data]) + return "".join([i if ord(i) < 128 else "" for i in data]) def _add_env_if_not_empty(lst, value): @@ -191,47 +263,209 @@ def _add_env_if_not_empty(lst, value): lst.add(value) +def find_files(directory, patterns, recursive=True, exclude_dirs=[]): + if recursive: + items = os.walk(directory, followlinks=False) + else: + items = [next(os.walk(directory, followLinks=False))] + if not isinstance(patterns, list): + patterns = [patterns] + for root, dirs, files in items: + dirs[:] = [d for d in dirs if d not in exclude_dirs] + for basename in files: + match = False + for pattern in patterns: + if fnmatch.fnmatch(basename, pattern): + match = True + break + if match: + filename = os.path.join(root, basename) + yield filename + + +def generate_toc(root): + res = ( + try_to_run(["git", "ls-files"], cwd=root) + or try_to_run(["git", "ls-files"]) + or try_to_run(["hg", "locate"], cwd=root) + or try_to_run(["hg", "locate"]) + ) + if res is None: + return "" + return str(res).strip() or "" + + +def retry_upload(url, request_method, retries=5, break_codes=(200,), **kwargs): + wait_seconds = 2 + for i in range(retries): + res = request_method(url, **kwargs) + if res.status_code in break_codes: + return res + write(" Retrying {0}/{1} in {2}s..".format(i + 1, retries, wait_seconds)) + sleep(wait_seconds) + return res + + def main(*argv, **kwargs): root = os.getcwd() # Build Parser # ------------ - parser = argparse.ArgumentParser(prog='codecov', add_help=True, - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog="""Upload reports to Codecov""") - basics = parser.add_argument_group('======================== Basics ========================') - basics.add_argument('--version', action='version', version='Codecov py-v'+version+" - https://codecov.io/") - basics.add_argument('--token', '-t', default=os.getenv("CODECOV_TOKEN"), help="Private repository token or @filename for file containing the token. Defaults to $CODECOV_TOKEN. Not required for public repositories on Travis-CI, CircleCI and AppVeyor") - basics.add_argument('--file', '-f', nargs="*", default=None, help="Target a specific file for uploading") - basics.add_argument('--flags', '-F', nargs="*", default=None, help="Flag these uploaded files with custom labels") - basics.add_argument('--env', '-e', nargs="*", default=None, help="Store environment variables to help distinguish CI builds.") - basics.add_argument('--required', action="store_true", default=False, help="If Codecov fails it will exit 1: failing the CI build.") - basics.add_argument('--name', '-n', default=None, help="Custom defined name of the upload. Visible in Codecov UI.") - - gcov = parser.add_argument_group('======================== gcov ========================') - gcov.add_argument('--gcov-root', default=None, help="Project root directory when preparing gcov") - gcov.add_argument('--gcov-glob', nargs="*", default=[], help="Paths to ignore during gcov gathering") - gcov.add_argument('--gcov-exec', default='gcov', help="gcov executable to run. Defaults to 'gcov'") - gcov.add_argument('--gcov-args', default='', help="extra arguments to pass to gcov") - - advanced = parser.add_argument_group('======================== Advanced ========================') - advanced.add_argument('-X', '--disable', nargs="*", default=[], help="Disable features. Accepting **search** to disable crawling through directories, **detect** to disable detecting CI provider, **gcov** disable gcov commands, `pycov` disables running python `coverage xml`, **fix** to disable report adjustments http://bit.ly/1O4eBpt") - advanced.add_argument('--root', default=None, help="Project directory. Default: current direcory or provided in CI environment variables") - advanced.add_argument('--commit', '-c', default=None, help="Commit sha, set automatically") - advanced.add_argument('--branch', '-b', default=None, help="Branch name") - advanced.add_argument('--build', default=None, help="Specify a custom build number to distinguish ci jobs, provided automatically for supported ci companies") - advanced.add_argument('--pr', default=None, help="Specify a custom pr number, provided automatically for supported ci companies") - advanced.add_argument('--tag', default=None, help="Git tag") - - enterprise = parser.add_argument_group('======================== Enterprise ========================') - enterprise.add_argument('--slug', '-r', default=os.getenv("CODECOV_SLUG"), help="Specify repository slug for Enterprise ex. owner/repo") - enterprise.add_argument('--url', '-u', default=os.getenv("CODECOV_URL", "https://codecov.io"), help="Your Codecov endpoint") - enterprise.add_argument('--cacert', default=os.getenv("CODECOV_CACERT", os.getenv("CURL_CA_BUNDLE")), help="Certificate pem bundle used to verify with your Codecov instance") - - debugging = parser.add_argument_group('======================== Debugging ========================') - debugging.add_argument('--dump', action="store_true", help="Dump collected data and do not send to Codecov") - debugging.add_argument('-v', '--verbose', action="store_true", help="No comfigured yet") - debugging.add_argument('--no-color', action="store_true", help="Do not output with color") + parser = argparse.ArgumentParser( + prog="codecov", + add_help=True, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""Upload reports to Codecov""", + ) + basics = parser.add_argument_group( + "======================== Basics ========================" + ) + basics.add_argument( + "--version", + action="version", + version="Codecov py-v" + version + " - https://codecov.io/", + ) + basics.add_argument( + "--token", + "-t", + default=os.getenv("CODECOV_TOKEN"), + help="Private repository token or @filename for file containing the token. Defaults to $CODECOV_TOKEN. Not required for public repositories on Travis CI, CircleCI, AppVeyor and CirrusCI", + ) + basics.add_argument( + "--file", + "-f", + nargs="*", + default=None, + help="Target a specific file for uploading", + ) + basics.add_argument( + "--flags", + "-F", + nargs="*", + default=os.getenv("CODECOV_FLAGS"), + help="Flag these uploaded files with custom labels", + ) + basics.add_argument( + "--env", + "-e", + nargs="*", + default=None, + help="Store environment variables to help distinguish CI builds.", + ) + basics.add_argument( + "--required", + action="store_true", + default=False, + help="If Codecov fails it will exit 1 - possibly failing the CI build.", + ) + basics.add_argument( + "--name", + "-n", + default=os.getenv("CODECOV_NAME"), + help="Custom defined name of the upload. Visible in Codecov UI. Defaults to $CODECOV_NAME.", + ) + + gcov = parser.add_argument_group( + "======================== gcov ========================" + ) + gcov.add_argument( + "--gcov-root", default=None, help="Project root directory when preparing gcov" + ) + gcov.add_argument( + "--gcov-glob", + nargs="*", + default=[], + help="Paths to ignore during gcov gathering", + ) + gcov.add_argument( + "--gcov-exec", default="gcov", help="gcov executable to run. Defaults to 'gcov'" + ) + gcov.add_argument( + "--no-gcov-out", action="store_true", default=False, help="Disable gcov output" + ) + gcov.add_argument("--gcov-args", default="", help="extra arguments to pass to gcov") + + advanced = parser.add_argument_group( + "======================== Advanced ========================" + ) + advanced.add_argument( + "-X", + "--disable", + nargs="*", + default=[], + help="Disable features. Accepting **search** to disable crawling through directories, **detect** to disable detecting CI provider, **gcov** disable gcov commands, `pycov` disables running python `coverage xml`, **fix** to disable report adjustments https://docs.codecov.io/docs/fixing-reports", + ) + advanced.add_argument( + "--root", + default=None, + help="Project directory. Default: current direcory or provided in CI environment variables", + ) + advanced.add_argument( + "--commit", "-c", default=None, help="Commit SHA, set automatically" + ) + advanced.add_argument( + "--prefix", + "-P", + default=None, + help="Prefix network paths to help resolve paths: https://github.com/codecov/support/issues/472", + ) + advanced.add_argument("--branch", "-b", default=None, help="Branch name") + advanced.add_argument( + "--build", + default=None, + help="Specify a custom build number to distinguish CI jobs, provided automatically for supported CI companies", + ) + advanced.add_argument( + "--pr", + default=None, + help="Specify a custom pr number, provided automatically for supported CI companies", + ) + advanced.add_argument("--tag", default=None, help="Git tag") + advanced.add_argument( + "--tries", + default=5, + type=int, + help="Specify the total number of attempts to make when uploading coverage report", + ) + + enterprise = parser.add_argument_group( + "======================== Enterprise ========================" + ) + enterprise.add_argument( + "--slug", + "-r", + default=os.getenv("CODECOV_SLUG"), + help="Specify repository slug for Enterprise ex. owner/repo", + ) + enterprise.add_argument( + "--url", + "-u", + default=os.getenv("CODECOV_URL", "https://codecov.io"), + help="Your Codecov endpoint", + ) + enterprise.add_argument( + "--cacert", + default=os.getenv("CODECOV_CACERT", os.getenv("CURL_CA_BUNDLE")), + help="Certificate pem bundle used to verify with your Codecov instance", + ) + + debugging = parser.add_argument_group( + "======================== Debugging ========================" + ) + debugging.add_argument( + "--dump", + action="store_true", + help="Dump collected data and do not send to Codecov", + ) + debugging.add_argument( + "-v", + "--verbose", + action="store_true", + help="Be verbose, e.g. dump the collected data", + ) + debugging.add_argument( + "--no-color", action="store_true", help="Do not output with color" + ) # Parse Arguments # --------------- @@ -249,467 +483,680 @@ def main(*argv, **kwargs): if codecov.env: # -e VAR1,VAR2 or -e VAR1 -e VAR2 for env in codecov.env: - for e in env.split(','): + for e in env.split(","): include_env.add(e.strip()) # add from env if os.getenv("CODECOV_ENV"): - for env in os.getenv("CODECOV_ENV").split(','): + for env in os.getenv("CODECOV_ENV").split(","): include_env.add(env.strip()) - write('Codecov v'+version) - query = dict(commit='', branch='', job='', pr='', build_url='', - token=codecov.token) + write("Codecov v" + version) + query = dict(commit="", branch="", job="", pr="", build_url="", token=codecov.token) language = None - if os.getenv('TOXENV'): - _add_env_if_not_empty(include_env, 'TOXENV') + if os.getenv("TOXENV"): + _add_env_if_not_empty(include_env, "TOXENV") # Detect CI # --------- - if 'detect' in codecov.disable: - write('XX> Detecting CI provider disabled.') + if "detect" in codecov.disable: + write("XX> Detecting CI provider disabled.") else: - write('==> Detecting CI provider') + write("==> Detecting CI provider") # ------- # Jenkins # ------- - if os.getenv('JENKINS_URL'): + if os.getenv("JENKINS_URL"): # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables - query.update(dict(branch=os.getenv('ghprbSourceBranch') or os.getenv('GIT_BRANCH') or os.getenv('BRANCH_NAME'), - service='jenkins', - commit=os.getenv('ghprbActualCommit') or os.getenv('GIT_COMMIT'), - pr=os.getenv('ghprbPullId') or os.getenv('CHANGE_ID'), - build=os.getenv('BUILD_NUMBER'), - build_url=os.getenv('BUILD_URL'))) - root = os.getenv('WORKSPACE') or root - write(' Jenkins Detected') + query.update( + dict( + branch=os.getenv("ghprbSourceBranch") + or os.getenv("GIT_BRANCH") + or os.getenv("BRANCH_NAME"), + service="jenkins", + commit=os.getenv("ghprbActualCommit") or os.getenv("GIT_COMMIT"), + pr=os.getenv("ghprbPullId") or os.getenv("CHANGE_ID"), + build=os.getenv("BUILD_NUMBER"), + build_url=os.getenv("BUILD_URL"), + ) + ) + root = os.getenv("WORKSPACE") or root + write(" Jenkins Detected") # --------- # Travis CI # --------- - elif os.getenv('CI') == 'true' and os.getenv('TRAVIS') == "true" and os.getenv('SHIPPABLE') != 'true': + elif ( + os.getenv("CI") == "true" + and os.getenv("TRAVIS") == "true" + and os.getenv("SHIPPABLE") != "true" + ): # http://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables - query.update(dict(branch=os.getenv('TRAVIS_BRANCH'), - service='travis', - build=os.getenv('TRAVIS_JOB_NUMBER'), - pr=os.getenv('TRAVIS_PULL_REQUEST'), - job=os.getenv('TRAVIS_JOB_ID'), - tag=os.getenv('TRAVIS_TAG'), - slug=os.getenv('TRAVIS_REPO_SLUG'), - commit=os.getenv('TRAVIS_COMMIT'))) - root = os.getenv('TRAVIS_BUILD_DIR') or root - write(' Travis Detected') - language = (list(filter(lambda l: os.getenv('TRAVIS_%s_VERSION' % l.upper()), - ('dart', 'go', 'haxe', 'jdk', 'julia', 'node', 'otp', 'xcode', - 'perl', 'php', 'python', 'r', 'ruby', 'rust', 'scala'))) + [''])[0] - - _add_env_if_not_empty(include_env, 'TRAVIS_OS_NAME') + query.update( + dict( + branch=os.getenv("TRAVIS_BRANCH"), + service="travis", + build=os.getenv("TRAVIS_JOB_NUMBER"), + pr=os.getenv("TRAVIS_PULL_REQUEST"), + job=os.getenv("TRAVIS_JOB_ID"), + tag=os.getenv("TRAVIS_TAG"), + slug=os.getenv("TRAVIS_REPO_SLUG"), + commit=os.getenv("TRAVIS_COMMIT"), + ) + ) + root = os.getenv("TRAVIS_BUILD_DIR") or root + write(" Travis Detected") + language = ( + list( + filter( + lambda l: os.getenv("TRAVIS_%s_VERSION" % l.upper()), + ( + "dart", + "go", + "haxe", + "jdk", + "julia", + "node", + "otp", + "xcode", + "perl", + "php", + "python", + "r", + "ruby", + "rust", + "scala", + ), + ) + ) + + [""] + )[0] + + _add_env_if_not_empty(include_env, "TRAVIS_OS_NAME") if language: - _add_env_if_not_empty(include_env, 'TRAVIS_%s_VERSION' % language.upper()) + _add_env_if_not_empty( + include_env, "TRAVIS_%s_VERSION" % language.upper() + ) # -------- # Codeship # -------- - elif os.getenv('CI') == 'true' and os.getenv('CI_NAME') == 'codeship': + elif os.getenv("CI") == "true" and os.getenv("CI_NAME") == "codeship": # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/ - query.update(dict(branch=os.getenv('CI_BRANCH'), - service='codeship', - build=os.getenv('CI_BUILD_NUMBER'), - build_url=os.getenv('CI_BUILD_URL'), - commit=os.getenv('CI_COMMIT_ID'))) - write(' Codeship Detected') + query.update( + dict( + branch=os.getenv("CI_BRANCH"), + service="codeship", + build=os.getenv("CI_BUILD_NUMBER"), + build_url=os.getenv("CI_BUILD_URL"), + commit=os.getenv("CI_COMMIT_ID"), + ) + ) + write(" Codeship Detected") # --------- # Buildkite # --------- - elif os.getenv('CI') == 'true' and os.getenv('BUILDKITE') == 'true': + elif os.getenv("CI") == "true" and os.getenv("BUILDKITE") == "true": # https://buildkite.com/docs/guides/environment-variables - query.update(dict(branch=os.getenv('BUILDKITE_BRANCH'), - service='buildkite', - build=os.getenv('BUILDKITE_BUILD_NUMBER') + '.' + os.getenv('BUILDKITE_JOB_ID'), - slug=os.getenv('BUILDKITE_PROJECT_SLUG'), - build_url=os.getenv('BUILDKITE_BUILD_URL'), - commit=os.getenv('BUILDKITE_COMMIT'))) - write(' Buildkite Detected') + query.update( + dict( + branch=os.getenv("BUILDKITE_BRANCH"), + service="buildkite", + build=os.getenv("BUILDKITE_BUILD_NUMBER") + + "." + + os.getenv("BUILDKITE_JOB_ID"), + slug=os.getenv("BUILDKITE_PROJECT_SLUG"), + build_url=os.getenv("BUILDKITE_BUILD_URL"), + commit=os.getenv("BUILDKITE_COMMIT"), + ) + ) + write(" Buildkite Detected") # --------- # Circle CI # --------- - elif os.getenv('CI') == 'true' and os.getenv('CIRCLECI') == 'true': + elif os.getenv("CI") == "true" and os.getenv("CIRCLECI") == "true": # https://circleci.com/docs/environment-variables - query.update(dict(branch=os.getenv('CIRCLE_BRANCH'), - service='circleci', - build=os.getenv('CIRCLE_BUILD_NUM') + "." + os.getenv('CIRCLE_NODE_INDEX'), - job=os.getenv('CIRCLE_BUILD_NUM') + "." + os.getenv('CIRCLE_NODE_INDEX'), - pr=os.getenv('CIRCLE_PR_NUMBER'), - slug=os.getenv('CIRCLE_PROJECT_USERNAME') + "/" + os.getenv('CIRCLE_PROJECT_REPONAME'), - commit=os.getenv('CIRCLE_SHA1'))) - write(' Circle CI Detected') + query.update( + dict( + branch=os.getenv("CIRCLE_BRANCH"), + service="circleci", + build=os.getenv("CIRCLE_BUILD_NUM") + + "." + + os.getenv("CIRCLE_NODE_INDEX"), + job=os.getenv("CIRCLE_BUILD_NUM") + + "." + + os.getenv("CIRCLE_NODE_INDEX"), + pr=os.getenv("CIRCLE_PR_NUMBER"), + slug=os.getenv("CIRCLE_PROJECT_USERNAME") + + "/" + + os.getenv("CIRCLE_PROJECT_REPONAME"), + commit=os.getenv("CIRCLE_SHA1"), + ) + ) + write(" Circle CI Detected") # --------- # Semaphore # --------- - elif os.getenv('CI') == 'true' and os.getenv('SEMAPHORE') == 'true': + elif os.getenv("CI") == "true" and os.getenv("SEMAPHORE") == "true": # https://semaphoreapp.com/docs/available-environment-variables.html - query.update(dict(branch=os.getenv('BRANCH_NAME'), - service='semaphore', - build=os.getenv('SEMAPHORE_BUILD_NUMBER') + '.' + os.getenv('SEMAPHORE_CURRENT_THREAD'), - slug=os.getenv('SEMAPHORE_REPO_SLUG'), - commit=os.getenv('REVISION'))) - write(' Semaphore Detected') + query.update( + dict( + branch=os.getenv("BRANCH_NAME"), + service="semaphore", + build=os.getenv("SEMAPHORE_BUILD_NUMBER") + + "." + + os.getenv("SEMAPHORE_CURRENT_THREAD"), + slug=os.getenv("SEMAPHORE_REPO_SLUG"), + commit=os.getenv("REVISION"), + ) + ) + write(" Semaphore Detected") # ---------- # Greenhouse # ---------- - elif os.getenv('GREENHOUSE') == 'true': + elif os.getenv("GREENHOUSE") == "true": # http://docs.greenhouseci.com/docs/environment-variables-files - query.update(dict(branch=os.getenv('GREENHOUSE_BRANCH'), - service='greenhouse', - build=os.getenv('GREENHOUSE_BUILD_NUMBER'), - build_url=os.getenv('GREENHOUSE_BUILD_URL'), - pr=os.getenv('GREENHOUSE_PULL_REQUEST'), - commit=os.getenv('GREENHOUSE_COMMIT'))) - write(' Greenhouse Detected') + query.update( + dict( + branch=os.getenv("GREENHOUSE_BRANCH"), + service="greenhouse", + build=os.getenv("GREENHOUSE_BUILD_NUMBER"), + build_url=os.getenv("GREENHOUSE_BUILD_URL"), + pr=os.getenv("GREENHOUSE_PULL_REQUEST"), + commit=os.getenv("GREENHOUSE_COMMIT"), + ) + ) + write(" Greenhouse Detected") # -------- # drone.io # -------- - elif os.getenv('CI') == "true" and os.getenv('DRONE') == "true": + elif os.getenv("CI") == "drone" and os.getenv("DRONE") == "true": # http://docs.drone.io/env.html - query.update(dict(branch=os.getenv('DRONE_BRANCH'), - service='drone.io', - build=os.getenv('DRONE_BUILD_NUMBER'), - build_url=os.getenv('DRONE_BUILD_URL'))) - root = os.getenv('DRONE_BUILD_DIR') or root - write(' Drone Detected') + query.update( + dict( + branch=os.getenv("DRONE_BRANCH"), + service="drone.io", + build=os.getenv("DRONE_BUILD_NUMBER"), + build_url=os.getenv("DRONE_BUILD_LINK"), + ) + ) + root = os.getenv("DRONE_BUILD_DIR") or root + write(" Drone Detected") # -------- # TeamCity # -------- - elif os.getenv('TEAMCITY_VERSION'): + elif os.getenv("TEAMCITY_VERSION"): # https://confluence.jetbrains.com/plugins/servlet/mobile#content/view/74847298 - query.update(dict(service='teamcity', - build=os.getenv('BUILD_NUMBER'), - commit=os.getenv('BUILD_VCS_NUMBER'))) - write(' TeamCity CI Detected') + query.update( + dict( + service="teamcity", + build=os.getenv("BUILD_NUMBER"), + commit=os.getenv("BUILD_VCS_NUMBER"), + ) + ) + write(" TeamCity CI Detected") # -------- # AppVeyor # -------- - elif os.getenv('CI') == "True" and os.getenv('APPVEYOR') == 'True': + elif ( + os.getenv("CI", "false").lower() == "true" + and os.getenv("APPVEYOR", "false").lower() == "true" + ): # http://www.appveyor.com/docs/environment-variables - query.update(dict(branch=os.getenv('APPVEYOR_REPO_BRANCH'), - service="appveyor", - job='/'.join((os.getenv('APPVEYOR_ACCOUNT_NAME'), os.getenv('APPVEYOR_PROJECT_SLUG'), os.getenv('APPVEYOR_BUILD_VERSION'))), - build=os.getenv('APPVEYOR_JOB_ID'), - pr=os.getenv('APPVEYOR_PULL_REQUEST_NUMBER'), - slug=os.getenv('APPVEYOR_REPO_NAME'), - commit=os.getenv('APPVEYOR_REPO_COMMIT'))) - write(' AppVeyor Detected') - codecov.disable.append('search') + query.update( + dict( + branch=os.getenv("APPVEYOR_REPO_BRANCH"), + service="appveyor", + job="/".join( + ( + os.getenv("APPVEYOR_ACCOUNT_NAME"), + os.getenv("APPVEYOR_PROJECT_SLUG"), + os.getenv("APPVEYOR_BUILD_VERSION"), + ) + ), + build=os.getenv("APPVEYOR_JOB_ID"), + pr=os.getenv("APPVEYOR_PULL_REQUEST_NUMBER"), + slug=os.getenv("APPVEYOR_REPO_NAME"), + commit=os.getenv("APPVEYOR_REPO_COMMIT"), + ) + ) + write(" AppVeyor Detected") + codecov.disable.append("search") # ------- # Wercker # ------- - elif os.getenv('CI') == "true" and os.getenv('WERCKER_GIT_BRANCH'): + elif os.getenv("CI") == "true" and os.getenv("WERCKER_GIT_BRANCH"): # http://devcenter.wercker.com/articles/steps/variables.html - query.update(dict(branch=os.getenv('WERCKER_GIT_BRANCH'), - service="wercker", - build=os.getenv('WERCKER_MAIN_PIPELINE_STARTED'), - slug=os.getenv('WERCKER_GIT_OWNER') + '/' + os.getenv('WERCKER_GIT_REPOSITORY'), - commit=os.getenv('WERCKER_GIT_COMMIT'))) - write(' Wercker Detected') - - # ------- - # Snap CI - # ------- - elif os.getenv('CI') == "true" and os.getenv('SNAP_CI') == "true": - # https://docs.snap-ci.com/environment-variables/ - query.update(dict(branch=os.getenv('SNAP_BRANCH') or os.getenv('SNAP_UPSTREAM_BRANCH'), - service="snap", - job=os.getenv('SNAP_STAGE_NAME'), - build=os.getenv('SNAP_PIPELINE_COUNTER'), - pr=os.getenv('SNAP_PULL_REQUEST_NUMBER'), - commit=os.getenv('SNAP_COMMIT') or os.getenv('SNAP_UPSTREAM_COMMIT'))) - _add_env_if_not_empty(include_env, 'DISPLAY') - write(' Snap CI Detected') + query.update( + dict( + branch=os.getenv("WERCKER_GIT_BRANCH"), + service="wercker", + build=os.getenv("WERCKER_MAIN_PIPELINE_STARTED"), + slug=os.getenv("WERCKER_GIT_OWNER") + + "/" + + os.getenv("WERCKER_GIT_REPOSITORY"), + commit=os.getenv("WERCKER_GIT_COMMIT"), + ) + ) + write(" Wercker Detected") # ------ # Magnum # ------ - elif os.getenv('CI') == "true" and os.getenv('MAGNUM') == 'true': + elif os.getenv("CI") == "true" and os.getenv("MAGNUM") == "true": # https://magnum-ci.com/docs/environment - query.update(dict(service="magnum", - branch=os.getenv('CI_BRANCH'), - build=os.getenv('CI_BUILD_NUMBER'), - commit=os.getenv('CI_COMMIT'))) - write(' Magnum Detected') + query.update( + dict( + service="magnum", + branch=os.getenv("CI_BRANCH"), + build=os.getenv("CI_BUILD_NUMBER"), + commit=os.getenv("CI_COMMIT"), + ) + ) + write(" Magnum Detected") # --------- # Shippable # --------- - elif os.getenv('SHIPPABLE') == "true": + elif os.getenv("SHIPPABLE") == "true": # http://docs.shippable.com/en/latest/config.html#common-environment-variables - query.update(dict(branch=os.getenv('BRANCH'), - service='shippable', - build=os.getenv('BUILD_NUMBER'), - build_url=os.getenv('BUILD_URL'), - pr=os.getenv('PULL_REQUEST'), - slug=os.getenv('REPO_NAME'), - commit=os.getenv('COMMIT'))) - write(' Shippable Detected') + query.update( + dict( + branch=os.getenv("BRANCH"), + service="shippable", + build=os.getenv("BUILD_NUMBER"), + build_url=os.getenv("BUILD_URL"), + pr=os.getenv("PULL_REQUEST"), + slug=os.getenv("REPO_NAME"), + commit=os.getenv("COMMIT"), + ) + ) + write(" Shippable Detected") # --------- # Gitlab CI # --------- - elif os.getenv('CI_SERVER_NAME', '').startswith("GitLab"): - # http://doc.gitlab.com/ci/examples/README.html#environmental-variables - # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb#L96 - query.update(dict(service='gitlab', - branch=os.getenv('CI_BUILD_REF_NAME'), - build=os.getenv('CI_BUILD_ID'), - commit=os.getenv('CI_BUILD_REF'))) - if os.getenv('CI_PROJECT_DIR', '').startswith('/'): - root = os.getenv('CI_PROJECT_DIR') + elif os.getenv("CI_SERVER_NAME", "").startswith("GitLab"): + # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html + # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb + query.update( + dict( + service="gitlab", + branch=os.getenv( + "CI_COMMIT_REF_NAME", os.getenv("CI_BUILD_REF_NAME") + ), + build=os.getenv("CI_JOB_ID", os.getenv("CI_BUILD_ID")), + commit=os.getenv("CI_COMMIT_SHA", os.getenv("CI_BUILD_REF")), + ) + ) + if sys.platform == "win32" or os.getenv("CI_PROJECT_DIR", "").startswith( + "/" + ): + root = os.getenv("CI_PROJECT_DIR") else: - root = os.getenv('HOME') + '/' + os.getenv('CI_PROJECT_DIR', '') + root = os.getenv("HOME") + "/" + os.getenv("CI_PROJECT_DIR", "") - if os.getenv('CI_BUILD_REPO'): - query['slug'] = os.getenv('CI_BUILD_REPO').split('/', 3)[-1].replace('.git', '') - elif os.getenv('CI_REPOSITORY_URL'): - query['slug'] = os.getenv('CI_REPOSITORY_URL').split('/', 3)[-1].replace('.git', '') + if os.getenv("CI_BUILD_REPO"): + query["slug"] = ( + os.getenv("CI_BUILD_REPO").split("/", 3)[-1].replace(".git", "") + ) + elif os.getenv("CI_REPOSITORY_URL"): + query["slug"] = ( + os.getenv("CI_REPOSITORY_URL").split("/", 3)[-1].replace(".git", "") + ) + + write(" Gitlab CI Detected") + + # -------------- + # GitHub Actions + # -------------- + elif os.getenv("GITHUB_ACTION"): + # https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables + query.update( + dict( + service="github-actions", + build=os.getenv("GITHUB_RUN_ID"), + commit=os.getenv("GITHUB_SHA"), + slug=os.getenv("GITHUB_REPOSITORY"), + build_url="http://github.com/" + + os.getenv("GITHUB_REPOSITORY") + + "/actions/runs/" + + os.getenv("GITHUB_RUN_ID"), + ) + ) - write(' Gitlab CI Detected') + if os.getenv("GITHUB_REF"): + query["branch"] = os.getenv("GITHUB_REF").split("/", 3)[-1] + if os.getenv("GITHUB_HEAD_REF"): + # PR refs are in the format: refs/pull/7/merge + query["pr"] = os.getenv("GITHUB_REF").split("/")[-2] + query["branch"] = os.getenv("GITHUB_HEAD_REF") + + write(" GitHub Actions CI Detected") + + # --------- + # Cirrus CI + # --------- + elif os.getenv("CIRRUS_CI"): + # https://cirrus-ci.org/guide/writing-tasks/#environment-variables + query.update( + dict( + service="cirrus-ci", + slug=os.getenv("CIRRUS_REPO_FULL_NAME"), + branch=os.getenv("CIRRUS_BRANCH"), + pr=os.getenv("CIRRUS_PR"), + commit=os.getenv("CIRRUS_CHANGE_IN_REPO"), + build=os.getenv("CIRRUS_BUILD_ID"), + build_url="https://cirrus-ci.com/task/" + os.getenv("CIRRUS_TASK_ID"), + job=os.getenv("CIRRUS_TASK_NAME"), + ) + ) + write(" Cirrus CI Detected") else: - query.update(dict(commit=os.getenv('VCS_COMMIT_ID', ''), - branch=os.getenv('VCS_BRANCH_NAME', ''), - pr=os.getenv('VCS_PULL_REQUEST', ''), - slug=os.getenv('VCS_SLUG', ''), - build_url=os.getenv('CI_BUILD_URL', ''), - build=os.getenv('CI_BUILD_ID', ''))) + query.update( + dict( + commit=os.getenv("VCS_COMMIT_ID", ""), + branch=os.getenv("VCS_BRANCH_NAME", ""), + pr=os.getenv("VCS_PULL_REQUEST", ""), + slug=os.getenv("VCS_SLUG", ""), + build_url=os.getenv("CI_BUILD_URL", ""), + build=os.getenv("CI_BUILD_ID", ""), + ) + ) # ------ # git/hg # ------ - if not query.get('branch'): + if not query.get("branch"): try: # find branch, commit, repo from git command - branch = try_to_run('git rev-parse --abbrev-ref HEAD || hg branch') - query['branch'] = branch if branch != 'HEAD' else '' - write(' -> Got branch from git/hg') + branch = try_to_run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"] + ) or try_to_run(["hg", "branch"]) + query["branch"] = branch if branch != "HEAD" else "" + write(" -> Got branch from git/hg") except: - write(' x> Failed to get branch from git/hg') + write(" x> Failed to get branch from git/hg") - if not query.get('commit'): + if not query.get("commit"): try: - query['commit'] = try_to_run("git rev-parse HEAD || hg id -i --debug | tr -d '+'") - write(' -> Got sha from git/hg') + query["commit"] = try_to_run( + ["git", "rev-parse", "HEAD"] + ) or try_to_run(["hg", "log", "-r", ".", "-T", "{node}"]) + write(" -> Got sha from git/hg") except: # pragma: no cover - write(' x> Failed to get sha from git/hg') + write(" x> Failed to get sha from git/hg") # Update Query # ------------ if codecov.name: - query['name'] = codecov.name + query["name"] = codecov.name if codecov.flags: - query['flags'] = ','.join(codecov.flags) + query["flags"] = ",".join(codecov.flags) if codecov.build: - query['build'] = codecov.build + query["build"] = codecov.build + + if codecov.pr: + query["pr"] = codecov.pr if codecov.commit: - query['commit'] = codecov.commit + query["commit"] = codecov.commit - else: + elif query["pr"] and query["pr"] != "false": # Merge Commits # ------------- - res = try_to_run('git log -1 --pretty=%B') + res = try_to_run(["git", "log", "-1", "--pretty=%B"]) if res and is_merge_commit.match(res.strip()): - query['commit'] = res.split(' ')[1] + query["commit"] = res.split(" ")[1] + write(" Fixing merge commit SHA") if codecov.slug: - query['slug'] = codecov.slug + query["slug"] = codecov.slug if codecov.branch: - query['branch'] = codecov.branch - - if codecov.pr: - query['pr'] = codecov.pr + query["branch"] = codecov.branch if codecov.tag: - query['tag'] = codecov.tag + query["tag"] = codecov.tag if codecov.root: root = codecov.root + root = quote(root) + # Upload # ------ try: - write('==> Preparing upload') + write("==> Preparing upload") # Read token from file # -------------------- - if query.get('token') and query.get('token')[0] == '@': - write(' Reading token from file') - query['token'] = fopen(opj(os.getcwd(), query['token'][1:])).strip() + if query.get("token") and query.get("token")[0] == "@": + write(" Reading token from file") + query["token"] = fopen(opj(os.getcwd(), query["token"][1:])).strip() - assert query.get('commit') not in ('', None), "Commit sha is missing. Please specify via --commit=:sha" + assert query.get("commit") not in ( + "", + None, + ), "Commit sha is missing. Please specify via --commit=:sha" # Build TOC # --------- - toc = str((try_to_run('cd %s && git ls-files' % root) or - try_to_run('git ls-files') or - try_to_run('cd %s && hg locate' % root) or - try_to_run('hg locate') or '').strip()) + toc = generate_toc(root) + + if codecov.prefix: + prefix = codecov.prefix.strip("/") + toc = "{}/{}".format(prefix, toc.replace("\n", "\n{}/".format(prefix))) # Detect codecov.yml location - yaml_location = re.search( - r'\.?codecov\.ya?ml$', - toc, - re.M - ) + yaml_location = re.search(r"\.?codecov\.ya?ml$", toc, re.M) if yaml_location: yaml_location = yaml_location.group() yaml_path = opj(root, yaml_location) if os.path.exists(yaml_path): - query['yaml'] = yaml_location + query["yaml"] = yaml_location yaml = fopen(yaml_path) _token = re.search( - r'token: (\'|\")?([0-9a-f]{8}(-?[0-9a-f]{4}){3}-?[0-9a-f]{12})', + r"token: (\'|\")?([0-9a-f]{8}(-?[0-9a-f]{4}){3}-?[0-9a-f]{12})", yaml, - re.M + re.M, ) if _token: - query['token'] = _token.groups()[1] + query["token"] = _token.groups()[1] _slug = re.search( - r'slug: (\'|\")?([\w\-\.\+]+\/[\w\-\.\+]+)', - yaml, - re.M + r"slug: (\'|\")?([\w\-\.\+]+\/[\w\-\.\+]+)", yaml, re.M ) if _slug: - query['slug'] = _slug.groups()[1] - - assert query.get('job') or query.get('token'), "Missing repository upload token" + query["slug"] = _slug.groups()[1] # Processing gcov # --------------- - if 'gcov' in codecov.disable: - write('XX> Skip processing gcov') + if "gcov" in codecov.disable: + write("XX> Skip processing gcov") else: - write('==> Processing gcov (disable by -X gcov)') - cmd = "find %s -type f -name '*.gcno' %s -exec %s -pb %s {} +" % ( - (codecov.gcov_root or root), - " ".join(map(lambda a: "-not -path '%s'" % a, codecov.gcov_glob)), - (codecov.gcov_exec or ''), - (codecov.gcov_args or '')) - write(' Executing gcov (%s)' % cmd) - try_to_run(cmd) + dont_search_here = ["bower_components" "node_modules" "vendor"] + if codecov.gcov_glob: + dont_search_here.append(codecov.gcov_glob) + + write("==> Processing gcov (disable by -X gcov)") + for path in find_files( + sanitize_arg("", codecov.gcov_root or root), + "*.gcno", + True, + dont_search_here, + ): + cmd = sanitize_arg("", codecov.gcov_exec or "").split(" ") + cmd.append("-pb") + if codecov.gcov_args: + cmd.append(sanitize_arg("", codecov.gcov_args or "")) + cmd.append(path) + if not codecov.no_gcov_out: + write(" Executing gcov (%s)" % cmd) + gcov_out = try_to_run(cmd) + if not codecov.no_gcov_out: + write(gcov_out) # Collect Reports # --------------- - write('==> Collecting reports') + write("==> Collecting reports") reports = [] - if 'search' in codecov.disable: - write('XX> Searching for reports disabled') + if "search" in codecov.disable: + write("XX> Searching for reports disabled") else: # Detect .bowerrc # --------------- - bower_components = '/bower_components' - bowerrc = opj(root, '.bowerrc') + bower_components = "/bower_components" + bowerrc = opj(root, ".bowerrc") if os.path.exists(bowerrc): - write(' Detecting .bowerrc file') + write(" Detecting .bowerrc file") try: - bower_components = '/' + (loads(fopen(bowerrc)).get('directory') or 'bower_components').replace('./', '').strip('/') - write(' .bowerrc detected, ignoring ' + bower_components) + bower_components = "/" + ( + loads(fopen(bowerrc)).get("directory") or "bower_components" + ).replace("./", "").strip("/") + write(" .bowerrc detected, ignoring " + bower_components) except Exception as e: - write(' .bowerrc parsing error: ' + str(e)) + write(" .bowerrc parsing error: " + str(e)) # Find reports # ------------ for _root, dirs, files in os.walk(root): # need to replace('\\', '/') for Windows - if not ignored_path(_root.replace('\\', '/')) and bower_components not in _root.replace('\\', '/'): + if not ignored_path( + _root.replace("\\", "/") + ) and bower_components not in _root.replace("\\", "/"): # add data to tboc for filepath in files: fullpath = opj(_root, filepath) - if not codecov.file and is_report(fullpath.replace('\\', '/')) and not ignored_report(fullpath.replace('\\', '/')): + if ( + not codecov.file + and is_report(fullpath.replace("\\", "/")) + and not ignored_report(fullpath.replace("\\", "/")) + ): # found report reports.append(read(fullpath)) # Read Reports # ------------ if codecov.file: - write(' Targeting specific files') + write(" Targeting specific files") reports.extend(filter(bool, map(read, codecov.file))) - elif 'pycov' not in codecov.disable: + elif "pycov" not in codecov.disable: # Call `coverage xml` when .coverage exists # ----------------------------------------- # Ran from current directory - if os.path.exists(opj(os.getcwd(), '.coverage')) and not os.path.exists(opj(os.getcwd(), 'coverage.xml')): - if glob.glob(opj(os.getcwd(), '.coverage.*')): - write(' Mergeing coverage reports') - try_to_run('coverage merge') - - write(' Generating coverage xml reports for Python') + if glob.glob(opj(os.getcwd(), ".coverage.*")): + write(" Merging coverage reports") + # The `-a` option is mandatory here. If we + # have a `.coverage` in the current directory, calling + # without the option would delete the previous data + run_python_coverage(["combine", "-a"]) + + if os.path.exists(opj(os.getcwd(), ".coverage")) and not os.path.exists( + opj(os.getcwd(), "coverage.xml") + ): + write(" Generating coverage xml reports for Python") # using `-i` to ignore "No source for code" error - try_to_run('coverage xml -i') - reports.append(read(opj(os.getcwd(), 'coverage.xml'))) + run_python_coverage(["xml", "-i"]) + reports.append(read(opj(os.getcwd(), "coverage.xml"))) reports = list(filter(bool, reports)) assert len(reports) > 0, "No coverage report found" # Storing Environment # ------------------- - env = '' + env = "" if include_env: - write('==> Appending environment variables') + write("==> Appending environment variables") for k in include_env: if k: - write(' + ' + k) + write(" + " + k) - env = '\n'.join(["%s=%s" % (k, os.getenv(k, '')) for k in include_env if k]) + '\n<<<<<< ENV' + env = ( + "\n".join(["%s=%s" % (k, os.getenv(k, "")) for k in include_env if k]) + + "\n<<<<<< ENV" + ) # join reports together - reports = '\n'.join((env, (toc or ''), '<<<<<< network', - '\n<<<<<< EOF\n'.join(reports), - '<<<<<< EOF')) + reports = "\n".join( + ( + env, + (toc or ""), + "<<<<<< network", + "\n<<<<<< EOF\n".join(reports), + "<<<<<< EOF", + ) + ) - query['package'] = "py" + VERSION - urlargs = (urlencode(dict([(k, v.strip()) for k, v in query.items() if v not in ('', None)]))).replace("+", "%20") + query["package"] = "py" + version + urlargs = ( + urlencode( + dict([(k, v.strip()) for k, v in query.items() if v not in ("", None)]) + ) + ).replace("+", "%20") - result = '' + result = "" if codecov.dump: - write('-------------------- Debug --------------------') - write(' .url ' + codecov.url) - write(' .query ' + remove_token('token=', urlargs)) + write("-------------------- Debug --------------------") + write(" .url " + codecov.url) + write(" .query " + remove_token("token=", urlargs)) write(reports) - write('-------------------- EOF --------------------') + write("-------------------- EOF --------------------") else: - write('==> Uploading') - write(' .url ' + codecov.url) - write(' .query ' + remove_token('token=', urlargs)) - - s3 = None - trys = 0 - while trys < 3: - trys += 1 + write("==> Uploading") + write(" .url " + codecov.url) + write(" .query " + remove_token("token=", urlargs)) + if codecov.verbose: + write("-------------------- Reports --------------------") + write(reports) + write("-------------------------------------------------") + + # Handle reports encoding for Python 2 and 3 + if not isinstance(reports, bytes): + reports = reports.encode("utf-8") + + # Compress reports using zlib and output with gzip header + write(" Gzipping contents..") + gzip_worker = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) + reports_gzip = gzip_worker.compress(reports) + gzip_worker.flush() + write(" Compressed contents to {0} bytes".format(len(reports_gzip))) + + success = False + if "s3" not in codecov.disable: try: - write(' Pinging Codecov...') - res = requests.post('%s/upload/v4?%s' % (codecov.url, urlargs), - verify=codecov.cacert, - headers={'Accept': 'text/plain'}) + write(" Pinging Codecov...") + res = retry_upload( + "%s/upload/v4?%s" % (codecov.url, urlargs), + requests.post, + retries=codecov.tries, + break_codes=(200, 400, 406), + verify=codecov.cacert, + headers={ + "Accept": "text/plain", + "X-Reduced-Redundancy": "false", + "X-Content-Type": "application/x-gzip", + }, + ) if res.status_code in (400, 406): raise Exception(res.text) @@ -718,56 +1165,77 @@ def main(*argv, **kwargs): res = res.text.strip().split() result, upload_url = res[0], res[1] - write(' Uploading to S3...') - s3 = requests.put(upload_url, data=reports, - headers={'Content-Type': 'text/plain', - 'x-amz-acl': 'public-read', - 'x-amz-storage-class': 'REDUCED_REDUNDANCY'}) + write(" Uploading to S3...") + s3 = retry_upload( + upload_url, + requests.put, + retries=codecov.tries, + verify=codecov.cacert, + data=reports_gzip, + headers={ + "Content-Type": "application/x-gzip", + "Content-Encoding": "gzip", + }, + ) s3.raise_for_status() assert s3.status_code == 200 - write(' ' + result) - break + write(" Uploading to S3 took %s" % s3.elapsed) + write(" " + result) + success = True except AssertionError: - write(' Direct to s3 failed. Using backup v2 endpoint.') - write(' Uploading to Codecov...') - # just incase, try traditional upload - res = requests.post('%s/upload/v2?%s' % (codecov.url, urlargs), - verify=codecov.cacert, - data='\n'.join((reports, s3.reason if s3 else '', s3.text if s3 else '')), - headers={"Accept": "text/plain"}) + write(" Direct to s3 failed. Using backup v2 endpoint.") + + # just incase, try traditional upload + if not success: + write(" Uploading to Codecov...") + res = retry_upload( + "%s/upload/v2?%s" % (codecov.url, urlargs), + requests.post, + retries=codecov.tries, + verify=codecov.cacert, + data=reports_gzip, + headers={ + "Accept": "text/plain", + "Content-Type": "application/x-gzip", + "Content-Encoding": "gzip", + }, + ) if res.status_code < 500: - write(' ' + res.text) + write(" " + res.text) res.raise_for_status() result = res.text return - write(' Retrying... in %ds' % (trys * 30)) - sleep(trys * 30) - except Exception as e: - write('Error: ' + str(e)) - if kwargs.get('debug'): + write("Error: " + str(e)) + if kwargs.get("debug"): raise - write('') + write("") # detect language if language: - write('Tip: See an example %s repo: https://github.com/codecov/example-%s' % (language, language)) + write( + "Tip: See an example %s repo: https://github.com/codecov/example-%s" + % (language, language) + ) else: - write('Tip: See all example repositories: https://github.com/codecov?query=example') + write( + "Tip: See all example repositories: https://github.com/codecov?query=example" + ) - write('Support channels:', 'green') - write(' Email: hello@codecov.io\n' - ' IRC: #codecov\n' - ' Gitter: https://gitter.im/codecov/support\n' - ' Twitter: @codecov\n') sys.exit(1 if codecov.required else 0) else: - if kwargs.get('debug'): - return dict(reports=reports, codecov=codecov, query=query, urlargs=urlargs, result=result) + if kwargs.get("debug"): + return dict( + reports=reports, + codecov=codecov, + query=query, + urlargs=urlargs, + result=result, + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/codecov/__main__.py b/codecov/__main__.py index 291e202e..cc2be956 100644 --- a/codecov/__main__.py +++ b/codecov/__main__.py @@ -1,5 +1,5 @@ import codecov -if __name__ == '__main__': +if __name__ == "__main__": codecov.main() diff --git a/codecov/__version__.py b/codecov/__version__.py new file mode 100644 index 00000000..60d08bb3 --- /dev/null +++ b/codecov/__version__.py @@ -0,0 +1,8 @@ +__author__ = "Codecov" +__author_email__ = "support@codecov.io" +__copyright__ = "Copyright 2020 Codecov" +__description__ = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" +__license__ = "Apache 2.0" +__title__ = "codecov" +__url__ = "https://github.com/codecov/codecov-python" +__version__ = "2.1.13" diff --git a/setup.py b/setup.py index 2637ab7c..1025c49b 100644 --- a/setup.py +++ b/setup.py @@ -1,37 +1,58 @@ #!/usr/bin/env python +from codecs import open +import os from setuptools import setup -import sys +from setuptools.command.install import install -version = '2.0.9' -classifiers = ["Development Status :: 5 - Production/Stable", - "Environment :: Plugins", - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: Implementation :: PyPy", - "License :: OSI Approved :: Apache Software License", - "Topic :: Software Development :: Testing"] +class PostInstallCommand(install): + def run(self): + install.run(self) + print("\n**** The codecov package has been deprecated and will be removed by the team in the future. Please update to use the uploader (https://docs.codecov.com/docs/codecov-uploader) to prevent any breakages in workflow. ****\n") -if sys.version_info >= (2, 7): - install_requires = ["requests>=2.7.9", "coverage"] -else: - install_requires = ["requests>=2.7.9", "coverage", "argparse"] -setup(name='codecov', - version=version, - description="Hosted coverage reports for Github, Bitbucket and Gitlab", - long_description=None, - classifiers=classifiers, - keywords='coverage codecov code python java scala php', - author='@codecov', - author_email='hello@codecov.io', - url='http://github.com/codecov/codecov-python', - license='http://www.apache.org/licenses/LICENSE-2.0', - packages=['codecov'], - include_package_data=True, - zip_safe=True, - install_requires=install_requires, - tests_require=["unittest2"], - entry_points={'console_scripts': ['codecov=codecov:main']}) +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Plugins", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: PyPy", + "License :: OSI Approved :: Apache Software License", + "Topic :: Software Development :: Testing", +] + +filepath = os.path.abspath(os.path.dirname(__file__)) + +about = {} +with open(os.path.join(filepath, "codecov", "__version__.py"), "r", "utf-8") as f: + exec(f.read(), about) + +setup( + name=about["__title__"], + version=about["__version__"], + description=about["__description__"], + long_description=None, + classifiers=classifiers, + keywords="coverage codecov code python java scala php", + author=about["__author__"], + author_email=about["__author_email__"], + url=about["__url__"], + license=about["__license__"], + packages=["codecov"], + include_package_data=True, + zip_safe=True, + install_requires=["requests>=2.7.9", "coverage"], + entry_points={"console_scripts": ["codecov=codecov:main"]}, + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + cmdclass={ + 'install': PostInstallCommand, + }, +) diff --git a/tests/requirements.txt b/tests/requirements.txt index 7a8e6842..7dbe1dda 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,8 +1,8 @@ -coverage +coverage==5.5 ddt mock -pytest +pytest>=4.6.0 pytest-cov funcsigs requests -unittest2 +black ; python_version >= '3.7' diff --git a/tests/test.py b/tests/test.py index 88233b0f..98a4412a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,9 +2,10 @@ import sys import pickle import itertools +import zlib from ddt import ddt, data from mock import patch, Mock -import unittest2 as unittest +import unittest import subprocess @@ -15,12 +16,12 @@ class TestUploader(unittest.TestCase): maxDiff = None here = os.path.dirname(__file__) - bowerrc = os.path.join(os.path.dirname(__file__), '../.bowerrc') - token = os.path.join(os.path.dirname(__file__), '../.token') - jacoco = os.path.join(os.path.dirname(__file__), '../jacoco.xml') - filepath = os.path.join(os.path.dirname(__file__), 'coverage.xml') - coverage = os.path.join(os.path.dirname(__file__), '../.coverage') - defaults = dict(commit='a', branch='a', token='a') + bowerrc = os.path.join(os.path.dirname(__file__), "../.bowerrc") + token = os.path.join(os.path.dirname(__file__), "../.token") + jacoco = os.path.join(os.path.dirname(__file__), "../jacoco.xml") + filepath = os.path.join(os.path.dirname(__file__), "coverage.xml") + coverage = os.path.join(os.path.dirname(__file__), "../.coverage") + defaults = dict(commit="a", branch="a", token="a") @classmethod def setUpClass(self): @@ -32,194 +33,338 @@ def tearDownClass(self): def setUp(self): # set all environ back - os.environ['CI'] = "true" - for key in ("TRAVIS", "TRAVIS_BRANCH", "TRAVIS_COMMIT", "TRAVIS_BUILD_DIR", "TRAVIS_JOB_ID", "TRAVIS_PULL_REQUEST", - "CI_NAME", "CI_BRANCH", "CI_COMMIT_ID", "SNAP_CI", "SHIPPABLE", - "CI_BUILD_NUMBER", "MAGNUM", "CI_COMMIT", "APPVEYOR_ACCOUNT_NAME", "APPVEYOR_PROJECT_SLUG", "APPVEYOR_PULL_REQUEST_NUMBER", - "SNAP_UPSTREAM_BRANCH", "SNAP_BRANCH", "SNAP_PIPELINE_COUNTER", "SNAP_PULL_REQUEST_NUMBER", "SNAP_COMMIT", "SNAP_UPSTREAM_COMMIT", "SNAP_STAGE_NAME", - "CIRCLECI", "CIRCLE_BRANCH", "CIRCLE_ARTIFACTS", "CIRCLE_SHA1", "CIRCLE_NODE_INDEX", "CIRCLE_PR_NUMBER", - "SEMAPHORE", "BRANCH_NAME", "SEMAPHORE_PROJECT_DIR", "REVISION", - "BUILDKITE", "BUILDKITE_BUILD_NUMBER", "BUILDKITE_JOB_ID", "BUILDKITE_BRANCH", "BUILDKITE_PROJECT_SLUG", "BUILDKITE_COMMIT", - "DRONE", "DRONE_BRANCH", "DRONE_BUILD_DIR", "JENKINS_URL", "TRAVIS_TAG", - "GIT_BRANCH", "GIT_COMMIT", "WORKSPACE", "BUILD_NUMBER", "CI_BUILD_URL", "SEMAPHORE_REPO_SLUG", "SEMAPHORE_CURRENT_THREAD", - "DRONE_BUILD_URL", "TRAVIS_REPO_SLUG", "CODECOV_TOKEN", "APPVEYOR", "APPVEYOR_REPO_BRANCH", - "APPVEYOR_BUILD_VERSION", "APPVEYOR_JOB_ID", "APPVEYOR_REPO_NAME", "APPVEYOR_REPO_COMMIT", "WERCKER_GIT_BRANCH", - "WERCKER_MAIN_PIPELINE_STARTED", "WERCKER_GIT_OWNER", "WERCKER_GIT_REPOSITORY", - "CI_BUILD_REF_NAME", "CI_BUILD_ID", "CI_BUILD_REPO", "CI_PROJECT_DIR", "CI_BUILD_REF", "CI_SERVER_NAME", - "ghprbActualCommit", "ghprbSourceBranch", "ghprbPullId", "WERCKER_GIT_COMMIT", "CHANGE_ID"): + os.environ["CI"] = "true" + for key in ( + "TRAVIS", + "TRAVIS_BRANCH", + "TRAVIS_COMMIT", + "TRAVIS_BUILD_DIR", + "TRAVIS_JOB_ID", + "TRAVIS_PULL_REQUEST", + "CI_NAME", + "CI_BRANCH", + "CI_COMMIT_ID", + "SHIPPABLE", + "CI_BUILD_NUMBER", + "MAGNUM", + "CI_COMMIT", + "APPVEYOR_ACCOUNT_NAME", + "APPVEYOR_PROJECT_SLUG", + "APPVEYOR_PULL_REQUEST_NUMBER", + "CIRCLECI", + "CIRCLE_BRANCH", + "CIRCLE_ARTIFACTS", + "CIRCLE_SHA1", + "CIRCLE_NODE_INDEX", + "CIRCLE_PR_NUMBER", + "SEMAPHORE", + "BRANCH_NAME", + "SEMAPHORE_PROJECT_DIR", + "REVISION", + "BUILDKITE", + "BUILDKITE_BUILD_NUMBER", + "BUILDKITE_JOB_ID", + "BUILDKITE_BRANCH", + "BUILDKITE_PROJECT_SLUG", + "BUILDKITE_COMMIT", + "DRONE", + "DRONE_BRANCH", + "DRONE_BUILD_DIR", + "JENKINS_URL", + "TRAVIS_TAG", + "GIT_BRANCH", + "GIT_COMMIT", + "WORKSPACE", + "BUILD_NUMBER", + "CI_BUILD_URL", + "SEMAPHORE_REPO_SLUG", + "SEMAPHORE_CURRENT_THREAD", + "DRONE_BUILD_LINK", + "TRAVIS_REPO_SLUG", + "CODECOV_TOKEN", + "CODECOV_NAME", + "APPVEYOR", + "APPVEYOR_REPO_BRANCH", + "APPVEYOR_BUILD_VERSION", + "APPVEYOR_JOB_ID", + "APPVEYOR_REPO_NAME", + "APPVEYOR_REPO_COMMIT", + "WERCKER_GIT_BRANCH", + "WERCKER_MAIN_PIPELINE_STARTED", + "WERCKER_GIT_OWNER", + "WERCKER_GIT_REPOSITORY", + "CI_BUILD_REF_NAME", + "CI_BUILD_ID", + "CI_BUILD_REPO", + "CI_PROJECT_DIR", + "CI_BUILD_REF", + "CI_SERVER_NAME", + "CI_COMMIT_REF_NAME", + "CI_JOB_ID", + "CI_REPOSITORY_URL", + "CI_COMMIT_SHA", + "ghprbActualCommit", + "ghprbSourceBranch", + "ghprbPullId", + "WERCKER_GIT_COMMIT", + "CHANGE_ID", + ): os.environ[key] = "" def tearDown(self): self.delete(self.filepath, self.coverage, self.jacoco, self.bowerrc) - self.delete('hello', 'hello.c', 'hello.gcda', 'hello.c.gcov', 'hello.gcno') + self.delete("hello", "hello.c", "hello.gcda", "hello.c.gcov", "hello.gcno") def set_env(self, **kwargs): for key in kwargs: os.environ[key] = str(kwargs[key]) def run_cli(self, dump=True, *args, **kwargs): - inline = list(itertools.chain(*[['--%s' % key, str(value)] for key, value in kwargs.items() if value])) + inline = list( + itertools.chain( + *[["--%s" % key, str(value)] for key, value in kwargs.items() if value] + ) + ) if dump: - inline.append('--dump') + inline.append("--dump") inline.extend(args) return codecov.main(*inline, debug=True) def fake_report(self): - with open(self.filepath, 'w+') as f: - f.write('__data__') + with open(self.filepath, "w+") as f: + f.write("__data__") def delete(self, *paths): for path in paths: if os.path.exists(path): os.remove(path) - path = os.path.join(os.path.dirname(__file__), '../', path) + path = os.path.join(os.path.dirname(__file__), "../", path) if os.path.exists(path): os.remove(path) - @data('vendor', 'node_modules', 'js/generated/coverage', '__pycache__', 'coverage/instrumented', - 'build/lib', 'htmlcov', '.egg-info', '.git', '.tox', 'venv', '.venv-python-2.7') + @data( + "vendor", + "node_modules", + "js/generated/coverage", + "__pycache__", + "coverage/instrumented", + "build/lib", + "htmlcov", + ".egg-info", + ".git", + ".tox", + "venv", + ".venv-python-2.7", + ) def test_ignored_path(self, path): - self.assertTrue(bool(codecov.ignored_path('/home/ubuntu/' + path)), path + ' should be ignored') - self.assertTrue(bool(codecov.ignored_path('/home/ubuntu/' + path + '/more paths')), path + ' should be ignored') - - @data('coverage.xml', 'jacoco.xml', 'jacocoTestResults.xml', 'coverage.txt', - 'gcov.lst', 'cov.gcov', 'info.lcov', 'clover.xml', 'cobertura.xml', - 'luacov.report.out', 'gcov.info', 'nosetests.xml') + self.assertTrue( + bool(codecov.ignored_path("/home/ubuntu/" + path)), + path + " should be ignored", + ) + self.assertTrue( + bool(codecov.ignored_path("/home/ubuntu/" + path + "/more paths")), + path + " should be ignored", + ) + + @data( + "coverage.xml", + "jacoco.xml", + "jacocoTestResults.xml", + "coverage.txt", + "gcov.lst", + "cov.gcov", + "info.lcov", + "clover.xml", + "cobertura.xml", + "luacov.report.out", + "gcov.info", + "nosetests.xml", + ) def test_is_report(self, path): - self.assertFalse(bool(codecov.ignored_report('/home/file/' + path)), path + ' should not be ignored') - self.assertTrue(bool(codecov.is_report('/home/file/' + path)), path + ' should be a report') - - @data('.coverage.worker10', 'coverage.jade', 'include.lst', 'inputFiles.lst', - 'createdFiles.lst', 'scoverage.measurements.blackandwhite.xml', 'test_hello_coverage.txt', - 'conftest_blackwhite.c.gcov') + self.assertFalse( + bool(codecov.ignored_report("/home/file/" + path)), + path + " should not be ignored", + ) + self.assertTrue( + bool(codecov.is_report("/home/file/" + path)), path + " should be a report" + ) + + @data( + ".coverage.worker10", + "coverage.jade", + "include.lst", + "inputFiles.lst", + "createdFiles.lst", + "scoverage.measurements.blackandwhite.xml", + "test_hello_coverage.txt", + "conftest_blackwhite.c.gcov", + ) def test_ignore_report(self, path): - self.assertTrue(bool(codecov.ignored_report('/home/file/' + path)), path + ' should be ignored') + self.assertTrue( + bool(codecov.ignored_report("/home/file/" + path)), + path + " should be ignored", + ) def test_command(self): try: - self.run_cli(True, '--help') + self.run_cli(True, "--help") except SystemExit as e: - self.assertEqual(str(e), '0') + self.assertEqual(str(e), "0") else: raise Exception("help not shown") def test_exits_0(self): try: - sys.argv = [''] + sys.argv = [""] codecov.main() except SystemExit as e: - self.assertEqual(str(e), '0') + self.assertEqual(str(e), "0") else: raise Exception("did not exit") def test_exits_1(self): try: - sys.argv = [''] - codecov.main('--required') + sys.argv = [""] + codecov.main("--required") except SystemExit as e: - self.assertEqual(str(e), '1') + self.assertEqual(str(e), "1") else: raise Exception("did not exit") + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) def test_returns_none(self): - with patch('requests.post') as post: - with patch('requests.put') as put: - post.return_value = Mock(status_code=200, text='target\ns3') + with patch("requests.post") as post: + with patch("requests.put") as put: + post.return_value = Mock(status_code=200, text="target\ns3") put.return_value = Mock(status_code=200) - with open(self.filepath, 'w+') as f: - f.write('coverage data') - sys.argv = ['', '--commit=8ed84d96bc225deff66605486180cd555366806b', - '--branch=master', - '--token=473c8c5b-10ee-4d83-86c6-bfd72a185a27'] + with open(self.filepath, "w+") as f: + f.write("coverage data") + sys.argv = [ + "", + "--commit=8ed84d96bc225deff66605486180cd555366806b", + "--branch=master", + "--token=473c8c5b-10ee-4d83-86c6-bfd72a185a27", + ] self.assertEqual(codecov.main(), None) assert post.called and put.called + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) def test_send(self): - with patch('requests.post') as post: - with patch('requests.put') as put: - post.return_value = Mock(status_code=200, text='target\ns3') + with patch("requests.post") as post: + with patch("requests.put") as put: + post.return_value = Mock(status_code=200, text="target\ns3") put.return_value = Mock(status_code=200) - with open(self.filepath, 'w+') as f: - f.write('coverage data') - res = self.run_cli(False, commit='a'*40, branch='master', token='') - self.assertEqual(res['result'].strip(), 'target') - assert 'https://codecov.io/upload/v4?' in post.call_args[0][0] - assert 'commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' in post.call_args[0][0] - assert 'token=%3Ctoken%3E' in post.call_args[0][0] - assert 'branch=master' in post.call_args[0][0] - assert 'tests/test.py' in put.call_args[1]['data'] + with open(self.filepath, "w+") as f: + f.write("coverage data") + res = self.run_cli( + False, commit="a" * 40, branch="master", token="" + ) + self.assertEqual(res["result"].strip(), "target") + assert "https://codecov.io/upload/v4?" in post.call_args[0][0] + assert ( + "commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + in post.call_args[0][0] + ) + assert "token=%3Ctoken%3E" in post.call_args[0][0] + assert "branch=master" in post.call_args[0][0] + gzip_worker = zlib.decompressobj(zlib.MAX_WBITS | 16) + reports = ( + gzip_worker.decompress(put.call_args[1]["data"]) + + gzip_worker.flush() + ) + assert "tests/test.py".encode("utf-8") in reports def test_send_error(self): - with patch('requests.post') as post: - post.return_value = Mock(status_code=400, text='error') - with open(self.filepath, 'w+') as f: - f.write('coverage data') + with patch("requests.post") as post: + post.return_value = Mock(status_code=400, text="error") + with open(self.filepath, "w+") as f: + f.write("coverage data") try: - self.run_cli(False, token='not-a-token', commit='a'*40, branch='master') + self.run_cli( + False, token="not-a-token", commit="a" * 40, branch="master" + ) except Exception: pass else: - raise Exception('400 never raised') - - @data((dict(commit='sha'), 'Missing repository upload token'), ) - def test_require_branch(self, dd): - (kwargs, reason) = dd - # this is so we dont get branch for local git - self.set_env(JENKINS_URL='hello') - try: - self.run_cli(**kwargs) - except AssertionError as e: - self.assertEqual(str(e), reason) - else: - raise Exception("Did not raise AssertionError") + raise Exception("400 never raised") + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) def test_read_token_file(self): - with open(self.token, 'w+') as f: - f.write('a') - with open(self.filepath, 'w+') as f: - f.write('coverage data') - res = self.run_cli(token='@'+self.token, commit='a', branch='b') - self.assertIn('token=a', res['urlargs']) + with open(self.token, "w+") as f: + f.write("a") + with open(self.filepath, "w+") as f: + f.write("coverage data") + res = self.run_cli(token="@" + self.token, commit="a", branch="b") + self.assertIn("token=a", res["urlargs"]) def test_bowerrc(self): - with open(self.bowerrc, 'w+') as f: + with open(self.bowerrc, "w+") as f: f.write('{"directory": "tests"}') - with open(self.filepath, 'w+') as f: - f.write('coverage data') + with open(self.filepath, "w+") as f: + f.write("coverage data") try: self.run_cli(**self.defaults) except AssertionError as e: - self.assertEqual(str(e), "No coverage report found") + self.assertEqual(str(e), "No coverage report found") else: raise Exception("Did not raise AssertionError") def test_disable_search(self): self.fake_report() try: - self.run_cli(disable='search', token='a', branch='b', commit='c') + self.run_cli(disable="search", token="a", branch="b", commit="c") except AssertionError as e: self.assertEqual(str(e), "No coverage report found") else: raise Exception("Did not raise AssertionError") + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) + def test_prefix(self): + self.fake_report() + res = self.run_cli( + prefix="/foo/bar/", dump=True, token="a", branch="b", commit="c" + ) + assert "\nfoo/bar/.gitignore" in res["reports"] + def write_c(self): - c = '\n'.join(('#include ', - 'static int t = 1;' - 'int main()', '{', - 'if (t)', 'printf("on this line\\n");', - 'else', 'printf("but not here\\n");', - 'return 0;', '}')) - with open(os.path.join(os.path.dirname(__file__), '../hello.c'), 'w+') as f: + c = "\n".join( + ( + "#include ", + "static int t = 1;" "int main()", + "{", + "if (t)", + 'printf("on this line\\n");', + "else", + 'printf("but not here\\n");', + "return 0;", + "}", + ) + ) + with open(os.path.join(os.path.dirname(__file__), "../hello.c"), "w+") as f: f.write(c) - codecov.try_to_run('clang -coverage -O0 hello.c -o hello && ./hello') + codecov.try_to_run( + ["clang", "-coverage", "-O0", "hello.c", "-o", "hello", "&&", "./hello"] + ) def test_disable_gcov(self): - if self._env.get('TRAVIS') == 'true': + if self._env.get("TRAVIS") == "true": self.write_c() try: - self.run_cli(disable='gcov', token='a', branch='b', commit='c') + self.run_cli(disable="gcov", token="a", branch="b", commit="c") except AssertionError as e: - self.assertEqual(os.path.exists('hello.c.gcov'), False) + self.assertEqual(os.path.exists("hello.c.gcov"), False) self.assertEqual(str(e), "No coverage report found") else: raise Exception("Did not raise AssertionError") @@ -227,74 +372,91 @@ def test_disable_gcov(self): self.skipTest("Skipped, works on Travis only.") def test_gcov(self): - if self._env.get('TRAVIS') == 'true': - self.write_c() - output = self.run_cli(token='a', branch='b', commit='c') - self.assertEqual(os.path.exists('hello.c.gcov'), True) - report = output['reports'].split('<<<<<< network\n')[1].splitlines() - self.assertIn('hello.c.gcov', report[0]) - else: - self.skipTest("Skipped, works on Travis only.") + self.skipTest("Need to fix this test...") + # if self._env.get('TRAVIS') == 'true': + # self.write_c() + # output = self.run_cli(token='a', branch='b', commit='c') + # self.assertEqual(os.path.exists('hello.c.gcov'), True) + # report = output['reports'].split('<<<<<< network\n')[1].splitlines() + # self.assertIn('hello.c.gcov', report[0]) + # else: + # self.skipTest("Skipped, works on Travis only.") def test_disable_detect(self): - self.set_env(JENKINS_URL='a', GIT_BRANCH='b', GIT_COMMIT='c', CODECOV_TOKEN='d') + self.set_env( + JENKINS_URL="a", + GIT_BRANCH="b", + GIT_COMMIT="c", + CODECOV_TOKEN="d", + CODECOV_NAME="e", + ) self.fake_report() try: - self.run_cli(disable='detect') + self.run_cli(disable="detect") except AssertionError as e: - self.assertEqual(str(e), "Commit sha is missing. Please specify via --commit=:sha") + self.assertEqual( + str(e), "Commit sha is missing. Please specify via --commit=:sha" + ) else: raise Exception("Did not raise AssertionError") + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) def test_bowerrc_none(self): - with open(self.bowerrc, 'w+') as f: + with open(self.bowerrc, "w+") as f: f.write('{"other_key": "tests"}') - with open(self.filepath, 'w+') as f: - f.write('coverage data') + with open(self.filepath, "w+") as f: + f.write("coverage data") res = self.run_cli(**self.defaults) - self.assertIn('tests/test.py', res['reports']) + self.assertIn("tests/test.py", res["reports"]) + @unittest.skipIf( + os.getenv("CI") == "True" and os.getenv("APPVEYOR") == "True", + "Skip AppVeyor CI test", + ) def test_discovers(self): - with open(self.jacoco, 'w+') as f: - f.write('') - with open(self.filepath, 'w+') as f: - f.write('coverage data') + with open(self.jacoco, "w+") as f: + f.write("") + with open(self.filepath, "w+") as f: + f.write("coverage data") res = self.run_cli(**self.defaults) - self.assertIn('coverage.xml', res['reports']) - self.assertIn('coverage data', res['reports']) - self.assertIn('jacoco.xml', res['reports']) - self.assertIn('', res['reports']) + self.assertIn("coverage.xml", res["reports"]) + self.assertIn("coverage data", res["reports"]) + self.assertIn("jacoco.xml", res["reports"]) + self.assertIn("", res["reports"]) def test_not_jacoco(self): - with open(self.filepath, 'w+') as f: - f.write('') - res = self.run_cli(file='tests/coverage.xml', **self.defaults) - res = res['reports'].split('<<<<<< network\n')[1].splitlines() - self.assertEqual(res[0], '# path=tests/coverage.xml') - self.assertEqual(res[1], '') + with open(self.filepath, "w+") as f: + f.write("") + res = self.run_cli(file="tests/coverage.xml", **self.defaults) + res = res["reports"].split("<<<<<< network\n")[1].splitlines() + self.assertEqual(res[0], "# path=tests/coverage.xml") + self.assertEqual(res[1], "") def test_run_coverage(self): - self.skipTest('Not sure how to pull off atm') - with open(self.coverage, 'w+') as f: + self.skipTest("Not sure how to pull off atm") + with open(self.coverage, "w+") as f: f.write(pickle.dumps()) res = self.run_cli(**self.defaults) - self.assertIn('', res['reports']) + self.assertIn('', res["reports"]) def test_run_coverage_fails(self): - with open(self.coverage, 'w+') as f: - f.write('bad data') + with open(self.coverage, "w+") as f: + f.write("bad data") try: self.run_cli(**self.defaults) except AssertionError as e: - self.assertEqual(str(e), 'No coverage report found') + self.assertEqual(str(e), "No coverage report found") else: raise Exception("Did not raise AssertionError") def test_include_env(self): - self.set_env(HELLO='WORLD') + self.set_env(HELLO="WORLD") self.fake_report() - res = self.run_cli(env='HELLO', file=self.filepath, **self.defaults) - self.assertIn('HELLO=WORLD', res['reports']) + res = self.run_cli(env="HELLO", file=self.filepath, **self.defaults) + self.assertIn("HELLO=WORLD", res["reports"]) def test_none_found(self): try: @@ -304,271 +466,465 @@ def test_none_found(self): else: raise Exception("Did not raise AssertionError") + def test_sanitize_arg(self): + self.assertEqual( + codecov.sanitize_arg("", "& echo test > vuln1.txt"), + " echo test > vuln1.txt", + ) + + @unittest.skipUnless(os.getenv("JENKINS_URL"), "Skip Jenkins CI test") def test_ci_jenkins(self): - self.set_env(BUILD_URL='https://....', - JENKINS_URL='https://....', - GIT_BRANCH='master', - GIT_COMMIT='c739768fcac68144a3a6d82305b9c4106934d31a', - BUILD_NUMBER='41', - CODECOV_TOKEN='token') + self.set_env( + BUILD_URL="https://....", + JENKINS_URL="https://....", + GIT_BRANCH="master", + GIT_COMMIT="c739768fcac68144a3a6d82305b9c4106934d31a", + BUILD_NUMBER="41", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'jenkins') - self.assertEqual(res['query']['commit'], 'c739768fcac68144a3a6d82305b9c4106934d31a') - self.assertEqual(res['query']['build'], '41') - self.assertEqual(res['query']['build_url'], 'https://....') - self.assertEqual(res['query']['pr'], '') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "jenkins") + self.assertEqual( + res["query"]["commit"], "c739768fcac68144a3a6d82305b9c4106934d31a" + ) + self.assertEqual(res["query"]["build"], "41") + self.assertEqual(res["query"]["build_url"], "https://....") + self.assertEqual(res["query"]["pr"], "") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless(os.getenv("JENKINS_URL"), "Skip Jenkins CI test") def test_ci_jenkins_env(self): - self.set_env(JENKINS_URL='https://....', - BUILD_URL='https://....', - ghprbSourceBranch='master', - ghprbActualCommit='c739768fcac68144a3a6d82305b9c4106934d31a', - ghprbPullId='1', - BUILD_NUMBER='41', - CODECOV_TOKEN='token') + self.set_env( + JENKINS_URL="https://....", + BUILD_URL="https://....", + ghprbSourceBranch="master", + ghprbActualCommit="c739768fcac68144a3a6d82305b9c4106934d31a", + ghprbPullId="1", + BUILD_NUMBER="41", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'jenkins') - self.assertEqual(res['query']['commit'], 'c739768fcac68144a3a6d82305b9c4106934d31a') - self.assertEqual(res['query']['build'], '41') - self.assertEqual(res['query']['build_url'], 'https://....') - self.assertEqual(res['query']['pr'], '1') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "jenkins") + self.assertEqual( + res["query"]["commit"], "c739768fcac68144a3a6d82305b9c4106934d31a" + ) + self.assertEqual(res["query"]["build"], "41") + self.assertEqual(res["query"]["build_url"], "https://....") + self.assertEqual(res["query"]["pr"], "1") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless(os.getenv("JENKINS_URL"), "Skip Jenkins CI test") def test_ci_jenkins_blue_ocean(self): - self.set_env(JENKINS_URL='https://....', - BUILD_URL='https://....', - BRANCH_NAME='master', - CHANGE_ID='1', - BUILD_NUMBER='41', - CODECOV_TOKEN='token') + self.set_env( + JENKINS_URL="https://....", + BUILD_URL="https://....", + BRANCH_NAME="master", + CHANGE_ID="1", + BUILD_NUMBER="41", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'jenkins') - self.assertEqual(res['query']['commit'], codecov.check_output(("git", "rev-parse", "HEAD"))) - self.assertEqual(res['query']['build'], '41') - self.assertEqual(res['query']['build_url'], 'https://....') - self.assertEqual(res['query']['pr'], '1') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "jenkins") + self.assertEqual( + res["query"]["commit"], codecov.check_output(("git", "rev-parse", "HEAD")) + ) + self.assertEqual(res["query"]["build"], "41") + self.assertEqual(res["query"]["build_url"], "https://....") + self.assertEqual(res["query"]["pr"], "1") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" + and os.getenv("TRAVIS") == "true" + and os.getenv("SHIPPABLE") != "true", + "Skip Travis CI test", + ) def test_ci_travis(self): - self.set_env(TRAVIS="true", - TRAVIS_BRANCH="master", - TRAVIS_COMMIT="c739768fcac68144a3a6d82305b9c4106934d31a", - TRAVIS_REPO_SLUG='owner/repo', - TRAVIS_JOB_ID="33116958", - TRAVIS_TAG="v1.1.1", - TRAVIS_JOB_NUMBER="4.1") + self.set_env( + TRAVIS="true", + TRAVIS_BRANCH="master", + TRAVIS_COMMIT="c739768fcac68144a3a6d82305b9c4106934d31a", + TRAVIS_REPO_SLUG="owner/repo", + TRAVIS_JOB_ID="33116958", + TRAVIS_TAG="v1.1.1", + TRAVIS_JOB_NUMBER="4.1", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'travis') - self.assertEqual(res['query']['commit'], 'c739768fcac68144a3a6d82305b9c4106934d31a') - self.assertEqual(res['query']['build'], '4.1') - self.assertEqual(res['query']['pr'], '') - self.assertEqual(res['query']['tag'], 'v1.1.1') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, '') - + self.assertEqual(res["query"]["service"], "travis") + self.assertEqual( + res["query"]["commit"], "c739768fcac68144a3a6d82305b9c4106934d31a" + ) + self.assertEqual(res["query"]["build"], "4.1") + self.assertEqual(res["query"]["pr"], "") + self.assertEqual(res["query"]["tag"], "v1.1.1") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("CI_NAME") == "codeship", + "Skip Codeship CI test", + ) def test_ci_codeship(self): - self.set_env(CI_NAME='codeship', - CI_BRANCH='master', - CI_BUILD_NUMBER='20', - CI_BUILD_URL='https://codeship.io/build/1', - CI_COMMIT_ID='743b04806ea677403aa2ff26c6bdeb85005de658', - CODECOV_TOKEN='token') + self.set_env( + CI_NAME="codeship", + CI_BRANCH="master", + CI_BUILD_NUMBER="20", + CI_BUILD_URL="https://codeship.io/build/1", + CI_COMMIT_ID="743b04806ea677403aa2ff26c6bdeb85005de658", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'codeship') - self.assertEqual(res['query']['commit'], '743b04806ea677403aa2ff26c6bdeb85005de658') - self.assertEqual(res['query']['build'], '20') - self.assertEqual(res['query']['build_url'], 'https://codeship.io/build/1') - self.assertEqual(res['query']['pr'], '') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "codeship") + self.assertEqual( + res["query"]["commit"], "743b04806ea677403aa2ff26c6bdeb85005de658" + ) + self.assertEqual(res["query"]["build"], "20") + self.assertEqual(res["query"]["build_url"], "https://codeship.io/build/1") + self.assertEqual(res["query"]["pr"], "") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("CIRCLECI") == "true", + "Skip Circle CI test", + ) def test_ci_circleci(self): - self.set_env(CIRCLECI='true', - CIRCLE_BUILD_NUM='57', - CIRCLE_NODE_INDEX='1', - CIRCLE_PR_NUMBER='1', - CIRCLE_BRANCH='master', - CIRCLE_PROJECT_USERNAME='owner', - CIRCLE_PROJECT_REPONAME='repo', - CIRCLE_SHA1='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b') + self.set_env( + CIRCLECI="true", + CIRCLE_BUILD_NUM="57", + CIRCLE_NODE_INDEX="1", + CIRCLE_PR_NUMBER="1", + CIRCLE_BRANCH="master", + CIRCLE_PROJECT_USERNAME="owner", + CIRCLE_PROJECT_REPONAME="repo", + CIRCLE_SHA1="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'circleci') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '57.1') - self.assertEqual(res['query']['pr'], '1') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['branch'], 'master') - + self.assertEqual(res["query"]["service"], "circleci") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "57.1") + self.assertEqual(res["query"]["pr"], "1") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["branch"], "master") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("BUILDKITE") == "true", + "Skip BuildKit CI test", + ) def test_ci_buildkite(self): - self.set_env(CI='true', - BUILDKITE='true', - BUILDKITE_BUILD_NUMBER='57', - BUILDKITE_JOB_ID='1', - BUILDKITE_BRANCH='master', - BUILDKITE_PROJECT_SLUG='owner/repo', - BUILDKITE_COMMIT='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - CODECOV_TOKEN='token') + self.set_env( + CI="true", + BUILDKITE="true", + BUILDKITE_BUILD_NUMBER="57", + BUILDKITE_JOB_ID="1", + BUILDKITE_BRANCH="master", + BUILDKITE_PROJECT_SLUG="owner/repo", + BUILDKITE_COMMIT="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'buildkite') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '57.1') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['branch'], 'master') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "buildkite") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "57.1") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("SEMAPHORE") == "true", + "Skip Semaphore CI test", + ) def test_ci_semaphore(self): - self.set_env(SEMAPHORE='true', - BRANCH_NAME='master', - SEMAPHORE_BUILD_NUMBER='10', - SEMAPHORE_CURRENT_THREAD='1', - SEMAPHORE_REPO_SLUG='owner/repo', - REVISION='743b04806ea677403aa2ff26c6bdeb85005de658', - CODECOV_TOKEN='token') - self.fake_report() - res = self.run_cli() - self.assertEqual(res['query']['service'], 'semaphore') - self.assertEqual(res['query']['commit'], '743b04806ea677403aa2ff26c6bdeb85005de658') - self.assertEqual(res['query']['build'], '10.1') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['branch'], 'master') - - def test_ci_snap(self): - self.set_env(SNAP_BRANCH='master', - SNAP_CI='true', - SNAP_STAGE_NAME='default', - SNAP_PIPELINE_COUNTER='10', - SNAP_PULL_REQUEST_NUMBER='10', - SNAP_COMMIT='743b04806ea677403aa2ff26c6bdeb85005de658', - CODECOV_TOKEN='token') + self.set_env( + SEMAPHORE="true", + BRANCH_NAME="master", + SEMAPHORE_BUILD_NUMBER="10", + SEMAPHORE_CURRENT_THREAD="1", + SEMAPHORE_REPO_SLUG="owner/repo", + REVISION="743b04806ea677403aa2ff26c6bdeb85005de658", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'snap') - self.assertEqual(res['query']['commit'], '743b04806ea677403aa2ff26c6bdeb85005de658') - self.assertEqual(res['query']['build'], '10') - self.assertEqual(res['query']['pr'], '10') - self.assertEqual(res['query']['job'], 'default') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "semaphore") + self.assertEqual( + res["query"]["commit"], "743b04806ea677403aa2ff26c6bdeb85005de658" + ) + self.assertEqual(res["query"]["build"], "10.1") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["branch"], "master") + + @unittest.skipUnless( + os.getenv("CI") == "drone" and os.getenv("DRONE") == "true", + "Skip Drone CI test", + ) def test_ci_drone(self): - self.set_env(DRONE='true', - DRONE_BUILD_NUMBER='10', - DRONE_BRANCH='master', - DRONE_BUILD_URL='https://drone.io/github/builds/1', - CODECOV_TOKEN='token') + self.set_env( + CI="drone", + DRONE="true", + DRONE_BUILD_NUMBER="10", + DRONE_BRANCH="master", + DRONE_BUILD_LINK="https://drone.io/github/builds/1", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'drone.io') - self.assertEqual(res['query']['commit'], codecov.check_output(("git", "rev-parse", "HEAD"))) - self.assertEqual(res['query']['build'], '10') - self.assertEqual(res['query']['build_url'], 'https://drone.io/github/builds/1') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "drone.io") + self.assertEqual( + res["query"]["commit"], codecov.check_output(("git", "rev-parse", "HEAD")) + ) + self.assertEqual(res["query"]["build"], "10") + self.assertEqual(res["query"]["build_url"], "https://drone.io/github/builds/1") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless(os.getenv("SHIPPABLE") == "true", "Skip Shippable CI test") def test_ci_shippable(self): - self.set_env(SHIPPABLE='true', - BUILD_NUMBER='10', - REPO_NAME='owner/repo', - BRANCH='master', - BUILD_URL='https://shippable.com/...', - COMMIT='743b04806ea677403aa2ff26c6bdeb85005de658', - CODECOV_TOKEN='token') + self.set_env( + SHIPPABLE="true", + BUILD_NUMBER="10", + REPO_NAME="owner/repo", + BRANCH="master", + BUILD_URL="https://shippable.com/...", + COMMIT="743b04806ea677403aa2ff26c6bdeb85005de658", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'shippable') - self.assertEqual(res['query']['commit'], '743b04806ea677403aa2ff26c6bdeb85005de658') - self.assertEqual(res['query']['build'], '10') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['build_url'], 'https://shippable.com/...') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "shippable") + self.assertEqual( + res["query"]["commit"], "743b04806ea677403aa2ff26c6bdeb85005de658" + ) + self.assertEqual(res["query"]["build"], "10") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["build_url"], "https://shippable.com/...") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + # @unittest.skipUnless(os.getenv('CI') == "True" and os.getenv('APPVEYOR') == 'True', 'Skip AppVeyor CI test') + @unittest.skip("Skip AppVeyor test") def test_ci_appveyor(self): - self.set_env(APPVEYOR='True', - CI='True', - APPVEYOR_JOB_ID='9r2qufuu8', - APPVEYOR_BUILD_VERSION='1.2.3', - APPVEYOR_ACCOUNT_NAME='owner', - APPVEYOR_PROJECT_SLUG='repo', - APPVEYOR_PULL_REQUEST_NUMBER='1', - APPVEYOR_REPO_BRANCH='master', - APPVEYOR_REPO_NAME='owner/repo', - APPVEYOR_REPO_COMMIT='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - CODECOV_TOKEN='token') + self.set_env( + APPVEYOR="True", + CI="True", + APPVEYOR_JOB_ID="9r2qufuu8", + APPVEYOR_BUILD_VERSION="1.2.3", + APPVEYOR_ACCOUNT_NAME="owner", + APPVEYOR_PROJECT_SLUG="repo", + APPVEYOR_PULL_REQUEST_NUMBER="1", + APPVEYOR_REPO_BRANCH="master", + APPVEYOR_REPO_NAME="owner/repo", + APPVEYOR_REPO_COMMIT="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli(file=self.filepath) - self.assertEqual(res['query']['service'], 'appveyor') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['job'], 'owner/repo/1.2.3') - self.assertEqual(res['query']['build'], '9r2qufuu8') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['query']['pr'], '1') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "appveyor") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["job"], "owner/repo/1.2.3") + self.assertEqual(res["query"]["build"], "9r2qufuu8") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["query"]["pr"], "1") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("WERCKER_GIT_BRANCH"), + "Skip Wercker CI test", + ) def test_ci_wercker(self): - self.set_env(WERCKER_GIT_BRANCH='master', - WERCKER_MAIN_PIPELINE_STARTED='1399372237', - WERCKER_GIT_OWNER='owner', - WERCKER_GIT_REPOSITORY='repo', - WERCKER_GIT_COMMIT='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - CODECOV_TOKEN='token') + self.set_env( + WERCKER_GIT_BRANCH="master", + WERCKER_MAIN_PIPELINE_STARTED="1399372237", + WERCKER_GIT_OWNER="owner", + WERCKER_GIT_REPOSITORY="repo", + WERCKER_GIT_COMMIT="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'wercker') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '1399372237') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "wercker") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "1399372237") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("MAGNUM") == "true", + "Skip Magnum CI test", + ) def test_ci_magnum(self): - self.set_env(CI_BRANCH='master', - CI_BUILD_NUMBER='1399372237', - MAGNUM='true', - CI='true', - CI_COMMIT='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - CODECOV_TOKEN='token') + self.set_env( + CI_BRANCH="master", + CI_BUILD_NUMBER="1399372237", + MAGNUM="true", + CI="true", + CI_COMMIT="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'magnum') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '1399372237') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "magnum") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "1399372237") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI_SERVER_NAME", "").startswith("GitLab"), "Skip GitLab CI test" + ) + def test_ci_gitlab_pre9(self): + self.set_env( + CI_BUILD_REF_NAME="master", + CI_BUILD_ID="1399372237", + CI_BUILD_REPO="https://gitlab.com/owner/repo.git", + CI_SERVER_NAME="GitLab CI", + CI_BUILD_REF="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + HOME="/", + CI_PROJECT_DIR=os.getcwd().strip("/"), + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) + self.fake_report() + res = self.run_cli() + self.assertEqual(res["query"]["service"], "gitlab") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "1399372237") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI_SERVER_NAME", "").startswith("GitLab"), "Skip GitLab CI test" + ) def test_ci_gitlab(self): - self.set_env(CI_BUILD_REF_NAME='master', - CI_BUILD_ID='1399372237', - CI_BUILD_REPO='https://gitlab.com/owner/repo.git', - CI_SERVER_NAME='GitLab CI', - CI_BUILD_REF='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - HOME='/', - CI_PROJECT_DIR=os.getcwd().strip('/'), - CODECOV_TOKEN='token') + self.set_env( + CI_COMMIT_REF_NAME="master", + CI_JOB_ID="1399372237", + CI_REPOSITORY_URL="https://gitlab.com/owner/repo.git", + CI_SERVER_NAME="GitLab CI", + CI_COMMIT_SHA="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + HOME="/", + CI_PROJECT_DIR=os.getcwd().strip("/"), + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) self.fake_report() res = self.run_cli() - self.assertEqual(res['query']['service'], 'gitlab') - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '1399372237') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['codecov'].token, 'token') - + self.assertEqual(res["query"]["service"], "gitlab") + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "1399372237") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("GITHUB_ACTION"), + "Skip GitHub Actions CI test", + ) + def test_ci_github(self): + self.set_env( + HOME="/", + CODECOV_TOKEN="token", + CODECOV_NAME="name", + ) + self.fake_report() + res = self.run_cli() + self.assertEqual(res["query"]["service"], "github-actions") + self.assertEqual(res["query"]["commit"], os.getenv("GITHUB_SHA")) + self.assertEqual(res["query"]["build"], os.getenv("GITHUB_RUN_ID")) + self.assertEqual(res["query"]["slug"], os.getenv("GITHUB_REPOSITORY")) + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") + + @unittest.skipUnless( + os.getenv("CI") == "true" and os.getenv("CIRRUS_CI") == "true", + "Skip Cirrus CI", + ) + def test_ci_cirrus(self): + # The data used in this test follows the test case data in + # https://github.com/codecov/codecov-bash/pull/127 + # Discussion about using codecov without token for Cirrus CI can be seen in: + # https://community.codecov.com/t/add-support-of-uploading-from-cirrus-ci-without-token/1028/36 + self.set_env( + HOME="/", + CIRRUS_CI="true", + CIRRUS_REPO_FULL_NAME="codecov/ci-repo", + CIRRUS_BRANCH="master", + CIRRUS_PR="1", + CIRRUS_CHANGE_IN_REPO="180c0d097354fc1a451da8a3be5aba255f2ffd9f", + CIRRUS_BUILD_ID="777", + CIRRUS_TASK_ID="239", + CIRRUS_TASK_NAME="test" + ) + self.fake_report() + res = self.run_cli() + self.assertEqual(res["query"]["service"], "cirrus-ci") + self.assertEqual(res["query"]["slug"], "codecov/ci-repo") + self.assertEqual(res["query"]["branch"], "master") + self.assertEqual(res["query"]["pr"], "1") + self.assertEqual(res["query"]["commit"], os.getenv("CIRRUS_CHANGE_IN_REPO")) + self.assertEqual(res["query"]["build"], "777") + self.assertEqual(res["query"]["build_url"], "https://cirrus-ci.com/task/239") + self.assertEqual(res["query"]["job"], "test") + + @unittest.skip("Skip CI None") def test_ci_none(self): - self.set_env(CODECOV_TOKEN='token') + self.set_env(CODECOV_TOKEN="token", CODECOV_NAME="name") self.fake_report() - res = self.run_cli(build=10, - commit='d653b934ed59c1a785cc1cc79d08c9aaa4eba73b', - slug='owner/repo', - token='token') - self.assertEqual(res['query'].get('service'), None) - self.assertEqual(res['query']['commit'], 'd653b934ed59c1a785cc1cc79d08c9aaa4eba73b') - self.assertEqual(res['query']['build'], '10') - self.assertEqual(res['query']['slug'], 'owner/repo') - self.assertEqual(res['codecov'].token, 'token') + res = self.run_cli( + build=10, + commit="d653b934ed59c1a785cc1cc79d08c9aaa4eba73b", + slug="owner/repo", + token="token", + ) + self.assertEqual(res["query"].get("service"), None) + self.assertEqual( + res["query"]["commit"], "d653b934ed59c1a785cc1cc79d08c9aaa4eba73b" + ) + self.assertEqual(res["query"]["build"], "10") + self.assertEqual(res["query"]["slug"], "owner/repo") + self.assertEqual(res["codecov"].token, "token") + self.assertEqual(res["codecov"].name, "name") diff --git a/tox.ini b/tox.ini index 45fed510..84880fa6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] -envlist = py26, py27, py34 +envlist = py27, py34, py35, py36, py37 [testenv] deps = -r{toxinidir}/tests/requirements.txt commands = - py.test tests/test.py + pytest tests/test.py