Compare commits

..

85 Commits

Author SHA1 Message Date
Ronny Pfannschmidt
b239199061 release 2.2.1
Some checks failed
build / build (ubuntu-latest, 3.5, py35-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.6, py36-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.7, linting) (push) Failing after 1s
build / build (ubuntu-latest, 3.7, py37-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-psutil) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-pytestmaster) (push) Failing after 1s
build / build (ubuntu-latest, 3.9, py39-pytestlatest) (push) Failing after 1s
build / linting (push) Failing after 1s
build / build (windows-latest, 3.5, py35-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.6, py36-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.7, linting) (push) Has been cancelled
build / build (windows-latest, 3.7, py37-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-psutil) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-pytestmaster) (push) Has been cancelled
build / build (windows-latest, 3.9, py39-pytestlatest) (push) Has been cancelled
build / deploy (push) Has been cancelled
2021-02-09 21:55:43 +01:00
Ronny Pfannschmidt
e4bcbbee78 Merge pull request #618 from staticdev/add-python-39-docs
Add python 3.9 to classifiers
2021-02-07 20:58:07 +01:00
Ronny Pfannschmidt
f6308cad01 Merge pull request #623 from pytest-dev/pytest-fspath-deprecation-prep
prepare for Node.fspath deprecation
2021-02-07 20:57:16 +01:00
Ronny Pfannschmidt
aa89c42ad7 prepare for Node.fspath deprecation
calculate topdir based on config.rootpath/rootdir

addresses pytest-dev/pytest#8251
2021-02-07 20:44:31 +01:00
staticdev
40fa7b091d Add python 3.9 to classifiers 2021-01-19 20:39:44 +01:00
Michał Górny
ad99d943de Disable pytest-services plugin in test to avoid worker_id conflict (#612)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-12-15 09:07:13 -03:00
Bruno Oliveira
0d2480f7e3 Merge pull request #610 from nicoddemus/release-2.2.0
Release 2.2.0
2020-12-14 09:15:48 -03:00
Bruno Oliveira
8f90fb73db Release 2.2.0
Some checks failed
build / build (ubuntu-latest, 3.5, py35-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.6, py36-pytestlatest) (push) Failing after 2s
build / build (ubuntu-latest, 3.7, linting) (push) Failing after 1s
build / build (ubuntu-latest, 3.7, py37-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-psutil) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-pytestlatest) (push) Failing after 1s
build / build (ubuntu-latest, 3.8, py38-pytestmaster) (push) Failing after 1s
build / build (ubuntu-latest, 3.9, py39-pytestlatest) (push) Failing after 1s
build / linting (push) Failing after 1s
build / build (windows-latest, 3.5, py35-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.6, py36-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.7, linting) (push) Has been cancelled
build / build (windows-latest, 3.7, py37-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-psutil) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-pytestlatest) (push) Has been cancelled
build / build (windows-latest, 3.8, py38-pytestmaster) (push) Has been cancelled
build / build (windows-latest, 3.9, py39-pytestlatest) (push) Has been cancelled
build / deploy (push) Has been cancelled
2020-12-14 09:07:49 -03:00
Bruno Oliveira
c5fadcd734 Merge pull request #608 from nicoddemus/better-errors
Propagate internal errors to the master node
2020-12-14 09:04:11 -03:00
Bruno Oliveira
16d636391b Propagate internal errors to the master node
This should help users diagnose internal errors in workers like
exceptions from hooks or in pytest itself.
2020-12-13 20:05:31 -03:00
Bruno Oliveira
948f13730d Merge pull request #609 from nicoddemus/gh-actions 2020-12-13 19:58:48 -03:00
Bruno Oliveira
db896a9d33 tox linting env expects Python 3.7 2020-12-12 12:06:33 -03:00
Bruno Oliveira
cf45eab977 Use GHA badge in README 2020-12-12 12:02:17 -03:00
Bruno Oliveira
00b0464d4b Remove travis and appveyor configs 2020-12-12 12:01:16 -03:00
Bruno Oliveira
b3a1bf3c2e Fix test_warning_captured_deprecated_in_pytest_6
The test was failing because *other* warnings were being triggered on the
master node. This makes the test more reliable by checking only that
the warning from the worker node is not emitted.

Fix #601
2020-12-12 11:58:41 -03:00
Bruno Oliveira
3fb369c8ac Fix linting 2020-12-12 11:52:47 -03:00
Bruno Oliveira
16694a978d Pass PRE_COMMIT_HOME to reuse local cache 2020-12-12 11:52:37 -03:00
Bruno Oliveira
2dcb40799f Setup GitHub Actions 2020-12-12 11:45:40 -03:00
Bruno Oliveira
14bafa6dd5 Rename HOWTORELEASE.rst to RELEASING.rst
To comply with pytest's nomenclature.
2020-09-22 09:45:02 -03:00
Bruno Oliveira
b51286aaa2 Merge pull request #599 from yili1992/master 2020-09-12 07:56:49 -03:00
易陆
03ddee6620 docs: fix invalid url 2020-09-10 10:16:16 +08:00
易陆
e03566b7d6 docs: fix invalid url 2020-09-10 10:14:37 +08:00
Bruno Oliveira
46cf204c64 Merge pull request #591 from nicoddemus/release-2.1.0
Release 2.1.0
2020-08-25 09:40:01 -03:00
Bruno Oliveira
5b7eeab7dc Release 2.1.0 2020-08-25 09:01:37 -03:00
Bruno Oliveira
9fd3b66292 Merge pull request #590 from nicoddemus/auto-hook 2020-08-25 09:00:38 -03:00
Bruno Oliveira
fe48256f1a Remove universal wheel setting: pytest-xdist is Python 3 only 2020-08-25 08:45:45 -03:00
Bruno Oliveira
607d8288d4 Add psutil extra and introduce pytest_xdist_auto_num_workers hook
This makes using psutil optional and opens up the possibility of customization
through the pytest_xdist_auto_num_workers hook, making things like #477 possible.

