Compare commits

..

71 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
Aleksandr Mezin
b591be4868 Release 3.2.1
Some checks failed
deploy / deploy (push) Failing after 1s
2023-03-12 14:42:09 +02:00
Aleksandr Mezin
b6c195a56c Merge pull request #885 from amezin/steal-hang-fix
Fix hang caused by `steal` command with empty test queue
2023-03-10 13:12:30 +02:00
Aleksandr Mezin
6abcdfc22e Fix hang caused by steal command with empty test queue
Fixes #884
2023-03-09 16:44:34 +02:00
pre-commit-ci[bot]
58fd7ccc05 [pre-commit.ci] pre-commit autoupdate (#881)
updates:
- [github.com/pre-commit/mirrors-mypy: v1.0.0 → v1.0.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.0...v1.0.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-27 08:53:38 -03:00
Bruno Oliveira
ba526fad5a Merge pull request #879 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-02-14 07:29:00 -03:00
pre-commit-ci[bot]
efe674b265 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0)
2023-02-14 03:23:39 +00:00
Bruno Oliveira
5d692a7d63 Merge pull request #878 from akx/patch-1
docs: Remove unused statement in one-log-per-worker example
2023-02-13 11:01:58 -03:00
Aarni Koskela
5e795d88e7 docs: Remove unused statement in one-log-per-worker example 2023-02-13 15:38:59 +02:00
Aleksandr Mezin
2329d3454f Merge pull request #875 from pytest-dev/release-3.2.0
Release 3.2.0
2023-02-07 17:08:32 +02:00
22 changed files with 486 additions and 111 deletions

View File

@@ -1,33 +1,55 @@
name: deploy name: deploy
on: on:
push: workflow_dispatch:
tags: inputs:
- "v*" version:
description: 'Release version'
required: true
default: '1.2.3'
jobs: jobs:
deploy:
package:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}
steps: steps:
- uses: actions/checkout@v3 - 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: with:
# Needed to fetch tags, which are required by setuptools-scm. name: Packages
fetch-depth: 0 path: dist
- 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: Publish package to PyPI - 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: with:
user: __token__ files: dist/*
password: ${{ secrets.pypi_token }} tag_name: v${{ github.event.inputs.version }}

View File

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

View File

@@ -1,32 +1,32 @@
repos: repos:
- repo: https://github.com/PyCQA/autoflake - repo: https://github.com/PyCQA/autoflake
rev: v2.0.1 rev: v2.2.1
hooks: hooks:
- id: autoflake - id: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"] args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 23.11.0
hooks: hooks:
- id: black - id: black
args: [--safe, --quiet, --target-version, py35] args: [--safe, --quiet, --target-version, py35]
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: 1.13.0 rev: 1.16.0
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: [black==23.1.0] additional_dependencies: [black==23.1.0]
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.5.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 6.1.0
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.3.1 rev: v3.15.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py3-plus] args: [--py3-plus]
@@ -39,7 +39,7 @@ repos:
language: python language: python
additional_dependencies: [pygments, restructuredtext_lint] additional_dependencies: [pygments, restructuredtext_lint]
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991 rev: v1.7.0
hooks: hooks:
- id: mypy - id: mypy
files: ^(src/|testing/) 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,58 @@
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)
===============================
Bug Fixes
---------
- `#884 <https://github.com/pytest-dev/pytest-xdist/issues/884>`_: Fixed hang in ``worksteal`` scheduler.
pytest-xdist 3.2.0 (2023-02-07) pytest-xdist 3.2.0 (2023-02-07)
=============================== ===============================

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 $ 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 #. Merge the release PR to ``master``.
$ 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`.

View File

@@ -50,7 +50,8 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
.. _distribution modes: .. _distribution modes:
* ``--dist load`` **(default)**: Sends pending tests to any worker that is * ``--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* * ``--dist loadscope``: Tests are grouped by **module** for *test functions*
and by **class** for *test methods*. Groups are distributed to available 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 corresponds to the position of that test in the original collection
list. This works because all nodes have the same collection list, and list. This works because all nodes have the same collection list, and
saves bandwidth because the **controller** can now tell one of the saves bandwidth because the **controller** can now tell one of the
workers to just *execute test index 3* index of passing the full test workers to just *execute test index 3* instead of passing the full
id. test id.
4. If **dist-mode** is **each**: the **controller** just sends the full 4. If **dist-mode** is **each**: the **controller** just sends the full
list of test indexes to each node at this moment. list of test indexes to each node at this moment.

View File

@@ -221,7 +221,6 @@ Example:
def pytest_configure(config): def pytest_configure(config):
worker_id = os.environ.get("PYTEST_XDIST_WORKER") worker_id = os.environ.get("PYTEST_XDIST_WORKER")
if worker_id is not None: if worker_id is not None:
log_file = config.getini("worker_log_file")
logging.basicConfig( logging.basicConfig(
format=config.getini("log_file_format"), format=config.getini("log_file_format"),
filename=f"tests_{worker_id}.log", filename=f"tests_{worker_id}.log",

View File

@@ -9,13 +9,16 @@ Sending tests to remote SSH accounts
.. warning:: .. 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 in terms of reproducing the development environment in the remote
worker, and there is no clear solution moving forward. 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. 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 Suppose you have a package ``mypkg`` which contains some
tests that you can successfully run locally. And you tests that you can successfully run locally. And you
have a ssh-reachable machine ``myhost``. Then 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 name = pytest-xdist
description = pytest xdist plugin for distributed testing, most importantly across multiple CPUs description = pytest xdist plugin for distributed testing, most importantly across multiple CPUs
long_description = file: README.rst long_description = file: README.rst
long_description_content_type = text/x-rst
license = MIT license = MIT
author = holger krekel and contributors author = holger krekel and contributors
author_email = pytest-dev@python.org,holger@merlinux.eu author_email = pytest-dev@python.org,holger@merlinux.eu
@@ -29,6 +30,7 @@ classifiers =
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
license_file = LICENSE license_file = LICENSE
project_urls = project_urls =
Documentation=https://pytest-xdist.readthedocs.io/en/latest 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 import pytest
from xdist.remote import Producer from xdist.remote import Producer
@@ -113,11 +118,14 @@ class DSession:
assert self.sched is not None assert self.sched is not None
self.shouldstop = False self.shouldstop = False
pending_exception = None
while not self.session_finished: while not self.session_finished:
self.loop_once() self.loop_once()
if self.shouldstop: if self.shouldstop:
self.triggershutdown() self.triggershutdown()
raise Interrupted(str(self.shouldstop)) pending_exception = Interrupted(str(self.shouldstop))
if pending_exception:
raise pending_exception
return True return True
def loop_once(self): def loop_once(self):
@@ -251,14 +259,16 @@ class DSession:
self._session.testscollected = len(ids) self._session.testscollected = len(ids)
self.sched.add_node_collection(node, ids) self.sched.add_node_collection(node, ids)
if self.terminal: 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.sched.collection_is_completed:
if self.terminal and not self.sched.has_pending: if self.terminal and not self.sched.has_pending:
self.trdist.ensure_show_status() self.trdist.ensure_show_status()
self.terminal.write_line("") self.terminal.write_line("")
if self.config.option.verbose > 0: if self.config.option.verbose > 0:
self.terminal.write_line( self.terminal.write_line(
"scheduling tests via %s" % (self.sched.__class__.__name__) f"scheduling tests via {self.sched.__class__.__name__}"
) )
self.sched.schedule() self.sched.schedule()
@@ -344,14 +354,19 @@ class DSession:
def _handlefailures(self, rep): def _handlefailures(self, rep):
if rep.failed: if rep.failed:
self.countfailures += 1 self.countfailures += 1
if self.maxfail and self.countfailures >= self.maxfail: if (
self.shouldstop = "stopping after %d failures" % (self.countfailures) self.maxfail
and self.countfailures >= self.maxfail
and not self.shouldstop
):
self.shouldstop = f"stopping after {self.countfailures} failures"
def triggershutdown(self): def triggershutdown(self):
self.log("triggering shutdown") if not self.shuttingdown:
self.shuttingdown = True self.log("triggering shutdown")
for node in self.sched.nodes: self.shuttingdown = True
node.shutdown() for node in self.sched.nodes:
node.shutdown()
def handle_crashitem(self, nodeid, worker): def handle_crashitem(self, nodeid, worker):
# XXX get more reporting info by recording pytest_runtest_logstart? # XXX get more reporting info by recording pytest_runtest_logstart?
@@ -372,32 +387,51 @@ class DSession:
self.config.hook.pytest_runtest_logreport(report=rep) 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: class TerminalDistReporter:
def __init__(self, config): def __init__(self, config) -> None:
self.config = config self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter") self.tr = config.pluginmanager.getplugin("terminalreporter")
self._status = {} self._status: dict[str, tuple[WorkerStatus, int]] = {}
self._lastlen = 0 self._lastlen = 0
self._isatty = getattr(self.tr, "isatty", self.tr.hasmarkup) 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) self.tr.write_line(msg)
def ensure_show_status(self): def ensure_show_status(self) -> None:
if not self._isatty: if not self._isatty:
self.write_line(self.getstatus()) self.write_line(self.getstatus())
def setstatus(self, spec, status, show=True): def setstatus(
self._status[spec.id] = status self, spec, status: WorkerStatus, *, tests_collected: int, show: bool = True
) -> None:
self._status[spec.id] = (status, tests_collected)
if show and self._isatty: if show and self._isatty:
self.rewrite(self.getstatus()) self.rewrite(self.getstatus())
def getstatus(self): def getstatus(self) -> str:
if self.config.option.verbose >= 0: if self.config.option.verbose >= 0:
parts = [f"{spec.id} {self._status[spec.id]}" for spec in self._specs] line = get_workers_status_line(list(self._status.values()))
return " / ".join(parts) if line:
else: return line
return "bringing up nodes..."
return "bringing up nodes..."
def rewrite(self, line, newline=False): def rewrite(self, line, newline=False):
pline = line + " " * max(self._lastlen - len(line), 0) pline = line + " " * max(self._lastlen - len(line), 0)
@@ -409,37 +443,40 @@ class TerminalDistReporter:
self.tr.rewrite(pline, bold=True) self.tr.rewrite(pline, bold=True)
@pytest.hookimpl @pytest.hookimpl
def pytest_xdist_setupnodes(self, specs): def pytest_xdist_setupnodes(self, specs) -> None:
self._specs = specs self._specs = specs
for spec in specs: for spec in specs:
self.setstatus(spec, "I", show=False) self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=False)
self.setstatus(spec, "I", show=True) self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=True)
self.ensure_show_status() self.ensure_show_status()
@pytest.hookimpl @pytest.hookimpl
def pytest_xdist_newgateway(self, gateway): def pytest_xdist_newgateway(self, gateway) -> None:
if self.config.option.verbose > 0: if self.config.option.verbose > 0:
rinfo = gateway._rinfo() rinfo = gateway._rinfo()
version = "%s.%s.%s" % rinfo.version_info[:3] different_interpreter = rinfo.executable != sys.executable
self.rewrite( if different_interpreter:
"[%s] %s Python %s cwd: %s" version = "%s.%s.%s" % rinfo.version_info[:3]
% (gateway.id, rinfo.platform, version, rinfo.cwd), self.rewrite(
newline=True, 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 @pytest.hookimpl
def pytest_testnodeready(self, node): def pytest_testnodeready(self, node) -> None:
if self.config.option.verbose > 0: if self.config.option.verbose > 0:
d = node.workerinfo d = node.workerinfo
infoline = "[{}] Python {}".format( different_interpreter = d.get("executable") != sys.executable
d["id"], d["version"].replace("\n", " -- ") if different_interpreter:
) version = d["version"].replace("\n", " -- ")
self.rewrite(infoline, newline=True) self.rewrite(f"[{d['id']}] Python {version}", newline=True)
self.setstatus(node.gateway.spec, "ok") self.setstatus(
node.gateway.spec, WorkerStatus.ReadyForCollection, tests_collected=0
)
@pytest.hookimpl @pytest.hookimpl
def pytest_testnodedown(self, node, error): def pytest_testnodedown(self, node, error) -> None:
if not error: if not error:
return return
self.write_line(f"[{node.gateway.id}] node down: {error}") 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) # if --max-worker-restart was not provided, use a reasonable default (#226)
result = config.option.numprocesses * 4 result = config.option.numprocesses * 4
return result 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( @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): def pytest_xdist_rsyncstart(source, gateways):
"""called before rsyncing a directory to remote gateways takes place.""" """called before rsyncing a directory to remote gateways takes place."""
@pytest.hookspec( @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): def pytest_xdist_rsyncfinish(source, gateways):
"""called after rsyncing a directory to remote gateways takes place.""" """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. " "one - might be useful for a small number of slow tests. "
"Larger numbers will allow the scheduler to submit consecutive " "Larger numbers will allow the scheduler to submit consecutive "
"chunks of tests to workers - allows reusing fixtures. " "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." "Unlimited if not set."
), ),
) )

View File

@@ -58,6 +58,7 @@ def worker_title(title):
class WorkerInteractor: class WorkerInteractor:
SHUTDOWN_MARK = object() SHUTDOWN_MARK = object()
QUEUE_REPLACED_MARK = object()
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
@@ -72,6 +73,15 @@ class WorkerInteractor:
def _make_queue(self): def _make_queue(self):
return self.channel.gateway.execmodel.queue.Queue() return self.channel.gateway.execmodel.queue.Queue()
def _get_next_item_index(self):
"""Gets the next item from test queue. Handles the case when the queue
is replaced concurrently in another thread.
"""
result = self.torun.get()
while result is self.QUEUE_REPLACED_MARK:
result = self.torun.get()
return result
def sendevent(self, name, **kwargs): def sendevent(self, name, **kwargs):
self.log("sending", name, kwargs) self.log("sending", name, kwargs)
self.channel.send((name, kwargs)) self.channel.send((name, kwargs))
@@ -136,19 +146,22 @@ class WorkerInteractor:
self.torun.put(i) self.torun.put(i)
self.sendevent("unscheduled", indices=stolen) self.sendevent("unscheduled", indices=stolen)
old_queue.put(self.QUEUE_REPLACED_MARK)
@pytest.hookimpl @pytest.hookimpl
def pytest_runtestloop(self, session): def pytest_runtestloop(self, session):
self.log("entering main loop") self.log("entering main loop")
self.channel.setcallback(self.handle_command, endmarker=self.SHUTDOWN_MARK) self.channel.setcallback(self.handle_command, endmarker=self.SHUTDOWN_MARK)
self.nextitem_index = self.torun.get() self.nextitem_index = self._get_next_item_index()
while self.nextitem_index is not self.SHUTDOWN_MARK: while self.nextitem_index is not self.SHUTDOWN_MARK:
self.run_one_test() self.run_one_test()
return True return True
def run_one_test(self): def run_one_test(self):
self.item_index = self.nextitem_index
self.nextitem_index = self._get_next_item_index()
items = self.session.items items = self.session.items
self.item_index, self.nextitem_index = self.nextitem_index, self.torun.get()
item = items[self.item_index] item = items[self.item_index]
if self.nextitem_index is self.SHUTDOWN_MARK: if self.nextitem_index is self.SHUTDOWN_MARK:
nextitem = None nextitem = None

View File

@@ -350,11 +350,18 @@ class LoadScopeScheduling:
return return
# Determine chunks of work (scopes) # Determine chunks of work (scopes)
unsorted_workqueue = OrderedDict()
for nodeid in self.collection: for nodeid in self.collection:
scope = self._split_scope(nodeid) 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 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 # Avoid having more workers than work
extra_nodes = len(self.nodes) - len(self.workqueue) 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 = 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 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( p1 = pytester.makepyfile(
""" """
import time
def test_fail1(): def test_fail1():
time.sleep(0.1)
assert 0 assert 0
def test_fail2(): def test_fail2():
time.sleep(0.2)
def test_fail3():
time.sleep(0.3)
assert 0 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 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: def test_basetemp_in_subprocesses(self, pytester: pytest.Pytester) -> None:
p1 = pytester.makepyfile( p1 = pytester.makepyfile(
@@ -151,7 +182,12 @@ class TestDistribution:
""" """
) )
result = pytester.runpytest(p1, "-d", "-v") 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 assert result.ret == 1
def test_dist_tests_with_crash(self, pytester: pytest.Pytester) -> None: def test_dist_tests_with_crash(self, pytester: pytest.Pytester) -> None:
@@ -237,9 +273,6 @@ class TestDistribution:
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*0* *cwd*",
# "RSyncStart: [G1]",
# "RSyncFinished: [G1]",
"*1 passed*", "*1 passed*",
] ]
) )
@@ -276,7 +309,11 @@ class TestDistribution:
p1 = pytester.makepyfile("def test_func(): pass") p1 = pytester.makepyfile("def test_func(): pass")
result = pytester.runpytest("-v", p1, "-d", "--tx=popen") result = pytester.runpytest("-v", p1, "-d", "--tx=popen")
result.stdout.fnmatch_lines( 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 assert result.ret == 0
@@ -393,14 +430,14 @@ class TestTerminalReporting:
out = result.stdout.str() out = result.stdout.str()
if verbosity == "-v": if verbosity == "-v":
assert "scheduling tests" in out assert "scheduling tests" in out
assert "gw" in out assert "1 worker [1 item]" in out
elif verbosity == "-q": elif verbosity == "-q":
assert "scheduling tests" not in out assert "scheduling tests" not in out
assert "gw" not in out assert "gw" not in out
assert "bringing up nodes..." in out assert "bringing up nodes..." in out
else: else:
assert "scheduling tests" not in out 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: def test_pass_skip_fail(self, pytester: pytest.Pytester) -> None:
pytester.makepyfile( pytester.makepyfile(
@@ -1099,8 +1136,9 @@ def test_color_yes_collection_on_non_atty(pytester, request) -> None:
result = pytester.runpytest(*args) result = pytester.runpytest(*args)
assert "test session starts" in result.stdout.str() assert "test session starts" in result.stdout.str()
assert "\x1b[1m" in result.stdout.str() assert "\x1b[1m" in result.stdout.str()
assert "gw0 [10] / gw1 [10]" in result.stdout.str() assert "created: 2/2 workers" in result.stdout.str()
assert "gw0 C / gw1 C" not 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: 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 = 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() assert "INTERNALERROR" not in result.stderr.str()
@@ -1194,6 +1232,22 @@ class TestLoadScope:
"test_a.py::TestB", result.outlines "test_a.py::TestB", result.outlines
) in ({"gw0": 10}, {"gw1": 10}) ) 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: 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).""" """Fix test suite never finishing in case all workers start with a single test (#277)."""
test_file1 = """ test_file1 = """
@@ -1554,8 +1608,8 @@ def test_collection_crash(testdir):
assert result.ret == 1 assert result.ret == 1
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"gw0 I", "created: 1/1 worker",
"gw0 [[]0[]]", "1 worker [[]0 items[]]",
"*_ ERROR collecting test_collection_crash.py _*", "*_ ERROR collecting test_collection_crash.py _*",
"E assert 0", "E assert 0",
"*= 1 error in *", "*= 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.report import report_collection_diff
from xdist.scheduler import EachScheduling, LoadScheduling, WorkStealingScheduling from xdist.scheduler import EachScheduling, LoadScheduling, WorkStealingScheduling
from typing import Optional from typing import Sequence
import pytest import pytest
import execnet import execnet
@@ -473,7 +479,7 @@ def test_report_collection_diff_equal() -> None:
def test_default_max_worker_restart() -> None: def test_default_max_worker_restart() -> None:
class config: class config:
class option: class option:
maxworkerrestart: Optional[str] = None maxworkerrestart: str | None = None
numprocesses: int = 0 numprocesses: int = 0
assert get_default_max_worker_restart(config) is None 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 = pytester.inline_run("-n1")
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
assert 0 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

@@ -271,6 +271,40 @@ class TestWorkerInteractor:
ev = worker.popevent("workerfinished") ev = worker.popevent("workerfinished")
assert "workeroutput" in ev.kwargs assert "workeroutput" in ev.kwargs
def test_steal_empty_queue(self, worker: WorkerSetup, unserialize_report) -> None:
worker.pytester.makepyfile(
"""
def test_func(): pass
def test_func2(): pass
"""
)
worker.setup()
ev = worker.popevent("collectionfinish")
ids = ev.kwargs["ids"]
assert len(ids) == 2
worker.sendcommand("runtests_all")
for when in ["setup", "call", "teardown"]:
ev = worker.popevent("testreport")
rep = unserialize_report(ev.kwargs["data"])
assert rep.nodeid.endswith("::test_func")
assert rep.when == when
worker.sendcommand("steal", indices=[0, 1])
ev = worker.popevent("unscheduled")
assert ev.kwargs["indices"] == []
worker.sendcommand("shutdown")
for when in ["setup", "call", "teardown"]:
ev = worker.popevent("testreport")
rep = unserialize_report(ev.kwargs["data"])
assert rep.nodeid.endswith("::test_func2")
assert rep.when == when
ev = worker.popevent("workerfinished")
assert "workeroutput" in ev.kwargs
def test_remote_env_vars(pytester: pytest.Pytester) -> None: def test_remote_env_vars(pytester: pytest.Pytester) -> None:
pytester.makepyfile( pytester.makepyfile(

View File

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

View File

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