Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pytest-dev/pytest-xdist
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.2.1
Choose a base ref
...
head repository: pytest-dev/pytest-xdist
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.3.0
Choose a head ref
  • 15 commits
  • 11 files changed
  • 6 contributors

Commits on Mar 12, 2023

  1. Merge pull request #888 from amezin/release-3.2.1

    Release 3.2.1
    amezin authored Mar 12, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    gaborbernat Bernát Gábor
    Copy the full SHA
    6e7bbfa View commit details

Commits on Mar 14, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#889)

    updates:
    - [github.com/PyCQA/autoflake: v2.0.1 → v2.0.2](PyCQA/autoflake@v2.0.1...v2.0.2)
    - [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](pre-commit/mirrors-mypy@v1.0.1...v1.1.1)
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Mar 14, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    6719dfd View commit details

Commits on Apr 4, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#892)

    updates:
    - [github.com/psf/black: 23.1.0 → 23.3.0](psf/black@23.1.0...23.3.0)
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Apr 4, 2023
    Copy the full SHA
    c7d8609 View commit details

Commits on Apr 6, 2023

  1. Copy the full SHA
    982ef00 View commit details

Commits on Apr 11, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#895)

    updates:
    - [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](pre-commit/mirrors-mypy@v1.1.1...v1.2.0)
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Apr 11, 2023
    Copy the full SHA
    4bed067 View commit details

Commits on Apr 21, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#896)

    updates:
    - [github.com/PyCQA/autoflake: v2.0.2 → v2.1.0](PyCQA/autoflake@v2.0.2...v2.1.0)
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Apr 21, 2023
    Copy the full SHA
    6317cc3 View commit details

Commits on Apr 25, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#897)

    updates:
    - [github.com/PyCQA/autoflake: v2.1.0 → v2.1.1](PyCQA/autoflake@v2.1.0...v2.1.1)
    - [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](asottile/pyupgrade@v3.3.1...v3.3.2)
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Apr 25, 2023
    Copy the full SHA
    8fbecd8 View commit details

Commits on May 9, 2023

  1. [pre-commit.ci] pre-commit autoupdate

    updates:
    - [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](asottile/pyupgrade@v3.3.2...v3.4.0)
    pre-commit-ci[bot] authored May 9, 2023
    Copy the full SHA
    a338075 View commit details

Commits on May 10, 2023

  1. Merge pull request #900 from pytest-dev/pre-commit-ci-update-config

    [pre-commit.ci] pre-commit autoupdate
    bluetech authored May 10, 2023
    Copy the full SHA
    e48dc3c View commit details

Commits on May 12, 2023

  1. Copy the full SHA
    be1d5c4 View commit details
  2. Deploy via a protected environment and using PyPI trusted publishers (#…

    …902)
    
    Following recent discussions, this changes the development process as follows:
    
    1. The deploy is now manually triggered after the release PR is approved.
    2. The deploy workflow tags the repository only after the package has been published to PyPI.
    3. Use PyPI trusted publishers instead of API tokens.
    
    
    Co-authored-by: Ran Benita <ran@unusedvar.com>
    nicoddemus and bluetech authored May 12, 2023
    Copy the full SHA
    37b9dbd View commit details
  3. Release 3.3.0

    nicoddemus committed May 12, 2023
    Copy the full SHA
    103ee05 View commit details
  4. Add check-package step also to 'test' workflow

    This ensures we do not have any surprises when we try to deploy,
    which uses the same step to generate the package.
    nicoddemus committed May 12, 2023
    Copy the full SHA
    fa08e09 View commit details
  5. Only test on-push for master and a specific branch name

    This avoids testing twice when pushing to the main repository, once for the push, and another for a PR.
    nicoddemus committed May 12, 2023
    Copy the full SHA
    657ba9a View commit details
  6. Copy the full SHA
    f5b4a5d View commit details
Showing with 260 additions and 77 deletions.
  1. +35 −21 .github/workflows/deploy.yml
  2. +15 −1 .github/workflows/test.yml
  3. +4 −4 .pre-commit-config.yaml
  4. +9 −0 CHANGELOG.rst
  5. +3 −9 RELEASING.rst
  6. +2 −1 docs/distribution.rst
  7. +1 −0 setup.cfg
  8. +89 −26 src/xdist/dsession.py
  9. +2 −0 src/xdist/plugin.py
  10. +24 −12 testing/acceptance_test.py
  11. +76 −3 testing/test_dsession.py
56 changes: 35 additions & 21 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
name: deploy

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
version:
description: 'Release version'
required: true
default: '1.2.3'

jobs:
deploy:

package:
runs-on: ubuntu-latest
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}

steps:
- uses: actions/checkout@v3

- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5

deploy:
needs: package
runs-on: ubuntu-latest
environment: deploy
permissions:
id-token: write # For PyPI trusted publishers.
contents: write # For tag.

steps:
- uses: actions/checkout@v3

- name: Download Package
uses: actions/download-artifact@v3
with:
# Needed to fetch tags, which are required by setuptools-scm.
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Install build
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: |
python -m build
name: Packages
path: dist

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_token }}
uses: pypa/gh-action-pypi-publish@v1.8.5

