diff --git a/.github/actions/bootstrap-poetry/action.yaml b/.github/actions/bootstrap-poetry/action.yaml index cec135d89..1ab66f8c0 100644 --- a/.github/actions/bootstrap-poetry/action.yaml +++ b/.github/actions/bootstrap-poetry/action.yaml @@ -35,10 +35,7 @@ runs: allow-prereleases: ${{ inputs.python-prereleases == 'true' }} update-environment: false - - run: > - pipx install \ - ${{ inputs.python-version != 'default' && format('--python "{0}"', steps.setup-python.outputs.python-path) || '' }} \ - '${{ inputs.poetry-spec }}' + - run: pipx install ${{ inputs.python-version != 'default' && format('--python "{0}"', steps.setup-python.outputs.python-path) || '' }} '${{ inputs.poetry-spec }}' shell: bash # Enable handling long path names (+260 char) on the Windows platform diff --git a/.github/actions/poetry-install/action.yaml b/.github/actions/poetry-install/action.yaml index 9436303e3..37109a0de 100644 --- a/.github/actions/poetry-install/action.yaml +++ b/.github/actions/poetry-install/action.yaml @@ -16,10 +16,6 @@ outputs: description: Whether an exact cache hit occured value: ${{ steps.cache.outputs.cache-hit }} -defaults: - run: - working-directory: ${{ inputs.path }} - runs: using: composite steps: @@ -48,10 +44,13 @@ runs: enableCrossOsArchive: true - run: poetry install ${{ inputs.args }} + working-directory: ${{ inputs.path }} shell: bash - run: poetry env info + working-directory: ${{ inputs.path }} shell: bash - run: poetry show + working-directory: ${{ inputs.path }} shell: bash diff --git a/.github/workflows/.tests-matrix.yaml b/.github/workflows/.tests-matrix.yaml new file mode 100644 index 000000000..68fa9071d --- /dev/null +++ b/.github/workflows/.tests-matrix.yaml @@ -0,0 +1,98 @@ +# Reusable workflow consumed by tests.yaml; used to share a single matrix across jobs. +on: + workflow_call: + inputs: + runner: + required: true + type: string + python-version: + required: true + type: string + run-mypy: + required: true + type: boolean + run-pytest: + required: true + type: boolean + run-pytest-poetry: + required: true + type: boolean + +defaults: + run: + shell: bash + +jobs: + mypy: + name: mypy + runs-on: ${{ inputs.runner }} + if: inputs.run-mypy + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: ./.github/actions/bootstrap-poetry + id: bootstrap-poetry + with: + python-version: ${{ inputs.python-version }} + + - uses: ./.github/actions/poetry-install + + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4 + with: + path: .mypy_cache + key: mypy-${{ runner.os }}-py${{ steps.bootstrap-poetry.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'poetry.lock') }} + restore-keys: | + mypy-${{ runner.os }}-py${{ steps.bootstrap-poetry.outputs.python-version }}- + mypy-${{ runner.os }}- + + - run: poetry run mypy + + pytest: + name: pytest + runs-on: ${{ inputs.runner }} + if: inputs.run-pytest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: ./.github/actions/bootstrap-poetry + with: + python-version: ${{ inputs.python-version }} + + - uses: ./.github/actions/poetry-install + with: + args: --with github-actions + + - run: poetry run pytest --integration -v + + - run: git diff --exit-code --stat HEAD + + pytest-poetry: + name: pytest (Poetry) + runs-on: ${{ inputs.runner }} + if: inputs.run-pytest-poetry + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + path: poetry-core + + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + path: poetry + repository: python-poetry/poetry + + - uses: ./poetry-core/.github/actions/bootstrap-poetry + with: + python-version: ${{ inputs.python-version }} + + - uses: ./poetry-core/.github/actions/poetry-install + with: + args: --with github-actions + path: ./poetry + + - run: poetry add ../poetry-core + working-directory: ./poetry + + - run: poetry run pytest -v + working-directory: ./poetry + + - run: git -C poetry diff --exit-code --stat HEAD diff --git a/.github/workflows/downstream.yaml b/.github/workflows/downstream.yaml deleted file mode 100644 index 9e53293c8..000000000 --- a/.github/workflows/downstream.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: Poetry Downstream Tests - -on: - pull_request: {} - push: - branches: [main] - -jobs: - tests: - name: ${{ matrix.ref }} - runs-on: ubuntu-latest - strategy: - matrix: - ref: ["main"] - fail-fast: false - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - with: - path: poetry-core - - - uses: actions/checkout@v4 - with: - path: poetry - repository: python-poetry/poetry - ref: ${{ matrix.ref }} - - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Get full python version - id: full-python-version - run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT - - - name: Set up Poetry - run: | - pip install poetry - poetry config virtualenvs.in-project true - - - name: Set up cache - uses: actions/cache@v4 - id: cache - with: - path: ./poetry/.venv - key: venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Ensure cache is healthy - if: steps.cache.outputs.cache-hit == 'true' - working-directory: ./poetry - run: timeout 10s poetry run pip --version >/dev/null 2>&1 || rm -rf .venv - - - name: Switch downstream to development poetry-core - working-directory: ./poetry - run: | - # remove poetry-core from main group to avoid version conflicts - # with a potential entry in the test group - poetry remove poetry-core - # add to test group to overwrite a potential entry in that group - poetry add --lock --group test ../poetry-core - - - name: Install downstream dependencies - working-directory: ./poetry - run: | - # force update of directory dependency in cached venv - # (even if directory dependency with same version is already installed) - poetry run pip uninstall -y poetry-core - poetry install - - # TODO: mark run as success even when this fails and add comment to PR instead - - name: Run downstream test suite - working-directory: ./poetry - run: poetry run pytest diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 42a7b9a56..848439558 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,81 +1,102 @@ name: Tests on: - pull_request: {} + merge_group: + pull_request: push: - branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +defaults: + run: + shell: bash env: PYTHONWARNDEFAULTENCODING: 'true' jobs: - tests: - name: ${{ matrix.os }} / ${{ matrix.python-version }} - runs-on: "${{ matrix.os }}-latest" + changes: + name: Detect changed files + runs-on: ubuntu-latest + outputs: + project: ${{ steps.changes.outputs.project }} + src: ${{ steps.changes.outputs.src }} + tests: ${{ steps.changes.outputs.tests }} + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 + id: changes + with: + filters: | + workflow: &workflow + - '.github/actions/**' + - '.github/workflows/tests.yaml' + - '.github/workflows/.tests-matrix.yaml' + project: &project + - *workflow + - 'poetry.lock' + - 'pyproject.toml' + src: + - *project + - 'src/**/*.py' + tests: + - *project + - 'src/**/*.py' + - 'tests/**' + + lockfile: + name: Check poetry.lock + runs-on: ubuntu-latest + if: needs.changes.outputs.project == 'true' + needs: changes + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: ./.github/actions/bootstrap-poetry + + - run: poetry check --lock + + tests-matrix: + # Use this matrix with multiple jobs defined in a reusable workflow: + uses: ./.github/workflows/.tests-matrix.yaml + name: ${{ matrix.os.name }} (Python ${{ matrix.python-version }}) + if: '!failure() && !cancelled()' + needs: + - lockfile + - changes + with: + runner: ${{ matrix.os.image }} + python-version: ${{ matrix.python-version }} + run-mypy: ${{ needs.changes.outputs.src == 'true' }} + run-pytest: ${{ needs.changes.outputs.tests == 'true' }} + run-pytest-poetry: ${{ needs.changes.outputs.src == 'true' }} + secrets: inherit strategy: matrix: - os: [Ubuntu, MacOS, Windows] + os: + - name: Ubuntu + image: ubuntu-22.04 + - name: macOS + image: macos-13 + - name: Windows + image: windows-2022 python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] include: - - os: Ubuntu - python-version: pypy-3.8 + - os: {name: Ubuntu, image: ubuntu-22.04} + python-version: pypy3.9 + - os: {name: Ubuntu, image: ubuntu-22.04} + python-version: pypy3.10 fail-fast: false - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Get full Python version - id: full-python-version - run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT - - - name: Bootstrap poetry - run: | - curl -sSL https://install.python-poetry.org | python - -y - - name: Update PATH - if: ${{ matrix.os != 'Windows' }} - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Update Path for Windows - if: ${{ matrix.os == 'Windows' }} - run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH - - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - - name: Set up cache - uses: actions/cache@v4 - id: cache - with: - path: .venv - key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} - - - name: Ensure cache is healthy - if: steps.cache.outputs.cache-hit == 'true' - run: | - # `timeout` is not available on macOS, so we define a custom function. - [ "$(command -v timeout)" ] || function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } - # Using `timeout` is a safeguard against the Poetry command hanging for some reason. - timeout 10s poetry run pip --version || rm -rf .venv - - - name: Check lock file - run: poetry lock --check - - - name: Install dependencies - run: poetry install - - - name: Run tests - run: poetry run python -m pytest -p no:sugar -q tests/ - - - name: Run integration tests - run: poetry run python -m pytest -p no:sugar --integration -q tests/integration - - - name: Run mypy - run: poetry run mypy + status: + name: Status + runs-on: ubuntu-latest + if: '!cancelled()' + needs: + - lockfile + - tests-matrix + steps: + - run: ${{ (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && 'false' || 'true' }}