Compare commits

...

62 Commits

Author SHA1 Message Date
Bruno Oliveira
95b309e980 Release 3.5.0 2023-11-21 11:33:54 -03:00
Henk-Jaap Wagenaar
3fe877bc6d LoadScope scheduler: Sort scopes by number of tests to assign biggest scopes first (#778)
Follow up to # 632.

---------

Co-authored-by: Jörg Kohlsdorf <joerg@cornershopapp.com>
2023-11-21 11:32:29 -03:00
Bruno Oliveira
f36ea25cb5 Create GitHub release after deploy (#973)
Fixes #972
2023-11-21 11:26:23 -03:00
Bruno Oliveira
d81f57508d Merge pull request #969 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-11-14 08:43:15 -03:00
pre-commit-ci[bot]
1d2616418c [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0)
2023-11-13 21:01:19 +00:00
Bruno Oliveira
5f43575b42 Merge pull request #966 from pytest-dev/release-3.4.0
Release 3.4.0
2023-11-11 13:10:39 -03:00
Bruno Oliveira
a6b56112f9 Update CHANGELOG 2023-11-11 12:13:49 -03:00
Bruno Oliveira
64f9388f41 Add support for Python 3.12
Fix changelog entries from "improvement" to "feature" (there's "improvement" in our towncrier configuration).
2023-11-11 12:13:49 -03:00
Paul Ollis
230ba6ad10 Properly wait for workers when test run terminates early (#963)
Currently, a reason to terminate early (e.g. test failure with --exitfail
option set) causes DSession to immediately raise an Interrupt exception.
Subsequent reports generated by the workers, during the shutdown phase, are
discarded.

One consequence is that, for the failing test, teardown and testfinish are
ignored, which prevents corresponding hooks pytest_runtest_logreport and
pytest_runtest_logfinish being executed for the failing test (this problem
covered in #54). The reporting of tests executing in other workers is also left in an
indeterminate state. This can affect other plugin code.

This is a relatively simple fix, which appears to have minimal and, I think,
acceptable impact on text execution behaviour.

The observable differences are differences in what is reported about a test
run. For example, when running, for example with the '-x/--exitfail' option, it
is possible that more than a single test failure is reported. This is because
more than one test did fail before the test run was completely stopped. The
reporting is absolutely correct; and complete. Prior to this change, only a
single failure would have been reported, but because of incomplete and arguably
incorrect reporting.
2023-11-11 11:24:21 -03:00
Phillip Yen
93ca202b40 fix typo index -> instead 2023-11-04 14:24:08 -03:00
pre-commit-ci[bot]
8f3d1ad92f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.9.1 → 23.10.1](https://github.com/psf/black/compare/23.9.1...23.10.1)
- [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1)
2023-10-24 08:34:08 -03:00
Bruno Oliveira
ab3d6a9ee5 Merge pull request #955 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-17 09:08:19 -03:00
pre-commit-ci[bot]
a5210d4103 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.6.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.6.0)
2023-10-16 20:24:41 +00:00
Bruno Oliveira
0b6b9c3c38 Merge pull request #954 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-10-10 08:15:59 -03:00
Bruno Oliveira
838434c269 Install requirements.txt while building docs 2023-10-10 08:14:19 -03:00
Bruno Oliveira
b85a4717f9 Add docs/requirements.txt 2023-10-10 08:13:46 -03:00
pre-commit-ci[bot]
b2770e65e1 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)
- [github.com/asottile/pyupgrade: v3.14.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.14.0...v3.15.0)
2023-10-09 21:00:06 +00:00
pre-commit-ci[bot]
4a3a8b499b [pre-commit.ci] pre-commit autoupdate (#953)
updates:
- [github.com/asottile/pyupgrade: v3.13.0 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.14.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-03 09:28:00 -03:00
pre-commit-ci[bot]
4016c2a173 [pre-commit.ci] pre-commit autoupdate (#951)
updates:
- [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-26 06:58:37 -03:00
pre-commit-ci[bot]
ddc28f3870 [pre-commit.ci] pre-commit autoupdate (#949)
updates:
- [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-19 10:10:24 -03:00
pre-commit-ci[bot]
02ddfc9860 [pre-commit.ci] pre-commit autoupdate (#948)
updates:
- [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-12 07:50:26 -03:00
Bruno Oliveira
f15de24e5a Merge pull request #946 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-09-05 07:24:48 -03:00
Bruno Oliveira
49a1093574 Add readthedocs configuration file -- now required 2023-09-05 07:18:41 -03:00
Bruno Oliveira
470f4ee53c Fix warn_on_impl parameter on newhooks.py 2023-09-05 07:18:11 -03:00
pre-commit-ci[bot]
733d769bc0 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/autoflake: v2.2.0 → v2.2.1](https://github.com/PyCQA/autoflake/compare/v2.2.0...v2.2.1)
2023-09-05 08:25:26 +00:00
pre-commit-ci[bot]
8ff5c62cfe [pre-commit.ci] pre-commit autoupdate (#941)
updates:
- [github.com/asottile/blacken-docs: 1.15.0 → 1.16.0](https://github.com/asottile/blacken-docs/compare/1.15.0...1.16.0)
- [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-26 16:38:37 -03:00
Bruno Oliveira
6b52e0a23d Merge pull request #939 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-08-15 09:26:03 -03:00
pre-commit-ci[bot]
d5313c5660 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0)
2023-08-15 07:24:55 +00:00
Bruno Oliveira
bfd28ff398 Merge pull request #934 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-08-02 09:51:08 -03:00
Ronny Pfannschmidt
17da7cbe59 correct the type compare the updated flake8 found 2023-08-01 09:12:00 +00:00
pre-commit-ci[bot]
caff114004 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0)
- [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1)
2023-08-01 08:52:03 +00:00
pre-commit-ci[bot]
6f0cb26fc0 [pre-commit.ci] pre-commit autoupdate (#929)
updates:
- [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0)
- [github.com/asottile/blacken-docs: 1.14.0 → 1.15.0](https://github.com/asottile/blacken-docs/compare/1.14.0...1.15.0)
- [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-11 07:58:21 -03:00
Bruno Oliveira
5661f498c7 Docs: clarify that only rsync support is deprecated (#927)
Related to #839
2023-07-10 07:53:38 -03:00
Ronny Pfannschmidt
380f978243 Merge pull request #926 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-07-04 09:29:28 +02:00
pre-commit-ci[bot]
f609fb7de4 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.7.0 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.8.0)
2023-07-04 06:41:04 +00:00
pre-commit-ci[bot]
03a64d91f3 [pre-commit.ci] pre-commit autoupdate (#923)
updates:
- [github.com/PyCQA/autoflake: v2.1.1 → v2.2.0](https://github.com/PyCQA/autoflake/compare/v2.1.1...v2.2.0)
- [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-27 08:07:12 -03:00
pre-commit-ci[bot]
7d14d50f65 [pre-commit.ci] pre-commit autoupdate (#920)
updates:
- [github.com/asottile/blacken-docs: 1.13.0 → 1.14.0](https://github.com/asottile/blacken-docs/compare/1.13.0...1.14.0)
- [github.com/asottile/pyupgrade: v3.6.0 → v3.7.0](https://github.com/asottile/pyupgrade/compare/v3.6.0...v3.7.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-20 07:56:49 -03:00
Bruno Oliveira
56a4f212b7 Merge pull request #918 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-06-13 08:22:42 -03:00
pre-commit-ci[bot]
a4c887b058 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0)
2023-06-13 05:13:50 +00:00
Bruno Oliveira
7e1768f838 Merge pull request #910 from pytest-dev/release-3.3.1
Release 3.3.1
2023-05-19 07:55:03 -03:00
Bruno Oliveira
9791021f02 Release 3.3.1 2023-05-19 07:44:21 -03:00
Bruno Oliveira
4e7bd0239e Avoid unnecessary rinfo calls after creating gateways (#909)
Hopefully this fixes #907, as seems this is the only
change in #901 which is somehow related.

---------

Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
2023-05-19 07:43:18 -03:00
pre-commit-ci[bot]
52a61434fb [pre-commit.ci] pre-commit autoupdate (#908)
updates:
- [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-16 06:20:14 -03:00
Bruno Oliveira
9efc64eef9 Merge pull request #906 from nicoddemus/ci-improvements
CI improvements
2023-05-15 08:21:30 -03:00
Bruno Oliveira
6d39025be3 Use same package in all test runs 2023-05-12 17:59:46 -03:00
Bruno Oliveira
d8fb60ba7f Fix tag creation in deploy workflow
As commented in #905, an annotated tag needs a configured user.
2023-05-12 17:52:21 -03:00
Bruno Oliveira
4d230b6de7 Merge pull request #905 from pytest-dev/release-3.3.0
Release 3.3.0
2023-05-12 17:48:57 -03:00
Bruno Oliveira
f5b4a5d6ed Configure 'test' workflow to cancel running jobs for the branch 2023-05-12 17:33:18 -03:00
Bruno Oliveira
657ba9ac7f Only test on-push for master and a specific branch name
This avoids testing twice when pushing to the main repository, once for the push, and another for a PR.
2023-05-12 17:33:18 -03:00
Bruno Oliveira
fa08e0987b Add check-package step also to 'test' workflow
This ensures we do not have any surprises when we try to deploy,
which uses the same step to generate the package.
2023-05-12 17:33:18 -03:00
Bruno Oliveira
103ee05f01 Release 3.3.0 2023-05-12 12:24:43 -03:00
Bruno Oliveira
37b9dbdbf4 Deploy via a protected environment and using PyPI trusted publishers (#902)
Following recent discussions, this changes the development process as follows:

1. The deploy is now manually triggered after the release PR is approved.
2. The deploy workflow tags the repository only after the package has been published to PyPI.
3. Use PyPI trusted publishers instead of API tokens.


Co-authored-by: Ran Benita <ran@unusedvar.com>
2023-05-12 12:17:55 -03:00
Bruno Oliveira
be1d5c401f Make collection progress output less verbose (#901)
Fix #555
2023-05-12 10:36:32 -03:00
Ran Benita
e48dc3c6ad Merge pull request #900 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-05-10 14:48:21 +03:00
pre-commit-ci[bot]
a338075c0b [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](https://github.com/asottile/pyupgrade/compare/v3.3.2...v3.4.0)
2023-05-09 05:16:48 +00:00
pre-commit-ci[bot]
8fbecd8a23 [pre-commit.ci] pre-commit autoupdate (#897)
updates:
- [github.com/PyCQA/autoflake: v2.1.0 → v2.1.1](https://github.com/PyCQA/autoflake/compare/v2.1.0...v2.1.1)
- [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-25 07:37:20 -03:00
pre-commit-ci[bot]
6317cc32fa [pre-commit.ci] pre-commit autoupdate (#896)
updates:
- [github.com/PyCQA/autoflake: v2.0.2 → v2.1.0](https://github.com/PyCQA/autoflake/compare/v2.0.2...v2.1.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-21 15:15:53 -03:00
pre-commit-ci[bot]
4bed067b06 [pre-commit.ci] pre-commit autoupdate (#895)
updates:
- [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-11 07:55:12 -03:00
Robert Schweizer
982ef0097a Improve docs of maxschedchunk (#893) 2023-04-06 09:46:34 -03:00
pre-commit-ci[bot]
c7d86097e9 [pre-commit.ci] pre-commit autoupdate (#892)
updates:
- [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-04 09:03:28 -03:00
pre-commit-ci[bot]
6719dfd6f8 [pre-commit.ci] pre-commit autoupdate (#889)
updates:
- [github.com/PyCQA/autoflake: v2.0.1 → v2.0.2](https://github.com/PyCQA/autoflake/compare/v2.0.1...v2.0.2)
- [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-14 07:52:23 -03:00
Aleksandr Mezin
6e7bbfae37 Merge pull request #888 from amezin/release-3.2.1
Release 3.2.1
2023-03-12 17:11:47 +02:00
19 changed files with 428 additions and 108 deletions

View File

@@ -1,33 +1,55 @@
name: deploy
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
version:
description: 'Release version'
required: true
default: '1.2.3'
jobs:
deploy:
package:
runs-on: ubuntu-latest
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}
steps:
- uses: actions/checkout@v3
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5
deploy:
needs: package
runs-on: ubuntu-latest
environment: deploy
permissions:
id-token: write # For PyPI trusted publishers.
contents: write # For tag.
steps:
- uses: actions/checkout@v3
- name: Download Package
uses: actions/download-artifact@v3
with:
# Needed to fetch tags, which are required by setuptools-scm.
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Install build
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: |
python -m build
name: Packages
path: dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@v1.8.5
- name: Push tag
run: |
git config user.name "pytest bot"
git config user.email "pytestbot@gmail.com"
git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }}
git push origin v${{ github.event.inputs.version }}
- name: GitHub Release
uses: softprops/action-gh-release@v1
with:
user: __token__
password: ${{ secrets.pypi_token }}
files: dist/*
tag_name: v${{ github.event.inputs.version }}

View File

@@ -3,15 +3,31 @@ name: test
on:
push:
branches:
- "*"
- master
- "test-me-*"
pull_request:
branches:
- "*"
# Cancel running jobs for the same workflow and branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5
test:
needs: [package]
runs-on: ${{ matrix.os }}
strategy:
@@ -22,9 +38,9 @@ jobs:
- "py38-pytestlatest"
- "py39-pytestlatest"
- "py310-pytestlatest"
- "py310-pytestmain"
- "py311-pytestlatest"
- "py311-pytestmain"
- "py312-pytestlatest"
- "py310-psutil"
- "py310-setproctitle"
@@ -38,12 +54,12 @@ jobs:
python: "3.9"
- tox_env: "py310-pytestlatest"
python: "3.10"
- tox_env: "py310-pytestmain"
python: "3.10"
- tox_env: "py311-pytestlatest"
python: "3.11"
- tox_env: "py311-pytestmain"
python: "3.11"
- tox_env: "py312-pytestlatest"
python: "3.12"
- tox_env: "py310-psutil"
python: "3.10"
- tox_env: "py310-setproctitle"
@@ -54,14 +70,24 @@ jobs:
with:
# Needed to fetch tags, which are required by setuptools-scm.
fetch-depth: 0
- name: Download Package
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Test
shell: bash
run: |
tox -e ${{ matrix.tox_env }}
tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz`

View File

@@ -1,32 +1,32 @@
repos:
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.1
rev: v2.2.1
hooks:
- id: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.11.0
hooks:
- id: black
args: [--safe, --quiet, --target-version, py35]
- repo: https://github.com/asottile/blacken-docs
rev: 1.13.0
rev: 1.16.0
hooks:
- id: blacken-docs
additional_dependencies: [black==23.1.0]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statements
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py3-plus]
@@ -39,7 +39,7 @@ repos:
language: python
additional_dependencies: [pygments, restructuredtext_lint]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1
rev: v1.7.0
hooks:
- id: mypy
files: ^(src/|testing/)

14
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,14 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
python:
install:
- path: .
- requirements: docs/requirements.txt

View File

@@ -1,3 +1,49 @@
pytest-xdist 3.5.0 (2023-11-21)
===============================
Features
--------
- `#632 <https://github.com/pytest-dev/pytest-xdist/issues/632>`_: ``--dist=loadscope`` now sorts scopes by number of tests to assign largest scopes early -- in many cases this should improve overall test session running time, as there is less chance of a large scope being left to be processed near the end of the session, leaving other workers idle.
pytest-xdist 3.4.0 (2023-11-11)
===============================
Features
--------
- `#963 <https://github.com/pytest-dev/pytest-xdist/issues/963>`_: Wait for workers to finish reporting when test run stops early.
This makes sure that the results of in-progress tests are displayed.
Previously these reports were being discarded, losing information about the
test run.
- `#965 <https://github.com/pytest-dev/pytest-xdist/issues/965>`_: Added support for Python 3.12.
pytest-xdist 3.3.1 (2023-05-19)
===============================
Bug Fixes
---------
- `#907 <https://github.com/pytest-dev/pytest-xdist/issues/907>`_: Avoid remote calls during startup as ``execnet`` by default does not ensure remote affinity with the
main thread and might accidentally schedule the pytest worker into a non-main thread, which breaks numerous frameworks,
for example ``asyncio``, ``anyio``, ``PyQt/PySide``, etc.
A more safe correction will require thread affinity in ``execnet`` (`pytest-dev/execnet#96 <https://github.com/pytest-dev/execnet/issues/96>`__).
pytest-xdist 3.3.0 (2023-05-12)
===============================
Features
--------
- `#555 <https://github.com/pytest-dev/pytest-xdist/issues/555>`_: Improved progress output when collecting nodes to be less verbose.
pytest-xdist 3.2.1 (2023-03-12)
===============================

View File

@@ -32,14 +32,8 @@ To publish a new release ``X.Y.Z``, the steps are as follows:
$ tox -e release -- X.Y.Z
#. Commit and push the branch for review.
#. Commit and push the branch to ``upstream`` and open a PR.
#. Once PR is **green** and **approved**, create and push a tag::
#. Once the PR is **green** and **approved**, start the ``deploy`` workflow manually from the branch ``release-VERSION``, passing ``VERSION`` as parameter.
$ export VERSION=X.Y.Z
$ git tag v$VERSION release-$VERSION
$ git push git@github.com:pytest-dev/pytest-xdist.git v$VERSION
That will build the package and publish it on ``PyPI`` automatically.
#. Merge the release PR to `master`.
#. Merge the release PR to ``master``.

View File

@@ -50,7 +50,8 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
.. _distribution modes:
* ``--dist load`` **(default)**: Sends pending tests to any worker that is
available, without any guaranteed order.
available, without any guaranteed order. Scheduling can be fine-tuned with
the `--maxschedchunk` option, see output of `pytest --help`.
* ``--dist loadscope``: Tests are grouped by **module** for *test functions*
and by **class** for *test methods*. Groups are distributed to available

View File

@@ -29,8 +29,8 @@ The execution flow is:
corresponds to the position of that test in the original collection
list. This works because all nodes have the same collection list, and
saves bandwidth because the **controller** can now tell one of the
workers to just *execute test index 3* index of passing the full test
id.
workers to just *execute test index 3* instead of passing the full
test id.
4. If **dist-mode** is **each**: the **controller** just sends the full
list of test indexes to each node at this moment.

View File

@@ -9,13 +9,16 @@ Sending tests to remote SSH accounts
.. warning::
This feature is deprecated because the support for ``rsync`` is faulty
The ``rsync`` feature is deprecated because its implementation is faulty
in terms of reproducing the development environment in the remote
worker, and there is no clear solution moving forward.
This feature is scheduled to be removed in release 4.0, to let the team
For that reason, ``rsync`` is scheduled to be removed in release 4.0, to let the team
focus on a smaller set of features.
Note that SSH and socket server are not planned for removal, as they are part
of the ``execnet`` feature set.
Suppose you have a package ``mypkg`` which contains some
tests that you can successfully run locally. And you
have a ssh-reachable machine ``myhost``. Then

2
docs/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
sphinx
sphinx-rtd-theme

View File

@@ -2,6 +2,7 @@
name = pytest-xdist
description = pytest xdist plugin for distributed testing, most importantly across multiple CPUs
long_description = file: README.rst
long_description_content_type = text/x-rst
license = MIT
author = holger krekel and contributors
author_email = pytest-dev@python.org,holger@merlinux.eu
@@ -29,6 +30,7 @@ classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
license_file = LICENSE
project_urls =
Documentation=https://pytest-xdist.readthedocs.io/en/latest

View File

@@ -1,3 +1,8 @@
from __future__ import annotations
import sys
from enum import Enum, auto
from typing import Sequence
import pytest
from xdist.remote import Producer
@@ -113,11 +118,14 @@ class DSession:
assert self.sched is not None
self.shouldstop = False
pending_exception = None
while not self.session_finished:
self.loop_once()
if self.shouldstop:
self.triggershutdown()
raise Interrupted(str(self.shouldstop))
pending_exception = Interrupted(str(self.shouldstop))
if pending_exception:
raise pending_exception
return True
def loop_once(self):
@@ -251,14 +259,16 @@ class DSession:
self._session.testscollected = len(ids)
self.sched.add_node_collection(node, ids)
if self.terminal:
self.trdist.setstatus(node.gateway.spec, "[%d]" % (len(ids)))
self.trdist.setstatus(
node.gateway.spec, WorkerStatus.CollectionDone, tests_collected=len(ids)
)
if self.sched.collection_is_completed:
if self.terminal and not self.sched.has_pending:
self.trdist.ensure_show_status()
self.terminal.write_line("")
if self.config.option.verbose > 0:
self.terminal.write_line(
"scheduling tests via %s" % (self.sched.__class__.__name__)
f"scheduling tests via {self.sched.__class__.__name__}"
)
self.sched.schedule()
@@ -344,10 +354,15 @@ class DSession:
def _handlefailures(self, rep):
if rep.failed:
self.countfailures += 1
if self.maxfail and self.countfailures >= self.maxfail:
self.shouldstop = "stopping after %d failures" % (self.countfailures)
if (
self.maxfail
and self.countfailures >= self.maxfail
and not self.shouldstop
):
self.shouldstop = f"stopping after {self.countfailures} failures"
def triggershutdown(self):
if not self.shuttingdown:
self.log("triggering shutdown")
self.shuttingdown = True
for node in self.sched.nodes:
@@ -372,31 +387,50 @@ class DSession:
self.config.hook.pytest_runtest_logreport(report=rep)
class WorkerStatus(Enum):
"""Status of each worker during creation/collection."""
# Worker spec has just been created.
Created = auto()
# Worker has been initialized.
Initialized = auto()
# Worker is now ready for collection.
ReadyForCollection = auto()
# Worker has finished collection.
CollectionDone = auto()
class TerminalDistReporter:
def __init__(self, config):
def __init__(self, config) -> None:
self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter")
self._status = {}
self._status: dict[str, tuple[WorkerStatus, int]] = {}
self._lastlen = 0
self._isatty = getattr(self.tr, "isatty", self.tr.hasmarkup)
def write_line(self, msg):
def write_line(self, msg: str) -> None:
self.tr.write_line(msg)
def ensure_show_status(self):
def ensure_show_status(self) -> None:
if not self._isatty:
self.write_line(self.getstatus())
def setstatus(self, spec, status, show=True):
self._status[spec.id] = status
def setstatus(
self, spec, status: WorkerStatus, *, tests_collected: int, show: bool = True
) -> None:
self._status[spec.id] = (status, tests_collected)
if show and self._isatty:
self.rewrite(self.getstatus())
def getstatus(self):
def getstatus(self) -> str:
if self.config.option.verbose >= 0:
parts = [f"{spec.id} {self._status[spec.id]}" for spec in self._specs]
return " / ".join(parts)
else:
line = get_workers_status_line(list(self._status.values()))
if line:
return line
return "bringing up nodes..."
def rewrite(self, line, newline=False):
@@ -409,37 +443,40 @@ class TerminalDistReporter:
self.tr.rewrite(pline, bold=True)
@pytest.hookimpl
def pytest_xdist_setupnodes(self, specs):
def pytest_xdist_setupnodes(self, specs) -> None:
self._specs = specs
for spec in specs:
self.setstatus(spec, "I", show=False)
self.setstatus(spec, "I", show=True)
self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=False)
self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=True)
self.ensure_show_status()
@pytest.hookimpl
def pytest_xdist_newgateway(self, gateway):
def pytest_xdist_newgateway(self, gateway) -> None:
if self.config.option.verbose > 0:
rinfo = gateway._rinfo()
different_interpreter = rinfo.executable != sys.executable
if different_interpreter:
version = "%s.%s.%s" % rinfo.version_info[:3]
self.rewrite(
"[%s] %s Python %s cwd: %s"
% (gateway.id, rinfo.platform, version, rinfo.cwd),
f"[{gateway.id}] {rinfo.platform} Python {version} cwd: {rinfo.cwd}",
newline=True,
)
self.setstatus(gateway.spec, "C")
self.setstatus(gateway.spec, WorkerStatus.Initialized, tests_collected=0)
@pytest.hookimpl
def pytest_testnodeready(self, node):
def pytest_testnodeready(self, node) -> None:
if self.config.option.verbose > 0:
d = node.workerinfo
infoline = "[{}] Python {}".format(
d["id"], d["version"].replace("\n", " -- ")
different_interpreter = d.get("executable") != sys.executable
if different_interpreter:
version = d["version"].replace("\n", " -- ")
self.rewrite(f"[{d['id']}] Python {version}", newline=True)
self.setstatus(
node.gateway.spec, WorkerStatus.ReadyForCollection, tests_collected=0
)
self.rewrite(infoline, newline=True)
self.setstatus(node.gateway.spec, "ok")
@pytest.hookimpl
def pytest_testnodedown(self, node, error):
def pytest_testnodedown(self, node, error) -> None:
if not error:
return
self.write_line(f"[{node.gateway.id}] node down: {error}")
@@ -457,3 +494,36 @@ def get_default_max_worker_restart(config):
# if --max-worker-restart was not provided, use a reasonable default (#226)
result = config.option.numprocesses * 4
return result
def get_workers_status_line(
status_and_items: Sequence[tuple[WorkerStatus, int]]
) -> str:
"""
Return the line to display during worker setup/collection based on the
status of the workers and number of tests collected for each.
"""
statuses = [s for s, c in status_and_items]
total_workers = len(statuses)
workers_noun = "worker" if total_workers == 1 else "workers"
if status_and_items and all(s == WorkerStatus.CollectionDone for s in statuses):
# All workers collect the same number of items, so we grab
# the total number of items from the first worker.
first = status_and_items[0]
status, tests_collected = first
tests_noun = "item" if tests_collected == 1 else "items"
return f"{total_workers} {workers_noun} [{tests_collected} {tests_noun}]"
if WorkerStatus.CollectionDone in statuses:
done = sum(1 for s, c in status_and_items if c > 0)
return f"collecting: {done}/{total_workers} {workers_noun}"
if WorkerStatus.ReadyForCollection in statuses:
ready = statuses.count(WorkerStatus.ReadyForCollection)
return f"ready: {ready}/{total_workers} {workers_noun}"
if WorkerStatus.Initialized in statuses:
initialized = statuses.count(WorkerStatus.Initialized)
return f"initialized: {initialized}/{total_workers} {workers_noun}"
if WorkerStatus.Created in statuses:
created = statuses.count(WorkerStatus.Created)
return f"created: {created}/{total_workers} {workers_noun}"
return ""

View File

@@ -25,14 +25,18 @@ def pytest_xdist_newgateway(gateway):
@pytest.hookspec(
warn_on_impl="rsync feature is deprecated and will be removed in pytest-xdist 4.0"
warn_on_impl=DeprecationWarning(
"rsync feature is deprecated and will be removed in pytest-xdist 4.0"
)
)
def pytest_xdist_rsyncstart(source, gateways):
"""called before rsyncing a directory to remote gateways takes place."""
@pytest.hookspec(
warn_on_impl="rsync feature is deprecated and will be removed in pytest-xdist 4.0"
warn_on_impl=DeprecationWarning(
"rsync feature is deprecated and will be removed in pytest-xdist 4.0"
)
)
def pytest_xdist_rsyncfinish(source, gateways):
"""called after rsyncing a directory to remote gateways takes place."""

View File

@@ -173,6 +173,8 @@ def pytest_addoption(parser):
"one - might be useful for a small number of slow tests. "
"Larger numbers will allow the scheduler to submit consecutive "
"chunks of tests to workers - allows reusing fixtures. "
"Due to implementation reasons, at least 2 tests are scheduled per "
"worker at the start. Only later tests can be scheduled one by one. "
"Unlimited if not set."
),
)

View File

@@ -350,11 +350,18 @@ class LoadScopeScheduling:
return
# Determine chunks of work (scopes)
unsorted_workqueue = OrderedDict()
for nodeid in self.collection:
scope = self._split_scope(nodeid)
work_unit = self.workqueue.setdefault(scope, default=OrderedDict())
work_unit = unsorted_workqueue.setdefault(scope, default=OrderedDict())
work_unit[nodeid] = False
# Insert tests scopes into work queue ordered by number of tests.
for scope, nodeids in sorted(
unsorted_workqueue.items(), key=lambda item: -len(item[1])
):
self.workqueue[scope] = nodeids
# Avoid having more workers than work
extra_nodes = len(self.nodes) - len(self.workqueue)

View File

@@ -101,21 +101,52 @@ class TestDistribution:
"""
)
result = pytester.runpytest(p1, "-v", "-d", "--tx=popen", "--tx=popen")
result.stdout.fnmatch_lines(["*1*Python*", "*2 failed, 1 passed, 1 skipped*"])
result.stdout.fnmatch_lines(
[
"created: 2/2 workers",
"*2 failed, 1 passed, 1 skipped*",
]
)
assert result.ret == 1
def test_n1_fail_minus_x(self, pytester: pytest.Pytester) -> None:
def test_exitfail_waits_for_workers_to_finish(
self, pytester: pytest.Pytester
) -> None:
"""The DSession waits for workers before exiting early on failure.
When -x/--exitfail is set, the DSession wait for the workers to finish
before raising an Interrupt exception. This prevents reports from the
faiing test and other tests from being discarded.
"""
p1 = pytester.makepyfile(
"""
import time
def test_fail1():
time.sleep(0.1)
assert 0
def test_fail2():
time.sleep(0.2)
def test_fail3():
time.sleep(0.3)
assert 0
def test_fail4():
time.sleep(0.3)
def test_fail5():
time.sleep(0.3)
def test_fail6():
time.sleep(0.3)
"""
)
result = pytester.runpytest(p1, "-x", "-v", "-n1")
result = pytester.runpytest(p1, "-x", "-rA", "-v", "-n2")
assert result.ret == 2
result.stdout.fnmatch_lines(["*Interrupted: stopping*1*", "*1 failed*"])
result.stdout.re_match_lines([".*Interrupted: stopping.*[12].*"])
m = re.search(r"== (\d+) failed, (\d+) passed in ", str(result.stdout))
assert m
n_failed, n_passed = (int(s) for s in m.groups())
assert 1 <= n_failed <= 2
assert 1 <= n_passed <= 3
assert (n_passed + n_failed) < 6
def test_basetemp_in_subprocesses(self, pytester: pytest.Pytester) -> None:
p1 = pytester.makepyfile(
@@ -151,7 +182,12 @@ class TestDistribution:
"""
)
result = pytester.runpytest(p1, "-d", "-v")
result.stdout.fnmatch_lines(["*2*Python*", "*2 failed, 1 passed, 1 skipped*"])
result.stdout.fnmatch_lines(
[
"created: 3/3 workers",
"*2 failed, 1 passed, 1 skipped*",
]
)
assert result.ret == 1
def test_dist_tests_with_crash(self, pytester: pytest.Pytester) -> None:
@@ -237,9 +273,6 @@ class TestDistribution:
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*0* *cwd*",
# "RSyncStart: [G1]",
# "RSyncFinished: [G1]",
"*1 passed*",
]
)
@@ -276,7 +309,11 @@ class TestDistribution:
p1 = pytester.makepyfile("def test_func(): pass")
result = pytester.runpytest("-v", p1, "-d", "--tx=popen")
result.stdout.fnmatch_lines(
["*0*Python*", "*calculated result is 49*", "*1 passed*"]
[
"created: 1/1 worker",
"*calculated result is 49*",
"*1 passed*",
]
)
assert result.ret == 0
@@ -393,14 +430,14 @@ class TestTerminalReporting:
out = result.stdout.str()
if verbosity == "-v":
assert "scheduling tests" in out
assert "gw" in out
assert "1 worker [1 item]" in out
elif verbosity == "-q":
assert "scheduling tests" not in out
assert "gw" not in out
assert "bringing up nodes..." in out
else:
assert "scheduling tests" not in out
assert "gw" in out
assert "1 worker [1 item]" in out
def test_pass_skip_fail(self, pytester: pytest.Pytester) -> None:
pytester.makepyfile(
@@ -1099,8 +1136,9 @@ def test_color_yes_collection_on_non_atty(pytester, request) -> None:
result = pytester.runpytest(*args)
assert "test session starts" in result.stdout.str()
assert "\x1b[1m" in result.stdout.str()
assert "gw0 [10] / gw1 [10]" in result.stdout.str()
assert "gw0 C / gw1 C" not in result.stdout.str()
assert "created: 2/2 workers" in result.stdout.str()
assert "2 workers [10 items]" in result.stdout.str()
assert "collecting:" not in result.stdout.str()
def test_without_terminal_plugin(pytester, request) -> None:
@@ -1138,7 +1176,7 @@ def test_internal_error_with_maxfail(pytester: pytest.Pytester) -> None:
"""
)
result = pytester.runpytest_subprocess("--maxfail=1", "-n1")
result.stdout.fnmatch_lines(["* 1 error in *"])
result.stdout.re_match_lines([".* [12] errors? in .*"])
assert "INTERNALERROR" not in result.stderr.str()
@@ -1194,6 +1232,22 @@ class TestLoadScope:
"test_a.py::TestB", result.outlines
) in ({"gw0": 10}, {"gw1": 10})
def test_workqueue_ordered_by_size(self, pytester: pytest.Pytester) -> None:
test_file = """
import pytest
@pytest.mark.parametrize('i', range({}))
def test(i):
pass
"""
pytester.makepyfile(test_a=test_file.format(10), test_b=test_file.format(20))
result = pytester.runpytest("-n2", "--dist=loadscope", "-v")
assert get_workers_and_test_count_by_prefix(
"test_a.py::test", result.outlines
) == {"gw1": 10}
assert get_workers_and_test_count_by_prefix(
"test_b.py::test", result.outlines
) == {"gw0": 20}
def test_module_single_start(self, pytester: pytest.Pytester) -> None:
"""Fix test suite never finishing in case all workers start with a single test (#277)."""
test_file1 = """
@@ -1554,8 +1608,8 @@ def test_collection_crash(testdir):
assert result.ret == 1
result.stdout.fnmatch_lines(
[
"gw0 I",
"gw0 [[]0[]]",
"created: 1/1 worker",
"1 worker [[]0 items[]]",
"*_ ERROR collecting test_collection_crash.py _*",
"E assert 0",
"*= 1 error in *",

View File

@@ -1,7 +1,13 @@
from xdist.dsession import DSession, get_default_max_worker_restart
from __future__ import annotations
from xdist.dsession import (
DSession,
get_default_max_worker_restart,
get_workers_status_line,
WorkerStatus,
)
from xdist.report import report_collection_diff
from xdist.scheduler import EachScheduling, LoadScheduling, WorkStealingScheduling
from typing import Optional
from typing import Sequence
import pytest
import execnet
@@ -473,7 +479,7 @@ def test_report_collection_diff_equal() -> None:
def test_default_max_worker_restart() -> None:
class config:
class option:
maxworkerrestart: Optional[str] = None
maxworkerrestart: str | None = None
numprocesses: int = 0
assert get_default_max_worker_restart(config) is None
@@ -527,3 +533,70 @@ def test_pytest_issue419(pytester: pytest.Pytester) -> None:
reprec = pytester.inline_run("-n1")
reprec.assertoutcome(passed=2)
assert 0
Created = WorkerStatus.Created
Initialized = WorkerStatus.Initialized
ReadyForCollection = WorkerStatus.ReadyForCollection
CollectionDone = WorkerStatus.CollectionDone
@pytest.mark.parametrize(
"status_and_items, expected",
[
(
[],
"",
),
(
[(Created, 0)],
"created: 1/1 worker",
),
(
[(Created, 0), (Created, 0)],
"created: 2/2 workers",
),
(
[(Initialized, 0), (Created, 0)],
"initialized: 1/2 workers",
),
(
[(Initialized, 0), (Initialized, 0)],
"initialized: 2/2 workers",
),
(
[(ReadyForCollection, 0), (Created, 0)],
"ready: 1/2 workers",
),
(
[(ReadyForCollection, 0), (ReadyForCollection, 0)],
"ready: 2/2 workers",
),
(
[(CollectionDone, 12), (Created, 0)],
"collecting: 1/2 workers",
),
(
[(CollectionDone, 12), (CollectionDone, 12)],
"2 workers [12 items]",
),
(
[(CollectionDone, 1), (CollectionDone, 1)],
"2 workers [1 item]",
),
(
[(CollectionDone, 1)],
"1 worker [1 item]",
),
# Different number of tests collected will raise an error and should not happen in practice,
# but we test for it anyway.
(
[(CollectionDone, 1), (CollectionDone, 12)],
"2 workers [1 item]",
),
],
)
def test_get_workers_status_line(
status_and_items: Sequence[tuple[WorkerStatus, int]], expected: str
) -> None:
assert get_workers_status_line(status_and_items) == expected

View File

@@ -374,7 +374,7 @@ def test_unserialize_warning_msg(w_cls):
v1 = getattr(w_msg, k)
v2 = getattr(w_msg2, k)
if k == "message":
assert type(v1) == type(v2)
assert type(v1) is type(v2)
assert v1.args == v2.args
else:
assert v1 == v2

View File

@@ -1,7 +1,7 @@
[tox]
envlist=
linting
py{37,38,39,310,311}-pytestlatest
py{37,38,39,310,311,312}-pytestlatest
py310-pytestmain
py310-psutil
py310-setproctitle