Fix #585
2020-08-25 08:38:28 -03:00
Bruno Oliveira
e469fc7b80 Revert "Merge pull request #560 from utapyngo/logical-cpu-count"
This reverts commit 0094b29a14, reversing
changes made to c6255faad4.
2020-08-24 14:50:36 -03:00
Bruno Oliveira
7caf7437e3 Merge pull request #588 from pelmini/fix_readme 2020-08-24 14:46:33 -03:00
pelmini
457386f837 Fixed typo in README 2020-08-23 17:15:02 -07:00
Bruno Oliveira
1d08799c82 Merge pull request #582 from nicoddemus/fix-docs
Fix session-scoped fixtures example in README
2020-08-15 07:56:28 -03:00
Bruno Oliveira
ac5b9956bb Fix session-scoped fixtures example in README 2020-08-15 07:43:11 -03:00
Bruno Oliveira
995b34aadc Merge pull request #575 from nicoddemus/release-2.0.0 2020-08-14 11:34:48 -03:00
Bruno Oliveira
43dad454c6 Prepare release 2.0.0 2020-08-12 17:28:27 -03:00
Christine M
c211d247a7 Noisy rsync (#473)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-08-12 17:23:03 -03:00
Thomas Grainger
2b8b8408bd run pyupgrade in ci (#573)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-08-06 13:03:06 -03:00
Bruno Oliveira
30606257fc Merge pull request #572 from pytest-dev/pytest6
Remove compat for pytest < 6
2020-08-06 09:12:58 -03:00
Zac-HD
d153e0a4c4 Remove compat for pytest < 6 2020-08-06 15:48:33 +10:00
Sylvain Marié
ba8398478e New methods is_xdist_worker, is_xdist_master, get_xdist_worker_id. (#505)
Co-authored-by: Sylvain MARIE <sylvain.marie@se.com>
Co-authored-by: Zac Hatfield-Dodds <Zac-HD@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-08-05 20:41:54 -03:00
Bruno Oliveira
abc1ae0e02 Merge pull request #571 from nicoddemus/remove-pytest-logwarning
Drop features from old pytest versions
2020-08-05 20:27:57 -03:00
Bruno Oliveira
047816f33a Drop features from old pytest versions 2020-08-05 19:55:56 -03:00
Bruno Oliveira
56a852665d Merge pull request #570 from nicoddemus/fix-config-init 2020-08-05 19:44:55 -03:00
Bruno Oliveira
55838a126d Remove unnecessary skipif and changelog 2020-08-05 19:24:53 -03:00
Michał Górny
bcfbf9f94b tox.ini: Extend testing to py39 2020-08-05 19:21:27 -03:00
Michał Górny
94bdd18a03 Fix skipping test_config_initialization for pytest<5
Fix test_config_initialization to be correctly skipped on pytest<5,
by explicitly checking pytest version (idea copied from
test_looponfail_removed_test).  The current conditions are insufficient
-- the outer check wrongly assumes pytest>=5 will always be used
on Python 3 (which is not true if you need the same version to support
both Python 2 and Python 3), and the inner condition apparently
wrongly assuming that invocation_params attribute is not present
in pytest-4.
2020-08-05 19:20:14 -03:00
Bruno Oliveira
fe459ba716 Merge pull request #541 from pytest-dev/emancipation 2020-08-05 19:18:02 -03:00
Bruno Oliveira
2f014c39df Minor changelog adjustment 2020-08-05 19:02:50 -03:00
Bruno Oliveira
e5cdb771f7 Add CHANGELOG about dropping Python 2.7
Fix #569
2020-08-05 19:01:57 -03:00
Bruno Oliveira
bdf66f2c8e Add CHANGELOG about requiring pytest 6.0+ 2020-08-05 18:57:25 -03:00
Hugo
62bc333e3e Fix tests 2020-08-03 22:52:35 +10:00
Hugo
dddac7c7e9 Remove six library 2020-08-03 22:52:18 +10:00
Hugo
9decca0023 Drop support for EOL Python 2 2020-08-03 22:52:03 +10:00
Hugo
22ae7ff7af Upgrade Python syntax with pyupgrade --py3-plus 2020-08-03 22:52:03 +10:00
Hugo
6f315356be Set py35 as target version for Black 2020-08-03 22:52:03 +10:00
Zac-HD
de3e54fd27 Finish removal of "slave" terminology
See issue #234.  It's time.
2020-08-03 22:51:42 +10:00
Zac-HD
f23c717a36 Delete old issues file 2020-08-03 22:50:13 +10:00
Zac Hatfield-Dodds
0094b29a14 Merge pull request #560 from utapyngo/logical-cpu-count 2020-08-03 22:49:09 +10:00
Ivan Zaikin
e528a42ddc Support OpenBSD and NetBSD 2020-07-28 08:55:32 +07:00
Bruno Oliveira
c6255faad4 Merge pull request #564 from nicoddemus/release-1.34.0
Release 1.34.0
2020-07-27 20:09:04 -03:00
Bruno Oliveira
7bf654775d Release 1.34.0 2020-07-27 19:36:42 -03:00
Adam Johnson
b524317863 Make "--pdb" imply "--dist no"
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-07-24 20:07:42 -03:00
Daniel Hahler
467ac33d0b tests: check that PYTEST_ADDOPTS is not processed twice 2020-07-24 19:04:16 -03:00
Bruno Oliveira
fac0c14e28 Merge pull request #531 from mgorny/timeout 2020-07-24 15:47:20 -03:00
Adam Johnson
82730e5c6c Improve description of testing with multiple CPU's
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-07-24 15:46:49 -03:00
Bruno Oliveira
78eaa4c0e2 Merge pull request #563 from nicoddemus/pytest-warning-captured-562
Do not trigger pytest_warning_captured in pytest 6.0+
2020-07-24 15:40:19 -03:00
Bruno Oliveira
18b18870a8 Do not trigger pytest_warning_captured in pytest 6.0+
Fix #562
2020-07-24 15:19:07 -03:00
Ivan Zaikin
3312dcde61 Apply black 2020-07-23 10:42:18 +07:00
Ivan Zaikin
1c85f1817a Add changelog 2020-07-23 10:42:04 +07:00
Ivan Zaikin
77e5fdfef9 Use psutil.cpu_count(logical=False) in auto_detect_cpus() 2020-07-23 10:18:43 +07:00
Bruno Oliveira
2fc21fddbb Merge pull request #558 from nicoddemus/fix-rsync-dirs
Fix rsyncdirs usage with pytest 6.0
2020-07-13 15:09:48 -03:00
Bruno Oliveira
443dc21061 Fix rsyncdirs usage with pytest 6.0
Fix pytest-dev/pytest#7487
2020-07-13 12:09:02 -03:00
Bruno Oliveira
727c41e957 Merge pull request #556 from nicoddemus/release-1.33.0 2020-07-09 20:47:50 -03:00
Bruno Oliveira
7396ffb7e8 Release 1.33.0 2020-07-09 20:20:02 -03:00
Bruno Oliveira
fbfe3b9a91 Add missing changelog for #548 2020-07-09 20:19:17 -03:00
Bruno Oliveira
075a74202b Add missing changelog for #554 2020-07-09 20:14:37 -03:00
Bruno Oliveira
b8e1e63c7a Merge pull request #554 from nicoddemus/fix-compat-pytest-master 2020-07-09 16:49:50 -03:00
Bruno Oliveira
bc0866f216 Merge pull request #548 from swt2c/exclude_scm_ci_files
Exclude SCM and CI files from distribution
2020-07-08 18:12:33 -03:00
Bruno Oliveira
b25757199c Support new `pytest_warning_recorded` hook from pytest 6.0 2020-07-08 17:51:45 -03:00
Bruno Oliveira
77a1db27cd Fix compatibility with pytest master
Fix the autouse fixture which diverts atexit.register calls to support the correct signature.
2020-07-08 15:58:31 -03:00
Bruno Oliveira
f5021313ec Merge pull request #545 from lgeiger/list-comprehensions
Prefer list comprehensions over list.append()
2020-07-08 15:44:25 -03:00
Scott Talbert
925829cb72 Exclude SCM and CI files from distribution
These files don't make sense outside of the git respository.
2020-06-26 12:02:03 -04:00
Lukas Geiger
311cbfabfb Prefer list comprehensions over list.append() 2020-06-21 01:00:02 +02:00
Bruno Oliveira
e48491d4fd Fix linting 2020-05-03 09:38:57 -03:00
Michał Górny
27519e8018 Increase pexpect timeouts to prevent tests from failing 2020-05-03 11:24:03 +02:00
Bruno Oliveira
59caed8e2f Merge pull request #530 from pytest-dev/release-1.32.0 2020-05-02 20:50:47 -03:00
31 changed files with 625 additions and 381 deletions

View File

@@ -1,21 +0,0 @@
environment:
matrix:
- TOXENV: "py27-pytestlatest"
- TOXENV: "py35-pytestlatest"
- TOXENV: "py36-pytestlatest"
- TOXENV: "py37-pytestlatest"
- TOXENV: "py38-pytestlatest"
- TOXENV: "py38-pytestmaster"
install:
- C:\Python38\python -m pip install -U pip setuptools virtualenv
- C:\Python38\python -m pip install -U tox setuptools_scm
build: false # Not a C# project, build stuff at the test step instead.
test_script:
- C:\Python38\python -m tox
# We don't deploy anything on tags with AppVeyor, we use Travis instead, so we
# might as well save resources
skip_tags: true

99
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,99 @@
name: build
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
tox_env:
- "py35-pytestlatest"
- "py36-pytestlatest"
- "py37-pytestlatest"
- "py38-pytestlatest"
- "py39-pytestlatest"
- "py38-pytestmaster"
- "py38-psutil"
- "linting"
os: [ubuntu-latest, windows-latest]
include:
- tox_env: "py35-pytestlatest"
python: "3.5"
- tox_env: "py36-pytestlatest"
python: "3.6"
- tox_env: "py37-pytestlatest"
python: "3.7"
- tox_env: "py38-pytestlatest"
python: "3.8"
- tox_env: "py39-pytestlatest"
python: "3.9"
- tox_env: "py38-pytestmaster"
python: "3.8"
- tox_env: "py38-psutil"
python: "3.8"
- tox_env: "linting"
python: "3.7"
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Test
run: |
tox -e ${{ matrix.tox_env }}
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.7"
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Linting
run: |
tox -e linting
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
runs-on: ubuntu-latest
needs: [build, linting]
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.7"
- name: Install wheel
run: |
python -m pip install --upgrade pip
pip install wheel
- name: Build package
run: |
python setup.py sdist bdist_wheel
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_token }}

View File

@@ -3,7 +3,7 @@ repos:
rev: 19.10b0 rev: 19.10b0
hooks: hooks:
- id: black - id: black
args: [--safe, --quiet] args: [--safe, --quiet, --target-version, py35]
language_version: python3.7 language_version: python3.7
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0 rev: v2.4.0
@@ -13,6 +13,11 @@ repos:
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- id: flake8 - id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
hooks:
- id: pyupgrade
args: [--py3-plus]
- repo: local - repo: local
hooks: hooks:
- id: rst - id: rst

View File

@@ -1,62 +0,0 @@
dist: xenial
language: python
notifications:
irc:
channels:
- 'chat.freenode.net#pytest'
on_success: change
on_failure: change
skip_join: true
email:
- pytest-commit@python.org
install:
- pip install -U pip setuptools
- pip install tox setuptools_scm
script: tox
stages:
- baseline
- test
- name: deploy
if: repo = pytest-dev/pytest-xdist AND tag IS present
jobs:
include:
- stage: baseline
python: '3.7'
env: TOXENV=linting
cache:
directories:
- $HOME/.cache/pre-commit
- python: '3.8'
env: TOXENV=py38-pytestlatest
- python: '2.7'
env: TOXENV=py27-pytestlatest
- stage: test
python: "3.5"
env: TOXENV=py35-pytestlatest
- python: "3.6"
env: TOXENV=py36-pytestlatest
- python: "3.7"
env: TOXENV=py37-pytestlatest
- python: "3.8"
env: TOXENV=py38-pytestmaster
- stage: deploy
python: '3.8'
env:
install: pip install -U setuptools setuptools_scm
script: skip
deploy:
provider: pypi
user: ronny
distributions: sdist bdist_wheel
skip_upload_docs: true
password:
secure: cxmSDho5d+PYKEM4ZCg8ms1P4lzhYkrw6fEOm2HtTcsuCyY6aZMSgImWAnEYbJHSkdzgcxlXK9UKJ9B0YenXmBCkAr7UjdnpNXNmkySr0sYzlH/sfqt/dDATCHFaRKxnkOSOVywaDYhT9n8YudbXI77pXwD12i/CeSSJDbHhsu0JYUfAcb+D6YjRYoA2SEGCnzSzg+gDDfwXZx4ZiODCGLVwieNp1klCg88YROUE1BaYYNuUOONvfXX8+TWowbCF6ChH1WL/bZ49OStEYQNuYxZQZr4yClIqu9VJbchrU8j860K9ott2kkGTgfB/dDrQB/XncBubyIX9ikzCQAmmBXWAI3eyvWLPDk2Jz7kW2l2RT7syct80tCq3JhvQ1qdwr5ap7siocTLgnBW0tF4tkHSTFN3510fkc43npnp6FThebESQpnI24vqpwJ9hI/kW5mYi014Og2E/cpCXnz2XO8iZPDbqAMQpDsqEQoyhfGNgPTGp4K30TxRtwZBI5hHhDKnnR16fXtRgt1gYPvz/peUQvvpOm4JzIzGXPzluuutpnCBy75v5+oiwT3YRrLL/Meims9FtDDXL3qQubAE/ezIOOpm0N5XXV8DxIom8EN71yq5ab1tqhM+tBX7owRjy4FR4If2Q8feBdmTuh26DIQt/y+qSG8VkB9Sw/JCjc7c=
on:
tags: true
repo: pytest-dev/pytest-xdist

View File

@@ -1,3 +1,103 @@
pytest-xdist 2.2.1 (2021-02-09)
===============================
Bug Fixes
---------
- `#623 <https://github.com/pytest-dev/pytest-xdist/issues/623>`_: Gracefully handle the pending deprecation of Node.fspath by using config.rootpath for topdir.
pytest-xdist 2.2.0 (2020-12-14)
===============================
Features
--------
- `#608 <https://github.com/pytest-dev/pytest-xdist/issues/608>`_: Internal errors in workers are now propagated to the master node.
pytest-xdist 2.1.0 (2020-08-25)
===============================
Features
--------
- `#585 <https://github.com/pytest-dev/pytest-xdist/issues/585>`_: New ``pytest_xdist_auto_num_workers`` hook can be implemented by plugins or ``conftest.py`` files to control the number of workers when ``--numprocesses=auto`` is given in the command-line.
Trivial Changes
---------------
- `#585 <https://github.com/pytest-dev/pytest-xdist/issues/585>`_: ``psutil`` has proven to make ``pytest-xdist`` installation in certain platforms and containers problematic, so to use it for automatic number of CPUs detection users need to install the ``psutil`` extra::
pip install pytest-xdist[psutil]
pytest-xdist 2.0.0 (2020-08-12)
===============================
Deprecations and Removals
-------------------------
- `#541 <https://github.com/pytest-dev/pytest-xdist/issues/541>`_: Drop backward-compatibility "slave" aliases related to worker nodes. We deliberately moved away from this terminology years ago, and it seems like the right time to finish the deprecation and removal process.
- `#569 <https://github.com/pytest-dev/pytest-xdist/issues/569>`_: ``pytest-xdist`` no longer supports Python 2.7.
Features
--------
- `#504 <https://github.com/pytest-dev/pytest-xdist/issues/504>`_: New functions ``xdist.is_xdist_worker``, ``xdist.is_xdist_master``, ``xdist.get_xdist_worker_id``, to easily identify the current node.
Bug Fixes
---------
- `#471 <https://github.com/pytest-dev/pytest-xdist/issues/471>`_: Fix issue with Rsync reporting in quiet mode.
- `#553 <https://github.com/pytest-dev/pytest-xdist/issues/553>`_: When using ``-n auto``, count the number of physical CPU cores instead of logical ones.
Trivial Changes
---------------
- `#541 <https://github.com/pytest-dev/pytest-xdist/issues/541>`_: ``pytest-xdist`` now requires ``pytest>=6.0``.
pytest-xdist 1.34.0 (2020-07-27)
================================
Features
--------
- `#549 <https://github.com/pytest-dev/pytest-xdist/issues/549>`_: Make ``--pdb`` imply ``--dist no``, as the two options cannot really work together at the moment.
Bug Fixes
---------
- `#478 <https://github.com/pytest-dev/pytest-xdist/issues/478>`_: Fix regression with duplicated arguments via $PYTEST_ADDOPTS in 1.30.0.
- `#558 <https://github.com/pytest-dev/pytest-xdist/issues/558>`_: Fix ``rsyncdirs`` usage with pytest 6.0.
- `#562 <https://github.com/pytest-dev/pytest-xdist/issues/562>`_: Do not trigger the deprecated ``pytest_warning_captured`` in pytest 6.0+.
pytest-xdist 1.33.0 (2020-07-09)
================================
Features
--------
- `#554 <https://github.com/pytest-dev/pytest-xdist/issues/554>`_: Fix warnings support for upcoming pytest 6.0.
Trivial Changes
---------------
- `#548 <https://github.com/pytest-dev/pytest-xdist/issues/548>`_: SCM and CI files are no longer included in the source distribution.
pytest-xdist 1.32.0 (2020-05-03) pytest-xdist 1.32.0 (2020-05-03)
================================ ================================

View File

@@ -1,31 +0,0 @@
next release critical
-----------------------------------------------
tag: bug
miserably fails: --dist=each --tx popen --tx socket=...
rename / hooks
-----------------------------------------------
tag: bug
node -> slave
transition for hooks?
configure_node -> configure_slave
allow to remotely run xdist tests with xdist
-----------------------------------------------
tag: feature
allow to run xdist own tests using its own mechanism.
currently this doesn't work because the remote side
has no pytest plugin. How to configure/do
register "xdist.plugin" on the remote side?
see to avoid any "from _pytest" internal imports
-----------------------------------------------
tag: feature
currently tests and even xdist core code imports
names from the internal _pytest namespace.
See to avoid it.

5
MANIFEST.in Normal file
View File

@@ -0,0 +1,5 @@
exclude .appveyor.yml
exclude .gitignore
exclude .pre-commit-config.yaml
exclude .travis.yml
prune .github

View File

@@ -11,13 +11,8 @@
:alt: Python versions :alt: Python versions
:target: https://pypi.python.org/pypi/pytest-xdist :target: https://pypi.python.org/pypi/pytest-xdist
.. image:: https://travis-ci.org/pytest-dev/pytest-xdist.svg?branch=master .. image:: https://github.com/pytest-dev/pytest-xdist/workflows/build/badge.svg
:alt: Travis CI build status :target: https://github.com/pytest-dev/pytest-xdist/actions
:target: https://travis-ci.org/pytest-dev/pytest-xdist
.. image:: https://ci.appveyor.com/api/projects/status/56eq1a1avd4sdd7e/branch/master?svg=true
:alt: AppVeyor build status
:target: https://ci.appveyor.com/project/pytestbot/pytest-xdist
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black :target: https://github.com/ambv/black
@@ -57,43 +52,46 @@ Install the plugin with::
pip install pytest-xdist pip install pytest-xdist
or use the package in develop/in-place mode with
a checkout of the `pytest-xdist repository`_ ::
pip install --editable . To use ``psutil`` for detection of the number of CPUs available, install the ``psutil`` extra::
pip install pytest-xdist[psutil]
.. _parallelization: .. _parallelization:
Speed up test runs by sending tests to multiple CPUs Speed up test runs by sending tests to multiple CPUs
---------------------------------------------------- ----------------------------------------------------
To send tests to multiple CPUs, type:: To send tests to multiple CPUs, use the ``-n`` (or ``--numprocesses``) option::
pytest -n NUM pytest -n NUMCPUS
Especially for longer running tests or tests requiring Pass ``-n auto`` to use as many processes as your computer has CPU cores. This
a lot of I/O this can lead to considerable speed ups. This option can can lead to considerable speed ups, especially if your test suite takes a
also be set to ``auto`` for automatic detection of the number of CPUs. noticeable amount of time.
If a test crashes the interpreter, pytest-xdist will automatically restart If a test crashes a worker, pytest-xdist will automatically restart that worker
that worker and report the failure as usual. You can use the and report the tests failure. You can use the ``--max-worker-restart`` option
``--max-worker-restart`` option to limit the number of workers that can to limit the number of worker restarts that are allowed, or disable restarting
be restarted, or disable restarting altogether using ``--max-worker-restart=0``. altogether using ``--max-worker-restart 0``.
By default, the ``-n`` option will send pending tests to any worker that is available, without By default, using ``--numprocesses`` will send pending tests to any worker that
any guaranteed order, but you can control this with these options: is available, without any guaranteed order. You can change the test
distribution algorithm this with the ``--dist`` option. It takes these values:
* ``--dist=loadscope``: tests will be grouped by **module** for *test functions* and * ``--dist no``: The default algorithm, distributing one test at a time.
by **class** for *test methods*, then each group will be sent to an available worker,
guaranteeing that all tests in a group run in the same process. This can be useful if you have
expensive module-level or class-level fixtures. Currently the groupings can't be customized,
with grouping by class takes priority over grouping by module.
This feature was added in version ``1.19``.
* ``--dist=loadfile``: tests will be grouped by file name, and then will be sent to an available * ``--dist loadscope``: Tests are grouped by **module** for *test functions*
worker, guaranteeing that all tests in a group run in the same worker. This feature was added and by **class** for *test methods*. Groups are distributed to available
in version ``1.21``. workers as whole units. This guarantees that all tests in a group run in the
same process. This can be useful if you have expensive module-level or
class-level fixtures. Grouping by class takes priority over grouping by
module.
* ``--dist loadfile``: Tests are grouped by their containing file. Groups are
distributed to available workers as whole units. This guarantees that all
tests in a file run in the same worker.
Making session-scoped fixtures execute only once Making session-scoped fixtures execute only once
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -124,7 +122,7 @@ Here is the code:
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def session_data(tmp_path_factory, worker_id): def session_data(tmp_path_factory, worker_id):
if not worker_id: if worker_id == "master":
# not executing in with multiple workers, just produce the data and let # not executing in with multiple workers, just produce the data and let
# pytest's fixture caching do its job # pytest's fixture caching do its job
return produce_expensive_data() return produce_expensive_data()
@@ -244,7 +242,7 @@ at once. The specifications strings use the `xspec syntax`_.
.. _`xspec syntax`: http://codespeak.net/execnet/basics.html#xspec .. _`xspec syntax`: http://codespeak.net/execnet/basics.html#xspec
.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/2af991418160/execnet/script/socketserver.py .. _`socketserver.py`: https://raw.githubusercontent.com/pytest-dev/execnet/master/execnet/script/socketserver.py
.. _`execnet`: http://codespeak.net/execnet .. _`execnet`: http://codespeak.net/execnet
@@ -266,7 +264,7 @@ a test or fixture, you may use the ``worker_id`` fixture to do so:
When ``xdist`` is disabled (running with ``-n0`` for example), then When ``xdist`` is disabled (running with ``-n0`` for example), then
``worker_id`` will return ``"master"``. ``worker_id`` will return ``"master"``.
Additionally, worker processes have the following environment variables Worker processes also have the following environment variables
defined: defined:
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``. * ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
@@ -276,6 +274,34 @@ defined:
The information about the worker_id in a test is stored in the ``TestReport`` as The information about the worker_id in a test is stored in the ``TestReport`` as
well, under the ``worker_id`` attribute. well, under the ``worker_id`` attribute.
Since version 2.0, the following functions are also available in the ``xdist`` module:
.. code-block:: python
def is_xdist_worker(request_or_session) -> bool:
"""Return `True` if this is an xdist worker, `False` otherwise
:param request_or_session: the `pytest` `request` or `session` object
"""
def is_xdist_master(request_or_session) -> bool:
"""Return `True` if this is the xdist master, `False` otherwise
Note: this method also returns `False` when distribution has not been
activated at all.
:param request_or_session: the `pytest` `request` or `session` object
"""
def get_xdist_worker_id(request_or_session) -> str:
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
if running on the 'master' node.
If not distributing tests (for example passing `-n0` or not passing `-n` at all) also return 'master'.
:param request_or_session: the `pytest` `request` or `session` object
"""
Uniquely identifying the current test run Uniquely identifying the current test run
----------------------------------------- -----------------------------------------

View File

@@ -1,6 +1,3 @@
[bdist_wheel]
universal = 1
[metadata] [metadata]
license_file = LICENSE license_file = LICENSE

View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
install_requires = ["execnet>=1.1", "pytest>=4.4.0", "pytest-forked", "six"] install_requires = ["execnet>=1.1", "pytest>=6.0.0", "pytest-forked"]
with open("README.rst") as f: with open("README.rst") as f:
@@ -9,8 +9,7 @@ with open("README.rst") as f:
setup( setup(
name="pytest-xdist", name="pytest-xdist",
use_scm_version={"write_to": "src/xdist/_version.py"}, use_scm_version={"write_to": "src/xdist/_version.py"},
description="pytest xdist plugin for distributed testing" description="pytest xdist plugin for distributed testing and loop-on-failing modes",
" and loop-on-failing modes",
long_description=long_description, long_description=long_description,
license="MIT", license="MIT",
author="holger krekel and contributors", author="holger krekel and contributors",
@@ -19,12 +18,12 @@ setup(
platforms=["linux", "osx", "win32"], platforms=["linux", "osx", "win32"],
packages=find_packages(where="src"), packages=find_packages(where="src"),
package_dir={"": "src"}, package_dir={"": "src"},
extras_require={"testing": ["filelock"]}, extras_require={"testing": ["filelock"], "psutil": ["psutil>=3.0"]},
entry_points={ entry_points={
"pytest11": ["xdist = xdist.plugin", "xdist.looponfail = xdist.looponfail"] "pytest11": ["xdist = xdist.plugin", "xdist.looponfail = xdist.looponfail"]
}, },
zip_safe=False, zip_safe=False,
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", python_requires=">=3.5",
install_requires=install_requires, install_requires=install_requires,
setup_requires=["setuptools_scm"], setup_requires=["setuptools_scm"],
classifiers=[ classifiers=[
@@ -39,12 +38,12 @@ setup(
"Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Quality Assurance",
"Topic :: Utilities", "Topic :: Utilities",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
], ],
) )

View File

@@ -1,3 +1,4 @@
from xdist.plugin import is_xdist_worker, is_xdist_master, get_xdist_worker_id
from xdist._version import version as __version__ from xdist._version import version as __version__
__all__ = ["__version__"] __all__ = ["__version__", "is_xdist_worker", "is_xdist_master", "get_xdist_worker_id"]

View File

@@ -10,14 +10,14 @@ from xdist.scheduler import (
) )
from six.moves.queue import Empty, Queue from queue import Empty, Queue
class Interrupted(KeyboardInterrupt): class Interrupted(KeyboardInterrupt):
""" signals an immediate interruption. """ """ signals an immediate interruption. """
class DSession(object): class DSession:
"""A pytest plugin which runs a distributed test session """A pytest plugin which runs a distributed test session
At the beginning of the test session this creates a NodeManager At the beginning of the test session this creates a NodeManager
@@ -150,9 +150,6 @@ class DSession(object):
node.workerinfo["id"] = node.gateway.id node.workerinfo["id"] = node.gateway.id
node.workerinfo["spec"] = node.gateway.spec node.workerinfo["spec"] = node.gateway.spec
# TODO: (#234 task) needs this for pytest. Remove when refactor in pytest repo
node.slaveinfo = node.workerinfo
self.config.hook.pytest_testnodeready(node=node) self.config.hook.pytest_testnodeready(node=node)
if self.shuttingdown: if self.shuttingdown:
node.shutdown() node.shutdown()
@@ -177,6 +174,24 @@ class DSession(object):
assert not crashitem, (crashitem, node) assert not crashitem, (crashitem, node)
self._active_nodes.remove(node) self._active_nodes.remove(node)
def worker_internal_error(self, node, formatted_error):
"""
pytest_internalerror() was called on the worker.
pytest_internalerror() arguments are an excinfo and an excrepr, which can't
be serialized, so we go with a poor man's solution of raising an exception
here ourselves using the formatted message.
"""
self._active_nodes.remove(node)
try:
assert False, formatted_error
except AssertionError:
from _pytest._code import ExceptionInfo
excinfo = ExceptionInfo.from_current()
excrepr = excinfo.getrepr()
self.config.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
def worker_errordown(self, node, error): def worker_errordown(self, node, error):
"""Emitted by the WorkerController when a node dies.""" """Emitted by the WorkerController when a node dies."""
self.config.hook.pytest_testnodedown(node=node, error=error) self.config.hook.pytest_testnodedown(node=node, error=error)
@@ -271,16 +286,18 @@ class DSession(object):
assert not rep.passed assert not rep.passed
self._failed_worker_collectreport(node, rep) self._failed_worker_collectreport(node, rep)
def worker_logwarning(self, message, code, nodeid, fslocation):
"""Emitted when a node calls the pytest_logwarning hook."""
kwargs = dict(message=message, code=code, nodeid=nodeid, fslocation=fslocation)
self.config.hook.pytest_logwarning.call_historic(kwargs=kwargs)
def worker_warning_captured(self, warning_message, when, item): def worker_warning_captured(self, warning_message, when, item):
"""Emitted when a node calls the pytest_logwarning hook.""" """Emitted when a node calls the pytest_warning_captured hook (deprecated in 6.0)."""
kwargs = dict(warning_message=warning_message, when=when, item=item) kwargs = dict(warning_message=warning_message, when=when, item=item)
self.config.hook.pytest_warning_captured.call_historic(kwargs=kwargs) self.config.hook.pytest_warning_captured.call_historic(kwargs=kwargs)
def worker_warning_recorded(self, warning_message, when, nodeid, location):
"""Emitted when a node calls the pytest_warning_recorded hook."""
kwargs = dict(
warning_message=warning_message, when=when, nodeid=nodeid, location=location
)
self.config.hook.pytest_warning_recorded.call_historic(kwargs=kwargs)
def _clone_node(self, node): def _clone_node(self, node):
"""Return new node based on an existing one. """Return new node based on an existing one.
@@ -329,7 +346,7 @@ class DSession(object):
self.config.hook.pytest_runtest_logreport(report=rep) self.config.hook.pytest_runtest_logreport(report=rep)
class TerminalDistReporter(object): class TerminalDistReporter:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter") self.tr = config.pluginmanager.getplugin("terminalreporter")

View File

@@ -6,7 +6,6 @@
processes) otherwise changes to source code can crash processes) otherwise changes to source code can crash
the controlling process which should best never happen. the controlling process which should best never happen.
""" """
from __future__ import print_function
import py import py
import pytest import pytest
import sys import sys
@@ -55,7 +54,7 @@ def looponfail_main(config):
print() print()
class RemoteControl(object): class RemoteControl:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.failures = [] self.failures = []
@@ -165,7 +164,7 @@ def init_worker_session(channel, args, option_dict):
WorkerFailSession(config, channel).main() WorkerFailSession(config, channel).main()
class WorkerFailSession(object): class WorkerFailSession:
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
self.channel = channel self.channel = channel
@@ -220,7 +219,7 @@ class WorkerFailSession(object):
self.channel.send((trails, failreports, self.collection_failed)) self.channel.send((trails, failreports, self.collection_failed))
class StatRecorder(object): class StatRecorder:
def __init__(self, rootdirlist): def __init__(self, rootdirlist):
self.rootdirlist = rootdirlist self.rootdirlist = rootdirlist
self.statcache = {} self.statcache = {}

View File

@@ -55,3 +55,13 @@ def pytest_xdist_node_collection_finished(node, ids):
@pytest.mark.firstresult @pytest.mark.firstresult
def pytest_xdist_make_scheduler(config, log): def pytest_xdist_make_scheduler(config, log):
""" return a node scheduler implementation """ """ return a node scheduler implementation """
@pytest.mark.firstresult
def pytest_xdist_auto_num_workers(config):
"""
Return the number of workers to spawn when ``--numprocesses=auto`` is given in the
command-line.
.. versionadded:: 2.1
"""

View File

@@ -5,9 +5,21 @@ import py
import pytest import pytest
def auto_detect_cpus(): def pytest_xdist_auto_num_workers():
try:
import psutil
except ImportError:
pass
else:
count = psutil.cpu_count(logical=False) or psutil.cpu_count()
if count:
return count
try: try:
from os import sched_getaffinity from os import sched_getaffinity
def cpu_count():
return len(sched_getaffinity(0))
except ImportError: except ImportError:
if os.environ.get("TRAVIS") == "true": if os.environ.get("TRAVIS") == "true":
# workaround https://bitbucket.org/pypy/pypy/issues/2375 # workaround https://bitbucket.org/pypy/pypy/issues/2375
@@ -16,11 +28,6 @@ def auto_detect_cpus():
from os import cpu_count from os import cpu_count
except ImportError: except ImportError:
from multiprocessing import cpu_count from multiprocessing import cpu_count
else:
def cpu_count():
return len(sched_getaffinity(0))
try: try:
n = cpu_count() n = cpu_count()
except NotImplementedError: except NotImplementedError:
@@ -28,13 +35,9 @@ def auto_detect_cpus():
return n if n else 1 return n if n else 1
class AutoInt(int):
"""Mark value as auto-detected."""
def parse_numprocesses(s): def parse_numprocesses(s):
if s == "auto": if s == "auto":
return AutoInt(auto_detect_cpus()) return "auto"
elif s is not None: elif s is not None:
return int(s) return int(s)
@@ -62,14 +65,11 @@ def pytest_addoption(parser):
) )
group.addoption( group.addoption(
"--max-worker-restart", "--max-worker-restart",
"--max-slave-restart",
action="store", action="store",
default=None, default=None,
dest="maxworkerrestart", dest="maxworkerrestart",
help="maximum number of workers that can be restarted " help="maximum number of workers that can be restarted "
"when crashed (set to zero to disable this feature)\n" "when crashed (set to zero to disable this feature)",
"'--max-slave-restart' option is deprecated and will be removed in "
"a future release",
) )
group.addoption( group.addoption(
"--dist", "--dist",
@@ -190,8 +190,13 @@ def pytest_configure(config):
@pytest.mark.tryfirst @pytest.mark.tryfirst
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
usepdb = config.getoption("usepdb", False) # a core option usepdb = config.getoption("usepdb", False) # a core option
if isinstance(config.option.numprocesses, AutoInt): if config.option.numprocesses == "auto":
config.option.numprocesses = 0 if usepdb else int(config.option.numprocesses) if usepdb:
config.option.numprocesses = 0
config.option.dist = "no"
else:
auto_num_cpus = config.hook.pytest_xdist_auto_num_workers(config=config)
config.option.numprocesses = auto_num_cpus
if config.option.numprocesses: if config.option.numprocesses:
if config.option.dist == "no": if config.option.dist == "no":
@@ -203,28 +208,60 @@ def pytest_cmdline_main(config):
if config.option.distload: if config.option.distload:
config.option.dist = "load" config.option.dist = "load"
val = config.getvalue val = config.getvalue
if not val("collectonly"): if not val("collectonly") and val("dist") != "no" and usepdb:
if val("dist") != "no": raise pytest.UsageError(
if usepdb: "--pdb is incompatible with distributing tests; try using -n0 or -nauto."
raise pytest.UsageError( ) # noqa: E501
"--pdb is incompatible with distributing tests; try using -n0 or -nauto."
) # noqa: E501
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# fixtures # fixtures and API to easily know the role of current node
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def is_xdist_worker(request_or_session) -> bool:
"""Return `True` if this is an xdist worker, `False` otherwise
:param request_or_session: the `pytest` `request` or `session` object
"""
return hasattr(request_or_session.config, "workerinput")
def is_xdist_master(request_or_session) -> bool:
"""Return `True` if this is the xdist master, `False` otherwise
Note: this method also returns `False` when distribution has not been
activated at all.
:param request_or_session: the `pytest` `request` or `session` object
"""
return (
not is_xdist_worker(request_or_session)
and request_or_session.config.option.dist != "no"
)
def get_xdist_worker_id(request_or_session) -> str:
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
if running on the 'master' node.
If not distributing tests (for example passing `-n0` or not passing `-n` at all)
also return 'master'.
:param request_or_session: the `pytest` `request` or `session` object
"""
if hasattr(request_or_session.config, "workerinput"):
return request_or_session.config.workerinput["workerid"]
else:
return "master"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def worker_id(request): def worker_id(request):
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master' """Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
if running on the master node. if running on the master node.
""" """
if hasattr(request.config, "workerinput"): return get_xdist_worker_id(request)
return request.config.workerinput["workerid"]
else:
return "master"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")

View File

@@ -11,14 +11,13 @@ import os
import time import time
import py import py
import _pytest.hookspec
import pytest import pytest
from execnet.gateway_base import dumps, DumpError from execnet.gateway_base import dumps, DumpError
from _pytest.config import _prepareconfig, Config from _pytest.config import _prepareconfig, Config
class WorkerInteractor(object): class WorkerInteractor:
def __init__(self, config, channel): def __init__(self, config, channel):
self.config = config self.config = config
self.workerid = config.workerinput.get("workerid", "?") self.workerid = config.workerinput.get("workerid", "?")
@@ -34,8 +33,10 @@ class WorkerInteractor(object):
self.channel.send((name, kwargs)) self.channel.send((name, kwargs))
def pytest_internalerror(self, excrepr): def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"): formatted_error = str(excrepr)
for line in formatted_error.split("\n"):
self.log("IERROR>", line) self.log("IERROR>", line)
interactor.sendevent("internal_error", formatted_error=formatted_error)
def pytest_sessionstart(self, session): def pytest_sessionstart(self, session):
self.session = session self.session = session
@@ -92,20 +93,22 @@ class WorkerInteractor(object):
) )
def pytest_collection_finish(self, session): def pytest_collection_finish(self, session):
try:
topdir = str(self.config.rootpath)
except AttributeError: # pytest <= 6.1.0
topdir = str(self.config.rootdir)
self.sendevent( self.sendevent(
"collectionfinish", "collectionfinish",
topdir=str(session.fspath), topdir=topdir,
ids=[item.nodeid for item in session.items], ids=[item.nodeid for item in session.items],
) )
def pytest_runtest_logstart(self, nodeid, location): def pytest_runtest_logstart(self, nodeid, location):
self.sendevent("logstart", nodeid=nodeid, location=location) self.sendevent("logstart", nodeid=nodeid, location=location)
# the pytest_runtest_logfinish hook was introduced in pytest 3.4 def pytest_runtest_logfinish(self, nodeid, location):
if hasattr(_pytest.hookspec, "pytest_runtest_logfinish"): self.sendevent("logfinish", nodeid=nodeid, location=location)
def pytest_runtest_logfinish(self, nodeid, location):
self.sendevent("logfinish", nodeid=nodeid, location=location)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
data = self.config.hook.pytest_report_to_serializable( data = self.config.hook.pytest_report_to_serializable(
@@ -125,31 +128,14 @@ class WorkerInteractor(object):
) )
self.sendevent("collectreport", data=data) self.sendevent("collectreport", data=data)
# the pytest_logwarning hook was deprecated since pytest 4.0 def pytest_warning_recorded(self, warning_message, when, nodeid, location):
if hasattr( self.sendevent(
_pytest.hookspec, "pytest_logwarning" "warning_recorded",
) and not _pytest.hookspec.pytest_logwarning.pytest_spec.get("warn_on_impl"): warning_message_data=serialize_warning_message(warning_message),
when=when,
def pytest_logwarning(self, message, code, nodeid, fslocation): nodeid=nodeid,
self.sendevent( location=location,
"logwarning", )
message=message,
code=code,
nodeid=nodeid,
fslocation=str(fslocation),
)
# the pytest_warning_captured hook was introduced in pytest 3.8
if hasattr(_pytest.hookspec, "pytest_warning_captured"):
def pytest_warning_captured(self, warning_message, when, item):
self.sendevent(
"warning_captured",
warning_message_data=serialize_warning_message(warning_message),
when=when,
# item cannot be serialized and will always be None when used with xdist
item=None,
)
def serialize_warning_message(warning_message): def serialize_warning_message(warning_message):
@@ -254,8 +240,5 @@ if __name__ == "__channelexec__":
config._parser.prog = os.path.basename(workerinput["mainargv"][0]) config._parser.prog = os.path.basename(workerinput["mainargv"][0])
config.workerinput = workerinput config.workerinput = workerinput
config.workeroutput = {} config.workeroutput = {}
# TODO: deprecated name, backward compatibility only. Remove it in future
config.slaveinput = config.workerinput
config.slaveoutput = config.workeroutput
interactor = WorkerInteractor(config, channel) interactor = WorkerInteractor(config, channel)
config.hook.pytest_cmdline_main(config=config) config.hook.pytest_cmdline_main(config=config)

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
from difflib import unified_diff from difflib import unified_diff
@@ -13,9 +12,9 @@ def report_collection_diff(from_collection, to_collection, from_id, to_id):
diff = unified_diff(from_collection, to_collection, fromfile=from_id, tofile=to_id) diff = unified_diff(from_collection, to_collection, fromfile=from_id, tofile=to_id)
error_message = ( error_message = (
u"Different tests were collected between {from_id} and {to_id}. " "Different tests were collected between {from_id} and {to_id}. "
u"The difference is:\n" "The difference is:\n"
u"{diff}" "{diff}"
).format(from_id=from_id, to_id=to_id, diff="\n".join(diff)) ).format(from_id=from_id, to_id=to_id, diff="\n".join(diff))
msg = "\n".join([x.rstrip() for x in error_message.split("\n")]) msg = "\n".join([x.rstrip() for x in error_message.split("\n")])
return msg return msg

