diff --git a/src/buildkite_test_collector/collector/api.py b/src/buildkite_test_collector/collector/api.py index 0f3f9ef..99d31ae 100644 --- a/src/buildkite_test_collector/collector/api.py +++ b/src/buildkite_test_collector/collector/api.py @@ -1,7 +1,6 @@ """Buildkite Test Analytics API""" -from typing import Any, Generator, Optional -from os import environ +from typing import Any, Generator, Optional, Mapping import traceback from requests import post, Response from requests.exceptions import InvalidHeader, HTTPError @@ -9,37 +8,44 @@ from ..pytest_plugin.logger import logger -def submit(payload: Payload, batch_size=100) -> Generator[Optional[Response], Any, Any]: - """Submit a payload to the API""" - token = environ.get("BUILDKITE_ANALYTICS_TOKEN") - api_url = environ.get("BUILDKITE_ANALYTICS_API_URL", "https://analytics-api.buildkite.com/v1") - response = None +class API: + """Buildkite Test Analytics API client""" - if not token: - logger.warning("No `BUILDKITE_ANALYTICS_TOKEN` environment variable present") - yield None + def __init__(self, env: Mapping[str, Optional[str]]): + """Initialize the API client with environment variables""" + self.env = env - else: - for payload_slice in payload.into_batches(batch_size): - try: - response = post(api_url + "/uploads", - json=payload_slice.as_json(), - headers={ - "Content-Type": "application/json", - "Authorization": f"Token token=\"{token}\"" - }, - timeout=60) - response.raise_for_status() - yield response - except InvalidHeader as error: - logger.warning("Invalid `BUILDKITE_ANALYTICS_TOKEN` environment variable") - logger.warning(error) - yield None - except HTTPError as err: - logger.warning("Failed to uploads test results to buildkite") - logger.warning(err) - yield None - except Exception: # pylint: disable=broad-except - error_message = traceback.format_exc() - logger.warning(error_message) - yield None + def submit(self, payload: Payload, batch_size=100) -> Generator[Optional[Response], Any, Any]: + """Submit a payload to the API""" + token = self.env.get("BUILDKITE_ANALYTICS_TOKEN") + api_url = self.env.get("BUILDKITE_ANALYTICS_API_URL") or "https://analytics-api.buildkite.com/v1" + response = None + + if not token: + logger.warning("No `BUILDKITE_ANALYTICS_TOKEN` environment variable present") + yield None + + else: + for payload_slice in payload.into_batches(batch_size): + try: + response = post(api_url + "/uploads", + json=payload_slice.as_json(), + headers={ + "Content-Type": "application/json", + "Authorization": f"Token token=\"{token}\"" + }, + timeout=60) + response.raise_for_status() + yield response + except InvalidHeader as error: + logger.warning("Invalid `BUILDKITE_ANALYTICS_TOKEN` environment variable") + logger.warning(error) + yield None + except HTTPError as err: + logger.warning("Failed to uploads test results to buildkite") + logger.warning(err) + yield None + except Exception: # pylint: disable=broad-except + error_message = traceback.format_exc() + logger.warning(error_message) + yield None diff --git a/src/buildkite_test_collector/collector/run_env.py b/src/buildkite_test_collector/collector/run_env.py index fca692f..f0e5088 100644 --- a/src/buildkite_test_collector/collector/run_env.py +++ b/src/buildkite_test_collector/collector/run_env.py @@ -1,93 +1,119 @@ """Test Engine run_env""" from dataclasses import dataclass -from typing import Dict, Optional +from typing import Dict, Optional, Mapping from uuid import uuid4 -import os -from .constants import COLLECTOR_NAME, VERSION # pylint: disable=W0611 - -# pylint: disable=C0103 disable=R0902 - - -def __get_env(name: str) -> Optional[str]: - var = os.environ.get(name) - if (var is None or var == ''): - return None - - return var - - -def __buildkite_env() -> Optional['RunEnv']: - build_id = __get_env("BUILDKITE_BUILD_ID") - - if build_id is None: - return None - - return RunEnv( - ci="buildkite", - key=build_id, - url=__get_env("BUILDKITE_BUILD_URL"), - branch=__get_env("BUILDKITE_BRANCH"), - commit_sha=__get_env("BUILDKITE_COMMIT"), - number=__get_env("BUILDKITE_BUILD_NUMBER"), - job_id=__get_env("BUILDKITE_JOB_ID"), - message=__get_env("BUILDKITE_MESSAGE") - ) - -def __github_actions_env() -> Optional['RunEnv']: - action = __get_env("GITHUB_ACTION") - run_number = __get_env("GITHUB_RUN_NUMBER") - run_attempt = __get_env("GITHUB_RUN_ATTEMPT") - - if (action is None or run_number is None or run_attempt is None): - return None - - repo = __get_env("GITHUB_REPOSITORY") - run_id = __get_env("GITHUB_RUN_ID") - - return RunEnv( - ci="github_actions", - key=f"{action}-{run_number}-{run_attempt}", - url=f"https://github.com/{repo}/actions/runs/{run_id}", - branch=__get_env("GITHUB_REF"), - commit_sha=__get_env("GITHUB_SHA"), - number=run_number, - job_id=None, - message=__get_env("TEST_ANALYTICS_COMMIT_MESSAGE"), - ) - - -def __circle_ci_env() -> Optional['RunEnv']: - build_num = __get_env("CIRCLE_BUILD_NUM") - workflow_id = __get_env("CIRCLE_WORKFLOW_ID") +from .constants import COLLECTOR_NAME, VERSION # pylint: disable=W0611 - if (build_num is None or workflow_id is None): - return None +# pylint: disable=R0902 - return RunEnv( - ci="circleci", - key=f"{workflow_id}-{build_num}", - url=__get_env("CIRCLE_BUILD_URL"), - branch=__get_env("CIRCLE_BRANCH"), - commit_sha=__get_env("CIRCLE_SHA1"), - number=build_num, - job_id=None, - message=__get_env("TEST_ANALYTICS_COMMIT_MESSAGE"), - ) +class RunEnvBuilder: + """Builder class for RunEnv that allows injection of environment variables + Example usage: + # Normal usage + builder = RunEnvBuilder(os.environ) + env = builder.build() -def __generic_env() -> 'RunEnv': - return RunEnv( - ci="generic", - key=str(uuid4()), - url=None, - branch=None, - commit_sha=None, - number=None, - job_id=None, - message=None - ) + # Testing with fake environment + fake_env = { + "BUILDKITE_BUILD_ID": "test-build-123", + "BUILDKITE_BUILD_URL": "https://buildkite.com/test", + "BUILDKITE_BRANCH": "main", + "BUILDKITE_COMMIT": "abc123", + } + builder = RunEnvBuilder(fake_env) + env = builder.build() + assert env.ci == "buildkite" + assert env.key == "test-build-123" + """ + + def __init__(self, env: Mapping[str, Optional[str]]): + self.env = env + + def build(self) -> 'RunEnv': + """Build a RunEnv by detecting the CI system""" + return \ + self._buildkite_env() or \ + self._github_actions_env() or \ + self._circle_ci_env() or \ + self._generic_env() + + def _get_env(self, name: str) -> Optional[str]: + var = self.env.get(name) + if (var is None or var == ''): + return None + return var + + def _buildkite_env(self) -> Optional['RunEnv']: + build_id = self._get_env("BUILDKITE_BUILD_ID") + + if build_id is None: + return None + + return RunEnv( + ci="buildkite", + key=build_id, + url=self._get_env("BUILDKITE_BUILD_URL"), + branch=self._get_env("BUILDKITE_BRANCH"), + commit_sha=self._get_env("BUILDKITE_COMMIT"), + number=self._get_env("BUILDKITE_BUILD_NUMBER"), + job_id=self._get_env("BUILDKITE_JOB_ID"), + message=self._get_env("BUILDKITE_MESSAGE") + ) + + def _github_actions_env(self) -> Optional['RunEnv']: + action = self._get_env("GITHUB_ACTION") + run_number = self._get_env("GITHUB_RUN_NUMBER") + run_attempt = self._get_env("GITHUB_RUN_ATTEMPT") + + if (action is None or run_number is None or run_attempt is None): + return None + + repo = self._get_env("GITHUB_REPOSITORY") + run_id = self._get_env("GITHUB_RUN_ID") + + return RunEnv( + ci="github_actions", + key=f"{action}-{run_number}-{run_attempt}", + url=f"https://github.com/{repo}/actions/runs/{run_id}", + branch=self._get_env("GITHUB_REF"), + commit_sha=self._get_env("GITHUB_SHA"), + number=run_number, + job_id=None, + message=self._get_env("TEST_ANALYTICS_COMMIT_MESSAGE"), + ) + + def _circle_ci_env(self) -> Optional['RunEnv']: + build_num = self._get_env("CIRCLE_BUILD_NUM") + workflow_id = self._get_env("CIRCLE_WORKFLOW_ID") + + if (build_num is None or workflow_id is None): + return None + + return RunEnv( + ci="circleci", + key=f"{workflow_id}-{build_num}", + url=self._get_env("CIRCLE_BUILD_URL"), + branch=self._get_env("CIRCLE_BRANCH"), + commit_sha=self._get_env("CIRCLE_SHA1"), + number=build_num, + job_id=None, + message=self._get_env("TEST_ANALYTICS_COMMIT_MESSAGE"), + ) + + def _generic_env(self) -> 'RunEnv': + return RunEnv( + ci="generic", + key=str(uuid4()), + url=None, + branch=None, + commit_sha=None, + number=None, + job_id=None, + message=None + ) @dataclass(frozen=True) @@ -118,11 +144,3 @@ def as_json(self) -> Dict[str, str]: } return {k: v for k, v in attrs.items() if v is not None} - - -def detect_env() -> RunEnv: - """Attempt to detect the CI system we're running in""" - return __buildkite_env() or \ - __github_actions_env() or \ - __circle_ci_env() or \ - __generic_env() diff --git a/src/buildkite_test_collector/pytest_plugin/__init__.py b/src/buildkite_test_collector/pytest_plugin/__init__.py index 7684b11..52d4bfb 100644 --- a/src/buildkite_test_collector/pytest_plugin/__init__.py +++ b/src/buildkite_test_collector/pytest_plugin/__init__.py @@ -1,11 +1,11 @@ -# pylint: disable=line-too-long """Buildkite test collector for Pytest.""" +import os import pytest from ..collector.payload import Payload -from ..collector.run_env import detect_env -from ..collector.api import submit +from ..collector.run_env import RunEnvBuilder +from ..collector.api import API from .span_collector import SpanCollector from .buildkite_plugin import BuildkitePlugin @@ -22,9 +22,12 @@ def spans(request): @pytest.hookimpl(trylast=True) def pytest_configure(config): """pytest_configure hook callback""" - env = detect_env() + env = RunEnvBuilder(os.environ).build() - config.addinivalue_line("markers", "execution_tag(key, value): add tag to test execution for Buildkite Test Collector. Both key and value must be a string.") + config.addinivalue_line("markers", + "execution_tag(key, value): " + "add tag to test execution for Buildkite Test Collector. " + "Both key and value must be a string.") plugin = BuildkitePlugin(Payload.init(env)) setattr(config, '_buildkite', plugin) @@ -37,6 +40,7 @@ def pytest_unconfigure(config): plugin = getattr(config, '_buildkite', None) if plugin: + api = API(os.environ) xdist_enabled = ( config.pluginmanager.getplugin("xdist") is not None and config.getoption("numprocesses") is not None @@ -47,11 +51,12 @@ def pytest_unconfigure(config): # When xdist is not installed, or when it's installed and not enabled if not xdist_enabled: - list(submit(plugin.payload)) + list(api.submit(plugin.payload)) - # When xdist is activated, we want to submit from worker thread only, because they have access to tag data + # When xdist is activated, we want to submit from worker thread only, because they have + # access to tag data if xdist_enabled and is_xdist_worker: - list(submit(plugin.payload)) + list(api.submit(plugin.payload)) # We only want a single thread to write to the json file. # When xdist is enabled, that will be the controller thread. diff --git a/tests/buildkite_test_collector/collector/test_api.py b/tests/buildkite_test_collector/collector/test_api.py index 8dee412..26b8d45 100644 --- a/tests/buildkite_test_collector/collector/test_api.py +++ b/tests/buildkite_test_collector/collector/test_api.py @@ -6,24 +6,26 @@ import pytest import sys -from buildkite_test_collector.collector.run_env import detect_env -from buildkite_test_collector.collector.api import submit +from buildkite_test_collector.collector.run_env import RunEnvBuilder +from buildkite_test_collector.collector.api import API from buildkite_test_collector.collector.payload import Payload from requests.exceptions import ReadTimeout, ConnectTimeout def test_submit_with_missing_api_key_environment_variable_returns_none(): - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": ""}): - payload = Payload.init(detect_env()) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": None} + payload = Payload.init(RunEnvBuilder(env).build()) - assert next(submit(payload)) is None + api = API(env) + assert next(api.submit(payload)) is None def test_submit_with_invalid_api_key_environment_variable_returns_none(): - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": "\n"}): - payload = Payload.init(detect_env()) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": "\n"} + payload = Payload.init(RunEnvBuilder(env).build()) - assert next(submit(payload)) is None + api = API(env) + assert next(api.submit(payload)) is None @responses.activate @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher") @@ -33,17 +35,18 @@ def test_submit_with_payload_timeout_captures_ConnectTimeout_error(capfd, succes "https://analytics-api.buildkite.com/v1/uploads", body=ConnectTimeout("Error")) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(successful_test) - result = next(submit(payload)) - captured = capfd.readouterr() + api = API(env) + result = next(api.submit(payload)) + captured = capfd.readouterr() - assert captured.err.startswith("buildkite-test-collector - WARNING -") - assert "ConnectTimeout" in captured.err + assert captured.err.startswith("buildkite-test-collector - WARNING -") + assert "ConnectTimeout" in captured.err @responses.activate @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher") @@ -53,17 +56,18 @@ def test_submit_with_payload_timeout_captures_ReadTimeout_error(capfd, successfu "https://analytics-api.buildkite.com/v1/uploads", body=ReadTimeout("Error")) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(successful_test) - result = next(submit(payload)) - captured = capfd.readouterr() + api = API(env) + result = next(api.submit(payload)) + captured = capfd.readouterr() - assert captured.err.startswith("buildkite-test-collector - WARNING -") - assert "ReadTimeout" in captured.err + assert captured.err.startswith("buildkite-test-collector - WARNING -") + assert "ReadTimeout" in captured.err @responses.activate def test_submit_with_payload_returns_an_api_response(successful_test): @@ -78,21 +82,22 @@ def test_submit_with_payload_returns_an_api_response(successful_test): 'run_url': 'https://buildkite.com/organizations/alembic/analytics/suites/test/runs/52c5d9f6-a4f2-4a2d-a1e6-993335789c92'}, status=202) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(successful_test) - result = next(submit(payload)) - assert result + api = API(env) + result = next(api.submit(payload)) + assert result - assert result.status_code >= 200 - assert result.status_code < 300 + assert result.status_code >= 200 + assert result.status_code < 300 - json = result.json() - assert len(json["errors"]) == 0 - assert json['queued'] == 1 + json = result.json() + assert len(json["errors"]) == 0 + assert json['queued'] == 1 @responses.activate def test_submit_with_bad_response(successful_test): @@ -102,15 +107,16 @@ def test_submit_with_bad_response(successful_test): json={'error': str(uuid4())}, status=401) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(successful_test) - result = next(submit(payload)) + api = API(env) + result = next(api.submit(payload)) - assert result is None + assert result is None @responses.activate def test_submit_with_large_payload_batches_requests(successful_test, failed_test): @@ -135,24 +141,25 @@ def test_submit_with_large_payload_batches_requests(successful_test, failed_test 'run_url': 'https://buildkite.com/organizations/alembic/analytics/suites/test/runs/52c5d9f6-a4f2-4a2d-a1e6-993335789c92'}, status=202) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) - payload = payload.push_test_data(failed_test) + payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(failed_test) - results = [response for response in submit(payload, batch_size=1)] - assert len(results) == 2 + api = API(env) + results = [response for response in api.submit(payload, batch_size=1)] + assert len(results) == 2 - for result in results: - assert result - assert result.status_code >= 200 - assert result.status_code < 300 + for result in results: + assert result + assert result.status_code >= 200 + assert result.status_code < 300 - json = result.json() - assert len(json["errors"]) == 0 - assert json['queued'] == 1 + json = result.json() + assert len(json["errors"]) == 0 + assert json['queued'] == 1 @responses.activate def test_submit_with_batches_and_errors(capfd, successful_test, failed_test): @@ -171,29 +178,30 @@ def test_submit_with_batches_and_errors(capfd, successful_test, failed_test): 'run_url': 'https://buildkite.com/organizations/alembic/analytics/suites/test/runs/52c5d9f6-a4f2-4a2d-a1e6-993335789c92'}, status=202) - with mock.patch.dict(os.environ, {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())}): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + env = {"CI": "true", "BUILDKITE_ANALYTICS_TOKEN": str(uuid4())} + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) - payload = payload.push_test_data(failed_test) + payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(failed_test) - results = [response for response in submit(payload, batch_size=1)] - assert len(results) == 2 + api = API(env) + results = [response for response in api.submit(payload, batch_size=1)] + assert len(results) == 2 - assert not results[0] - captured = capfd.readouterr() - assert captured.err.startswith("buildkite-test-collector - WARNING -") - assert "ConnectTimeout" in captured.err + assert not results[0] + captured = capfd.readouterr() + assert captured.err.startswith("buildkite-test-collector - WARNING -") + assert "ConnectTimeout" in captured.err - result = results[1] - assert result - assert result.status_code >= 200 - assert result.status_code < 300 + result = results[1] + assert result + assert result.status_code >= 200 + assert result.status_code < 300 - json = result.json() - assert len(json["errors"]) == 0 - assert json['queued'] == 1 + json = result.json() + assert len(json["errors"]) == 0 + assert json['queued'] == 1 @responses.activate def test_api_url_override(successful_test): @@ -211,17 +219,17 @@ def test_api_url_override(successful_test): "BUILDKITE_ANALYTICS_TOKEN": str(uuid4()), } - with mock.patch.dict(os.environ, env): - payload = Payload.init(detect_env()) - payload = Payload.started(payload) + payload = Payload.init(RunEnvBuilder(env).build()) + payload = Payload.started(payload) - payload = payload.push_test_data(successful_test) + payload = payload.push_test_data(successful_test) - result = next(submit(payload)) - assert result + api = API(env) + result = next(api.submit(payload)) + assert result - assert result.status_code >= 200 - assert result.status_code < 300 + assert result.status_code >= 200 + assert result.status_code < 300 - json = result.json() - assert json['upload_id'] == upload_id + json = result.json() + assert json['upload_id'] == upload_id diff --git a/tests/buildkite_test_collector/collector/test_payload.py b/tests/buildkite_test_collector/collector/test_payload.py index 2ed4fe6..5a55b88 100644 --- a/tests/buildkite_test_collector/collector/test_payload.py +++ b/tests/buildkite_test_collector/collector/test_payload.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from functools import reduce import pytest diff --git a/tests/buildkite_test_collector/collector/test_run_env.py b/tests/buildkite_test_collector/collector/test_run_env.py index adec3ac..e906237 100644 --- a/tests/buildkite_test_collector/collector/test_run_env.py +++ b/tests/buildkite_test_collector/collector/test_run_env.py @@ -1,10 +1,8 @@ from random import randint from uuid import uuid4, UUID -import os -import mock -from buildkite_test_collector.collector.constants import COLLECTOR_NAME, VERSION # pylint: disable=W0611 -from buildkite_test_collector.collector.run_env import detect_env +from buildkite_test_collector.collector.constants import VERSION +from buildkite_test_collector.collector.run_env import RunEnv, RunEnvBuilder def test_detect_env_with_buildkite_api_env_vars_returns_the_correct_environment(): @@ -12,8 +10,7 @@ def test_detect_env_with_buildkite_api_env_vars_returns_the_correct_environment( commit = uuid4().hex number = str(randint(0, 1000)) job_id = str(randint(0, 1000)) - - env = { + run_env = RunEnvBuilder({ "BUILDKITE_BUILD_ID": id, "BUILDKITE_BUILD_URL": "https://example.test/buildkite", "BUILDKITE_BRANCH": "rufus", @@ -21,18 +18,16 @@ def test_detect_env_with_buildkite_api_env_vars_returns_the_correct_environment( "BUILDKITE_BUILD_NUMBER": number, "BUILDKITE_JOB_ID": job_id, "BUILDKITE_MESSAGE": "All we are is dust in the wind, dude.", - } - with mock.patch.dict(os.environ, env, clear=True): - run_env = detect_env() + }).build() - assert run_env.ci == "buildkite" - assert run_env.key == id - assert run_env.url == "https://example.test/buildkite" - assert run_env.branch == "rufus" - assert run_env.commit_sha == commit - assert run_env.number == number - assert run_env.job_id == job_id - assert run_env.message == "All we are is dust in the wind, dude." + assert run_env.ci == "buildkite" + assert run_env.key == id + assert run_env.url == "https://example.test/buildkite" + assert run_env.branch == "rufus" + assert run_env.commit_sha == commit + assert run_env.number == number + assert run_env.job_id == job_id + assert run_env.message == "All we are is dust in the wind, dude." def test_detect_env_with_github_actions_env_vars_returns_the_correct_environment(): @@ -40,8 +35,7 @@ def test_detect_env_with_github_actions_env_vars_returns_the_correct_environment run_attempt = str(randint(0, 1000)) run_id = str(uuid4()) commit = uuid4().hex - - env = { + run_env = RunEnvBuilder({ "GITHUB_ACTION": "bring-about-world-peace", "GITHUB_RUN_NUMBER": run_number, "GITHUB_RUN_ATTEMPT": run_attempt, @@ -50,60 +44,50 @@ def test_detect_env_with_github_actions_env_vars_returns_the_correct_environment "GITHUB_REF": "rufus", "GITHUB_SHA": commit, "TEST_ANALYTICS_COMMIT_MESSAGE": "excellent adventure" - } + }).build() - with mock.patch.dict(os.environ, env, clear=True): - run_env = detect_env() - - assert run_env.ci == "github_actions" - assert run_env.key == f"bring-about-world-peace-{run_number}-{run_attempt}" - assert run_env.url == f"https://github.com/bill-and-ted/phone-booth/actions/runs/{run_id}" - assert run_env.branch == "rufus" - assert run_env.commit_sha == commit - assert run_env.number == run_number - assert run_env.job_id is None - assert run_env.message == "excellent adventure" + assert run_env.ci == "github_actions" + assert run_env.key == f"bring-about-world-peace-{run_number}-{run_attempt}" + assert run_env.url == f"https://github.com/bill-and-ted/phone-booth/actions/runs/{run_id}" + assert run_env.branch == "rufus" + assert run_env.commit_sha == commit + assert run_env.number == run_number + assert run_env.job_id is None + assert run_env.message == "excellent adventure" def test_detect_env_with_circle_ci_env_vars_returns_the_correct_environment(): build_num = str(randint(0, 1000)) workflow_id = str(uuid4()) commit = uuid4().hex - - env = { + run_env = RunEnvBuilder({ "CIRCLE_BUILD_NUM": build_num, "CIRCLE_WORKFLOW_ID": workflow_id, "CIRCLE_BUILD_URL": "https://example.test/circle", "CIRCLE_BRANCH": "rufus", "CIRCLE_SHA1": commit, "TEST_ANALYTICS_COMMIT_MESSAGE": "excellent adventure" - } + }).build() - with mock.patch.dict(os.environ, env, clear=True): - run_env = detect_env() - - assert run_env.ci == "circleci" - assert run_env.key == f"{workflow_id}-{build_num}" - assert run_env.url == "https://example.test/circle" - assert run_env.branch == "rufus" - assert run_env.commit_sha == commit - assert run_env.number == build_num - assert run_env.job_id is None - assert run_env.message == "excellent adventure" + assert run_env.ci == "circleci" + assert run_env.key == f"{workflow_id}-{build_num}" + assert run_env.url == "https://example.test/circle" + assert run_env.branch == "rufus" + assert run_env.commit_sha == commit + assert run_env.number == build_num + assert run_env.job_id is None + assert run_env.message == "excellent adventure" def test_detect_env_with_generic_env_vars(): - env = {} - - with mock.patch.dict(os.environ, env, clear=True): - run_env = detect_env() - - assert run_env.ci == "generic" - assert UUID(run_env.key) - assert run_env.url is None - assert run_env.branch is None - assert run_env.commit_sha is None - assert run_env.number is None - assert run_env.job_id is None - assert run_env.message is None + run_env = RunEnvBuilder({}).build() + + assert run_env.ci == "generic" + assert UUID(run_env.key) + assert run_env.url is None + assert run_env.branch is None + assert run_env.commit_sha is None + assert run_env.number is None + assert run_env.job_id is None + assert run_env.message is None def test_env_as_json(fake_env): json = fake_env.as_json()