From b869546c12dac821972558e1e205ff82d44e5d5f Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 28 Mar 2025 20:20:30 +0100 Subject: [PATCH] create dedicated python script to combine reports --- .github/workflows/ci.yaml | 52 ++++++++++------ script/merge_pytest_execution_time_reports.py | 61 +++++++++++++++++++ 2 files changed, 93 insertions(+), 20 deletions(-) create mode 100755 script/merge_pytest_execution_time_reports.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a7e045f551a..0f8c286cafc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -885,19 +885,17 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Generate partial pytest execution time restore key - id: generate-pytest-execution-time-key + id: generate-pytest-execution-time-report-key run: | - echo "key=pytest-execution-time-${{ - env.HA_SHORT_VERSION }}-$(date -u '%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT + echo "key=pytest-execution-time-report-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore pytest execution time cache uses: actions/cache/restore@v4.2.3 with: - path: pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}.json + path: pytest-execution-time-report.json key: >- - ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ - steps.generate-pytest-execution-time-key.outputs.key }} + ${{ runner.os }}-${{ steps.generate-pytest-execution-time-report-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-pytest-pytest-execution-time-${{ env.HA_SHORT_VERSION }}- + ${{ runner.os }}-pytest-execution-time-report- - name: Restore base Python virtual environment id: cache-venv uses: actions/cache/restore@v4.2.3 @@ -911,7 +909,7 @@ jobs: run: | . venv/bin/activate python -m script.split_tests ${{ needs.info.outputs.test_group_count }} \ - tests pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}.json + tests pytest-execution-time-report.json - name: Upload pytest_buckets uses: actions/upload-artifact@v4.6.2 with: @@ -1045,11 +1043,27 @@ jobs: - pytest-full name: Combine test execution times steps: + - name: Check out code from GitHub + uses: actions/checkout@v4.2.2 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v5.5.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache/restore@v4.2.3 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} - name: Generate partial pytest execution time restore key - id: generate-pytest-execution-time-key + id: generate-pytest-execution-time-report-key run: | - echo "key=pytest-execution-time-${{env.HA_SHORT_VERSION }}- - $(date -u '%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT + echo "key=pytest-execution-time-report-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Download pytest execution time artifacts uses: actions/download-artifact@v4.2.1 with: @@ -1057,22 +1071,20 @@ jobs: merge-multiple: true - name: Combine files into one run: | - ls -alh pytest-execution-time-report-* - jq 'reduce inputs as $item ({}; . *= $item)' \ - pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}-*.json \ - > pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}.json + . venv/bin/activate + python -m merge_pytest_execution_time_reports pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}-*.json - name: Upload combined pytest execution time artifact uses: actions/upload-artifact@v4.6.2 with: - name: pytest-execution-time-report-${{ github.run_number }}-${{ env.DEFAULT_PYTHON }} - path: pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}.json + name: pytest-execution-time-report-${{ github.run_number }} + path: pytest-execution-time-report.json - name: Save pytest execution time cache uses: actions/cache/save@v4.2.3 with: - path: pytest-execution-time-report-${{ env.DEFAULT_PYTHON }}.json + path: pytest-execution-time-report.json key: >- - ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ - steps.generate-pytest-execution-time-key.outputs.key }} + ${{ runner.os }}-${{ + steps.generate-pytest-execution-time-report-key.outputs.key }} pytest-mariadb: runs-on: ubuntu-24.04 diff --git a/script/merge_pytest_execution_time_reports.py b/script/merge_pytest_execution_time_reports.py new file mode 100755 index 00000000000..4949af07faf --- /dev/null +++ b/script/merge_pytest_execution_time_reports.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Helper script to merge all pytest execution time reports into one file.""" + +from __future__ import annotations + +import argparse +import pathlib + +from homeassistant.helpers.json import save_json +from homeassistant.util.json import load_json_object + + +def merge_json_files(pattern: str, output_file: str) -> None: + """Merge JSON files matching the pattern into a single JSON file.""" + # Needs to be in sync with PytestExecutionTimeReport in conftest.py + result: dict[str, float] = {} + + for file in pathlib.Path().glob(pattern): + print(f"Processing {file}") + data = load_json_object(file) + if not isinstance(data, dict): + print(f"Skipping {file} due to invalid data format.") + continue + for key, value in data.items(): + if not isinstance(value, (int, float)): + print( + f"Skipping {key} in {file} due to invalid value type: {type(value)}." + ) + continue + if key in result: + result[key] += value + else: + result[key] = value + + # Write the merged data to the output file + save_json(output_file, result) + + +def main() -> None: + """Execute script.""" + parser = argparse.ArgumentParser( + description="Merge all pytest execution time reports into one file." + ) + parser.add_argument( + "pattern", + help="Glob pattern to match JSON pytest execution time report files", + type=str, + ) + parser.add_argument( + "output_file", + help="Path to the output file", + type=str, + nargs="?", + default="pytest-execution-time-report.json", + ) + arguments = parser.parse_args() + merge_json_files(arguments.pattern, arguments.output_file) + + +if __name__ == "__main__": + main()