View File

@@ -4,7 +4,7 @@ from xdist.workermanage import parse_spec_config
from xdist.report import report_collection_diff from xdist.report import report_collection_diff
class EachScheduling(object): class EachScheduling:
"""Implement scheduling of test items on all nodes """Implement scheduling of test items on all nodes
If a node gets added after the test run is started then it is If a node gets added after the test run is started then it is

View File

@@ -7,7 +7,7 @@ from xdist.workermanage import parse_spec_config
from xdist.report import report_collection_diff from xdist.report import report_collection_diff
class LoadScheduling(object): class LoadScheduling:
"""Implement load scheduling across nodes. """Implement load scheduling across nodes.
This distributes the tests collected across all nodes so each test This distributes the tests collected across all nodes so each test

View File

@@ -21,7 +21,7 @@ class LoadFileScheduling(LoadScopeScheduling):
""" """
def __init__(self, config, log=None): def __init__(self, config, log=None):
super(LoadFileScheduling, self).__init__(config, log) super().__init__(config, log)
if log is None: if log is None:
self.log = Producer("loadfilesched") self.log = Producer("loadfilesched")
else: else:

View File

@@ -6,7 +6,7 @@ from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config from xdist.workermanage import parse_spec_config
class LoadScopeScheduling(object): class LoadScopeScheduling:
"""Implement load scheduling across nodes, but grouping test by scope. """Implement load scheduling across nodes, but grouping test by scope.
This distributes the tests collected across all nodes so each test is run This distributes the tests collected across all nodes so each test is run

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import fnmatch import fnmatch
import os import os
import re import re
@@ -29,7 +28,7 @@ def parse_spec_config(config):
return xspeclist return xspeclist
class NodeManager(object): class NodeManager:
EXIT_TIMEOUT = 10 EXIT_TIMEOUT = 10
DEFAULT_IGNORES = [".*", "*.pyc", "*.pyo", "*~"] DEFAULT_IGNORES = [".*", "*.pyc", "*.pyo", "*~"]
@@ -63,10 +62,7 @@ class NodeManager(object):
def setup_nodes(self, putevent): def setup_nodes(self, putevent):
self.config.hook.pytest_xdist_setupnodes(config=self.config, specs=self.specs) self.config.hook.pytest_xdist_setupnodes(config=self.config, specs=self.specs)
self.trace("setting up nodes") self.trace("setting up nodes")
nodes = [] return [self.setup_node(spec, putevent) for spec in self.specs]
for spec in self.specs:
nodes.append(self.setup_node(spec, putevent))
return nodes
def setup_node(self, spec, putevent): def setup_node(self, spec, putevent):
gw = self.group.makegateway(spec) gw = self.group.makegateway(spec)
@@ -93,8 +89,16 @@ class NodeManager(object):
import pytest import pytest
import _pytest import _pytest
pytestpath = pytest.__file__.rstrip("co") def get_dir(p):
pytestdir = py.path.local(_pytest.__file__).dirpath() """Return the directory path if p is a package or the path to the .py file otherwise."""
stripped = p.rstrip("co")
if os.path.basename(stripped) == "__init__.py":
return os.path.dirname(p)
else:
return stripped
pytestpath = get_dir(pytest.__file__)
pytestdir = get_dir(_pytest.__file__)
config = self.config config = self.config
candidates = [py._pydir, pytestpath, pytestdir] candidates = [py._pydir, pytestpath, pytestdir]
candidates += config.option.rsyncdir candidates += config.option.rsyncdir
@@ -118,7 +122,7 @@ class NodeManager(object):
return { return {
"ignores": ignores, "ignores": ignores,
"verbose": getattr(self.config.option, "verbose", False), "verbose": getattr(self.config.option, "verbose", 0),
} }
def rsync(self, gateway, source, notify=None, verbose=False, ignores=None): def rsync(self, gateway, source, notify=None, verbose=False, ignores=None):
@@ -158,12 +162,11 @@ class HostRSync(execnet.RSync):
def __init__(self, sourcedir, *args, **kwargs): def __init__(self, sourcedir, *args, **kwargs):
self._synced = {} self._synced = {}
self._ignores = []
ignores = kwargs.pop("ignores", None) or [] ignores = kwargs.pop("ignores", None) or []
for x in ignores: self._ignores = [
x = getattr(x, "strpath", x) re.compile(fnmatch.translate(getattr(x, "strpath", x))) for x in ignores
self._ignores.append(re.compile(fnmatch.translate(x))) ]
super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs) super().__init__(sourcedir=sourcedir, **kwargs)
def filter(self, path): def filter(self, path):
path = py.path.local(path) path = py.path.local(path)
@@ -175,12 +178,10 @@ class HostRSync(execnet.RSync):
def add_target_host(self, gateway, finished=None): def add_target_host(self, gateway, finished=None):
remotepath = os.path.basename(self._sourcedir) remotepath = os.path.basename(self._sourcedir)
super(HostRSync, self).add_target( super().add_target(gateway, remotepath, finishedcallback=finished, delete=True)
gateway, remotepath, finishedcallback=finished, delete=True
)
def _report_send_file(self, gateway, modified_rel_path): def _report_send_file(self, gateway, modified_rel_path):
if self._verbose: if self._verbose > 0:
path = os.path.basename(self._sourcedir) + "/" + modified_rel_path path = os.path.basename(self._sourcedir) + "/" + modified_rel_path
remotepath = gateway.spec.chdir remotepath = gateway.spec.chdir
print("{}:{} <= {}".format(gateway.spec, remotepath, path)) print("{}:{} <= {}".format(gateway.spec, remotepath, path))
@@ -207,7 +208,7 @@ def make_reltoroot(roots, args):
return result return result
class WorkerController(object): class WorkerController:
ENDMARK = -1 ENDMARK = -1
class RemoteHook: class RemoteHook:
@@ -224,13 +225,9 @@ class WorkerController(object):
self.workerinput = { self.workerinput = {
"workerid": gateway.id, "workerid": gateway.id,
"workercount": len(nodemanager.specs), "workercount": len(nodemanager.specs),
"slaveid": gateway.id,
"slavecount": len(nodemanager.specs),
"testrunuid": nodemanager.testrunuid, "testrunuid": nodemanager.testrunuid,
"mainargv": sys.argv, "mainargv": sys.argv,
} }
# TODO: deprecated name, backward compatibility only. Remove it in future
self.slaveinput = self.workerinput
self._down = False self._down = False
self._shutdown_sent = False self._shutdown_sent = False
self.log = py.log.Producer("workerctl-%s" % gateway.id) self.log = py.log.Producer("workerctl-%s" % gateway.id)
@@ -292,7 +289,7 @@ class WorkerController(object):
if not self._down: if not self._down:
try: try:
self.sendcommand("shutdown") self.sendcommand("shutdown")
except (IOError, OSError): except OSError:
pass pass
self._shutdown_sent = True self._shutdown_sent = True
@@ -327,9 +324,11 @@ class WorkerController(object):
self.log("ignoring {}({})".format(eventname, kwargs)) self.log("ignoring {}({})".format(eventname, kwargs))
elif eventname == "workerready": elif eventname == "workerready":
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "internal_error":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "workerfinished": elif eventname == "workerfinished":
self._down = True self._down = True
self.workeroutput = self.slaveoutput = kwargs["workeroutput"] self.workeroutput = kwargs["workeroutput"]
self.notify_inproc("workerfinished", node=self) self.notify_inproc("workerfinished", node=self)
elif eventname in ("logstart", "logfinish"): elif eventname in ("logstart", "logfinish"):
self.notify_inproc(eventname, node=self, **kwargs) self.notify_inproc(eventname, node=self, **kwargs)
@@ -363,6 +362,17 @@ class WorkerController(object):
when=kwargs["when"], when=kwargs["when"],
item=kwargs["item"], item=kwargs["item"],
) )
elif eventname == "warning_recorded":
warning_message = unserialize_warning_message(
kwargs["warning_message_data"]
)
self.notify_inproc(
eventname,
warning_message=warning_message,
when=kwargs["when"],
nodeid=kwargs["nodeid"],
location=kwargs["location"],
)
else: else:
raise ValueError("unknown event: {}".format(eventname)) raise ValueError("unknown event: {}".format(eventname))
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -371,11 +381,7 @@ class WorkerController(object):
except: # noqa except: # noqa
from _pytest._code import ExceptionInfo from _pytest._code import ExceptionInfo
# ExceptionInfo API changed in pytest 4.1 excinfo = ExceptionInfo.from_current()
if hasattr(ExceptionInfo, "from_current"):
excinfo = ExceptionInfo.from_current()
else:
excinfo = ExceptionInfo()
print("!" * 20, excinfo) print("!" * 20, excinfo)
self.config.notify_exception(excinfo) self.config.notify_exception(excinfo)
self.shutdown() self.shutdown()

