diff --git a/coveralls/api.py b/coveralls/api.py index cb9db7fd..d5b1b589 100644 --- a/coveralls/api.py +++ b/coveralls/api.py @@ -16,6 +16,8 @@ log = logging.getLogger('coveralls.api') +NUMBER_REGEX = re.compile(r'(\d+)$', re.IGNORECASE) + class Coveralls: # pylint: disable=too-many-public-methods @@ -144,7 +146,33 @@ def load_config_from_semaphore(): def load_config_from_unknown(): return 'coveralls-python', None, None, None + def load_config_from_generic_ci_environment(self): + # Inspired by the official client: + # coveralls-ruby in lib/coveralls/configuration.rb + # (set_standard_service_params_for_generic_ci) + + config = { + 'service_name': os.environ.get('CI_NAME'), + 'service_number': os.environ.get('CI_BUILD_NUMBER'), + 'service_build_url': os.environ.get('CI_BUILD_URL'), + 'service_job_id': os.environ.get('CI_JOB_ID'), + 'service_branch': os.environ.get('CI_BRANCH'), + } + + pr_match = NUMBER_REGEX.findall(os.environ.get('CI_PULL_REQUEST', '')) + if pr_match: + config['service_pull_request'] = pr_match[-1] + + non_empty = {key: value for key, value in config.items() if value} + self.config.update(non_empty) + def load_config_from_ci_environment(self): + # As defined at the bottom of + # https://docs.coveralls.io/supported-ci-services + # there are a few env vars that should support any arbitrary CI. + # We load them first and allow more specific vars to overwrite + self.load_config_from_generic_ci_environment() + if os.environ.get('APPVEYOR'): name, job, number, pr = self.load_config_from_appveyor() elif os.environ.get('BUILDKITE'): @@ -166,7 +194,7 @@ def load_config_from_ci_environment(self): else: name, job, number, pr = self.load_config_from_unknown() - self.config['service_name'] = name + self.config.setdefault('service_name', name) if job: self.config['service_job_id'] = job if number: diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst index 6441306d..94c12b4e 100644 --- a/docs/usage/configuration.rst +++ b/docs/usage/configuration.rst @@ -156,3 +156,27 @@ you can copy from the coveralls.io website). As per `#245 `_, our users suggest leaving "keep this value secret" unchecked -- this may be secure enough as-is, in that a user making a PR cannot access this variable. + +Other CI systems +---------------- + +As specified in the Coveralls `official docs +` +other CI systems can be supported if the following environment variables are +defined:: + + CI_NAME + # Name of the CI service being used + CI_BUILD_NUMBER + # Number (counter) relative to the current build + CI_BUILD_URL + # URL to a webpage showing the build information/output + CI_BRANCH + # For pull requests this is the name of the branch targeted by the PR, + # otherwise it corresponds to the name of the current branch or tag + CI_JOB_ID (optional) + # Unique identifier of the job in the CI service. + # When missing, CI_BUILD_NUMBER is used + CI_PULL_REQUEST (optional) + # If given, corresponds to the number of the pull request, as specified + # in the supported repository hosting service (GitHub, GitLab, etc) diff --git a/tests/api/configuration_test.py b/tests/api/configuration_test.py index fe0c43fe..c0f7e7da 100644 --- a/tests/api/configuration_test.py +++ b/tests/api/configuration_test.py @@ -198,6 +198,40 @@ def test_semaphore_20_no_config(self): assert cover.config['service_number'] == 'b86b3adf' assert cover.config['service_pull_request'] == '9999' + @mock.patch.dict( + os.environ, + {'CI_NAME': 'generic-ci', + 'CI_PULL_REQUEST': 'pull/1234', + 'CI_JOB_ID': 'bb0e00166', + 'CI_BUILD_NUMBER': '3', + 'CI_BUILD_URL': 'https://generic-ci.local/build/123456789', + 'CI_BRANCH': 'fixup-branch', + 'COVERALLS_REPO_TOKEN': 'xxx'}, + clear=True) + def test_generic_no_config(self): + cover = Coveralls() + assert cover.config['service_name'] == 'generic-ci' + assert cover.config['service_job_id'] == 'bb0e00166' + assert cover.config['service_branch'] == 'fixup-branch' + assert cover.config['service_pull_request'] == '1234' + + @mock.patch.dict( + os.environ, + {'CI_NAME': 'generic-ci', + 'CI_PULL_REQUEST': '', + 'CI_JOB_ID': 'bb0e00166', + 'CI_BUILD_NUMBER': '3', + 'CI_BUILD_URL': 'https://generic-ci.local/build/123456789', + 'CI_BRANCH': 'fixup-branch', + 'COVERALLS_REPO_TOKEN': 'xxx'}, + clear=True) + def test_generic_no_config_no_pr(self): + cover = Coveralls() + assert cover.config['service_name'] == 'generic-ci' + assert cover.config['service_job_id'] == 'bb0e00166' + assert cover.config['service_branch'] == 'fixup-branch' + assert 'service_pull_request' not in cover.config + @mock.patch.dict(os.environ, {'COVERALLS_SERVICE_NAME': 'xxx'}, clear=True) def test_service_name_from_env(self): cover = Coveralls(repo_token='yyy')