misc.python.materialize.ci_util

Utility functions only useful in CI.

  1# Copyright Materialize, Inc. and contributors. All rights reserved.
  2#
  3# Use of this software is governed by the Business Source License
  4# included in the LICENSE file at the root of this repository.
  5#
  6# As of the Change Date specified in that file, in accordance with
  7# the Business Source License, use of this software will be governed
  8# by the Apache License, Version 2.0.
  9
 10"""Utility functions only useful in CI."""
 11
 12import os
 13from pathlib import Path
 14from typing import Any
 15
 16import requests
 17from semver.version import VersionInfo
 18
 19from materialize import MZ_ROOT, buildkite, cargo, ui
 20
 21
 22def junit_report_filename(suite: str) -> Path:
 23    """Compute the JUnit report filename for the specified test suite.
 24
 25    See also `upload_test_report`. In CI, the filename will include the
 26    Buildkite job ID.
 27
 28    Args:
 29        suite: The identifier for the test suite in Buildkite Test Analytics.
 30    """
 31    filename = f"junit_{suite}"
 32    if "BUILDKITE_JOB_ID" in os.environ:
 33        filename += "_" + os.environ["BUILDKITE_JOB_ID"]
 34    return Path(f"{filename}.xml")
 35
 36
 37def upload_junit_report(suite: str, junit_report: Path) -> None:
 38    """Upload a JUnit report to Buildkite Test Analytics.
 39
 40    Outside of CI, this function does nothing. Inside of CI, the API key for
 41    Buildkite Test Analytics is expected to be in the environment variable
 42    `BUILDKITE_TEST_ANALYTICS_API_KEY_{SUITE}`, where `{SUITE}` is the
 43    upper-snake-cased rendition of the `suite` parameter.
 44
 45    Args:
 46        suite: The identifier for the test suite in Buildkite Test Analytics.
 47        junit_report: The path to the JUnit XML-formatted report file.
 48    """
 49    if not buildkite.is_in_buildkite():
 50        return
 51    suite = suite.upper().replace("-", "_")
 52    token = os.getenv(f"BUILDKITE_TEST_ANALYTICS_API_KEY_{suite}")
 53    if not token:
 54        return
 55    try:
 56        res = requests.post(
 57            "https://analytics-api.buildkite.com/v1/uploads",
 58            headers={"Authorization": f"Token {token}"},
 59            json={
 60                "format": "junit",
 61                "run_env": {
 62                    "key": os.environ["BUILDKITE_BUILD_ID"],
 63                    "CI": "buildkite",
 64                    "number": os.environ["BUILDKITE_BUILD_NUMBER"],
 65                    "job_id": os.environ["BUILDKITE_JOB_ID"],
 66                    "branch": os.environ["BUILDKITE_BRANCH"],
 67                    "commit_sha": os.environ["BUILDKITE_COMMIT"],
 68                    "message": os.environ["BUILDKITE_MESSAGE"],
 69                    "url": os.environ["BUILDKITE_BUILD_URL"],
 70                },
 71                "data": junit_report.read_text(),
 72            },
 73        )
 74    except Exception as e:
 75        print(f"Got exception when uploading analytics: {e}")
 76    else:
 77        print(res.status_code, res.text)
 78
 79
 80def get_artifacts() -> Any:
 81    """Get artifact informations from Buildkite. Outside of CI, this function does nothing."""
 82
 83    if not buildkite.is_in_buildkite():
 84        return []
 85
 86    ui.section("Getting artifact informations from Buildkite")
 87    build = os.environ["BUILDKITE_BUILD_NUMBER"]
 88    build_id = os.environ["BUILDKITE_BUILD_ID"]
 89    job = os.environ["BUILDKITE_JOB_ID"]
 90    token = os.environ["BUILDKITE_AGENT_ACCESS_TOKEN"]
 91
 92    payload = {
 93        "query": "*",
 94        "step": job,
 95        "build": build,
 96        "state": "finished",
 97        "includeRetriedJobs": "false",
 98        "includeDuplicates": "false",
 99    }
100
101    res = requests.get(
102        f"https://agent.buildkite.com/v3/builds/{build_id}/artifacts/search",
103        params=payload,
104        headers={"Authorization": f"Token {token}"},
105    )
106
107    if res.status_code != 200:
108        print(f"Failed to get artifacts: {res.status_code} {res.text}")
109        return []
110
111    return res.json()
112
113
114def get_mz_version(workspace: cargo.Workspace | None = None) -> VersionInfo:
115    """Get the current Materialize version from Cargo.toml."""
116
117    if not workspace:
118        workspace = cargo.Workspace(MZ_ROOT)
119    return VersionInfo.parse(workspace.crates["mz-environmentd"].version_string)
def junit_report_filename(suite: str) -> pathlib.Path:
23def junit_report_filename(suite: str) -> Path:
24    """Compute the JUnit report filename for the specified test suite.
25
26    See also `upload_test_report`. In CI, the filename will include the
27    Buildkite job ID.
28
29    Args:
30        suite: The identifier for the test suite in Buildkite Test Analytics.
31    """
32    filename = f"junit_{suite}"
33    if "BUILDKITE_JOB_ID" in os.environ:
34        filename += "_" + os.environ["BUILDKITE_JOB_ID"]
35    return Path(f"{filename}.xml")