View File

@@ -1,10 +1,9 @@
import os import os
import re import re
import sys
import textwrap
import py import py
import pytest import pytest
import xdist
class TestDistribution: class TestDistribution:
@@ -115,9 +114,6 @@ class TestDistribution:
assert result.ret == 2 assert result.ret == 2
result.stdout.fnmatch_lines(["*Interrupted: stopping*1*", "*1 failed*"]) result.stdout.fnmatch_lines(["*Interrupted: stopping*1*", "*1 failed*"])
@pytest.mark.xfail(
reason="#527: Python 3.8 failure in pytest where testdir.tmpdir returns an unexpected value"
)
def test_basetemp_in_subprocesses(self, testdir): def test_basetemp_in_subprocesses(self, testdir):
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
@@ -189,7 +185,6 @@ class TestDistribution:
) )
assert result.ret == 1 assert result.ret == 1
@pytest.mark.xfail(reason="#527: Ignore Python 3.8 failure for the time being")
def test_distribution_rsyncdirs_example(self, testdir, monkeypatch): def test_distribution_rsyncdirs_example(self, testdir, monkeypatch):
# use a custom plugin that has a custom command-line option to ensure # use a custom plugin that has a custom command-line option to ensure
# this is propagated to workers (see #491) # this is propagated to workers (see #491)
@@ -246,22 +241,6 @@ class TestDistribution:
result.stderr.fnmatch_lines(["--foobar=123 active! *"]) result.stderr.fnmatch_lines(["--foobar=123 active! *"])
assert dest.join(subdir.basename).check(dir=1) assert dest.join(subdir.basename).check(dir=1)
def test_backward_compatibility_worker_terminology(self, testdir):
"""Ensure that we still support "config.slaveinput" for backward compatibility (#234).
Keep in mind that removing this compatibility will break a ton of plugins and user code.
"""
testdir.makepyfile(
"""
def test(pytestconfig):
assert hasattr(pytestconfig, 'slaveinput')
assert hasattr(pytestconfig, 'workerinput')
"""
)
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines("*1 passed*")
assert result.ret == 0
def test_data_exchange(self, testdir): def test_data_exchange(self, testdir):
testdir.makeconftest( testdir.makeconftest(
""" """
@@ -332,7 +311,7 @@ class TestDistribution:
time.sleep(10) time.sleep(10)
""" """
) )
child = testdir.spawn_pytest("-n1 -v") child = testdir.spawn_pytest("-n1 -v", expect_timeout=30.0)
child.expect(".*test_sleep.*") child.expect(".*test_sleep.*")
child.kill(2) # keyboard interrupt child.kill(2) # keyboard interrupt
child.expect(".*KeyboardInterrupt.*") child.expect(".*KeyboardInterrupt.*")
@@ -594,16 +573,13 @@ def test_fixture_teardown_failure(testdir):
assert result.ret assert result.ret
def test_config_initialization(testdir, pytestconfig): def test_config_initialization(testdir, monkeypatch, pytestconfig):
"""Ensure workers and master are initialized consistently. Integration test for #445""" """Ensure workers and master are initialized consistently. Integration test for #445"""
if not hasattr(pytestconfig, "invocation_params"):
pytest.skip(
"requires pytest >=5.1 (config has no attribute 'invocation_params')"
)
testdir.makepyfile( testdir.makepyfile(
**{ **{
"dir_a/test_foo.py": """ "dir_a/test_foo.py": """
def test_1(): pass def test_1(request):
assert request.config.option.verbose == 2
""" """
} }
) )
@@ -614,8 +590,10 @@ def test_config_initialization(testdir, pytestconfig):
testpaths=dir_a testpaths=dir_a
""", """,
) )
monkeypatch.setenv("PYTEST_ADDOPTS", "-v")
result = testdir.runpytest("-n2", "-c", "myconfig.ini", "-v") result = testdir.runpytest("-n2", "-c", "myconfig.ini", "-v")
result.stdout.fnmatch_lines(["dir_a/test_foo.py::test_1*"]) result.stdout.fnmatch_lines(["dir_a/test_foo.py::test_1*", "*= 1 passed in *"])
assert result.ret == 0
@pytest.mark.parametrize("when", ["setup", "call", "teardown"]) @pytest.mark.parametrize("when", ["setup", "call", "teardown"])
@@ -693,32 +671,6 @@ def test_skipping(testdir):
result.stdout.fnmatch_lines(["*hello*", "*1 skipped*"]) result.stdout.fnmatch_lines(["*hello*", "*1 skipped*"])
def test_issue34_pluginloading_in_subprocess(testdir):
import _pytest.hookspec
if not hasattr(_pytest.hookspec, "pytest_namespace"):
pytest.skip("this pytest version no longer supports pytest_namespace()")
testdir.tmpdir.join("plugin123.py").write(
textwrap.dedent(
"""
def pytest_namespace():
return {'sample_variable': 'testing'}
"""
)
)
testdir.makepyfile(
"""
import pytest
def test_hello():
assert pytest.sample_variable == "testing"
"""
)
result = testdir.runpytest_subprocess("-n1", "-p", "plugin123")
assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"])
def test_fixture_scope_caching_issue503(testdir): def test_fixture_scope_caching_issue503(testdir):
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
@@ -796,31 +748,49 @@ def test_sub_plugins_disabled(testdir, plugin):
class TestWarnings: class TestWarnings:
@pytest.mark.parametrize("n", ["-n0", "-n1"]) @pytest.mark.parametrize("n", ["-n0", "-n1"])
@pytest.mark.parametrize("warn_type", ["pytest", "builtin"]) def test_warnings(self, testdir, n):
def test_warnings(self, testdir, n, request, warn_type):
if warn_type == "builtin":
warn_code = """warnings.warn(UserWarning('this is a warning'))"""
elif warn_type == "pytest":
if not hasattr(request.config, "warn"):
pytest.skip("config.warn has been removed in pytest 4.1")
warn_code = """request.config.warn('', 'this is a warning',
fslocation=py.path.local())"""
else:
assert False
testdir.makepyfile( testdir.makepyfile(
""" """
import warnings, py, pytest import warnings, py, pytest
@pytest.mark.filterwarnings('ignore:config.warn has been deprecated') @pytest.mark.filterwarnings('ignore:config.warn has been deprecated')
def test_func(request): def test_func(request):
{warn_code} warnings.warn(UserWarning('this is a warning'))
""".format( """
warn_code=warn_code
)
) )
result = testdir.runpytest(n) result = testdir.runpytest(n)
result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warning*"]) result.stdout.fnmatch_lines(["*this is a warning*", "*1 passed, 1 warning*"])
def test_warning_captured_deprecated_in_pytest_6(self, testdir):
"""
Do not trigger the deprecated pytest_warning_captured hook in pytest 6+ (#562)
"""
import _pytest.hookspec
if not hasattr(_pytest.hookspec, "pytest_warning_recorded"):
pytest.skip("test requires pytest 6.0+")
testdir.makeconftest(
"""
def pytest_warning_captured(warning_message):
if warning_message == "my custom worker warning":
assert False, (
"this hook should not be called from workers "
"in this version: {}"
).format(warning_message)
"""
)
testdir.makepyfile(
"""
import warnings
def test():
warnings.warn("my custom worker warning")
"""
)
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines(["*1 passed*"])
result.stdout.no_fnmatch_line("*this hook should not be called in this version")
@pytest.mark.parametrize("n", ["-n0", "-n1"]) @pytest.mark.parametrize("n", ["-n0", "-n1"])
def test_custom_subclass(self, testdir, n): def test_custom_subclass(self, testdir, n):
"""Check that warning subclasses that don't honor the args attribute don't break """Check that warning subclasses that don't honor the args attribute don't break
@@ -867,9 +837,6 @@ class TestWarnings:
"""Check that warnings with unserializable _WARNING_DETAILS are """Check that warnings with unserializable _WARNING_DETAILS are
handled correctly (#379). handled correctly (#379).
""" """
if sys.version_info[0] < 3:
# The issue is only present in Python 3 warnings
return
testdir.makepyfile( testdir.makepyfile(
""" """
import warnings, pytest import warnings, pytest
@@ -1171,6 +1138,18 @@ def test_internal_error_with_maxfail(testdir):
assert "INTERNALERROR" not in result.stderr.str() assert "INTERNALERROR" not in result.stderr.str()
def test_internal_errors_propagate_to_master(testdir):
testdir.makeconftest(
"""
def pytest_collection_modifyitems():
raise RuntimeError("Some runtime error")
"""
)
testdir.makepyfile("def test(): pass")
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines(["*RuntimeError: Some runtime error*"])
class TestLoadScope: class TestLoadScope:
def test_by_module(self, testdir): def test_by_module(self, testdir):
test_file = """ test_file = """
@@ -1385,7 +1364,7 @@ def parse_tests_and_workers_from_output(lines):
r""" r"""
\[(gw\d)\] # worker \[(gw\d)\] # worker
\s* \s*
(?:\[\s*\d+%\])? # progress indicator (pytest >=3.3) (?:\[\s*\d+%\])? # progress indicator
\s(.*?) # status string ("PASSED") \s(.*?) # status string ("PASSED")
\s(.*::.*) # nodeid \s(.*::.*) # nodeid
""", """,
@@ -1404,3 +1383,39 @@ def get_workers_and_test_count_by_prefix(prefix, lines, expected_status="PASSED"
if expected_status == status and nodeid.startswith(prefix): if expected_status == status and nodeid.startswith(prefix):
result[worker] = result.get(worker, 0) + 1 result[worker] = result.get(worker, 0) + 1
return result return result
class TestAPI:
@pytest.fixture
def fake_request(self):
class FakeOption:
def __init__(self):
self.dist = "load"
class FakeConfig:
def __init__(self):
self.workerinput = {"workerid": "gw5"}
self.option = FakeOption()
class FakeRequest:
def __init__(self):
self.config = FakeConfig()
return FakeRequest()
def test_is_xdist_worker(self, fake_request):
assert xdist.is_xdist_worker(fake_request)
del fake_request.config.workerinput
assert not xdist.is_xdist_worker(fake_request)
def test_is_xdist_master(self, fake_request):
assert not xdist.is_xdist_master(fake_request)
del fake_request.config.workerinput
assert xdist.is_xdist_master(fake_request)
fake_request.config.option.dist = "no"
assert not xdist.is_xdist_master(fake_request)
def test_get_xdist_worker_id(self, fake_request):
assert xdist.get_xdist_worker_id(fake_request) == "gw5"
del fake_request.config.workerinput
assert xdist.get_xdist_worker_id(fake_request) == "master"

View File

@@ -1,20 +1,9 @@
import six
import py import py
import pytest import pytest
import execnet import execnet
pytest_plugins = "pytester" pytest_plugins = "pytester"
if six.PY2:
@pytest.fixture(scope="session", autouse=True)
def _ensure_imports():
# we import some modules because pytest-2.8's testdir fixture
# will unload all modules after each test and this cause
# (unknown) problems with execnet.Group()
execnet.Group
execnet.makegateway
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _divert_atexit(request, monkeypatch): def _divert_atexit(request, monkeypatch):
@@ -22,12 +11,16 @@ def _divert_atexit(request, monkeypatch):
finalizers = [] finalizers = []
def finish(): def fake_register(func, *args, **kwargs):
while finalizers: finalizers.append((func, args, kwargs))
finalizers.pop()()
monkeypatch.setattr(atexit, "register", finalizers.append) monkeypatch.setattr(atexit, "register", fake_register)
request.addfinalizer(finish)
yield
while finalizers:
func, args, kwargs = finalizers.pop()
func(*args, **kwargs)
def pytest_addoption(parser): def pytest_addoption(parser):

View File

@@ -203,7 +203,7 @@ class TestLoadScheduling:
different test ids are collected by workers. different test ids are collected by workers.
""" """
class CollectHook(object): class CollectHook:
""" """
Dummy hook that stores collection reports. Dummy hook that stores collection reports.
""" """

View File

@@ -284,7 +284,7 @@ class TestFunctional:
) )
# p = testdir.mkdir("sub").join(p1.basename) # p = testdir.mkdir("sub").join(p1.basename)
# p1.move(p) # p1.move(p)
child = testdir.spawn_pytest("-f %s --traceconfig" % p) child = testdir.spawn_pytest("-f %s --traceconfig" % p, expect_timeout=30.0)
child.expect("def test_one") child.expect("def test_one")
child.expect("x == 1") child.expect("x == 1")
child.expect("1 failed") child.expect("1 failed")
@@ -311,7 +311,7 @@ class TestFunctional:
pass pass
""" """
) )
child = testdir.spawn_pytest("-f %s" % p) child = testdir.spawn_pytest("-f %s" % p, expect_timeout=30.0)
child.expect("1 xpass") child.expect("1 xpass")
# child.expect("### LOOPONFAILING ####") # child.expect("### LOOPONFAILING ####")
child.expect("waiting for changes") child.expect("waiting for changes")

