name: CI run-name: "${{ github.event_name == 'workflow_dispatch' && format('CI: {0}', github.ref_name) || '' }}" # yamllint disable-line rule:truthy on: push: branches: - dev - rc - master pull_request: ~ workflow_dispatch: inputs: full: description: "Full run (regardless of changes)" default: false type: boolean lint-only: description: "Skip pytest" default: false type: boolean skip-coverage: description: "Skip coverage" default: false type: boolean pylint-only: description: "Only run pylint" default: false type: boolean mypy-only: description: "Only run mypy" default: false type: boolean audit-licenses-only: description: "Only run audit licenses" default: false type: boolean env: CACHE_VERSION: 2 UV_CACHE_VERSION: 1 MYPY_CACHE_VERSION: 1 HA_SHORT_VERSION: "2026.3" DEFAULT_PYTHON: "3.14.2" ALL_PYTHON_VERSIONS: "['3.14.2']" # 10.3 is the oldest supported version # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # 10.6 is the current long-term-support # - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023) # 10.10 is the latest short-term-support # - 10.10.3 is the latest (as of 6 Feb 2023) # 10.11 is the latest long-term-support # - 10.11.2 is the version currently shipped with Synology (as of 11 Oct 2023) # mysql 8.0.32 does not always behave the same as MariaDB # and some queries that work on MariaDB do not work on MySQL MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mariadb:10.11.2','mysql:8.0.32']" # 12 is the oldest supported version # - 12.14 is the latest (as of 9 Feb 2023) # 15 is the latest version # - 15.2 is the latest (as of 9 Feb 2023) POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']" UV_CACHE_DIR: /tmp/uv-cache APT_CACHE_BASE: /home/runner/work/apt APT_CACHE_DIR: /home/runner/work/apt/cache APT_LIST_CACHE_DIR: /home/runner/work/apt/lists SQLALCHEMY_WARN_20: 1 PYTHONASYNCIODEBUG: 1 HASS_CI: 1 permissions: {} concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: info: name: Collect information & changes data runs-on: ubuntu-24.04 permissions: contents: read # To check out the repository pull-requests: read # For paths-filter to detect changed files outputs: # In case of issues with the partial run, use the following line instead: # test_full_suite: 'true' core: ${{ steps.core.outputs.changes }} integrations_glob: ${{ steps.info.outputs.integrations_glob }} integrations: ${{ steps.integrations.outputs.changes }} apt_cache_key: ${{ steps.generate_apt_cache_key.outputs.key }} python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }} requirements: ${{ steps.core.outputs.requirements }} mariadb_groups: ${{ steps.info.outputs.mariadb_groups }} postgresql_groups: ${{ steps.info.outputs.postgresql_groups }} python_versions: ${{ steps.info.outputs.python_versions }} test_full_suite: ${{ steps.info.outputs.test_full_suite }} test_group_count: ${{ steps.info.outputs.test_group_count }} test_groups: ${{ steps.info.outputs.test_groups }} tests_glob: ${{ steps.info.outputs.tests_glob }} tests: ${{ steps.info.outputs.tests }} lint_only: ${{ steps.info.outputs.lint_only }} skip_coverage: ${{ steps.info.outputs.skip_coverage }} steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Generate partial Python venv restore key id: generate_python_cache_key env: HASH_REQUIREMENTS_TEST: ${{ hashFiles('requirements_test.txt', 'requirements_test_pre_commit.txt') }} HASH_REQUIREMENTS: ${{ hashFiles('requirements.txt') }} HASH_REQUIREMENTS_ALL: ${{ hashFiles('requirements_all.txt') }} HASH_PACKAGE_CONSTRAINTS: ${{ hashFiles('homeassistant/package_constraints.txt') }} HASH_GEN_REQUIREMENTS: ${{ hashFiles('script/gen_requirements_all.py') }} run: | # Include HA_SHORT_VERSION to force the immediate creation # of a new uv cache entry after a version bump. echo "key=venv-${CACHE_VERSION}-${HA_SHORT_VERSION}-${HASH_REQUIREMENTS_TEST}-${HASH_REQUIREMENTS}-${HASH_REQUIREMENTS_ALL}-${HASH_PACKAGE_CONSTRAINTS}-${HASH_GEN_REQUIREMENTS}" >> $GITHUB_OUTPUT - name: Generate partial apt restore key id: generate_apt_cache_key run: | echo "key=$(lsb_release -rs)-apt-${CACHE_VERSION}-${HA_SHORT_VERSION}" >> $GITHUB_OUTPUT - name: Filter for core changes uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: core with: filters: .core_files.yaml - name: Create a list of integrations to filter for changes run: | integrations=$(ls -Ad ./homeassistant/components/[!_]* | xargs -n 1 basename) touch .integration_paths.yaml for integration in $integrations; do echo "${integration}: [homeassistant/components/${integration}/**, tests/components/${integration}/**]" \ >> .integration_paths.yaml; done echo "Result:" cat .integration_paths.yaml - name: Filter for integration changes uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: integrations with: filters: .integration_paths.yaml - name: Collect additional information id: info env: INTEGRATION_CHANGES: ${{ steps.integrations.outputs.changes }} CORE_ANY: ${{ steps.core.outputs.any }} INPUT_FULL: ${{ github.event.inputs.full }} HAS_CI_FULL_RUN_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }} INPUT_LINT_ONLY: ${{ github.event.inputs.lint-only }} INPUT_PYLINT_ONLY: ${{ github.event.inputs.pylint-only }} INPUT_MYPY_ONLY: ${{ github.event.inputs.mypy-only }} INPUT_AUDIT_LICENSES_ONLY: ${{ github.event.inputs.audit-licenses-only }} REPO_FULL_NAME: ${{ github.event.repository.full_name }} INPUT_SKIP_COVERAGE: ${{ github.event.inputs.skip-coverage }} HAS_CI_SKIP_COVERAGE_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'ci-skip-coverage') }} run: | # Defaults integrations_glob="" mariadb_groups=${MARIADB_VERSIONS} postgresql_groups=${POSTGRESQL_VERSIONS} test_full_suite="true" test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" test_group_count=10 tests="[]" tests_glob="" lint_only="" skip_coverage="" if [[ "${INTEGRATION_CHANGES}" != "[]" ]]; then # Create a space-separated list of integrations integrations_glob=$(echo "${INTEGRATION_CHANGES}" | jq -r '. | join(" ")') # Create list of testable integrations possible_integrations=$(echo "${INTEGRATION_CHANGES}" | jq -cSr '.[]') tests=$( for integration in ${possible_integrations}; do if [[ -d "tests/components/${integration}" ]]; then echo -n "\"${integration}\","; fi; done ) [[ ! -z "${tests}" ]] && tests="${tests::-1}" tests="[${tests}]" test_groups="${tests}" # Test group count should be 1, we don't split partial tests test_group_count=1 # Create a space-separated list of test integrations tests_glob=$(echo "${tests}" | jq -r '. | join(" ")') mariadb_groups="[]" postgresql_groups="[]" test_full_suite="false" fi # We need to run the full suite on certain branches. # Or, in case core files are touched, for the full suite as well. if [[ "${GITHUB_REF}" == "refs/heads/dev" ]] \ || [[ "${GITHUB_REF}" == "refs/heads/master" ]] \ || [[ "${GITHUB_REF}" == "refs/heads/rc" ]] \ || [[ "${CORE_ANY}" == "true" ]] \ || [[ "${INPUT_FULL}" == "true" ]] \ || [[ "${HAS_CI_FULL_RUN_LABEL}" == "true" ]]; then mariadb_groups=${MARIADB_VERSIONS} postgresql_groups=${POSTGRESQL_VERSIONS} test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" test_group_count=10 test_full_suite="true" fi if [[ "${INPUT_LINT_ONLY}" == "true" ]] \ || [[ "${INPUT_PYLINT_ONLY}" == "true" ]] \ || [[ "${INPUT_MYPY_ONLY}" == "true" ]] \ || [[ "${INPUT_AUDIT_LICENSES_ONLY}" == "true" ]] \ || [[ "${GITHUB_EVENT_NAME}" == "push" \ && "${REPO_FULL_NAME}" != "home-assistant/core" ]]; then lint_only="true" skip_coverage="true" fi if [[ "${INPUT_SKIP_COVERAGE}" == "true" ]] \ || [[ "${HAS_CI_SKIP_COVERAGE_LABEL}" == "true" ]]; then skip_coverage="true" fi # Output & sent to GitHub Actions echo "mariadb_groups: ${mariadb_groups}" echo "mariadb_groups=${mariadb_groups}" >> $GITHUB_OUTPUT echo "postgresql_groups: ${postgresql_groups}" echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT echo "python_versions: ${ALL_PYTHON_VERSIONS}" echo "python_versions=${ALL_PYTHON_VERSIONS}" >> $GITHUB_OUTPUT echo "test_full_suite: ${test_full_suite}" echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT echo "integrations_glob: ${integrations_glob}" echo "integrations_glob=${integrations_glob}" >> $GITHUB_OUTPUT echo "test_group_count: ${test_group_count}" echo "test_group_count=${test_group_count}" >> $GITHUB_OUTPUT echo "test_groups: ${test_groups}" echo "test_groups=${test_groups}" >> $GITHUB_OUTPUT echo "tests: ${tests}" echo "tests=${tests}" >> $GITHUB_OUTPUT echo "tests_glob: ${tests_glob}" echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT echo "lint_only": ${lint_only} echo "lint_only=${lint_only}" >> $GITHUB_OUTPUT echo "skip_coverage: ${skip_coverage}" echo "skip_coverage=${skip_coverage}" >> $GITHUB_OUTPUT prek: name: Run prek checks runs-on: ubuntu-24.04 permissions: contents: read needs: [info] if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/yamllint.json" echo "::add-matcher::.github/workflows/matchers/check-json.json" echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json" echo "::add-matcher::.github/workflows/matchers/codespell.json" - name: Run prek uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1.1.1 env: PREK_SKIP: no-commit-to-branch,mypy,pylint,gen_requirements_all,hassfest,hassfest-metadata,hassfest-mypy-config,zizmor RUFF_OUTPUT_FORMAT: github zizmor: name: Check GitHub Actions workflows runs-on: ubuntu-24.04 permissions: contents: read # To check out the repository needs: [info] if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run zizmor uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1.1.1 with: extra-args: --all-files zizmor lint-hadolint: name: Check ${{ matrix.file }} runs-on: ubuntu-24.04 permissions: contents: read needs: [info] if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' strategy: fail-fast: false matrix: file: - Dockerfile - Dockerfile.dev - script/hassfest/docker/Dockerfile steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Register hadolint problem matcher run: | echo "::add-matcher::.github/workflows/matchers/hadolint.json" - name: Check ${{ matrix.file }} uses: docker://hadolint/hadolint:v2.12.0@sha256:30a8fd2e785ab6176eed53f74769e04f125afb2f74a6c52aef7d463583b6d45e with: args: hadolint ${{ matrix.file }} base: name: Prepare dependencies runs-on: ubuntu-24.04 permissions: contents: read needs: [info] timeout-minutes: 60 strategy: matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Generate partial uv restore key id: generate-uv-key run: | uv_version=$(cat requirements.txt | grep uv | cut -d '=' -f 3) echo "version=${uv_version}" >> $GITHUB_OUTPUT echo "key=uv-${UV_CACHE_VERSION}-${uv_version}-${HA_SHORT_VERSION}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore base Python virtual environment id: cache-venv uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Restore uv wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ${{ env.UV_CACHE_DIR }} key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ steps.generate-uv-key.outputs.key }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-uv-${{ env.UV_CACHE_VERSION }}-${{ steps.generate-uv-key.outputs.version }}-${{ env.HA_SHORT_VERSION }}- - name: Check if apt cache exists id: cache-apt-check uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: lookup-only: ${{ steps.cache-venv.outputs.cache-hit == 'true' }} path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies if: | steps.cache-venv.outputs.cache-hit != 'true' || steps.cache-apt-check.outputs.cache-hit != 'true' timeout-minutes: 10 env: APT_CACHE_HIT: ${{ steps.cache-apt-check.outputs.cache-hit }} run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list if [[ "${APT_CACHE_HIT}" != 'true' ]]; then mkdir -p ${APT_CACHE_DIR} mkdir -p ${APT_LIST_CACHE_DIR} fi sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg \ libxml2-utils \ libavcodec-dev \ libavdevice-dev \ libavfilter-dev \ libavformat-dev \ libavutil-dev \ libswresample-dev \ libswscale-dev \ libudev-dev if [[ "${APT_CACHE_HIT}" != 'true' ]]; then sudo chmod -R 755 ${APT_CACHE_BASE} fi - name: Save apt cache if: steps.cache-apt-check.outputs.cache-hit != 'true' uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | python -m venv venv . venv/bin/activate python --version pip install "$(grep '^uv' < requirements.txt)" uv pip install -U "pip>=25.2" uv pip install -r requirements.txt python -m script.gen_requirements_all ci uv pip install -r requirements_all_pytest.txt -r requirements_test.txt uv pip install -e . --config-settings editable_mode=compat - name: Dump pip freeze run: | python -m venv venv . venv/bin/activate python --version uv pip freeze >> pip_freeze.txt - name: Upload pip_freeze artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pip-freeze-${{ matrix.python-version }} path: pip_freeze.txt overwrite: true - name: Remove pip_freeze run: rm pip_freeze.txt - name: Remove generated requirements_all if: steps.cache-venv.outputs.cache-hit != 'true' run: rm requirements_all_pytest.txt requirements_all_wheels_*.txt - name: Check dirty run: | ./script/check_dirty hassfest: name: Check hassfest runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ libturbojpeg - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Run hassfest run: | . venv/bin/activate python -m script.hassfest --requirements --action validate gen-requirements-all: name: Check all requirements runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Run gen_requirements_all.py run: | . venv/bin/activate python -m script.gen_requirements_all validate gen-copilot-instructions: name: Check copilot instructions runs-on: ubuntu-24.04 permissions: contents: read needs: - info if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Run gen_copilot_instructions.py run: | python -m script.gen_copilot_instructions validate dependency-review: name: Dependency review runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' && needs.info.outputs.requirements == 'true' && github.event_name == 'pull_request' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Dependency review uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 with: license-check: false # We use our own license audit checks audit-licenses: name: Audit licenses runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base if: | (github.event.inputs.pylint-only != 'true' && github.event.inputs.mypy-only != 'true' || github.event.inputs.audit-licenses-only == 'true') && needs.info.outputs.requirements == 'true' strategy: fail-fast: false matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Extract license data env: PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python -m script.licenses extract --output-file=licenses-${PYTHON_VERSION}.json - name: Upload licenses uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: licenses-${{ github.run_number }}-${{ matrix.python-version }} path: licenses-${{ matrix.python-version }}.json - name: Check licenses env: PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python -m script.licenses check licenses-${PYTHON_VERSION}.json pylint: name: Check pylint runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base timeout-minutes: 20 if: | github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' || github.event.inputs.pylint-only == 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register pylint problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pylint.json" - name: Run pylint (fully) if: needs.info.outputs.test_full_suite == 'true' run: | . venv/bin/activate python --version pylint --ignore-missing-annotations=y homeassistant - name: Run pylint (partially) if: needs.info.outputs.test_full_suite == 'false' shell: bash env: INTEGRATIONS_GLOB: ${{ needs.info.outputs.integrations_glob }} run: | . venv/bin/activate python --version pylint --ignore-missing-annotations=y $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB}) pylint-tests: name: Check pylint on tests runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base timeout-minutes: 20 if: | (github.event.inputs.mypy-only != 'true' && github.event.inputs.audit-licenses-only != 'true' || github.event.inputs.pylint-only == 'true') && (needs.info.outputs.tests_glob || needs.info.outputs.test_full_suite == 'true') steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register pylint problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pylint.json" - name: Run pylint (fully) if: needs.info.outputs.test_full_suite == 'true' run: | . venv/bin/activate python --version pylint tests - name: Run pylint (partially) if: needs.info.outputs.test_full_suite == 'false' shell: bash env: TESTS_GLOB: ${{ needs.info.outputs.tests_glob }} run: | . venv/bin/activate python --version pylint $(printf "tests/components/%s " ${TESTS_GLOB}) mypy: name: Check mypy runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base if: | github.event.inputs.pylint-only != 'true' && github.event.inputs.audit-licenses-only != 'true' || github.event.inputs.mypy-only == 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Generate partial mypy restore key id: generate-mypy-key run: | mypy_version=$(cat requirements_test.txt | grep 'mypy.*=' | cut -d '=' -f 3) echo "version=${mypy_version}" >> $GITHUB_OUTPUT echo "key=mypy-${MYPY_CACHE_VERSION}-${mypy_version}-${HA_SHORT_VERSION}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Restore mypy cache uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: .mypy_cache key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ steps.generate-mypy-key.outputs.key }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-mypy-${{ env.MYPY_CACHE_VERSION }}-${{ steps.generate-mypy-key.outputs.version }}-${{ env.HA_SHORT_VERSION }}- - name: Register mypy problem matcher run: | echo "::add-matcher::.github/workflows/matchers/mypy.json" - name: Run mypy (fully) if: needs.info.outputs.test_full_suite == 'true' run: | . venv/bin/activate python --version mypy homeassistant pylint - name: Run mypy (partially) if: needs.info.outputs.test_full_suite == 'false' shell: bash env: INTEGRATIONS_GLOB: ${{ needs.info.outputs.integrations_glob }} run: | . venv/bin/activate python --version mypy $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB}) prepare-pytest-full: name: Split tests for full run runs-on: ubuntu-24.04 permissions: contents: read if: | needs.info.outputs.lint_only != 'true' && needs.info.outputs.test_full_suite == 'true' needs: - info - base - gen-requirements-all - hassfest - prek - mypy steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Run split_tests.py env: TEST_GROUP_COUNT: ${{ needs.info.outputs.test_group_count }} run: | . venv/bin/activate python -m script.split_tests ${TEST_GROUP_COUNT} tests - name: Upload pytest_buckets uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pytest_buckets path: pytest_buckets.txt overwrite: true pytest-full: name: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base - gen-requirements-all - hassfest - prek - mypy - prepare-pytest-full if: | needs.info.outputs.lint_only != 'true' && needs.info.outputs.test_full_suite == 'true' strategy: fail-fast: false matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} group: ${{ fromJson(needs.info.outputs.test_groups) }} steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg \ libxml2-utils - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Download pytest_buckets uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: pytest_buckets - name: Compile English translations run: | . venv/bin/activate python3 -m script.translations develop --all - name: Run pytest timeout-minutes: 60 id: pytest-full env: PYTHONDONTWRITEBYTECODE: 1 SKIP_COVERAGE: ${{ needs.info.outputs.skip_coverage }} TEST_GROUP: ${{ matrix.group }} PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python --version set -o pipefail cov_params=() if [[ "${SKIP_COVERAGE}" != "true" ]]; then cov_params+=(--cov="homeassistant") cov_params+=(--cov-report=xml) cov_params+=(--junitxml=junit.xml -o junit_family=legacy) fi echo "Test group ${TEST_GROUP}: $(sed -n "${TEST_GROUP},1p" pytest_buckets.txt)" python3 -b -X dev -m pytest \ -qq \ --timeout=9 \ --durations=10 \ --numprocesses auto \ --snapshot-details \ --dist=loadfile \ ${cov_params[@]} \ -o console_output_style=count \ -p no:sugar \ --exclude-warning-annotations \ $(sed -n "${TEST_GROUP},1p" pytest_buckets.txt) \ 2>&1 | tee pytest-${PYTHON_VERSION}-${TEST_GROUP}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-full.conclusion == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }} path: pytest-*.txt overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: coverage-${{ matrix.python-version }}-${{ matrix.group }} path: coverage.xml overwrite: true - name: Beautify test results # For easier identification of parsing errors if: needs.info.outputs.skip_coverage != 'true' run: | xmllint --format "junit.xml" > "junit.xml-tmp" mv "junit.xml-tmp" "junit.xml" - name: Upload test results artifact if: needs.info.outputs.skip_coverage != 'true' && !cancelled() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-results-full-${{ matrix.python-version }}-${{ matrix.group }} path: junit.xml - name: Remove pytest_buckets run: rm pytest_buckets.txt - name: Check dirty run: | ./script/check_dirty pytest-mariadb: name: Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }} runs-on: ubuntu-24.04 permissions: contents: read services: mariadb: image: ${{ matrix.mariadb-group }} # zizmor: ignore[unpinned-images] ports: - 3306:3306 env: MYSQL_ROOT_PASSWORD: password options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3 needs: - info - base - gen-requirements-all - hassfest - prek - mypy if: | needs.info.outputs.lint_only != 'true' && needs.info.outputs.mariadb_groups != '[]' strategy: fail-fast: false matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }} steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg \ libmariadb-dev-compat \ libxml2-utils - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Install SQL Python libraries run: | . venv/bin/activate uv pip install mysqlclient sqlalchemy_utils - name: Compile English translations run: | . venv/bin/activate python3 -m script.translations develop --all - name: Run pytest (partially) timeout-minutes: 20 id: pytest-partial shell: bash env: PYTHONDONTWRITEBYTECODE: 1 MARIADB_GROUP: ${{ matrix.mariadb-group }} SKIP_COVERAGE: ${{ needs.info.outputs.skip_coverage }} PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python --version set -o pipefail mariadb=$(echo "${MARIADB_GROUP}" | sed "s/:/-/g") echo "mariadb=${mariadb}" >> $GITHUB_OUTPUT cov_params=() if [[ "${SKIP_COVERAGE}" != "true" ]]; then cov_params+=(--cov="homeassistant.components.recorder") cov_params+=(--cov-report=xml) cov_params+=(--cov-report=term-missing) cov_params+=(--junitxml=junit.xml -o junit_family=legacy) fi python3 -b -X dev -m pytest \ -qq \ --timeout=20 \ --numprocesses 1 \ --snapshot-details \ ${cov_params[@]} \ -o console_output_style=count \ --durations=10 \ -p no:sugar \ --exclude-warning-annotations \ --dburl=mysql://root:password@127.0.0.1/homeassistant-test \ tests/components/history \ tests/components/logbook \ tests/components/recorder \ tests/components/sensor \ 2>&1 | tee pytest-${PYTHON_VERSION}-${mariadb}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-partial.conclusion == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.mariadb }} path: pytest-*.txt overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: coverage-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.mariadb }} path: coverage.xml overwrite: true - name: Beautify test results # For easier identification of parsing errors if: needs.info.outputs.skip_coverage != 'true' run: | xmllint --format "junit.xml" > "junit.xml-tmp" mv "junit.xml-tmp" "junit.xml" - name: Upload test results artifact if: needs.info.outputs.skip_coverage != 'true' && !cancelled() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-results-mariadb-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.mariadb }} path: junit.xml - name: Check dirty run: | ./script/check_dirty pytest-postgres: name: Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }} runs-on: ubuntu-24.04 permissions: contents: read services: postgres: image: ${{ matrix.postgresql-group }} # zizmor: ignore[unpinned-images] ports: - 5432:5432 env: POSTGRES_PASSWORD: password options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3 needs: - info - base - gen-requirements-all - hassfest - prek - mypy if: | needs.info.outputs.lint_only != 'true' && needs.info.outputs.postgresql_groups != '[]' strategy: fail-fast: false matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }} steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg \ libxml2-utils sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y sudo apt-get -y install \ postgresql-server-dev-14 - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Install SQL Python libraries run: | . venv/bin/activate uv pip install psycopg2 sqlalchemy_utils - name: Compile English translations run: | . venv/bin/activate python3 -m script.translations develop --all - name: Run pytest (partially) timeout-minutes: 20 id: pytest-partial shell: bash env: PYTHONDONTWRITEBYTECODE: 1 POSTGRESQL_GROUP: ${{ matrix.postgresql-group }} SKIP_COVERAGE: ${{ needs.info.outputs.skip_coverage }} PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python --version set -o pipefail postgresql=$(echo "${POSTGRESQL_GROUP}" | sed "s/:/-/g") echo "postgresql=${postgresql}" >> $GITHUB_OUTPUT cov_params=() if [[ "${SKIP_COVERAGE}" != "true" ]]; then cov_params+=(--cov="homeassistant.components.recorder") cov_params+=(--cov-report=xml) cov_params+=(--cov-report=term-missing) cov_params+=(--junitxml=junit.xml -o junit_family=legacy) fi python3 -b -X dev -m pytest \ -qq \ --timeout=9 \ --numprocesses 1 \ --snapshot-details \ ${cov_params[@]} \ -o console_output_style=count \ --durations=0 \ --durations-min=10 \ -p no:sugar \ --exclude-warning-annotations \ --dburl=postgresql://postgres:password@127.0.0.1/homeassistant-test \ tests/components/history \ tests/components/logbook \ tests/components/recorder \ tests/components/sensor \ 2>&1 | tee pytest-${PYTHON_VERSION}-${postgresql}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-partial.conclusion == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.postgresql }} path: pytest-*.txt overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: coverage-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.postgresql }} path: coverage.xml overwrite: true - name: Beautify test results # For easier identification of parsing errors if: needs.info.outputs.skip_coverage != 'true' run: | xmllint --format "junit.xml" > "junit.xml-tmp" mv "junit.xml-tmp" "junit.xml" - name: Upload test results artifact if: needs.info.outputs.skip_coverage != 'true' && !cancelled() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-results-postgres-${{ matrix.python-version }}-${{ steps.pytest-partial.outputs.postgresql }} path: junit.xml - name: Check dirty run: | ./script/check_dirty coverage-full: name: Upload test coverage to Codecov (full suite) runs-on: ubuntu-24.04 permissions: contents: read needs: - info - pytest-full - pytest-postgres - pytest-mariadb timeout-minutes: 10 if: needs.info.outputs.skip_coverage != 'true' steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Download all coverage artifacts uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: coverage-* - name: Upload coverage to Codecov if: needs.info.outputs.test_full_suite == 'true' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: fail_ci_if_error: true flags: full-suite token: ${{ secrets.CODECOV_TOKEN }} pytest-partial: name: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) runs-on: ubuntu-24.04 permissions: contents: read needs: - info - base - gen-requirements-all - hassfest - prek - mypy if: | needs.info.outputs.lint_only != 'true' && needs.info.outputs.tests_glob && needs.info.outputs.test_full_suite == 'false' strategy: fail-fast: false matrix: python-version: ${{ fromJson(needs.info.outputs.python_versions) }} group: ${{ fromJson(needs.info.outputs.test_groups) }} steps: - name: Restore apt cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ${{ env.APT_CACHE_DIR }} ${{ env.APT_LIST_CACHE_DIR }} fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }} - name: Install additional OS dependencies timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} sudo apt-get -y install \ -o Dir::Cache=${APT_CACHE_DIR} \ -o Dir::State::Lists=${APT_LIST_CACHE_DIR} \ bluez \ ffmpeg \ libturbojpeg \ libxml2-utils - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: venv fail-on-cache-miss: true key: >- ${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Compile English translations run: | . venv/bin/activate python3 -m script.translations develop --all - name: Run pytest timeout-minutes: 10 id: pytest-partial shell: bash env: PYTHONDONTWRITEBYTECODE: 1 TEST_GROUP: ${{ matrix.group }} SKIP_COVERAGE: ${{ needs.info.outputs.skip_coverage }} PYTHON_VERSION: ${{ matrix.python-version }} run: | . venv/bin/activate python --version set -o pipefail if [[ ! -f "tests/components/${TEST_GROUP}/__init__.py" ]]; then echo "::error:: missing file tests/components/${TEST_GROUP}/__init__.py" exit 1 fi cov_params=() if [[ "${SKIP_COVERAGE}" != "true" ]]; then cov_params+=(--cov="homeassistant.components.${TEST_GROUP}") cov_params+=(--cov-report=xml) cov_params+=(--cov-report=term-missing) cov_params+=(--junitxml=junit.xml -o junit_family=legacy) fi python3 -b -X dev -m pytest \ -qq \ --timeout=9 \ --numprocesses auto \ --snapshot-details \ ${cov_params[@]} \ -o console_output_style=count \ --durations=0 \ --durations-min=1 \ -p no:sugar \ --exclude-warning-annotations \ tests/components/${TEST_GROUP} \ 2>&1 | tee pytest-${PYTHON_VERSION}-${TEST_GROUP}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-partial.conclusion == 'failure' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }} path: pytest-*.txt overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: coverage-${{ matrix.python-version }}-${{ matrix.group }} path: coverage.xml overwrite: true - name: Beautify test results # For easier identification of parsing errors if: needs.info.outputs.skip_coverage != 'true' run: | xmllint --format "junit.xml" > "junit.xml-tmp" mv "junit.xml-tmp" "junit.xml" - name: Upload test results artifact if: needs.info.outputs.skip_coverage != 'true' && !cancelled() uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: test-results-partial-${{ matrix.python-version }}-${{ matrix.group }} path: junit.xml - name: Check dirty run: | ./script/check_dirty coverage-partial: name: Upload test coverage to Codecov (partial suite) if: needs.info.outputs.skip_coverage != 'true' runs-on: ubuntu-24.04 permissions: contents: read timeout-minutes: 10 needs: - info - pytest-partial steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Download all coverage artifacts uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: coverage-* - name: Upload coverage to Codecov if: needs.info.outputs.test_full_suite == 'false' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} upload-test-results: name: Upload test results to Codecov runs-on: ubuntu-24.04 needs: - info - pytest-partial - pytest-full - pytest-postgres - pytest-mariadb timeout-minutes: 10 permissions: id-token: write # For Codecov OIDC upload # codecov/test-results-action currently doesn't support tokenless uploads # therefore we can't run it on forks if: | (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) && needs.info.outputs.skip_coverage != 'true' && !cancelled() steps: - name: Download all coverage artifacts uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: test-results-* - name: Upload test results to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: report_type: test_results fail_ci_if_error: true verbose: true use_oidc: true