- name: Push tag
run: |
git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }}
git push origin v${{ github.event.inputs.version }}
16 changes: 15 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -3,13 +3,27 @@ name: test
on:
push:
branches:
- "*"
- master
- "test-me-*"

pull_request:
branches:
- "*"

# Cancel running jobs for the same workflow and branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:

check-package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5

test:

runs-on: ${{ matrix.os }}
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.1
rev: v2.1.1
hooks:
- id: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black
args: [--safe, --quiet, --target-version, py35]
@@ -26,7 +26,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.4.0
hooks:
- id: pyupgrade
args: [--py3-plus]
@@ -39,7 +39,7 @@ repos:
language: python
additional_dependencies: [pygments, restructuredtext_lint]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1
rev: v1.2.0
hooks:
- id: mypy
files: ^(src/|testing/)
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
pytest-xdist 3.3.0 (2023-05-12)
===============================

Features
--------

- `#555 <https://github.com/pytest-dev/pytest-xdist/issues/555>`_: Improved progress output when collecting nodes to be less verbose.


pytest-xdist 3.2.1 (2023-03-12)
===============================

12 changes: 3 additions & 9 deletions RELEASING.rst
Original file line number Diff line number Diff line change
@@ -32,14 +32,8 @@ To publish a new release ``X.Y.Z``, the steps are as follows:

$ tox -e release -- X.Y.Z

#. Commit and push the branch for review.
#. Commit and push the branch to ``upstream`` and open a PR.

#. Once PR is **green** and **approved**, create and push a tag::
#. Once the PR is **green** and **approved**, start the ``deploy`` workflow manually from the branch ``release-VERSION``, passing ``VERSION`` as parameter.

$ export VERSION=X.Y.Z
$ git tag v$VERSION release-$VERSION
$ git push git@github.com:pytest-dev/pytest-xdist.git v$VERSION

That will build the package and publish it on ``PyPI`` automatically.

#. Merge the release PR to `master`.
#. Merge the release PR to ``master``.
3 changes: 2 additions & 1 deletion docs/distribution.rst
Original file line number Diff line number Diff line change
@@ -50,7 +50,8 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
.. _distribution modes:

* ``--dist load`` **(default)**: Sends pending tests to any worker that is
available, without any guaranteed order.
available, without any guaranteed order. Scheduling can be fine-tuned with
the `--maxschedchunk` option, see output of `pytest --help`.

* ``--dist loadscope``: Tests are grouped by **module** for *test functions*
and by **class** for *test methods*. Groups are distributed to available
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
name = pytest-xdist
description = pytest xdist plugin for distributed testing, most importantly across multiple CPUs
long_description = file: README.rst
long_description_content_type = text/x-rst
license = MIT
author = holger krekel and contributors
author_email = pytest-dev@python.org,holger@merlinux.eu
115 changes: 89 additions & 26 deletions src/xdist/dsession.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from __future__ import annotations
import sys
from enum import Enum, auto
from typing import Sequence

import pytest

from xdist.remote import Producer
@@ -251,14 +256,16 @@ def worker_collectionfinish(self, node, ids):
self._session.testscollected = len(ids)
self.sched.add_node_collection(node, ids)
if self.terminal:
self.trdist.setstatus(node.gateway.spec, "[%d]" % (len(ids)))
self.trdist.setstatus(
node.gateway.spec, WorkerStatus.CollectionDone, tests_collected=len(ids)
)
if self.sched.collection_is_completed:
if self.terminal and not self.sched.has_pending:
self.trdist.ensure_show_status()
self.terminal.write_line("")
if self.config.option.verbose > 0:
self.terminal.write_line(
"scheduling tests via %s" % (self.sched.__class__.__name__)
f"scheduling tests via {self.sched.__class__.__name__}"
)
self.sched.schedule()

@@ -345,7 +352,7 @@ def _handlefailures(self, rep):
if rep.failed:
self.countfailures += 1
if self.maxfail and self.countfailures >= self.maxfail:
self.shouldstop = "stopping after %d failures" % (self.countfailures)
self.shouldstop = f"stopping after {self.countfailures} failures"

def triggershutdown(self):
self.log("triggering shutdown")
@@ -372,32 +379,51 @@ def handle_crashitem(self, nodeid, worker):
self.config.hook.pytest_runtest_logreport(report=rep)


class WorkerStatus(Enum):
"""Status of each worker during creation/collection."""

# Worker spec has just been created.
Created = auto()

# Worker has been initialized.
Initialized = auto()

# Worker is now ready for collection.
ReadyForCollection = auto()

# Worker has finished collection.
CollectionDone = auto()


class TerminalDistReporter:
def __init__(self, config):
def __init__(self, config) -> None:
self.config = config
self.tr = config.pluginmanager.getplugin("terminalreporter")
self._status = {}
self._status: dict[str, tuple[WorkerStatus, int]] = {}
self._lastlen = 0
self._isatty = getattr(self.tr, "isatty", self.tr.hasmarkup)

def write_line(self, msg):
def write_line(self, msg: str) -> None:
self.tr.write_line(msg)