View File

@@ -1,7 +1,11 @@
from contextlib import suppress
import py import py
import execnet import execnet
from xdist.workermanage import NodeManager from xdist.workermanage import NodeManager
import pytest
def test_dist_incompatibility_messages(testdir): def test_dist_incompatibility_messages(testdir):
result = testdir.runpytest("--pdb", "--looponfail") result = testdir.runpytest("--pdb", "--looponfail")
@@ -38,6 +42,11 @@ def test_auto_detect_cpus(testdir, monkeypatch):
import os import os
from xdist.plugin import pytest_cmdline_main as check_options from xdist.plugin import pytest_cmdline_main as check_options
with suppress(ImportError):
import psutil
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: None)
if hasattr(os, "sched_getaffinity"): if hasattr(os, "sched_getaffinity"):
monkeypatch.setattr(os, "sched_getaffinity", lambda _pid: set(range(99))) monkeypatch.setattr(os, "sched_getaffinity", lambda _pid: set(range(99)))
elif hasattr(os, "cpu_count"): elif hasattr(os, "cpu_count"):
@@ -51,19 +60,48 @@ def test_auto_detect_cpus(testdir, monkeypatch):
assert config.getoption("numprocesses") == 2 assert config.getoption("numprocesses") == 2
config = testdir.parseconfigure("-nauto") config = testdir.parseconfigure("-nauto")
check_options(config)
assert config.getoption("numprocesses") == 99 assert config.getoption("numprocesses") == 99
config = testdir.parseconfigure("-nauto", "--pdb") config = testdir.parseconfigure("-nauto", "--pdb")
check_options(config) check_options(config)
assert config.getoption("usepdb") assert config.getoption("usepdb")
assert config.getoption("numprocesses") == 0 assert config.getoption("numprocesses") == 0
assert config.getoption("dist") == "no"
monkeypatch.delattr(os, "sched_getaffinity", raising=False) monkeypatch.delattr(os, "sched_getaffinity", raising=False)
monkeypatch.setenv("TRAVIS", "true") monkeypatch.setenv("TRAVIS", "true")
config = testdir.parseconfigure("-nauto") config = testdir.parseconfigure("-nauto")
check_options(config)
assert config.getoption("numprocesses") == 2 assert config.getoption("numprocesses") == 2
def test_auto_detect_cpus_psutil(testdir, monkeypatch):
from xdist.plugin import pytest_cmdline_main as check_options
psutil = pytest.importorskip("psutil")
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: 42)
config = testdir.parseconfigure("-nauto")
check_options(config)
assert config.getoption("numprocesses") == 42
def test_hook_auto_num_workers(testdir, monkeypatch):
from xdist.plugin import pytest_cmdline_main as check_options
testdir.makeconftest(
"""
def pytest_xdist_auto_num_workers():
return 42
"""
)
config = testdir.parseconfigure("-nauto")
check_options(config)
assert config.getoption("numprocesses") == 42
def test_boxed_with_collect_only(testdir): def test_boxed_with_collect_only(testdir):
from xdist.plugin import pytest_cmdline_main as check_options from xdist.plugin import pytest_cmdline_main as check_options

