diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..d30f5081 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,33 @@ +name: deploy + +on: + push: + tags: + - "v*" + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@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: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 2df3b924..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: build - -on: [push, pull_request] - -jobs: - build: - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - tox_env: - - "py36-pytestlatest" - - "py37-pytestlatest" - - "py38-pytestlatest" - - "py39-pytestlatest" - - "py310-pytestlatest" - - "py38-pytestmain" - - "py38-psutil" - - "py38-setproctitle" - - os: [ubuntu-latest, windows-latest] - include: - - 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: "py310-pytestlatest" - python: "3.10" - - tox_env: "py38-pytestmain" - python: "3.8" - - tox_env: "py38-psutil" - python: "3.8" - - tox_env: "py38-setproctitle" - python: "3.8" - - steps: - - uses: actions/checkout@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: ${{ matrix.python }} - - name: Install tox - run: | - python -m pip install --upgrade pip - pip install tox - - name: Test - run: | - tox -e ${{ matrix.tox_env }} - - deploy: - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - - runs-on: ubuntu-latest - - needs: build - - steps: - - uses: actions/checkout@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.7" - - name: Install wheel - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: | - python -m build - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..7f6effe6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,67 @@ +name: test + +on: + push: + branches: + - "*" + + pull_request: + branches: + - "*" + +jobs: + test: + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + tox_env: + - "py37-pytestlatest" + - "py38-pytestlatest" + - "py39-pytestlatest" + - "py310-pytestlatest" + - "py310-pytestmain" + - "py311-pytestlatest" + - "py311-pytestmain" + - "py310-psutil" + - "py310-setproctitle" + + os: [ubuntu-latest, windows-latest] + include: + - tox_env: "py37-pytestlatest" + python: "3.7" + - tox_env: "py38-pytestlatest" + python: "3.8" + - tox_env: "py39-pytestlatest" + python: "3.9" + - tox_env: "py310-pytestlatest" + python: "3.10" + - tox_env: "py310-pytestmain" + python: "3.10" + - tox_env: "py311-pytestlatest" + python: "3.11" + - tox_env: "py311-pytestmain" + python: "3.11" + - tox_env: "py310-psutil" + python: "3.10" + - tox_env: "py310-setproctitle" + python: "3.10" + + steps: + - uses: actions/checkout@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: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: | + tox -e ${{ matrix.tox_env }} diff --git a/README.rst b/README.rst index 176fcba1..ee0c59d5 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ pytest-xdist :alt: Python versions :target: https://pypi.python.org/pypi/pytest-xdist -.. image:: https://github.com/pytest-dev/pytest-xdist/workflows/build/badge.svg +.. image:: https://github.com/pytest-dev/pytest-xdist/workflows/test/badge.svg :target: https://github.com/pytest-dev/pytest-xdist/actions .. image:: https://img.shields.io/badge/code%20style-black-000000.svg diff --git a/changelog/842.feature.rst b/changelog/842.feature.rst new file mode 100644 index 00000000..a991347a --- /dev/null +++ b/changelog/842.feature.rst @@ -0,0 +1 @@ +Python 3.11 is now officially supported. diff --git a/changelog/842.removal.rst b/changelog/842.removal.rst new file mode 100644 index 00000000..6daecd16 --- /dev/null +++ b/changelog/842.removal.rst @@ -0,0 +1 @@ +Python 3.6 is no longer supported. diff --git a/setup.cfg b/setup.cfg index 9b4a3dd9..3162a908 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,11 +24,11 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 license_file = LICENSE project_urls = Documentation=https://pytest-xdist.readthedocs.io/en/latest @@ -40,7 +40,7 @@ project_urls = packages = find: package_dir = =src zip_safe = False -python_requires = >=3.6 +python_requires = >=3.7 install_requires = execnet>=1.1 pytest>=6.2.0 diff --git a/src/xdist/dsession.py b/src/xdist/dsession.py index ab8332f7..4cea59bc 100644 --- a/src/xdist/dsession.py +++ b/src/xdist/dsession.py @@ -169,7 +169,7 @@ def worker_workerfinished(self, node): """ self.config.hook.pytest_testnodedown(node=node, error=None) if node.workeroutput["exitstatus"] == 2: # keyboard-interrupt - self.shouldstop = "{} received keyboard-interrupt".format(node) + self.shouldstop = f"{node} received keyboard-interrupt" self.worker_errordown(node, "keyboard-interrupt") return if node in self.sched.nodes: @@ -230,7 +230,7 @@ def worker_errordown(self, node, error): @pytest.hookimpl def pytest_terminal_summary(self, terminalreporter): if self.config.option.verbose >= 0 and self._summary_report: - terminalreporter.write_sep("=", "xdist: {}".format(self._summary_report)) + terminalreporter.write_sep("=", f"xdist: {self._summary_report}") def worker_collectionfinish(self, node, ids): """worker has finished test collection. @@ -345,7 +345,7 @@ def handle_crashitem(self, nodeid, worker): # 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}".format(worker.gateway.id, nodeid) + msg = f"worker {worker.gateway.id!r} crashed while running {nodeid!r}" rep = runner.TestReport( nodeid, (fspath, None, fspath), (), "failed", msg, "???" ) @@ -381,9 +381,7 @@ def setstatus(self, spec, status, show=True): def getstatus(self): if self.config.option.verbose >= 0: - parts = [ - "{} {}".format(spec.id, self._status[spec.id]) for spec in self._specs - ] + parts = [f"{spec.id} {self._status[spec.id]}" for spec in self._specs] return " / ".join(parts) else: return "bringing up nodes..." @@ -431,7 +429,7 @@ def pytest_testnodeready(self, node): def pytest_testnodedown(self, node, error): if not error: return - self.write_line("[{}] node down: {}".format(node.gateway.id, error)) + self.write_line(f"[{node.gateway.id}] node down: {error}") def get_default_max_worker_restart(config): diff --git a/src/xdist/looponfail.py b/src/xdist/looponfail.py index 77eff04f..59ba7ebe 100644 --- a/src/xdist/looponfail.py +++ b/src/xdist/looponfail.py @@ -146,7 +146,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: {}".format(rootdir), bold=True) + tr.line(f"### Watching: {rootdir}", bold=True) def init_worker_session(channel, args, option_dict): diff --git a/src/xdist/remote.py b/src/xdist/remote.py index 5d0c8997..1d2f6767 100644 --- a/src/xdist/remote.py +++ b/src/xdist/remote.py @@ -149,7 +149,7 @@ def pytest_collection_modifyitems(self, session, config, items): if len(mark.args) > 0 else mark.kwargs.get("name", "default") ) - item._nodeid = "{}@{}".format(item.nodeid, gname) + item._nodeid = f"{item.nodeid}@{gname}" @pytest.hookimpl def pytest_collection_finish(self, session): diff --git a/src/xdist/scheduler/loadscope.py b/src/xdist/scheduler/loadscope.py index fabe1eba..ad99125f 100644 --- a/src/xdist/scheduler/loadscope.py +++ b/src/xdist/scheduler/loadscope.py @@ -361,12 +361,12 @@ def schedule(self): extra_nodes = len(self.nodes) - len(self.workqueue) if extra_nodes > 0: - self.log("Shutting down {} nodes".format(extra_nodes)) + self.log(f"Shutting down {extra_nodes} nodes") for _ in range(extra_nodes): unused_node, assigned = self.assigned_work.popitem(last=True) - self.log("Shutting down unused node {}".format(unused_node)) + self.log(f"Shutting down unused node {unused_node}") unused_node.shutdown() # Assign initial workload diff --git a/src/xdist/workermanage.py b/src/xdist/workermanage.py index a940b84c..10f681cc 100644 --- a/src/xdist/workermanage.py +++ b/src/xdist/workermanage.py @@ -112,7 +112,7 @@ def get_dir(p): for root in candidates: root = Path(root).resolve() if not root.exists(): - raise pytest.UsageError("rsyncdir doesn't exist: {!r}".format(root)) + raise pytest.UsageError(f"rsyncdir doesn't exist: {root!r}") if root not in roots: roots.append(root) return roots @@ -192,7 +192,7 @@ def _report_send_file(self, gateway, modified_rel_path): if self._verbose > 0: path = os.path.basename(self._sourcedir) + "/" + modified_rel_path remotepath = gateway.spec.chdir - print("{}:{} <= {}".format(gateway.spec, remotepath, path)) + print(f"{gateway.spec}:{remotepath} <= {path}") def make_reltoroot(roots: Sequence[Path], args: List[str]) -> List[str]: @@ -219,7 +219,7 @@ def make_reltoroot(roots: Sequence[Path], args: List[str]) -> List[str]: parts[0] = root.name + "/" + str(x) break else: - raise ValueError("arg {} not relative to an rsync root".format(arg)) + raise ValueError(f"arg {arg} not relative to an rsync root") result.append(splitcode.join(parts)) return result @@ -249,7 +249,7 @@ def __init__(self, nodemanager, gateway, config, putevent): self.log = Producer(f"workerctl-{gateway.id}", enabled=config.option.debug) def __repr__(self): - return "<{} {}>".format(self.__class__.__name__, self.gateway.id) + return f"<{self.__class__.__name__} {self.gateway.id}>" @property def shutting_down(self): @@ -310,11 +310,11 @@ def shutdown(self): def sendcommand(self, name, **kwargs): """send a named parametrized command to the other side.""" - self.log("sending command {}(**{})".format(name, kwargs)) + self.log(f"sending command {name}(**{kwargs})") self.channel.send((name, kwargs)) def notify_inproc(self, eventname, **kwargs): - self.log("queuing {}(**{})".format(eventname, kwargs)) + self.log(f"queuing {eventname}(**{kwargs})") self.putevent((eventname, kwargs)) def process_from_remote(self, eventcall): # noqa too complex @@ -336,7 +336,7 @@ def process_from_remote(self, eventcall): # noqa too complex return eventname, kwargs = eventcall if eventname in ("collectionstart",): - self.log("ignoring {}({})".format(eventname, kwargs)) + self.log(f"ignoring {eventname}({kwargs})") elif eventname == "workerready": self.notify_inproc(eventname, node=self, **kwargs) elif eventname == "internal_error": @@ -389,7 +389,7 @@ def process_from_remote(self, eventcall): # noqa too complex location=kwargs["location"], ) else: - raise ValueError("unknown event: {}".format(eventname)) + raise ValueError(f"unknown event: {eventname}") except KeyboardInterrupt: # should not land in receiver-thread raise diff --git a/testing/test_remote.py b/testing/test_remote.py index 8a44b733..ddfed9e4 100644 --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -25,7 +25,7 @@ def __init__(self, eventcall): self.name, self.kwargs = eventcall def __str__(self): - return "".format(self.name, self.kwargs) + return f"" class WorkerSetup: @@ -60,7 +60,7 @@ def popevent(self, name=None): ev = EventCall(data) if name is None or ev.name == name: return ev - print("skipping {}".format(ev)) + print(f"skipping {ev}") def sendcommand(self, name, **kwargs): self.slp.sendcommand(name, **kwargs) diff --git a/tox.ini b/tox.ini index 20ffd99c..b0de6538 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,10 @@ [tox] envlist= linting - py{36,37,38,39,310}-pytestlatest - py38-pytestmain - py38-psutil - py38-setproctitle + py{37,38,39,310,311}-pytestlatest + py310-pytestmain + py310-psutil + py310-setproctitle isolated_build = true [testenv] extras = testing @@ -14,14 +14,14 @@ deps = commands= pytest {posargs} -[testenv:py38-psutil] +[testenv:py310-psutil] extras = testing psutil commands = pytest {posargs:-k psutil} -[testenv:py38-setproctitle] +[testenv:py310-setproctitle] extras = testing setproctitle @@ -40,7 +40,7 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:release] changedir= description = do a release, required posarg of the version number -basepython = python3.7 +basepython = python3.10 skipsdist = True usedevelop = True passenv = * @@ -50,7 +50,7 @@ commands = towncrier build --version {posargs} --yes [testenv:docs] -basepython = python3 +basepython = python3.10 usedevelop = True deps = sphinx