Compare commits

..

97 Commits

Author SHA1 Message Date
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
Sven-Hendrik Haase
d3e1a52caa Prepare version 1.32.0 2020-05-03 01:00:40 +02:00
Bruno Oliveira
905eb3244d Merge pull request #528 from ashokdelphia/fix-acessing-typo-in-readme 2020-05-02 11:41:18 -03:00
Ashok Argent-Katwala
b4fb97a6e0 Fix typo in readme. 2020-05-02 11:40:23 -03:00
Sven-Hendrik Haase
e8bbb4db2a Merge pull request #525 from pytest-dev/524-add-global-session-uuid
Add global testrun uid
2020-05-01 19:36:53 +02:00
Sven-Hendrik Haase
069f959690 Fix docs format 2020-05-01 18:56:09 +02:00
Sven-Hendrik Haase
f56596bcdc Merge branch 'master' into 524-add-global-session-uuid 2020-05-01 16:56:23 +02:00
Sven-Hendrik Haase
ec13ceaa0c Merge pull request #527 from pytest-dev/ignore-python38-failure
Ignore Python 3.8 specific failure in pytest for now
2020-05-01 16:54:21 +02:00
Sven-Hendrik Haase
8714a99d51 Ignore Python 3.8 specific failure in pytest for now 2020-04-30 21:08:21 +02:00
Sven-Hendrik Haase
2183d741bb Add global session uuid (fixes #524) 2020-04-30 19:43:16 +02:00
Daniel Hahler
453cbd88dd tox/ci: remove testing with pytest's features branch (#509)
pytest is using the master branch now only.
2020-03-31 11:11:05 +02:00
Bruno Oliveira
bea6f66174 Merge pull request #475 from hugovk/rm-3.4
Drop support for EOL Python 3.4
2020-03-28 23:01:12 -03:00
Hugo van Kemenade
3a470043c9 Merge branch 'master' into rm-3.4 2020-01-26 21:05:51 +02:00
Bruno Oliveira
6fd5b56491 Merge pull request #495 from nicoddemus/release-1.31.0
Release 1.31.0
2019-12-19 22:45:30 -03:00
Hugo
69d98fb079 Format with Black 2019-12-02 18:00:05 +02:00
Hugo
db5532cba3 Add news file 2019-12-02 17:57:52 +02:00
Hugo
157871c026 Upgrade Python syntax with pyupgrade 2019-12-02 17:57:38 +02:00
Hugo
cf59eb07b9 Remove: '[WARNING] Unexpected key(s) present on local => rst: python_version' 2019-12-02 17:56:16 +02:00
Hugo
8f1e6ad10c Drop support for EOL Python 3.4 2019-12-02 17:56:08 +02:00
Hugo
3a87f367fe pre-commit autoupdate 2019-12-02 17:53:52 +02:00
32 changed files with 761 additions and 413 deletions

View File

@@ -1,23 +0,0 @@
environment:
matrix:
- TOXENV: "py27-pytestlatest"
- TOXENV: "py34-pytestlatest"
- TOXENV: "py35-pytestlatest"
- TOXENV: "py36-pytestlatest"
- TOXENV: "py37-pytestlatest"
- TOXENV: "py38-pytestlatest"
- TOXENV: "py38-pytestmaster"
- TOXENV: "py38-pytestfeatures"
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

@@ -1,18 +1,23 @@
repos:
- repo: https://github.com/ambv/black
rev: 18.6b4
rev: 19.10b0
hooks:
- id: black
args: [--safe, --quiet]
args: [--safe, --quiet, --target-version, py35]
language_version: python3.7
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v1.3.0
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statements
- id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
hooks:
- id: pyupgrade
args: [--py3-plus]
- repo: local
hooks:
- id: rst

View File

@@ -1,66 +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.4"
env: TOXENV=py34-pytestlatest
- 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
- python: "3.8"
env: TOXENV=py38-pytestfeatures
- 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,110 @@
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)
================================
Deprecations and Removals
-------------------------
- `#475 <https://github.com/pytest-dev/pytest-xdist/issues/475>`_: Drop support for EOL Python 3.4.
Features
--------
- `#524 <https://github.com/pytest-dev/pytest-xdist/issues/524>`_: Add `testrun_uid` fixture. This is a shared value that uniquely identifies a test run among all workers.
This also adds a `PYTEST_XDIST_TESTRUNUID` environment variable that is accessible within a test as well as a command line option `--testrunuid` to manually set the value from outside.
pytest-xdist 1.31.0 (2019-12-19)
================================

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
:target: https://pypi.python.org/pypi/pytest-xdist
.. image:: https://travis-ci.org/pytest-dev/pytest-xdist.svg?branch=master
:alt: Travis CI build status
: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://github.com/pytest-dev/pytest-xdist/workflows/build/badge.svg
:target: https://github.com/pytest-dev/pytest-xdist/actions
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
@@ -57,43 +52,46 @@ Install the plugin with::
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:
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
a lot of I/O this can lead to considerable speed ups. This option can
also be set to ``auto`` for automatic detection of the number of CPUs.
Pass ``-n auto`` to use as many processes as your computer has CPU cores. This
can lead to considerable speed ups, especially if your test suite takes a
noticeable amount of time.
If a test crashes the interpreter, pytest-xdist will automatically restart
that worker and report the failure as usual. You can use the
``--max-worker-restart`` option to limit the number of workers that can
be restarted, or disable restarting altogether using ``--max-worker-restart=0``.
If a test crashes a worker, pytest-xdist will automatically restart that worker
and report the tests failure. You can use the ``--max-worker-restart`` option
to limit the number of worker restarts that are allowed, or disable restarting
altogether using ``--max-worker-restart 0``.
By default, the ``-n`` option will send pending tests to any worker that is available, without
any guaranteed order, but you can control this with these options:
By default, using ``--numprocesses`` will send pending tests to any worker that
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
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 no``: The default algorithm, distributing one test at a time.
* ``--dist=loadfile``: tests will be grouped by file name, and then will be sent to an available
worker, guaranteeing that all tests in a group run in the same worker. This feature was added
in version ``1.21``.
* ``--dist loadscope``: Tests are grouped by **module** for *test functions*
and by **class** for *test methods*. Groups are distributed to available
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -124,7 +122,7 @@ Here is the code:
@pytest.fixture(scope="session")
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
# pytest's fixture caching do its job
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
.. _`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
@@ -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
``worker_id`` will return ``"master"``.
Additionally, worker processes have the following environment variables
Worker processes also have the following environment variables
defined:
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
@@ -276,8 +274,71 @@ defined:
The information about the worker_id in a test is stored in the ``TestReport`` as
well, under the ``worker_id`` attribute.
Acessing ``sys.argv`` from the master node in workers
-----------------------------------------------------
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
-----------------------------------------
*New in version 1.32.*
If you need to globally distinguish one test run from others in your
workers, you can use the ``testrun_uid`` fixture. For instance, let's say you
wanted to create a separate database for each test run:
.. code-block:: python
import pytest
from posix_ipc import Semaphore, O_CREAT
@pytest.fixture(scope="session", autouse=True)
def create_unique_database(testrun_uid):
""" create a unique database for this particular test run """
database_url = f"psql://myapp-{testrun_uid}"
with Semaphore(f"/{testrun_uid}-lock", flags=O_CREAT, initial_value=1):
if not database_exists(database_url):
create_database(database_url)
@pytest.fixture()
def db(testrun_uid):
""" retrieve unique database """
database_url = f"psql://myapp-{testrun_uid}"
return database_get_instance(database_url)
Additionally, during a test run, the following environment variable is defined:
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
Accessing ``sys.argv`` from the master node in workers
------------------------------------------------------
To access the ``sys.argv`` passed to the command-line of the master node, use
``request.config.workerinput["mainargv"]``.

View File

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

View File

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

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__
__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):
""" signals an immediate interruption. """
class DSession(object):
class DSession:
"""A pytest plugin which runs a distributed test session
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["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)
if self.shuttingdown:
node.shutdown()
@@ -169,7 +166,7 @@ class DSession(object):
"""
self.config.hook.pytest_testnodedown(node=node, error=None)
if node.workeroutput["exitstatus"] == 2: # keyboard-interrupt
self.shouldstop = "%s received keyboard-interrupt" % (node,)
self.shouldstop = "{} received keyboard-interrupt".format(node)
self.worker_errordown(node, "keyboard-interrupt")
return
if node in self.sched.nodes:
@@ -177,6 +174,24 @@ class DSession(object):
assert not crashitem, (crashitem, 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):
"""Emitted by the WorkerController when a node dies."""
self.config.hook.pytest_testnodedown(node=node, error=error)
@@ -271,16 +286,18 @@ class DSession(object):
assert not rep.passed
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):
"""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)
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):
"""Return new node based on an existing one.
@@ -321,7 +338,7 @@ class DSession(object):
# XXX count no of failures and retry N times
runner = self.config.pluginmanager.getplugin("runner")
fspath = nodeid.split("::")[0]
msg = "worker %r crashed while running %r" % (worker.gateway.id, nodeid)
msg = "worker {!r} crashed while running {!r}".format(worker.gateway.id, nodeid)
rep = runner.TestReport(
nodeid, (fspath, None, fspath), (), "failed", msg, "???"
)
@@ -329,7 +346,7 @@ class DSession(object):
self.config.hook.pytest_runtest_logreport(report=rep)
class TerminalDistReporter(object):
class TerminalDistReporter:
def __init__(self, config):
self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter")
@@ -351,7 +368,9 @@ class TerminalDistReporter(object):
def getstatus(self):
if self.config.option.verbose >= 0:
parts = ["%s %s" % (spec.id, self._status[spec.id]) for spec in self._specs]
parts = [
"{} {}".format(spec.id, self._status[spec.id]) for spec in self._specs
]
return " / ".join(parts)
else:
return "bringing up nodes..."
@@ -386,14 +405,16 @@ class TerminalDistReporter(object):
def pytest_testnodeready(self, node):
if self.config.option.verbose > 0:
d = node.workerinfo
infoline = "[%s] Python %s" % (d["id"], d["version"].replace("\n", " -- "))
infoline = "[{}] Python {}".format(
d["id"], d["version"].replace("\n", " -- ")
)
self.rewrite(infoline, newline=True)
self.setstatus(node.gateway.spec, "ok")
def pytest_testnodedown(self, node, error):
if not error:
return
self.write_line("[%s] node down: %s" % (node.gateway.id, error))
self.write_line("[{}] node down: {}".format(node.gateway.id, error))
def get_default_max_worker_restart(config):

View File

@@ -6,7 +6,6 @@
processes) otherwise changes to source code can crash
the controlling process which should best never happen.
"""
from __future__ import print_function
import py
import pytest
import sys
@@ -55,7 +54,7 @@ def looponfail_main(config):
print()
class RemoteControl(object):
class RemoteControl:
def __init__(self, config):
self.config = config
self.failures = []
@@ -136,7 +135,7 @@ def repr_pytest_looponfailinfo(failreports, rootdirs):
tr.line(report, red=True)
tr.sep("#", "waiting for changes", bold=True)
for rootdir in rootdirs:
tr.line("### Watching: %s" % (rootdir,), bold=True)
tr.line("### Watching: {}".format(rootdir), bold=True)
def init_worker_session(channel, args, option_dict):
@@ -165,7 +164,7 @@ def init_worker_session(channel, args, option_dict):
WorkerFailSession(config, channel).main()
class WorkerFailSession(object):
class WorkerFailSession:
def __init__(self, config, channel):
self.config = config
self.channel = channel
@@ -220,7 +219,7 @@ class WorkerFailSession(object):
self.channel.send((trails, failreports, self.collection_failed))
class StatRecorder(object):
class StatRecorder:
def __init__(self, rootdirlist):
self.rootdirlist = rootdirlist
self.statcache = {}

View File

@@ -55,3 +55,13 @@ def pytest_xdist_node_collection_finished(node, ids):
@pytest.mark.firstresult
def pytest_xdist_make_scheduler(config, log):
""" 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

@@ -1,12 +1,25 @@
import os
import uuid
import py
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:
from os import sched_getaffinity
def cpu_count():
return len(sched_getaffinity(0))
except ImportError:
if os.environ.get("TRAVIS") == "true":
# workaround https://bitbucket.org/pypy/pypy/issues/2375
@@ -15,11 +28,6 @@ def auto_detect_cpus():
from os import cpu_count
except ImportError:
from multiprocessing import cpu_count
else:
def cpu_count():
return len(sched_getaffinity(0))
try:
n = cpu_count()
except NotImplementedError:
@@ -27,13 +35,9 @@ def auto_detect_cpus():
return n if n else 1
class AutoInt(int):
"""Mark value as auto-detected."""
def parse_numprocesses(s):
if s == "auto":
return AutoInt(auto_detect_cpus())
return "auto"
elif s is not None:
return int(s)
@@ -61,14 +65,11 @@ def pytest_addoption(parser):
)
group.addoption(
"--max-worker-restart",
"--max-slave-restart",
action="store",
default=None,
dest="maxworkerrestart",
help="maximum number of workers that can be restarted "
"when crashed (set to zero to disable this feature)\n"
"'--max-slave-restart' option is deprecated and will be removed in "
"a future release",
"when crashed (set to zero to disable this feature)",
)
group.addoption(
"--dist",
@@ -122,12 +123,22 @@ def pytest_addoption(parser):
metavar="GLOB",
help="add expression for ignores when rsyncing to remote tx nodes.",
)
group.addoption(
"--boxed",
action="store_true",
help="backward compatibility alias for pytest-forked --forked",
)
group.addoption(
"--testrunuid",
action="store",
help=(
"provide an identifier shared amongst all workers as the value of "
"the 'testrun_uid' fixture,\n\n,"
"if not provided, 'testrun_uid' is filled with a new unique string "
"on every test run."
),
)
parser.addini(
"rsyncdirs",
"list of (relative) paths to be rsynced for remote distributed testing.",
@@ -179,8 +190,13 @@ def pytest_configure(config):
@pytest.mark.tryfirst
def pytest_cmdline_main(config):
usepdb = config.getoption("usepdb", False) # a core option
if isinstance(config.option.numprocesses, AutoInt):
config.option.numprocesses = 0 if usepdb else int(config.option.numprocesses)
if config.option.numprocesses == "auto":
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.dist == "no":
@@ -192,25 +208,66 @@ def pytest_cmdline_main(config):
if config.option.distload:
config.option.dist = "load"
val = config.getvalue
if not val("collectonly"):
if val("dist") != "no":
if usepdb:
raise pytest.UsageError(
"--pdb is incompatible with distributing tests; try using -n0 or -nauto."
) # noqa: E501
if not val("collectonly") and val("dist") != "no" and usepdb:
raise pytest.UsageError(
"--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")
def worker_id(request):
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
if running on the master node.
"""
return get_xdist_worker_id(request)
@pytest.fixture(scope="session")
def testrun_uid(request):
"""Return the unique id of the current test."""
if hasattr(request.config, "workerinput"):
return request.config.workerinput["workerid"]
return request.config.workerinput["testrunuid"]
else:
return "master"
return uuid.uuid4().hex

View File

@@ -11,17 +11,17 @@ import os
import time
import py
import _pytest.hookspec
import pytest
from execnet.gateway_base import dumps, DumpError
from _pytest.config import _prepareconfig, Config
class WorkerInteractor(object):
class WorkerInteractor:
def __init__(self, config, channel):
self.config = config
self.workerid = config.workerinput.get("workerid", "?")
self.testrunuid = config.workerinput["testrunuid"]
self.log = py.log.Producer("worker-%s" % self.workerid)
if not config.option.debug:
py.log.setconsumer(self.log._keywords, None)
@@ -33,8 +33,10 @@ class WorkerInteractor(object):
self.channel.send((name, kwargs))
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)
interactor.sendevent("internal_error", formatted_error=formatted_error)
def pytest_sessionstart(self, session):
self.session = session
@@ -100,11 +102,8 @@ class WorkerInteractor(object):
def pytest_runtest_logstart(self, nodeid, location):
self.sendevent("logstart", nodeid=nodeid, location=location)
# the pytest_runtest_logfinish hook was introduced in pytest 3.4
if hasattr(_pytest.hookspec, "pytest_runtest_logfinish"):
def pytest_runtest_logfinish(self, nodeid, location):
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):
data = self.config.hook.pytest_report_to_serializable(
@@ -112,6 +111,7 @@ class WorkerInteractor(object):
)
data["item_index"] = self.item_index
data["worker_id"] = self.workerid
data["testrun_uid"] = self.testrunuid
assert self.session.items[self.item_index].nodeid == report.nodeid
self.sendevent("testreport", data=data)
@@ -123,31 +123,14 @@ class WorkerInteractor(object):
)
self.sendevent("collectreport", data=data)
# the pytest_logwarning hook was deprecated since pytest 4.0
if hasattr(
_pytest.hookspec, "pytest_logwarning"
) and not _pytest.hookspec.pytest_logwarning.pytest_spec.get("warn_on_impl"):
def pytest_logwarning(self, message, code, nodeid, fslocation):
self.sendevent(
"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 pytest_warning_recorded(self, warning_message, when, nodeid, location):
self.sendevent(
"warning_recorded",
warning_message_data=serialize_warning_message(warning_message),
when=when,
nodeid=nodeid,
location=location,
)
def serialize_warning_message(warning_message):
@@ -238,6 +221,7 @@ if __name__ == "__channelexec__":
importpath + os.pathsep + os.environ.get("PYTHONPATH", "")
)
os.environ["PYTEST_XDIST_TESTRUNUID"] = workerinput["testrunuid"]
os.environ["PYTEST_XDIST_WORKER"] = workerinput["workerid"]
os.environ["PYTEST_XDIST_WORKER_COUNT"] = str(workerinput["workercount"])
@@ -251,8 +235,5 @@ if __name__ == "__channelexec__":
config._parser.prog = os.path.basename(workerinput["mainargv"][0])
config.workerinput = workerinput
config.workeroutput = {}
# TODO: deprecated name, backward compatibility only. Remove it in future
config.slaveinput = config.workerinput
config.slaveoutput = config.workeroutput
interactor = WorkerInteractor(config, channel)
config.hook.pytest_cmdline_main(config=config)

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
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)
error_message = (
u"Different tests were collected between {from_id} and {to_id}. "
u"The difference is:\n"
u"{diff}"
"Different tests were collected between {from_id} and {to_id}. "
"The difference is:\n"
"{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")])
return msg

View File

@@ -4,7 +4,7 @@ from xdist.workermanage import parse_spec_config
from xdist.report import report_collection_diff
class EachScheduling(object):
class EachScheduling:
"""Implement scheduling of test items on all nodes
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
class LoadScheduling(object):
class LoadScheduling:
"""Implement load scheduling across nodes.
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):
super(LoadFileScheduling, self).__init__(config, log)
super().__init__(config, log)
if log is None:
self.log = Producer("loadfilesched")
else:

View File

@@ -6,7 +6,7 @@ from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
class LoadScopeScheduling(object):
class LoadScopeScheduling:
"""Implement load scheduling across nodes, but grouping test by scope.
This distributes the tests collected across all nodes so each test is run
@@ -358,12 +358,12 @@ class LoadScopeScheduling(object):
extra_nodes = len(self.nodes) - len(self.workqueue)
if extra_nodes > 0:
self.log("Shuting down {0} nodes".format(extra_nodes))
self.log("Shuting down {} nodes".format(extra_nodes))
for _ in range(extra_nodes):
unused_node, assigned = self.assigned_work.popitem(last=True)
self.log("Shuting down unused node {0}".format(unused_node))
self.log("Shuting down unused node {}".format(unused_node))
unused_node.shutdown()
# Assign initial workload

View File

@@ -1,8 +1,8 @@
from __future__ import print_function
import fnmatch
import os
import re
import sys
import uuid
import py
import pytest
@@ -28,13 +28,16 @@ def parse_spec_config(config):
return xspeclist
class NodeManager(object):
class NodeManager:
EXIT_TIMEOUT = 10
DEFAULT_IGNORES = [".*", "*.pyc", "*.pyo", "*~"]
def __init__(self, config, specs=None, defaultchdir="pyexecnetcache"):
self.config = config
self.trace = self.config.trace.get("nodemanager")
self.testrunuid = self.config.getoption("testrunuid")
if self.testrunuid is None:
self.testrunuid = uuid.uuid4().hex
self.group = execnet.Group()
if specs is None:
specs = self._getxspecs()
@@ -59,10 +62,7 @@ class NodeManager(object):
def setup_nodes(self, putevent):
self.config.hook.pytest_xdist_setupnodes(config=self.config, specs=self.specs)
self.trace("setting up nodes")
nodes = []
for spec in self.specs:
nodes.append(self.setup_node(spec, putevent))
return nodes
return [self.setup_node(spec, putevent) for spec in self.specs]
def setup_node(self, spec, putevent):
gw = self.group.makegateway(spec)
@@ -89,8 +89,16 @@ class NodeManager(object):
import pytest
import _pytest
pytestpath = pytest.__file__.rstrip("co")
pytestdir = py.path.local(_pytest.__file__).dirpath()
def get_dir(p):
"""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
candidates = [py._pydir, pytestpath, pytestdir]
candidates += config.option.rsyncdir
@@ -101,7 +109,7 @@ class NodeManager(object):
for root in candidates:
root = py.path.local(root).realpath()
if not root.check():
raise pytest.UsageError("rsyncdir doesn't exist: %r" % (root,))
raise pytest.UsageError("rsyncdir doesn't exist: {!r}".format(root))
if root not in roots:
roots.append(root)
return roots
@@ -114,7 +122,7 @@ class NodeManager(object):
return {
"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):
@@ -154,12 +162,11 @@ class HostRSync(execnet.RSync):
def __init__(self, sourcedir, *args, **kwargs):
self._synced = {}
self._ignores = []
ignores = kwargs.pop("ignores", None) or []
for x in ignores:
x = getattr(x, "strpath", x)
self._ignores.append(re.compile(fnmatch.translate(x)))
super(HostRSync, self).__init__(sourcedir=sourcedir, **kwargs)
self._ignores = [
re.compile(fnmatch.translate(getattr(x, "strpath", x))) for x in ignores
]
super().__init__(sourcedir=sourcedir, **kwargs)
def filter(self, path):
path = py.path.local(path)
@@ -171,15 +178,13 @@ class HostRSync(execnet.RSync):
def add_target_host(self, gateway, finished=None):
remotepath = os.path.basename(self._sourcedir)
super(HostRSync, self).add_target(
gateway, remotepath, finishedcallback=finished, delete=True
)
super().add_target(gateway, remotepath, finishedcallback=finished, delete=True)
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
remotepath = gateway.spec.chdir
print("%s:%s <= %s" % (gateway.spec, remotepath, path))
print("{}:{} <= {}".format(gateway.spec, remotepath, path))
def make_reltoroot(roots, args):
@@ -198,12 +203,12 @@ def make_reltoroot(roots, args):
parts[0] = root.basename + "/" + x
break
else:
raise ValueError("arg %s not relative to an rsync root" % (arg,))
raise ValueError("arg {} not relative to an rsync root".format(arg))
result.append(splitcode.join(parts))
return result
class WorkerController(object):
class WorkerController:
ENDMARK = -1
class RemoteHook:
@@ -220,12 +225,9 @@ class WorkerController(object):
self.workerinput = {
"workerid": gateway.id,
"workercount": len(nodemanager.specs),
"slaveid": gateway.id,
"slavecount": len(nodemanager.specs),
"testrunuid": nodemanager.testrunuid,
"mainargv": sys.argv,
}
# TODO: deprecated name, backward compatibility only. Remove it in future
self.slaveinput = self.workerinput
self._down = False
self._shutdown_sent = False
self.log = py.log.Producer("workerctl-%s" % gateway.id)
@@ -233,7 +235,7 @@ class WorkerController(object):
py.log.setconsumer(self.log._keywords, None)
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.gateway.id)
return "<{} {}>".format(self.__class__.__name__, self.gateway.id)
@property
def shutting_down(self):
@@ -287,17 +289,17 @@ class WorkerController(object):
if not self._down:
try:
self.sendcommand("shutdown")
except (IOError, OSError):
except OSError:
pass
self._shutdown_sent = True
def sendcommand(self, name, **kwargs):
""" send a named parametrized command to the other side. """
self.log("sending command %s(**%s)" % (name, kwargs))
self.log("sending command {}(**{})".format(name, kwargs))
self.channel.send((name, kwargs))
def notify_inproc(self, eventname, **kwargs):
self.log("queuing %s(**%s)" % (eventname, kwargs))
self.log("queuing {}(**{})".format(eventname, kwargs))
self.putevent((eventname, kwargs))
def process_from_remote(self, eventcall): # noqa too complex
@@ -319,12 +321,14 @@ class WorkerController(object):
return
eventname, kwargs = eventcall
if eventname in ("collectionstart",):
self.log("ignoring %s(%s)" % (eventname, kwargs))
self.log("ignoring {}({})".format(eventname, kwargs))
elif eventname == "workerready":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "internal_error":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "workerfinished":
self._down = True
self.workeroutput = self.slaveoutput = kwargs["workeroutput"]
self.workeroutput = kwargs["workeroutput"]
self.notify_inproc("workerfinished", node=self)
elif eventname in ("logstart", "logfinish"):
self.notify_inproc(eventname, node=self, **kwargs)
@@ -358,19 +362,26 @@ class WorkerController(object):
when=kwargs["when"],
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:
raise ValueError("unknown event: %s" % (eventname,))
raise ValueError("unknown event: {}".format(eventname))
except KeyboardInterrupt:
# should not land in receiver-thread
raise
except: # noqa
from _pytest._code import ExceptionInfo
# ExceptionInfo API changed in pytest 4.1
if hasattr(ExceptionInfo, "from_current"):
excinfo = ExceptionInfo.from_current()
else:
excinfo = ExceptionInfo()
excinfo = ExceptionInfo.from_current()
print("!" * 20, excinfo)
self.config.notify_exception(excinfo)
self.shutdown()

View File

@@ -1,10 +1,9 @@
import os
import re
import sys
import textwrap
import py
import pytest
import xdist
class TestDistribution:
@@ -242,22 +241,6 @@ class TestDistribution:
result.stderr.fnmatch_lines(["--foobar=123 active! *"])
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):
testdir.makeconftest(
"""
@@ -328,7 +311,7 @@ class TestDistribution:
time.sleep(10)
"""
)
child = testdir.spawn_pytest("-n1 -v")
child = testdir.spawn_pytest("-n1 -v", expect_timeout=30.0)
child.expect(".*test_sleep.*")
child.kill(2) # keyboard interrupt
child.expect(".*KeyboardInterrupt.*")
@@ -590,16 +573,13 @@ def test_fixture_teardown_failure(testdir):
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"""
if not hasattr(pytestconfig, "invocation_params"):
pytest.skip(
"requires pytest >=5.1 (config has no attribute 'invocation_params')"
)
testdir.makepyfile(
**{
"dir_a/test_foo.py": """
def test_1(): pass
def test_1(request):
assert request.config.option.verbose == 2
"""
}
)
@@ -610,8 +590,10 @@ def test_config_initialization(testdir, pytestconfig):
testpaths=dir_a
""",
)
monkeypatch.setenv("PYTEST_ADDOPTS", "-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"])
@@ -689,32 +671,6 @@ def test_skipping(testdir):
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):
p1 = testdir.makepyfile(
"""
@@ -792,31 +748,49 @@ def test_sub_plugins_disabled(testdir, plugin):
class TestWarnings:
@pytest.mark.parametrize("n", ["-n0", "-n1"])
@pytest.mark.parametrize("warn_type", ["pytest", "builtin"])
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
def test_warnings(self, testdir, n):
testdir.makepyfile(
"""
import warnings, py, pytest
@pytest.mark.filterwarnings('ignore:config.warn has been deprecated')
def test_func(request):
{warn_code}
""".format(
warn_code=warn_code
)
warnings.warn(UserWarning('this is a warning'))
"""
)
result = testdir.runpytest(n)
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"])
def test_custom_subclass(self, testdir, n):
"""Check that warning subclasses that don't honor the args attribute don't break
@@ -863,9 +837,6 @@ class TestWarnings:
"""Check that warnings with unserializable _WARNING_DETAILS are
handled correctly (#379).
"""
if sys.version_info[0] < 3:
# The issue is only present in Python 3 warnings
return
testdir.makepyfile(
"""
import warnings, pytest
@@ -1066,6 +1037,29 @@ def test_worker_id_fixture(testdir, n):
assert worker_ids == {"gw0", "gw1"}
@pytest.mark.parametrize("n", [0, 2])
def test_testrun_uid_fixture(testdir, n):
import glob
f = testdir.makepyfile(
"""
import pytest
@pytest.mark.parametrize("run_num", range(2))
def test_testrun_uid1(testrun_uid, run_num):
with open("testrun_uid%s.txt" % run_num, "w") as f:
f.write(testrun_uid)
"""
)
result = testdir.runpytest(f, "-n%d" % n)
result.stdout.fnmatch_lines("* 2 passed in *")
testrun_uids = set()
for fname in glob.glob(str(testdir.tmpdir.join("*.txt"))):
with open(fname) as f:
testrun_uids.add(f.read().strip())
assert len(testrun_uids) == 1
assert len(testrun_uids.pop()) == 32
@pytest.mark.parametrize("tb", ["auto", "long", "short", "no", "line", "native"])
def test_error_report_styles(testdir, tb):
testdir.makepyfile(
@@ -1144,6 +1138,18 @@ def test_internal_error_with_maxfail(testdir):
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:
def test_by_module(self, testdir):
test_file = """
@@ -1358,7 +1364,7 @@ def parse_tests_and_workers_from_output(lines):
r"""
\[(gw\d)\] # worker
\s*
(?:\[\s*\d+%\])? # progress indicator (pytest >=3.3)
(?:\[\s*\d+%\])? # progress indicator
\s(.*?) # status string ("PASSED")
\s(.*::.*) # nodeid
""",
@@ -1377,3 +1383,39 @@ def get_workers_and_test_count_by_prefix(prefix, lines, expected_status="PASSED"
if expected_status == status and nodeid.startswith(prefix):
result[worker] = result.get(worker, 0) + 1
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 pytest
import execnet
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)
def _divert_atexit(request, monkeypatch):
@@ -22,12 +11,16 @@ def _divert_atexit(request, monkeypatch):
finalizers = []
def finish():
while finalizers:
finalizers.pop()()
def fake_register(func, *args, **kwargs):
finalizers.append((func, args, kwargs))
monkeypatch.setattr(atexit, "register", finalizers.append)
request.addfinalizer(finish)
monkeypatch.setattr(atexit, "register", fake_register)
yield
while finalizers:
func, args, kwargs = finalizers.pop()
func(*args, **kwargs)
def pytest_addoption(parser):

View File

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

View File

@@ -284,7 +284,7 @@ class TestFunctional:
)
# p = testdir.mkdir("sub").join(p1.basename)
# 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("x == 1")
child.expect("1 failed")
@@ -311,7 +311,7 @@ class TestFunctional:
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("### LOOPONFAILING ####")
child.expect("waiting for changes")

View File

@@ -15,7 +15,7 @@ class TestHooks:
def test_runtest_logreport(self, testdir):
"""Test that log reports from pytest_runtest_logreport when running
with xdist contain "node", "nodeid" and "worker_id" attributes. (#8)
with xdist contain "node", "nodeid", "worker_id", and "testrun_uid" attributes. (#8)
"""
testdir.makeconftest(
"""
@@ -23,20 +23,24 @@ class TestHooks:
if hasattr(report, 'node'):
if report.when == "call":
workerid = report.node.workerinput['workerid']
testrunuid = report.node.workerinput['testrunuid']
if workerid != report.worker_id:
print("HOOK: Worker id mismatch: %s %s"
% (workerid, report.worker_id))
elif testrunuid != report.testrun_uid:
print("HOOK: Testrun uid mismatch: %s %s"
% (testrunuid, report.testrun_uid))
else:
print("HOOK: %s %s"
% (report.nodeid, report.worker_id))
print("HOOK: %s %s %s"
% (report.nodeid, report.worker_id, report.testrun_uid))
"""
)
res = testdir.runpytest("-n1", "-s")
res.stdout.fnmatch_lines(
[
"*HOOK: test_runtest_logreport.py::test_a gw0*",
"*HOOK: test_runtest_logreport.py::test_b gw0*",
"*HOOK: test_runtest_logreport.py::test_c gw0*",
"*HOOK: test_runtest_logreport.py::test_a gw0 *",
"*HOOK: test_runtest_logreport.py::test_b gw0 *",
"*HOOK: test_runtest_logreport.py::test_c gw0 *",
"*3 passed*",
]
)

View File

@@ -1,7 +1,11 @@
from contextlib import suppress
import py
import execnet
from xdist.workermanage import NodeManager
import pytest
def test_dist_incompatibility_messages(testdir):
result = testdir.runpytest("--pdb", "--looponfail")
@@ -38,6 +42,11 @@ def test_auto_detect_cpus(testdir, monkeypatch):
import os
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"):
monkeypatch.setattr(os, "sched_getaffinity", lambda _pid: set(range(99)))
elif hasattr(os, "cpu_count"):
@@ -51,19 +60,48 @@ def test_auto_detect_cpus(testdir, monkeypatch):
assert config.getoption("numprocesses") == 2
config = testdir.parseconfigure("-nauto")
check_options(config)
assert config.getoption("numprocesses") == 99
config = testdir.parseconfigure("-nauto", "--pdb")
check_options(config)
assert config.getoption("usepdb")
assert config.getoption("numprocesses") == 0
assert config.getoption("dist") == "no"
monkeypatch.delattr(os, "sched_getaffinity", raising=False)
monkeypatch.setenv("TRAVIS", "true")
config = testdir.parseconfigure("-nauto")
check_options(config)
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):
from xdist.plugin import pytest_cmdline_main as check_options
@@ -95,6 +133,18 @@ def test_dsession_with_collect_only(testdir):
assert not config.pluginmanager.hasplugin("dsession")
def test_testrunuid_provided(testdir):
config = testdir.parseconfigure("--testrunuid", "test123", "--tx=popen")
nm = NodeManager(config)
assert nm.testrunuid == "test123"
def test_testrunuid_generated(testdir):
config = testdir.parseconfigure("--tx=popen")
nm = NodeManager(config)
assert len(nm.testrunuid) == 32
class TestDistOptions:
def test_getxspecs(self, testdir):
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")

View File

@@ -2,12 +2,13 @@ import py
import pprint
import pytest
import sys
import uuid
from xdist.workermanage import WorkerController
import execnet
import marshal
from six.moves.queue import Queue
from queue import Queue
WAIT_TIMEOUT = 10.0
@@ -25,7 +26,7 @@ class EventCall:
self.name, self.kwargs = eventcall
def __str__(self):
return "<EventCall %s(**%s)>" % (self.name, self.kwargs)
return "<EventCall {}(**{})>".format(self.name, self.kwargs)
class WorkerSetup:
@@ -44,6 +45,7 @@ class WorkerSetup:
putevent = self.use_callback and self.events.put or None
class DummyMananger:
testrunuid = uuid.uuid4().hex
specs = [0, 1]
self.slp = WorkerController(DummyMananger, self.gateway, config, putevent)
@@ -59,7 +61,7 @@ class WorkerSetup:
ev = EventCall(data)
if name is None or ev.name == name:
return ev
print("skipping %s" % (ev,))
print("skipping {}".format(ev))
def sendcommand(self, name, **kwargs):
self.slp.sendcommand(name, **kwargs)
@@ -220,6 +222,7 @@ def test_remote_env_vars(testdir):
"""
import os
def test():
assert len(os.environ['PYTEST_XDIST_TESTRUNUID']) == 32
assert os.environ['PYTEST_XDIST_WORKER'] in ('gw0', 'gw1')
assert os.environ['PYTEST_XDIST_WORKER_COUNT'] == '2'
"""

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import py
import pytest
import textwrap
@@ -35,7 +34,7 @@ def mysetup(tmpdir):
@pytest.fixture
def workercontroller(monkeypatch):
class MockController(object):
class MockController:
def __init__(self, *args):
pass
@@ -168,7 +167,7 @@ class TestNodeManager:
# assert nodemanager.config.topdir == source == config.topdir
nodemanager.makegateways()
nodemanager.rsync_roots()
p, = nodemanager.gwmanager.multi_exec(
(p,) = nodemanager.gwmanager.multi_exec(
"import os ; channel.send(os.getcwd())"
).receive_each()
p = py.path.local(p)
@@ -197,6 +196,25 @@ class TestNodeManager:
assert dest.join("dir1", "dir2", "hello").check()
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):
source, dest = mysetup.source, mysetup.dest
dir2 = source.ensure("dir1", "dir2", dir=1)
@@ -269,5 +287,5 @@ class TestNodeManager:
reprec = testdir.inline_run(
"-d", "--rsyncdir=%s" % testdir.tmpdir, "--tx", specssh, testdir.tmpdir
)
rep, = reprec.getreports("pytest_runtest_logreport")
(rep,) = reprec.getreports("pytest_runtest_logreport")
assert rep.passed

22
tox.ini
View File

@@ -1,22 +1,30 @@
[tox]
envlist=
linting
py{27,34,35,36,37,38}-pytestlatest
py38-pytest{master,features}
py{35,36,37,38,39}-pytestlatest
py38-pytestmaster
py38-psutil
[testenv]
passenv = USER USERNAME
extras = testing
deps =
pytestlatest: pytest
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
commands=
pytest {posargs}
[testenv:py38-psutil]
extras =
testing
psutil
deps = pytest
commands =
pytest {posargs:-k psutil}
[testenv:linting]
skipsdist = True
skip_install = True
usedevelop = True
passenv = PRE_COMMIT_HOME
deps =
pre-commit
commands = pre-commit run --all-files --show-diff-on-failure
@@ -29,9 +37,9 @@ skipsdist = True
usedevelop = True
passenv = *
deps =
towncrier
towncrier
commands =
towncrier --version {posargs} --yes
towncrier --version {posargs} --yes
[pytest]
addopts = -ra