View File

@@ -8,7 +8,7 @@ from xdist.workermanage import WorkerController
import execnet import execnet
import marshal import marshal
from six.moves.queue import Queue from queue import Queue
WAIT_TIMEOUT = 10.0 WAIT_TIMEOUT = 10.0

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import py import py
import pytest import pytest
import textwrap import textwrap
@@ -35,7 +34,7 @@ def mysetup(tmpdir):
@pytest.fixture @pytest.fixture
def workercontroller(monkeypatch): def workercontroller(monkeypatch):
class MockController(object): class MockController:
def __init__(self, *args): def __init__(self, *args):
pass pass
@@ -197,6 +196,25 @@ class TestNodeManager:
assert dest.join("dir1", "dir2", "hello").check() assert dest.join("dir1", "dir2", "hello").check()
nodemanager.teardown_nodes() nodemanager.teardown_nodes()
@pytest.mark.parametrize(
"flag, expects_report", [("-q", False), ("", False), ("-v", True)]
)
def test_rsync_report(
self, testdir, mysetup, workercontroller, capsys, flag, expects_report
):
source, dest = mysetup.source, mysetup.dest
dir1 = mysetup.source.mkdir("dir1")
args = "--tx", "popen//chdir=%s" % dest, "--rsyncdir", dir1, source
if flag:
args += (flag,)
nodemanager = NodeManager(testdir.parseconfig(*args))
nodemanager.setup_nodes(None) # calls .rsync_roots()
out, _ = capsys.readouterr()
if expects_report:
assert "<= pytest/__init__.py" in out
else:
assert "<= pytest/__init__.py" not in out
def test_init_rsync_roots(self, testdir, mysetup, workercontroller): def test_init_rsync_roots(self, testdir, mysetup, workercontroller):
source, dest = mysetup.source, mysetup.dest source, dest = mysetup.source, mysetup.dest
dir2 = source.ensure("dir1", "dir2", dir=1) dir2 = source.ensure("dir1", "dir2", dir=1)

