From 9068caa43e4adabd68b3d4ab0a6777ff83fd4dd5 Mon Sep 17 00:00:00 2001 From: Timo Rothenpieler Date: Sun, 5 Jul 2020 15:28:00 +0200 Subject: [PATCH] Add cli option to finish parallel build --- coveralls/api.py | 33 +++++++++++++++++++ coveralls/cli.py | 7 +++++ docs/usage/configuration.rst | 22 +++++++------ docs/usage/tox.rst | 1 + setup.py | 2 +- tests/cli_test.py | 61 ++++++++++++++++++++++++++++++++++++ tox.ini | 1 + 7 files changed, 116 insertions(+), 11 deletions(-) diff --git a/coveralls/api.py b/coveralls/api.py index fca874be..dc9af777 100644 --- a/coveralls/api.py +++ b/coveralls/api.py @@ -207,6 +207,39 @@ def wear(self, dry_run=False): except Exception as e: raise CoverallsException('Could not submit coverage: {}'.format(e)) + def parallel_finish(self): + payload = { + 'payload': { + 'status': 'done' + } + } + if self.config.get('repo_token'): + payload['repo_token'] = self.config['repo_token'] + if self.config.get('service_number'): + payload['payload']['build_num'] = self.config['service_number'] + + # Service-Specific Parameters + if os.environ.get('GITHUB_REPOSITORY'): + payload['repo_name'] = os.environ.get('GITHUB_REPOSITORY') + + endpoint = '{}/webhook'.format(self._coveralls_host.rstrip('/')) + verify = not bool(os.environ.get('COVERALLS_SKIP_SSL_VERIFY')) + response = requests.post(endpoint, json=payload, verify=verify) + try: + response.raise_for_status() + response = response.json() + except Exception as e: + raise CoverallsException('Parallel finish failed: {}'.format(e)) + + if 'error' in response: + e = response['error'] + raise CoverallsException('Parallel finish failed: {}'.format(e)) + + if 'done' not in response or not response['done']: + raise CoverallsException('Parallel finish failed') + + return response + def create_report(self): """Generate json dumped report for coveralls api.""" data = self.create_data() diff --git a/coveralls/cli.py b/coveralls/cli.py index aa174ea1..3244e2d5 100644 --- a/coveralls/cli.py +++ b/coveralls/cli.py @@ -21,6 +21,7 @@ --rcfile= Specify configuration file. [default: .coveragerc] --output= Write report to file. Doesn't send anything. --merge= Merge report from file when submitting. + --finish Finish parallel jobs. -h --help Display this help. -v --verbose Print extra info, always enabled when debugging. @@ -74,6 +75,12 @@ def main(argv=None): coverallz.save_report(options['--output']) return + if options['--finish']: + log.info('Finishing parallel jobs...') + coverallz.parallel_finish() + log.info('Done') + return + log.info('Submitting coverage to coveralls.io...') result = coverallz.wear() diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst index 836d1541..fedba8d8 100644 --- a/docs/usage/configuration.rst +++ b/docs/usage/configuration.rst @@ -60,7 +60,7 @@ Coveralls natively supports jobs running on Github Actions. You can directly pas run: | coveralls -For parallel builds you have to specify a unique flag-name for every step/job. You can use the official coveralls action to finalize the build:: +For parallel builds you have to add a final step to let coveralls know the parallel build is finished. You also have to set COVERALLS_FLAG_NAME to something unique to the specific step, so re-runs of the same job don't keep piling up builds:: jobs: test: @@ -74,20 +74,22 @@ For parallel builds you have to specify a unique flag-name for every step/job. Y - name: Checkout uses: actions/checkout@v2 - name: Test - run: | - ./run_tests.sh ${{ matrix.test-name }} - coveralls + run: ./run_tests.sh ${{ matrix.test-name }} + - name: Upload Coverage + run: coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: ${{ matrix.test-name }} COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: test-${{ matrix.test-env }} coveralls: - name: Coveralls + name: Finish Coveralls needs: test runs-on: ubuntu-latest + container: python:3-slim steps: - name: Finished - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/usage/tox.rst b/docs/usage/tox.rst index d57f09c8..b2d97f2f 100644 --- a/docs/usage/tox.rst +++ b/docs/usage/tox.rst @@ -68,6 +68,7 @@ All variables: - ``GITHUB_REF`` - ``GITHUB_SHA`` - ``GITHUB_HEAD_REF`` +- ``GITHUB_REPOSITORY`` - ``GITHUB_RUN_NUMBER`` - ``GITHUB_TOKEN`` diff --git a/setup.py b/setup.py index 714ba44c..309cc503 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ 'docopt>=0.6.1', 'requests>=1.0.0', ], - tests_require=['mock', 'pytest'], + tests_require=['mock', 'responses', 'pytest'], extras_require={ 'yaml': ['PyYAML>=3.10'], }, diff --git a/tests/cli_test.py b/tests/cli_test.py index 9c361b4d..92671259 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,6 +1,8 @@ +import json import os import mock +import responses import coveralls.cli from coveralls.exception import CoverallsException @@ -9,6 +11,10 @@ EXC = CoverallsException('bad stuff happened') +def req_json(request): + return json.loads(request.body.decode('utf-8')) + + @mock.patch.dict(os.environ, {'TRAVIS': 'True'}, clear=True) @mock.patch.object(coveralls.cli.log, 'info') @mock.patch.object(coveralls.Coveralls, 'wear') @@ -27,6 +33,61 @@ def test_debug_no_token(mock_wear, mock_log): mock_log.assert_has_calls([mock.call('Testing coveralls-python...')]) +@mock.patch.dict( + os.environ, + {'GITHUB_ACTIONS': 'true', + 'GITHUB_REPOSITORY': 'test/repo', + 'GITHUB_TOKEN': 'xxx', + 'GITHUB_RUN_ID': '123456789', + 'GITHUB_RUN_NUMBER': '123'}, + clear=True) +@mock.patch.object(coveralls.cli.log, 'info') +@responses.activate +def test_finish(mock_log): + responses.add(responses.POST, 'https://coveralls.io/webhook', + json={'done': True}, status=200) + expected_json = { + 'repo_token': 'xxx', + 'repo_name': 'test/repo', + 'payload': { + 'status': 'done', + 'build_num': '123' + } + } + + coveralls.cli.main(argv=['--finish']) + + mock_log.assert_has_calls( + [mock.call('Finishing parallel jobs...'), + mock.call('Done')]) + assert len(responses.calls) == 1 + assert req_json(responses.calls[0].request) == expected_json + + +@mock.patch.dict(os.environ, {'TRAVIS': 'True'}, clear=True) +@mock.patch.object(coveralls.cli.log, 'exception') +@responses.activate +def test_finish_exception(mock_log): + responses.add(responses.POST, 'https://coveralls.io/webhook', + json={'error': 'Mocked'}, status=200) + expected_json = { + 'payload': { + 'status': 'done' + } + } + msg = 'Parallel finish failed: Mocked' + + try: + coveralls.cli.main(argv=['--finish']) + assert 0 == 1 # Should never reach this line + except SystemExit: + pass + + mock_log.assert_has_calls([mock.call(CoverallsException(msg))]) + assert len(responses.calls) == 1 + assert req_json(responses.calls[0].request) == expected_json + + @mock.patch.object(coveralls.cli.log, 'info') @mock.patch.object(coveralls.Coveralls, 'wear') @mock.patch.dict(os.environ, {'TRAVIS': 'True'}, clear=True) diff --git a/tox.ini b/tox.ini index 0feccaf7..e796a758 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ passenv = * usedevelop = true deps = mock + responses pytest pyyaml: PyYAML>=3.10,<5.3 cov41: coverage>=4.1,<5.0