def ensure_show_status(self):
def ensure_show_status(self) -> None:
if not self._isatty:
self.write_line(self.getstatus())

def setstatus(self, spec, status, show=True):
self._status[spec.id] = status
def setstatus(
self, spec, status: WorkerStatus, *, tests_collected: int, show: bool = True
) -> None:
self._status[spec.id] = (status, tests_collected)
if show and self._isatty:
self.rewrite(self.getstatus())

def getstatus(self):
def getstatus(self) -> str:
if self.config.option.verbose >= 0:
parts = [f"{spec.id} {self._status[spec.id]}" for spec in self._specs]
return " / ".join(parts)
else:
return "bringing up nodes..."
line = get_workers_status_line(list(self._status.values()))
if line:
return line

return "bringing up nodes..."

def rewrite(self, line, newline=False):
pline = line + " " * max(self._lastlen - len(line), 0)
@@ -409,37 +435,41 @@ def rewrite(self, line, newline=False):
self.tr.rewrite(pline, bold=True)

@pytest.hookimpl
def pytest_xdist_setupnodes(self, specs):
def pytest_xdist_setupnodes(self, specs) -> None:
self._specs = specs
for spec in specs:
self.setstatus(spec, "I", show=False)
self.setstatus(spec, "I", show=True)
self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=False)
self.setstatus(spec, WorkerStatus.Created, tests_collected=0, show=True)
self.ensure_show_status()

@pytest.hookimpl
def pytest_xdist_newgateway(self, gateway):
if self.config.option.verbose > 0:
rinfo = gateway._rinfo()
def pytest_xdist_newgateway(self, gateway) -> None:
rinfo = gateway._rinfo()
is_local = rinfo.executable == sys.executable
if self.config.option.verbose > 0 and not is_local:
version = "%s.%s.%s" % rinfo.version_info[:3]
self.rewrite(
"[%s] %s Python %s cwd: %s"
% (gateway.id, rinfo.platform, version, rinfo.cwd),
newline=True,
)
self.setstatus(gateway.spec, "C")
self.setstatus(gateway.spec, WorkerStatus.Initialized, tests_collected=0)

@pytest.hookimpl
def pytest_testnodeready(self, node):
if self.config.option.verbose > 0:
d = node.workerinfo
def pytest_testnodeready(self, node) -> None:
d = node.workerinfo
is_local = d.get("executable") == sys.executable
if self.config.option.verbose > 0 and not is_local:
infoline = "[{}] Python {}".format(
d["id"], d["version"].replace("\n", " -- ")
)
self.rewrite(infoline, newline=True)
self.setstatus(node.gateway.spec, "ok")
self.setstatus(
node.gateway.spec, WorkerStatus.ReadyForCollection, tests_collected=0
)

@pytest.hookimpl
def pytest_testnodedown(self, node, error):
def pytest_testnodedown(self, node, error) -> None:
if not error:
return
self.write_line(f"[{node.gateway.id}] node down: {error}")
@@ -457,3 +487,36 @@ def get_default_max_worker_restart(config):
# if --max-worker-restart was not provided, use a reasonable default (#226)
result = config.option.numprocesses * 4
return result


def get_workers_status_line(
status_and_items: Sequence[tuple[WorkerStatus, int]]
) -> str:
"""
Return the line to display during worker setup/collection based on the
status of the workers and number of tests collected for each.
"""
statuses = [s for s, c in status_and_items]
total_workers = len(statuses)
workers_noun = "worker" if total_workers == 1 else "workers"
if status_and_items and all(s == WorkerStatus.CollectionDone for s in statuses):
# All workers collect the same number of items, so we grab
# the total number of items from the first worker.
first = status_and_items[0]
status, tests_collected = first
tests_noun = "item" if tests_collected == 1 else "items"
return f"{total_workers} {workers_noun} [{tests_collected} {tests_noun}]"
if WorkerStatus.CollectionDone in statuses:
done = sum(1 for s, c in status_and_items if c > 0)
return f"collecting: {done}/{total_workers} {workers_noun}"
if WorkerStatus.ReadyForCollection in statuses:
ready = statuses.count(WorkerStatus.ReadyForCollection)
return f"ready: {ready}/{total_workers} {workers_noun}"
if WorkerStatus.Initialized in statuses:
initialized = statuses.count(WorkerStatus.Initialized)
return f"initialized: {initialized}/{total_workers} {workers_noun}"
if WorkerStatus.Created in statuses:
created = statuses.count(WorkerStatus.Created)
return f"created: {created}/{total_workers} {workers_noun}"

return ""
2 changes: 2 additions & 0 deletions src/xdist/plugin.py
Original file line number Diff line number Diff line change
@@ -173,6 +173,8 @@ def pytest_addoption(parser):
"one - might be useful for a small number of slow tests. "
"Larger numbers will allow the scheduler to submit consecutive "
"chunks of tests to workers - allows reusing fixtures. "
"Due to implementation reasons, at least 2 tests are scheduled per "
"worker at the start. Only later tests can be scheduled one by one. "
"Unlimited if not set."
),
)
Loading