scripts/metric_reporter/main.py (110 lines of code) (raw):
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Main module for running the Metric Reporter."""
import argparse
import logging
from google.cloud import bigquery, storage
from google.oauth2.service_account import Credentials
from scripts.metric_reporter.config import Config, InvalidConfigError
from scripts.metric_reporter.gcs_client import GCSClient, GCSArtifacts, GCSClientError
from scripts.metric_reporter.parser.base_parser import ParserError
from scripts.metric_reporter.parser.coverage_json_parser import (
CoverageJsonGroup,
CoverageJsonParser,
)
from scripts.metric_reporter.parser.junit_xml_parser import JUnitXmlParser, JUnitXmlGroup
from scripts.metric_reporter.reporter.base_reporter import ReporterError
from scripts.metric_reporter.reporter.coverage_reporter import CoverageReporter
from scripts.metric_reporter.reporter.suite_reporter import SuiteReporter
# Configure logging
logging.basicConfig(level=logging.WARNING, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
def main(
config_file: str = "config.ini",
update_bigquery: bool | None = None,
) -> None:
"""Run the Metric Reporter.
Args:
config_file (str): Path to the configuration file.
update_bigquery (bool | None): Whether to update BigQuery tables. Overrides config file if
provided.
"""
try:
logger.info(f"Starting Metric Reporter with configuration file: {config_file}")
config = Config(config_file)
gcp_project_id: str = config.metric_reporter_config.gcp_project_id
test_result_bucket: str = config.metric_reporter_config.test_result_bucket
coverage_artifact_dir: str = config.metric_reporter_config.coverage_artifact_dir
junit_artifact_dir: str = config.metric_reporter_config.junit_artifact_dir
bigquery_dataset_name: str = config.metric_reporter_config.bigquery_dataset_name
service_account_file: str = config.metric_reporter_config.service_account_file
if update_bigquery is None:
update_bigquery = config.metric_reporter_config.update_bigquery
if not update_bigquery:
logger.warning(
"The metric reporter will not perform any action. "
"Use the --update-bigquery flag."
)
return
# Create GCS and BigQuery clients
credentials = Credentials.from_service_account_file(service_account_file) # type: ignore
storage_client = storage.Client(project=gcp_project_id, credentials=credentials)
bigquery_client = bigquery.Client(project=gcp_project_id, credentials=credentials)
# Report
gcs_client = GCSClient(
storage_client, test_result_bucket, coverage_artifact_dir, junit_artifact_dir
)
gcs_artifacts: list[GCSArtifacts] = gcs_client.get_artifacts()
report_coverage(
gcs_client, gcs_artifacts, bigquery_client, gcp_project_id, bigquery_dataset_name
)
report_suite_results(
gcs_client, gcs_artifacts, bigquery_client, gcp_project_id, bigquery_dataset_name
)
except InvalidConfigError as error:
logger.error(f"Configuration error: {error}")
except GCSClientError as error:
logger.error(f"GCS client error: {error}")
except ParserError as error:
logger.error(f"Parsing error: {error}")
except ReporterError as error:
logger.error(f"Test Suite Reporter error: {error}")
except Exception as error:
logger.error(f"Unexpected error: {error}", exc_info=error)
def report_coverage(
gcs_client: GCSClient,
gcs_artifacts: list[GCSArtifacts],
bigquery_client,
gcp_project_id: str,
bigquery_dataset_name: str,
) -> None:
"""Report results from Coverage JSON files to a BigQuery dataset table.
Args:
gcs_client (GCSClient): Storage client
gcs_artifacts (list[GCSArtifacts]): Lists of artifact files grouped by repository
bigquery_client: BigQuery client
gcp_project_id (str): The GCP project ID.
bigquery_dataset_name (str): The name of the BigQuery dataset.
"""
coverage_json_parser = CoverageJsonParser(gcs_client)
coverage_artifact_groups: list[CoverageJsonGroup] = [
group
for artifacts in gcs_artifacts
for group in coverage_json_parser.parse(artifacts.coverage_artifact_files)
]
for group in coverage_artifact_groups:
logger.info(f"Report coverage for {group.repository} {group.workflow} {group.test_suite}")
coverage_reporter = CoverageReporter(
group.repository, group.workflow, group.test_suite, group.coverage_jsons
)
coverage_reporter.update_table(bigquery_client, gcp_project_id, bigquery_dataset_name)
def report_suite_results(
gcs_client: GCSClient,
gcs_artifacts: list[GCSArtifacts],
bigquery_client,
gcp_project_id: str,
bigquery_dataset_name: str,
) -> None:
"""Report results from JUnit XML files to BigQuery dataset tables.
Args:
gcs_client (GCSClient): Storage client
gcs_artifacts (list[GCSArtifacts]): Lists of artifact files grouped by repository
bigquery_client: BigQuery client
gcp_project_id (str): The GCP project ID.
bigquery_dataset_name (str): The name of the BigQuery dataset.
"""
junit_xml_parser = JUnitXmlParser(gcs_client)
junit_artifact_groups: list[JUnitXmlGroup] = [
group
for artifacts in gcs_artifacts
for group in junit_xml_parser.parse(artifacts.junit_artifact_files)
]
for group in junit_artifact_groups:
logger.info(f"Report results for {group.repository} {group.workflow} {group.test_suite}")
suite_reporter = SuiteReporter(
group.repository, group.workflow, group.test_suite, group.junit_xmls
)
suite_reporter.update_table(bigquery_client, gcp_project_id, bigquery_dataset_name)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run the Metric Reporter")
parser.add_argument("--config", help="Path to the config.ini file", default="config.ini")
parser.add_argument(
"--update-bigquery", help="Update BigQuery tables", type=bool, default=None
)
parser_args = parser.parse_args()
main(
parser_args.config,
update_bigquery=parser_args.update_bigquery,
)