23
tox.ini
View File

@@ -1,11 +1,11 @@
[tox] [tox]
envlist= envlist=
linting linting
py{27,35,36,37,38}-pytestlatest py{35,36,37,38,39}-pytestlatest
py38-pytestmaster py38-pytestmaster
py38-psutil
[testenv] [testenv]
passenv = USER USERNAME
extras = testing extras = testing
deps = deps =
pytestlatest: pytest pytestlatest: pytest
@@ -13,9 +13,18 @@ deps =
commands= commands=
pytest {posargs} pytest {posargs}
[testenv:py38-psutil]
extras =
testing
psutil
deps = pytest
commands =
pytest {posargs:-k psutil}
[testenv:linting] [testenv:linting]
skipsdist = True skip_install = True
usedevelop = True usedevelop = True
passenv = PRE_COMMIT_HOME
deps = deps =
pre-commit pre-commit
commands = pre-commit run --all-files --show-diff-on-failure commands = pre-commit run --all-files --show-diff-on-failure
@@ -28,12 +37,14 @@ skipsdist = True
usedevelop = True usedevelop = True
passenv = * passenv = *
deps = deps =
towncrier towncrier
commands = commands =
towncrier --version {posargs} --yes towncrier --version {posargs} --yes
[pytest] [pytest]
addopts = -ra # pytest-services also defines a worker_id fixture, disable
# it so they don't conflict with each other (#611).
addopts = -ra -p no:pytest-services
testpaths = testing testpaths = testing
[flake8] [flake8]