Compute the JUnit report filename for the specified test suite.

See also upload_test_report. In CI, the filename will include the Buildkite job ID.

Args: suite: The identifier for the test suite in Buildkite Test Analytics.

def upload_junit_report(suite: str, junit_report: pathlib.Path) -> None:
38def upload_junit_report(suite: str, junit_report: Path) -> None:
39    """Upload a JUnit report to Buildkite Test Analytics.
40
41    Outside of CI, this function does nothing. Inside of CI, the API key for
42    Buildkite Test Analytics is expected to be in the environment variable
43    `BUILDKITE_TEST_ANALYTICS_API_KEY_{SUITE}`, where `{SUITE}` is the
44    upper-snake-cased rendition of the `suite` parameter.
45
46    Args:
47        suite: The identifier for the test suite in Buildkite Test Analytics.
48        junit_report: The path to the JUnit XML-formatted report file.
49    """
50    if not buildkite.is_in_buildkite():
51        return
52    suite = suite.upper().replace("-", "_")
53    token = os.getenv(f"BUILDKITE_TEST_ANALYTICS_API_KEY_{suite}")
54    if not token:
55        return
56    try:
57        res = requests.post(
58            "https://analytics-api.buildkite.com/v1/uploads",
59            headers={"Authorization": f"Token {token}"},
60            json={
61                "format": "junit",
62                "run_env": {
63                    "key": os.environ["BUILDKITE_BUILD_ID"],
64                    "CI": "buildkite",
65                    "number": os.environ["BUILDKITE_BUILD_NUMBER"],
66                    "job_id": os.environ["BUILDKITE_JOB_ID"],
67                    "branch": os.environ["BUILDKITE_BRANCH"],
68                    "commit_sha": os.environ["BUILDKITE_COMMIT"],
69                    "message": os.environ["BUILDKITE_MESSAGE"],
70                    "url": os.environ["BUILDKITE_BUILD_URL"],
71                },
72                "data": junit_report.read_text(),
73            },
74        )
75    except Exception as e:
76        print(f"Got exception when uploading analytics: {e}")
77    else:
78        print(res.status_code, res.text)

Upload a JUnit report to Buildkite Test Analytics.

Outside of CI, this function does nothing. Inside of CI, the API key for Buildkite Test Analytics is expected to be in the environment variable BUILDKITE_TEST_ANALYTICS_API_KEY_{SUITE}, where {SUITE} is the upper-snake-cased rendition of the suite parameter.

Args: suite: The identifier for the test suite in Buildkite Test Analytics. junit_report: The path to the JUnit XML-formatted report file.

def get_artifacts() -> Any:
 81def get_artifacts() -> Any:
 82    """Get artifact informations from Buildkite. Outside of CI, this function does nothing."""
 83
 84    if not buildkite.is_in_buildkite():
 85        return []
 86
 87    ui.section("Getting artifact informations from Buildkite")
 88    build = os.environ["BUILDKITE_BUILD_NUMBER"]
 89    build_id = os.environ["BUILDKITE_BUILD_ID"]
 90    job = os.environ["BUILDKITE_JOB_ID"]
 91    token = os.environ["BUILDKITE_AGENT_ACCESS_TOKEN"]
 92
 93    payload = {
 94        "query": "*",
 95        "step": job,
 96        "build": build,
 97        "state": "finished",
 98        "includeRetriedJobs": "false",
 99        "includeDuplicates": "false",
100    }
101
102    res = requests.get(
103        f"https://agent.buildkite.com/v3/builds/{build_id}/artifacts/search",
104        params=payload,
105        headers={"Authorization": f"Token {token}"},
106    )
107
108    if res.status_code != 200:
109        print(f"Failed to get artifacts: {res.status_code} {res.text}")
110        return []
111
112    return res.json()

Get artifact informations from Buildkite. Outside of CI, this function does nothing.

def get_mz_version( workspace: materialize.cargo.Workspace | None = None) -> semver.version.Version:
115def get_mz_version(workspace: cargo.Workspace | None = None) -> VersionInfo:
116    """Get the current Materialize version from Cargo.toml."""
117
118    if not workspace:
119        workspace = cargo.Workspace(MZ_ROOT)
120    return VersionInfo.parse(workspace.crates["mz-environmentd"].version_string)

Get the current Materialize version from Cargo.toml.